@jobber/components-native 0.45.1 → 0.46.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/dist/package.json +2 -2
- package/dist/src/AtlantisContext/AtlantisContext.js +2 -2
- package/dist/src/Form/context/AtlantisFormContext.js +2 -2
- package/dist/src/InputDate/InputDate.js +3 -8
- package/dist/src/InputText/context/InputAccessoriesContext.js +2 -2
- package/dist/src/InputTime/InputTime.js +3 -6
- package/dist/src/hooks/useAtlantisI18n/useAtlantisI18n.js +7 -3
- package/dist/src/hooks/useAtlantisI18n/utils/dateFormatter.js +20 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/AtlantisContext/AtlantisContext.d.ts +1 -1
- package/dist/types/src/Form/context/AtlantisFormContext.d.ts +1 -1
- package/dist/types/src/hooks/useAtlantisI18n/useAtlantisI18n.d.ts +17 -0
- package/dist/types/src/hooks/useAtlantisI18n/utils/dateFormatter.d.ts +6 -0
- package/package.json +2 -2
- package/src/AtlantisContext/AtlantisContext.test.tsx +3 -2
- package/src/AtlantisContext/AtlantisContext.tsx +2 -2
- package/src/ButtonGroup/ButtonGroup.test.tsx +2 -2
- package/src/Form/Form.test.tsx +2 -2
- package/src/Form/components/FormErrorBanner/FormErrorBanner.test.tsx +2 -2
- package/src/Form/context/AtlantisFormContext.test.tsx +2 -3
- package/src/Form/context/AtlantisFormContext.tsx +4 -3
- package/src/InputCurrency/InputCurrency.test.tsx +2 -2
- package/src/InputDate/InputDate.test.tsx +4 -4
- package/src/InputDate/InputDate.tsx +3 -8
- package/src/InputText/context/InputAccessoriesContext.ts +4 -2
- package/src/InputTime/InputTime.test.tsx +3 -3
- package/src/InputTime/InputTime.tsx +4 -7
- package/src/hooks/useAtlantisI18n/useAtlantisI18n.test.ts +105 -4
- package/src/hooks/useAtlantisI18n/useAtlantisI18n.ts +40 -4
- package/src/hooks/useAtlantisI18n/utils/dateFormatter.ts +31 -0
|
@@ -51,6 +51,6 @@ export interface AtlantisContextProps {
|
|
|
51
51
|
*/
|
|
52
52
|
readonly setHeaderHeight: (height: number) => void;
|
|
53
53
|
}
|
|
54
|
-
export declare const
|
|
54
|
+
export declare const atlantisContextDefaultValues: AtlantisContextProps;
|
|
55
55
|
export declare const AtlantisContext: import("react").Context<AtlantisContextProps>;
|
|
56
56
|
export declare function useAtlantisContext(): AtlantisContextProps;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { AtlantisFormContextProps } from "./types";
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const atlantisFormContextDefaultValues: {
|
|
4
4
|
useConfirmBeforeBack: () => import("react").MutableRefObject<() => undefined>;
|
|
5
5
|
useInternalFormLocalCache: () => {
|
|
6
6
|
setLocalCache: () => undefined;
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import en from "./locales/en.json";
|
|
2
2
|
export type I18nKeys = keyof typeof en;
|
|
3
3
|
export interface useAtlantisI18nValue {
|
|
4
|
+
/**
|
|
5
|
+
* The set locale based on the AtlantisContext.
|
|
6
|
+
*/
|
|
4
7
|
readonly locale: string;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the translated string depending on the locale. This accepts a 2nd
|
|
10
|
+
* param for string interpolation.
|
|
11
|
+
*/
|
|
5
12
|
readonly t: (message: I18nKeys, values?: Record<string, string>) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Returns a formatted date string based on the locale and the `dateFormat`
|
|
15
|
+
* set in AtlantisContext.
|
|
16
|
+
*/
|
|
17
|
+
readonly formatDate: (date: Date) => string;
|
|
18
|
+
/**
|
|
19
|
+
* Returns a formatted time string based on the locale and the `timeFormat`
|
|
20
|
+
* set in AtlantisContext.
|
|
21
|
+
*/
|
|
22
|
+
readonly formatTime: (date: Date) => string;
|
|
6
23
|
}
|
|
7
24
|
export declare function useAtlantisI18n(): useAtlantisI18nValue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native implementation of Atlantis",
|
|
6
6
|
"repository": {
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"react-native-reanimated": "^2.17.0",
|
|
85
85
|
"react-native-safe-area-context": "^4.5.2"
|
|
86
86
|
},
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "ebd19569f57d6c7ef08de4992894952452dfe583"
|
|
88
88
|
}
|
|
@@ -4,7 +4,7 @@ import { cleanup, renderHook } from "@testing-library/react-hooks";
|
|
|
4
4
|
import {
|
|
5
5
|
AtlantisContext,
|
|
6
6
|
AtlantisContextProps,
|
|
7
|
-
|
|
7
|
+
atlantisContextDefaultValues,
|
|
8
8
|
useAtlantisContext,
|
|
9
9
|
} from "./AtlantisContext";
|
|
10
10
|
|
|
@@ -14,6 +14,7 @@ const providerValues: AtlantisContextProps = {
|
|
|
14
14
|
dateFormat: "MM/DD/YYYY",
|
|
15
15
|
timeFormat: "hh:mm a",
|
|
16
16
|
timeZone: "America/Edmonton",
|
|
17
|
+
locale: "en",
|
|
17
18
|
isOnline: false,
|
|
18
19
|
onLogError: _ => {
|
|
19
20
|
return;
|
|
@@ -31,7 +32,7 @@ describe("AtlantisContext", () => {
|
|
|
31
32
|
it("should get the default values", () => {
|
|
32
33
|
const { result } = renderHook(() => useAtlantisContext());
|
|
33
34
|
|
|
34
|
-
expect(result.current).toMatchObject(
|
|
35
|
+
expect(result.current).toMatchObject(atlantisContextDefaultValues);
|
|
35
36
|
});
|
|
36
37
|
});
|
|
37
38
|
|
|
@@ -64,7 +64,7 @@ export interface AtlantisContextProps {
|
|
|
64
64
|
readonly setHeaderHeight: (height: number) => void;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
export const
|
|
67
|
+
export const atlantisContextDefaultValues: AtlantisContextProps = {
|
|
68
68
|
dateFormat: "PP",
|
|
69
69
|
// The system time is "p"
|
|
70
70
|
timeFormat: "p",
|
|
@@ -82,7 +82,7 @@ export const defaultValues: AtlantisContextProps = {
|
|
|
82
82
|
},
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
export const AtlantisContext = createContext(
|
|
85
|
+
export const AtlantisContext = createContext(atlantisContextDefaultValues);
|
|
86
86
|
|
|
87
87
|
export function useAtlantisContext(): AtlantisContextProps {
|
|
88
88
|
return useContext(AtlantisContext);
|
|
@@ -5,7 +5,7 @@ import { Alert } from "react-native";
|
|
|
5
5
|
import { ButtonGroup, ButtonGroupProps } from "./ButtonGroup";
|
|
6
6
|
import { Button } from "../Button";
|
|
7
7
|
import * as atlantisContext from "../AtlantisContext/AtlantisContext";
|
|
8
|
-
import {
|
|
8
|
+
import { atlantisContextDefaultValues } from "../AtlantisContext";
|
|
9
9
|
|
|
10
10
|
const mockOnOpen = jest.fn();
|
|
11
11
|
|
|
@@ -300,7 +300,7 @@ describe("ButtonGroup Offline/Online", () => {
|
|
|
300
300
|
it("should show an alert and not fire the onPress", () => {
|
|
301
301
|
const alertSpy = jest.spyOn(Alert, "alert");
|
|
302
302
|
atlantisContextSpy.mockReturnValue({
|
|
303
|
-
...
|
|
303
|
+
...atlantisContextDefaultValues,
|
|
304
304
|
isOnline: false,
|
|
305
305
|
});
|
|
306
306
|
|
package/src/Form/Form.test.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { Alert, Keyboard } from "react-native";
|
|
|
4
4
|
import { Host } from "react-native-portalize";
|
|
5
5
|
import { Form, FormBannerMessage, FormBannerMessageType } from ".";
|
|
6
6
|
import { FormBannerErrors, FormSubmitErrorType } from "./types";
|
|
7
|
-
import {
|
|
7
|
+
import { atlantisContextDefaultValues } from "../AtlantisContext";
|
|
8
8
|
import * as atlantisContext from "../AtlantisContext/AtlantisContext";
|
|
9
9
|
import { Text } from "../Text";
|
|
10
10
|
import { Checkbox } from "../Checkbox";
|
|
@@ -476,7 +476,7 @@ describe("Form", () => {
|
|
|
476
476
|
);
|
|
477
477
|
|
|
478
478
|
atlantisContextSpy.mockReturnValue({
|
|
479
|
-
...
|
|
479
|
+
...atlantisContextDefaultValues,
|
|
480
480
|
isOnline: false,
|
|
481
481
|
});
|
|
482
482
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { cleanup, render } from "@testing-library/react-native";
|
|
3
3
|
import { FormErrorBanner } from "./FormErrorBanner";
|
|
4
|
-
import {
|
|
4
|
+
import { atlantisContextDefaultValues } from "../../../AtlantisContext";
|
|
5
5
|
import * as atlantisContext from "../../../AtlantisContext/AtlantisContext";
|
|
6
6
|
|
|
7
7
|
describe("FormErrorBanner", () => {
|
|
@@ -9,7 +9,7 @@ describe("FormErrorBanner", () => {
|
|
|
9
9
|
|
|
10
10
|
beforeEach(() => {
|
|
11
11
|
atlantisContextSpy.mockReturnValue({
|
|
12
|
-
...
|
|
12
|
+
...atlantisContextDefaultValues,
|
|
13
13
|
isOnline: true,
|
|
14
14
|
});
|
|
15
15
|
});
|
|
@@ -3,7 +3,7 @@ import { renderHook } from "@testing-library/react-hooks";
|
|
|
3
3
|
import { AtlantisFormContextProps } from "./types";
|
|
4
4
|
import {
|
|
5
5
|
AtlantisFormContext,
|
|
6
|
-
|
|
6
|
+
atlantisFormContextDefaultValues,
|
|
7
7
|
useAtlantisFormContext,
|
|
8
8
|
} from "./AtlantisFormContext";
|
|
9
9
|
|
|
@@ -13,7 +13,6 @@ const useInternalFormLocalCacheMock = jest.fn();
|
|
|
13
13
|
const providerValues: AtlantisFormContextProps = {
|
|
14
14
|
useConfirmBeforeBack: useConfirmBeforeBackMock,
|
|
15
15
|
useInternalFormLocalCache: useInternalFormLocalCacheMock,
|
|
16
|
-
headerHeight: 50,
|
|
17
16
|
};
|
|
18
17
|
|
|
19
18
|
describe("AtlantisFormContext", () => {
|
|
@@ -25,7 +24,7 @@ describe("AtlantisFormContext", () => {
|
|
|
25
24
|
it("should get the default values", () => {
|
|
26
25
|
const { result } = renderHook(() => useAtlantisFormContext());
|
|
27
26
|
|
|
28
|
-
expect(result.current).toMatchObject(
|
|
27
|
+
expect(result.current).toMatchObject(atlantisFormContextDefaultValues);
|
|
29
28
|
});
|
|
30
29
|
});
|
|
31
30
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createContext, useContext, useRef } from "react";
|
|
2
2
|
import { AtlantisFormContextProps } from "./types";
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const atlantisFormContextDefaultValues = {
|
|
5
5
|
useConfirmBeforeBack: () => {
|
|
6
6
|
const ref = useRef(() => undefined);
|
|
7
7
|
return ref;
|
|
@@ -12,8 +12,9 @@ export const defaultValues = {
|
|
|
12
12
|
}),
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export const AtlantisFormContext =
|
|
16
|
-
|
|
15
|
+
export const AtlantisFormContext = createContext<AtlantisFormContextProps>(
|
|
16
|
+
atlantisFormContextDefaultValues,
|
|
17
|
+
);
|
|
17
18
|
|
|
18
19
|
export function useAtlantisFormContext(): AtlantisFormContextProps {
|
|
19
20
|
return useContext(AtlantisFormContext);
|
|
@@ -4,12 +4,12 @@ import { InputCurrency } from "./InputCurrency";
|
|
|
4
4
|
import {
|
|
5
5
|
AtlantisContext,
|
|
6
6
|
AtlantisContextProps,
|
|
7
|
-
|
|
7
|
+
atlantisContextDefaultValues,
|
|
8
8
|
} from "../AtlantisContext";
|
|
9
9
|
|
|
10
10
|
const mockCurrencySymbol = "£";
|
|
11
11
|
const atlantisContext: AtlantisContextProps = {
|
|
12
|
-
...
|
|
12
|
+
...atlantisContextDefaultValues,
|
|
13
13
|
currencySymbol: mockCurrencySymbol,
|
|
14
14
|
timeFormat: "p",
|
|
15
15
|
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
@@ -238,7 +238,7 @@ describe("InputDate", () => {
|
|
|
238
238
|
|
|
239
239
|
it("should display MM/DD/YYYY when dateFormat is 'P'", () => {
|
|
240
240
|
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
241
|
-
...atlantisContext.
|
|
241
|
+
...atlantisContext.atlantisContextDefaultValues,
|
|
242
242
|
dateFormat: "P",
|
|
243
243
|
});
|
|
244
244
|
const expectedDate = "05/24/2023";
|
|
@@ -252,7 +252,7 @@ describe("InputDate", () => {
|
|
|
252
252
|
|
|
253
253
|
it("should display mmmm d, yyyy when dateFormat is 'PP'", () => {
|
|
254
254
|
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
255
|
-
...atlantisContext.
|
|
255
|
+
...atlantisContext.atlantisContextDefaultValues,
|
|
256
256
|
dateFormat: "PP",
|
|
257
257
|
});
|
|
258
258
|
const expectedDate = "Feb 20, 2023";
|
|
@@ -266,7 +266,7 @@ describe("InputDate", () => {
|
|
|
266
266
|
|
|
267
267
|
it("should display mmmmm d, yyyy when dateFormat is 'PPP'", () => {
|
|
268
268
|
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
269
|
-
...atlantisContext.
|
|
269
|
+
...atlantisContext.atlantisContextDefaultValues,
|
|
270
270
|
dateFormat: "PPP",
|
|
271
271
|
});
|
|
272
272
|
const expectedDate = "July 7th, 2023";
|
|
@@ -280,7 +280,7 @@ describe("InputDate", () => {
|
|
|
280
280
|
|
|
281
281
|
it("should display dddd, mmmmm d, yyyy when dateFormat is 'PPPP'", () => {
|
|
282
282
|
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
283
|
-
...atlantisContext.
|
|
283
|
+
...atlantisContext.atlantisContextDefaultValues,
|
|
284
284
|
dateFormat: "PPPP",
|
|
285
285
|
});
|
|
286
286
|
const expectedDate = "Thursday, June 22nd, 2023";
|
|
@@ -3,12 +3,9 @@ import DateTimePicker from "react-native-modal-datetime-picker";
|
|
|
3
3
|
import { Platform } from "react-native";
|
|
4
4
|
import { FieldError, UseControllerProps } from "react-hook-form";
|
|
5
5
|
import { XOR } from "ts-xor";
|
|
6
|
-
import { utcToZonedTime } from "date-fns-tz";
|
|
7
|
-
import { format as formatDate } from "date-fns";
|
|
8
6
|
import { Clearable, InputFieldWrapperProps } from "../InputFieldWrapper";
|
|
9
7
|
import { FormField } from "../FormField";
|
|
10
8
|
import { InputPressable } from "../InputPressable";
|
|
11
|
-
import { useAtlantisContext } from "../AtlantisContext";
|
|
12
9
|
import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
|
|
13
10
|
|
|
14
11
|
interface BaseInputDateProps
|
|
@@ -153,8 +150,7 @@ function InternalInputDate({
|
|
|
153
150
|
accessibilityHint,
|
|
154
151
|
}: InputDateProps): JSX.Element {
|
|
155
152
|
const [showPicker, setShowPicker] = useState(false);
|
|
156
|
-
const { t, locale } = useAtlantisI18n();
|
|
157
|
-
const { timeZone, dateFormat } = useAtlantisContext();
|
|
153
|
+
const { t, locale, formatDate } = useAtlantisI18n();
|
|
158
154
|
|
|
159
155
|
const date = useMemo(() => {
|
|
160
156
|
if (typeof value === "string") return new Date(value);
|
|
@@ -163,12 +159,11 @@ function InternalInputDate({
|
|
|
163
159
|
|
|
164
160
|
const formattedDate = useMemo(() => {
|
|
165
161
|
if (date) {
|
|
166
|
-
|
|
167
|
-
return formatDate(zonedTime, dateFormat);
|
|
162
|
+
return formatDate(date);
|
|
168
163
|
}
|
|
169
164
|
|
|
170
165
|
return emptyValueLabel;
|
|
171
|
-
}, [date, emptyValueLabel
|
|
166
|
+
}, [date, emptyValueLabel]);
|
|
172
167
|
|
|
173
168
|
const canClearDate = formattedDate === emptyValueLabel ? "never" : clearable;
|
|
174
169
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createContext, useContext } from "react";
|
|
2
2
|
import { InputAccessoriesContextProps } from "./types";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const inputAccessoriesContextDefaultValues: InputAccessoriesContextProps = {
|
|
5
5
|
elements: {},
|
|
6
6
|
focusedInput: "",
|
|
7
7
|
canFocusNext: false,
|
|
@@ -14,7 +14,9 @@ const defaultValues: InputAccessoriesContextProps = {
|
|
|
14
14
|
setFocusedInput: () => undefined,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export const InputAccessoriesContext = createContext(
|
|
17
|
+
export const InputAccessoriesContext = createContext(
|
|
18
|
+
inputAccessoriesContextDefaultValues,
|
|
19
|
+
);
|
|
18
20
|
|
|
19
21
|
export function useInputAccessoriesContext(): InputAccessoriesContextProps {
|
|
20
22
|
return useContext(InputAccessoriesContext);
|
|
@@ -162,7 +162,7 @@ describe("Time picker", () => {
|
|
|
162
162
|
|
|
163
163
|
it("should be set to 24 hours", () => {
|
|
164
164
|
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
165
|
-
...atlantisContext.
|
|
165
|
+
...atlantisContext.atlantisContextDefaultValues,
|
|
166
166
|
timeZone: "UTC",
|
|
167
167
|
timeFormat: "HH:mm",
|
|
168
168
|
});
|
|
@@ -295,7 +295,7 @@ describe("Timezone conversion", () => {
|
|
|
295
295
|
);
|
|
296
296
|
it("should display the time in the account timezone", async () => {
|
|
297
297
|
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
298
|
-
...atlantisContext.
|
|
298
|
+
...atlantisContext.atlantisContextDefaultValues,
|
|
299
299
|
timeZone: "America/Los_Angeles",
|
|
300
300
|
});
|
|
301
301
|
|
|
@@ -309,7 +309,7 @@ describe("Timezone conversion", () => {
|
|
|
309
309
|
|
|
310
310
|
it("should have the correct offset on the time picker", async () => {
|
|
311
311
|
jest.spyOn(atlantisContext, "useAtlantisContext").mockReturnValue({
|
|
312
|
-
...atlantisContext.
|
|
312
|
+
...atlantisContext.atlantisContextDefaultValues,
|
|
313
313
|
timeZone: "America/Los_Angeles",
|
|
314
314
|
});
|
|
315
315
|
|
|
@@ -3,8 +3,6 @@ import { FieldError, UseControllerProps } from "react-hook-form";
|
|
|
3
3
|
import { XOR } from "ts-xor";
|
|
4
4
|
import DateTimePicker from "react-native-modal-datetime-picker";
|
|
5
5
|
import { View } from "react-native";
|
|
6
|
-
import { utcToZonedTime } from "date-fns-tz";
|
|
7
|
-
import { format as formatTime } from "date-fns";
|
|
8
6
|
import { styles } from "./InputTime.style";
|
|
9
7
|
import { getTimeZoneOffsetInMinutes, roundUpToNearestMinutes } from "./utils";
|
|
10
8
|
import { useAtlantisContext } from "../AtlantisContext";
|
|
@@ -134,9 +132,9 @@ function InternalInputTime({
|
|
|
134
132
|
showIcon = true,
|
|
135
133
|
}: InputTimeProps): JSX.Element {
|
|
136
134
|
const [showPicker, setShowPicker] = useState(false);
|
|
137
|
-
const { t } = useAtlantisI18n();
|
|
138
|
-
|
|
135
|
+
const { t, formatTime } = useAtlantisI18n();
|
|
139
136
|
const { timeZone, timeFormat } = useAtlantisContext();
|
|
137
|
+
|
|
140
138
|
const is24Hour = timeFormat === "HH:mm";
|
|
141
139
|
|
|
142
140
|
const dateTime = useMemo(
|
|
@@ -146,12 +144,11 @@ function InternalInputTime({
|
|
|
146
144
|
|
|
147
145
|
const formattedTime = useMemo(() => {
|
|
148
146
|
if (dateTime) {
|
|
149
|
-
|
|
150
|
-
return formatTime(zonedTime, timeFormat);
|
|
147
|
+
return formatTime(dateTime);
|
|
151
148
|
}
|
|
152
149
|
|
|
153
150
|
return emptyValueLabel;
|
|
154
|
-
}, [dateTime, emptyValueLabel
|
|
151
|
+
}, [dateTime, emptyValueLabel]);
|
|
155
152
|
|
|
156
153
|
const canClearTime = formattedTime === emptyValueLabel ? "never" : clearable;
|
|
157
154
|
|
|
@@ -10,6 +10,15 @@ jest.mock("../../AtlantisContext", () => ({
|
|
|
10
10
|
...jest.requireActual("../../AtlantisContext"),
|
|
11
11
|
}));
|
|
12
12
|
|
|
13
|
+
const spy = jest.spyOn(context, "useAtlantisContext");
|
|
14
|
+
const testDate = new Date("2020-01-01T00:00:00.000Z");
|
|
15
|
+
const dateAfterSpringForward = new Date("2020-04-10T00:00:00.000Z");
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.useFakeTimers();
|
|
19
|
+
jest.setSystemTime(testDate);
|
|
20
|
+
});
|
|
21
|
+
|
|
13
22
|
describe("useAtlantisI18n", () => {
|
|
14
23
|
it("should return english by default", () => {
|
|
15
24
|
const { result } = renderHook(useAtlantisI18n);
|
|
@@ -27,8 +36,10 @@ describe("useAtlantisI18n", () => {
|
|
|
27
36
|
|
|
28
37
|
describe("Español", () => {
|
|
29
38
|
it("should return español", () => {
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
spy.mockReturnValueOnce({
|
|
40
|
+
...context.atlantisContextDefaultValues,
|
|
41
|
+
locale: "es",
|
|
42
|
+
});
|
|
32
43
|
const { result } = renderHook(useAtlantisI18n);
|
|
33
44
|
|
|
34
45
|
expect(result.current.t("cancel")).toBe("Cancelar");
|
|
@@ -37,8 +48,10 @@ describe("useAtlantisI18n", () => {
|
|
|
37
48
|
|
|
38
49
|
describe("Unsupported language", () => {
|
|
39
50
|
it("should return the english translation", () => {
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
spy.mockReturnValueOnce({
|
|
52
|
+
...context.atlantisContextDefaultValues,
|
|
53
|
+
locale: "fr",
|
|
54
|
+
});
|
|
42
55
|
const { result } = renderHook(useAtlantisI18n);
|
|
43
56
|
|
|
44
57
|
expect(result.current.t("cancel")).toBe("Cancel");
|
|
@@ -50,4 +63,92 @@ describe("useAtlantisI18n", () => {
|
|
|
50
63
|
expect(Object.keys(en)).toEqual(Object.keys(es));
|
|
51
64
|
});
|
|
52
65
|
});
|
|
66
|
+
|
|
67
|
+
describe("formatDate", () => {
|
|
68
|
+
it("should return the formatted date", () => {
|
|
69
|
+
const { result } = renderHook(useAtlantisI18n);
|
|
70
|
+
expect(result.current.formatDate(testDate)).toBe("Jan 1, 2020");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should return the date formatted for es", () => {
|
|
74
|
+
spy.mockReturnValueOnce({
|
|
75
|
+
...context.atlantisContextDefaultValues,
|
|
76
|
+
locale: "es",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const { result } = renderHook(useAtlantisI18n);
|
|
80
|
+
expect(result.current.formatDate(testDate)).toBe("1 ene 2020");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("Timezone", () => {
|
|
84
|
+
it.each([
|
|
85
|
+
["America/New_York", "Dec 31, 2019"],
|
|
86
|
+
["America/Chicago", "Dec 31, 2019"],
|
|
87
|
+
["America/Denver", "Dec 31, 2019"],
|
|
88
|
+
["Europe/London", "Jan 1, 2020"],
|
|
89
|
+
["Australia/Sydney", "Jan 1, 2020"],
|
|
90
|
+
])("should return the %s time", (timeZone, expected) => {
|
|
91
|
+
spy.mockReturnValueOnce({
|
|
92
|
+
...context.atlantisContextDefaultValues,
|
|
93
|
+
timeZone,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const { result } = renderHook(useAtlantisI18n);
|
|
97
|
+
expect(result.current.formatDate(testDate)).toBe(expected);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("formatTime", () => {
|
|
103
|
+
it("should return the formatted time", () => {
|
|
104
|
+
const { result } = renderHook(useAtlantisI18n);
|
|
105
|
+
expect(result.current.formatTime(testDate)).toBe("12:00 AM");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should return the date formatted for es", () => {
|
|
109
|
+
spy.mockReturnValueOnce({
|
|
110
|
+
...context.atlantisContextDefaultValues,
|
|
111
|
+
locale: "es",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const { result } = renderHook(useAtlantisI18n);
|
|
115
|
+
expect(result.current.formatTime(testDate)).toBe("00:00");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("Timezone", () => {
|
|
119
|
+
it.each([
|
|
120
|
+
["America/New_York", "7:00 PM"],
|
|
121
|
+
["America/Chicago", "6:00 PM"],
|
|
122
|
+
["America/Denver", "5:00 PM"],
|
|
123
|
+
["Europe/London", "12:00 AM"],
|
|
124
|
+
["Australia/Sydney", "11:00 AM"],
|
|
125
|
+
])("should return the %s zoned time", (timeZone, expected) => {
|
|
126
|
+
spy.mockReturnValueOnce({
|
|
127
|
+
...context.atlantisContextDefaultValues,
|
|
128
|
+
timeZone,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const { result } = renderHook(useAtlantisI18n);
|
|
132
|
+
expect(result.current.formatTime(testDate)).toBe(expected);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it.each([
|
|
136
|
+
["America/New_York", "8:00 PM"],
|
|
137
|
+
["America/Chicago", "7:00 PM"],
|
|
138
|
+
["America/Denver", "6:00 PM"],
|
|
139
|
+
["Europe/London", "1:00 AM"],
|
|
140
|
+
["Australia/Sydney", "10:00 AM"],
|
|
141
|
+
])("should return the %s spring zoned time", (timeZone, expected) => {
|
|
142
|
+
spy.mockReturnValueOnce({
|
|
143
|
+
...context.atlantisContextDefaultValues,
|
|
144
|
+
timeZone,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const { result } = renderHook(useAtlantisI18n);
|
|
148
|
+
expect(result.current.formatTime(dateAfterSpringForward)).toBe(
|
|
149
|
+
expected,
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
53
154
|
});
|
|
@@ -1,20 +1,56 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
1
2
|
import en from "./locales/en.json";
|
|
2
3
|
import es from "./locales/es.json";
|
|
4
|
+
import { dateFormatter } from "./utils/dateFormatter";
|
|
3
5
|
import { useAtlantisContext } from "../../AtlantisContext";
|
|
4
6
|
|
|
5
7
|
export type I18nKeys = keyof typeof en;
|
|
6
8
|
|
|
7
9
|
export interface useAtlantisI18nValue {
|
|
10
|
+
/**
|
|
11
|
+
* The set locale based on the AtlantisContext.
|
|
12
|
+
*/
|
|
8
13
|
readonly locale: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns the translated string depending on the locale. This accepts a 2nd
|
|
17
|
+
* param for string interpolation.
|
|
18
|
+
*/
|
|
9
19
|
readonly t: (message: I18nKeys, values?: Record<string, string>) => string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns a formatted date string based on the locale and the `dateFormat`
|
|
23
|
+
* set in AtlantisContext.
|
|
24
|
+
*/
|
|
25
|
+
readonly formatDate: (date: Date) => string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns a formatted time string based on the locale and the `timeFormat`
|
|
29
|
+
* set in AtlantisContext.
|
|
30
|
+
*/
|
|
31
|
+
readonly formatTime: (date: Date) => string;
|
|
10
32
|
}
|
|
11
33
|
|
|
12
34
|
export function useAtlantisI18n(): useAtlantisI18nValue {
|
|
13
|
-
const { locale } = useAtlantisContext();
|
|
14
|
-
|
|
15
|
-
|
|
35
|
+
const { locale, dateFormat, timeFormat, timeZone } = useAtlantisContext();
|
|
36
|
+
|
|
37
|
+
const t = useCallback(
|
|
38
|
+
(messageKey: keyof typeof en, values?: Record<string, string>) =>
|
|
39
|
+
formatMessage(messageKey, values, locale),
|
|
40
|
+
[formatMessage, locale],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const formatDate = useCallback(
|
|
44
|
+
(date: Date) => dateFormatter(date, dateFormat, { locale, timeZone }),
|
|
45
|
+
[dateFormatter, locale],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const formatTime = useCallback(
|
|
49
|
+
(date: Date) => dateFormatter(date, timeFormat, { locale, timeZone }),
|
|
50
|
+
[dateFormatter, locale],
|
|
51
|
+
);
|
|
16
52
|
|
|
17
|
-
return { locale, t };
|
|
53
|
+
return { locale, t, formatDate, formatTime };
|
|
18
54
|
}
|
|
19
55
|
|
|
20
56
|
function getLocalizedStrings(locale: string): typeof en {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { format } from "date-fns";
|
|
2
|
+
import { utcToZonedTime } from "date-fns-tz";
|
|
3
|
+
import { es } from "date-fns/locale";
|
|
4
|
+
|
|
5
|
+
interface DateFormatterOptions {
|
|
6
|
+
readonly locale: string;
|
|
7
|
+
readonly timeZone: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function dateFormatter(
|
|
11
|
+
date: Date,
|
|
12
|
+
dateTimeFormat: string,
|
|
13
|
+
{ locale, timeZone }: DateFormatterOptions,
|
|
14
|
+
): string {
|
|
15
|
+
const zonedTime = utcToZonedTime(date, timeZone);
|
|
16
|
+
return format(zonedTime, dateTimeFormat, {
|
|
17
|
+
locale: getDateFnsLocale(locale),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Change locale string to date-fns locale object.
|
|
23
|
+
*/
|
|
24
|
+
function getDateFnsLocale(locale?: string): Locale | undefined {
|
|
25
|
+
switch (locale) {
|
|
26
|
+
case "es":
|
|
27
|
+
return es;
|
|
28
|
+
default:
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|