@jobber/components-native 0.76.0 → 0.77.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,3 @@
1
+ import { AtlantisThemeContextProviderProps, AtlantisThemeContextValue } from "./types";
2
+ export declare function AtlantisThemeContextProvider({ children, dangerouslyOverrideTheme, }: AtlantisThemeContextProviderProps): JSX.Element;
3
+ export declare function useAtlantisTheme(): AtlantisThemeContextValue;
@@ -0,0 +1,2 @@
1
+ export { AtlantisThemeContextProvider, useAtlantisTheme, } from "./AtlantisThemeContext";
2
+ export { Theme, AtlantisThemeContextProviderProps, AtlantisThemeContextValue, } from "./types";
@@ -0,0 +1,27 @@
1
+ import { iosTokens } from "@jobber/design";
2
+ export interface AtlantisThemeContextValue {
3
+ /**
4
+ * The active theme.
5
+ */
6
+ readonly theme: Theme;
7
+ /**
8
+ * The design tokens for the current theme.
9
+ */
10
+ readonly tokens: typeof iosTokens;
11
+ /**
12
+ * Change the current theme globally.
13
+ */
14
+ readonly setTheme: (theme: Theme) => void;
15
+ }
16
+ export interface AtlantisThemeContextProviderProps {
17
+ /**
18
+ * The children to render.
19
+ */
20
+ readonly children: React.ReactNode;
21
+ /**
22
+ * Force the theme for this provider to always be the same as the provided theme. Useful for sections that should remain the same theme regardless of the rest of the application's theme.
23
+ * This is dangerous because the children in this provider will not be able to change the theme.
24
+ */
25
+ readonly dangerouslyOverrideTheme?: Theme;
26
+ }
27
+ export type Theme = "light" | "dark";
@@ -2,6 +2,7 @@ export * from "./ActionItem";
2
2
  export * from "./ActionLabel";
3
3
  export * from "./ActivityIndicator";
4
4
  export * from "./AtlantisContext";
5
+ export * from "./AtlantisThemeContext";
5
6
  export * from "./AutoLink";
6
7
  export * from "./Banner";
7
8
  export * from "./BottomSheet";
@@ -1,2 +1,7 @@
1
1
  import { iosTokens } from "@jobber/design";
2
+ /**
3
+ * These design tokens don't react to theme changes. Only use these if you need to access
4
+ * tokens that are not affected by the current theme, otherwise you should be using our
5
+ * useAtlantisTheme() hook to access the current theme's tokens.
6
+ */
2
7
  export declare const tokens: typeof iosTokens;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.76.0",
3
+ "version": "0.77.0",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -80,5 +80,5 @@
80
80
  "react-native-safe-area-context": "^4.5.2",
81
81
  "react-native-svg": ">=12.0.0"
82
82
  },
83
- "gitHead": "3dbb361d102c6ae833d1dce373125b7bf68508c0"
83
+ "gitHead": "d0a55950b30bacc680792db9640d0ef1f9bc6f23"
84
84
  }
@@ -0,0 +1,106 @@
1
+ import React from "react";
2
+ import { act, renderHook } from "@testing-library/react-native";
3
+ import { darkTokens, iosTokens } from "@jobber/design";
4
+ import merge from "lodash/merge";
5
+ import {
6
+ AtlantisThemeContextProvider,
7
+ useAtlantisTheme,
8
+ } from "./AtlantisThemeContext";
9
+ import { AtlantisThemeContextProviderProps, Theme } from "./types";
10
+
11
+ const expectedDarkTokens = merge({}, iosTokens, darkTokens);
12
+ const expectedLightTokens = iosTokens;
13
+
14
+ function Wrapper({
15
+ children,
16
+ dangerouslyOverrideTheme,
17
+ }: AtlantisThemeContextProviderProps) {
18
+ return (
19
+ <AtlantisThemeContextProvider
20
+ dangerouslyOverrideTheme={dangerouslyOverrideTheme}
21
+ >
22
+ {children}
23
+ </AtlantisThemeContextProvider>
24
+ );
25
+ }
26
+
27
+ function WrapperWithOverride({
28
+ children,
29
+ dangerouslyOverrideTheme,
30
+ }: AtlantisThemeContextProviderProps) {
31
+ return (
32
+ <Wrapper>
33
+ <AtlantisThemeContextProvider
34
+ dangerouslyOverrideTheme={dangerouslyOverrideTheme}
35
+ >
36
+ {children}
37
+ </AtlantisThemeContextProvider>
38
+ </Wrapper>
39
+ );
40
+ }
41
+
42
+ describe("ThemeContext", () => {
43
+ it("defaults to the light theme", () => {
44
+ const { result } = renderHook(useAtlantisTheme, {
45
+ wrapper: (props: AtlantisThemeContextProviderProps) => (
46
+ <Wrapper {...props} />
47
+ ),
48
+ });
49
+
50
+ expect(result.current.theme).toBe("light");
51
+ expect(result.current.tokens).toEqual(expectedLightTokens);
52
+ });
53
+
54
+ it("updates the theme and tokens", () => {
55
+ const { result } = renderHook(useAtlantisTheme, {
56
+ wrapper: (props: AtlantisThemeContextProviderProps) => (
57
+ <Wrapper {...props} />
58
+ ),
59
+ });
60
+
61
+ act(() => result.current.setTheme("dark"));
62
+ expect(result.current.theme).toBe("dark");
63
+ expect(result.current.tokens).toEqual(expectedDarkTokens);
64
+
65
+ act(() => result.current.setTheme("light"));
66
+ expect(result.current.theme).toBe("light");
67
+ expect(result.current.tokens).toEqual(expectedLightTokens);
68
+ });
69
+
70
+ describe("when theme is forced for provider", () => {
71
+ it("ignores updates to the global theme", () => {
72
+ const { result } = renderHook(useAtlantisTheme, {
73
+ wrapper: (props: AtlantisThemeContextProviderProps) => (
74
+ <WrapperWithOverride {...props} dangerouslyOverrideTheme="light" />
75
+ ),
76
+ });
77
+
78
+ // Update the global theme
79
+ act(() => result.current.setTheme("dark"));
80
+
81
+ // This hook shouldn't be affected by it because it's set to the light theme
82
+ expect(result.current.theme).toBe("light");
83
+ expect(result.current.tokens).toEqual(expectedLightTokens);
84
+ });
85
+
86
+ it.each([
87
+ { defaultTheme: "light", expectedTokens: expectedLightTokens },
88
+ { defaultTheme: "dark", expectedTokens: expectedDarkTokens },
89
+ ] as { defaultTheme: Theme; expectedTokens: typeof iosTokens }[])(
90
+ "provides the dangerouslyOverrideTheme $defaultTheme tokens",
91
+ ({ defaultTheme, expectedTokens }) => {
92
+ const { result } = renderHook(useAtlantisTheme, {
93
+ wrapper: (props: AtlantisThemeContextProviderProps) => (
94
+ <WrapperWithOverride
95
+ {...props}
96
+ dangerouslyOverrideTheme={defaultTheme}
97
+ />
98
+ ),
99
+ });
100
+
101
+ expect(result.current.theme).toBe(defaultTheme);
102
+ expect(result.current.tokens).toEqual(expectedTokens);
103
+ },
104
+ );
105
+ });
106
+ });
@@ -0,0 +1,56 @@
1
+ import { androidTokens, darkTokens, iosTokens } from "@jobber/design";
2
+ import React, { createContext, useContext, useState } from "react";
3
+ import merge from "lodash/merge";
4
+ import { Platform } from "react-native";
5
+ import {
6
+ AtlantisThemeContextProviderProps,
7
+ AtlantisThemeContextValue,
8
+ Theme,
9
+ } from "./types";
10
+
11
+ const lightTokens = Platform.select({
12
+ ios: () => iosTokens,
13
+ android: () => androidTokens,
14
+ default: () => androidTokens,
15
+ })();
16
+
17
+ const completeDarkTokens = merge({}, lightTokens, darkTokens);
18
+
19
+ const AtlantisThemeContext = createContext<AtlantisThemeContextValue>({
20
+ theme: "light",
21
+ tokens: lightTokens,
22
+ setTheme: () => {
23
+ console.error(
24
+ "useAtlantisTheme accessed outside of AtlantisThemeContextProvider",
25
+ );
26
+ },
27
+ });
28
+
29
+ export function AtlantisThemeContextProvider({
30
+ children,
31
+ dangerouslyOverrideTheme,
32
+ }: AtlantisThemeContextProviderProps) {
33
+ // TODO: check last saved theme from local/device storage
34
+ const initialTheme: Theme = "light";
35
+ const [globalTheme, setGlobalTheme] = useState<Theme>(initialTheme);
36
+
37
+ const currentTheme = dangerouslyOverrideTheme ?? globalTheme;
38
+ const currentTokens =
39
+ currentTheme === "dark" ? completeDarkTokens : lightTokens;
40
+
41
+ return (
42
+ <AtlantisThemeContext.Provider
43
+ value={{
44
+ theme: currentTheme,
45
+ tokens: currentTokens,
46
+ setTheme: setGlobalTheme,
47
+ }}
48
+ >
49
+ {children}
50
+ </AtlantisThemeContext.Provider>
51
+ );
52
+ }
53
+
54
+ export function useAtlantisTheme() {
55
+ return useContext(AtlantisThemeContext);
56
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ AtlantisThemeContextProvider,
3
+ useAtlantisTheme,
4
+ } from "./AtlantisThemeContext";
5
+ export {
6
+ Theme,
7
+ AtlantisThemeContextProviderProps,
8
+ AtlantisThemeContextValue,
9
+ } from "./types";
@@ -0,0 +1,33 @@
1
+ import { iosTokens } from "@jobber/design";
2
+
3
+ export interface AtlantisThemeContextValue {
4
+ /**
5
+ * The active theme.
6
+ */
7
+ readonly theme: Theme;
8
+
9
+ /**
10
+ * The design tokens for the current theme.
11
+ */
12
+ readonly tokens: typeof iosTokens;
13
+
14
+ /**
15
+ * Change the current theme globally.
16
+ */
17
+ readonly setTheme: (theme: Theme) => void;
18
+ }
19
+
20
+ export interface AtlantisThemeContextProviderProps {
21
+ /**
22
+ * The children to render.
23
+ */
24
+ readonly children: React.ReactNode;
25
+
26
+ /**
27
+ * Force the theme for this provider to always be the same as the provided theme. Useful for sections that should remain the same theme regardless of the rest of the application's theme.
28
+ * This is dangerous because the children in this provider will not be able to change the theme.
29
+ */
30
+ readonly dangerouslyOverrideTheme?: Theme;
31
+ }
32
+
33
+ export type Theme = "light" | "dark";
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from "./ActionItem";
2
2
  export * from "./ActionLabel";
3
3
  export * from "./ActivityIndicator";
4
4
  export * from "./AtlantisContext";
5
+ export * from "./AtlantisThemeContext";
5
6
  export * from "./AutoLink";
6
7
  export * from "./Banner";
7
8
  export * from "./BottomSheet";
@@ -1,6 +1,11 @@
1
1
  import { Platform } from "react-native";
2
2
  import { androidTokens, iosTokens } from "@jobber/design";
3
3
 
4
+ /**
5
+ * These design tokens don't react to theme changes. Only use these if you need to access
6
+ * tokens that are not affected by the current theme, otherwise you should be using our
7
+ * useAtlantisTheme() hook to access the current theme's tokens.
8
+ */
4
9
  export const tokens: typeof iosTokens = Platform.select({
5
10
  ios: () => iosTokens,
6
11
  android: () => androidTokens,
@@ -8,6 +8,7 @@
8
8
  "AtlantisContext.Provider",
9
9
  "AtlantisFormContext.Consumer",
10
10
  "AtlantisFormContext.Provider",
11
+ "AtlantisThemeContextProvider",
11
12
  "AutoLink",
12
13
  "Banner",
13
14
  "BottomSheet",