@jobber/components-native 0.113.4-implement--94d34f8.2 → 0.113.4-implement--97f86be.3

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.
@@ -5,16 +5,22 @@ tokens.
5
5
 
6
6
  ## Design & usage guidelines
7
7
 
8
- Both the web and mobile components have the exact same API, except for one minor
9
- difference in how you update the theme.
8
+ Web and mobile both expose Atlantis theme context, but the runtime ownership is
9
+ slightly different across platforms.
10
10
 
11
11
  Each platform provides a `useAtlantisTheme` hook that you may use to access the
12
- `theme` and `tokens` in your components.
12
+ current theme information and design tokens in your components.
13
13
 
14
- On mobile, this hook also returns a `setTheme` function which you'll use to
15
- update the theme for the nearest `AtlantisThemeContextProvider` ancestor.
16
- Typically there will only be a single provider at the root, controlling the
17
- theme for the entire app.
14
+ On mobile, the hook returns:
15
+
16
+ * `theme`: the selected theme preference (`"system" | "light" | "dark"`)
17
+ * `effectiveTheme`: the resolved theme Atlantis uses for tokens
18
+ (`"light" | "dark"`)
19
+ * `tokens`
20
+ * `setTheme`
21
+
22
+ Typically there will only be a single provider at the root, and the consuming
23
+ app will control `theme` and persist that preference outside Atlantis.
18
24
 
19
25
  On web, you'll need to import the `updateTheme` function and call it with the
20
26
  new theme. This is a separate function because it synchronizes the theme update
@@ -86,7 +92,7 @@ function App() {
86
92
  }
87
93
 
88
94
  function ThemedComponent() {
89
- const { theme, tokens, setTheme } = useAtlantisTheme();
95
+ const { theme, effectiveTheme, tokens, setTheme } = useAtlantisTheme();
90
96
  return (
91
97
  <Content>
92
98
  <View
@@ -96,11 +102,16 @@ function ThemedComponent() {
96
102
  }}
97
103
  >
98
104
  <Content>
99
- <Text>The current theme is: {theme}.</Text>
105
+ <Text>The selected theme is: {theme}.</Text>
106
+ <Text>The effective theme is: {effectiveTheme}.</Text>
100
107
  <Text>
101
108
  The javascript tokens can be accessed via the tokens object.
102
109
  </Text>
103
110
  <Text>The theme can be changed using `setTheme`</Text>
111
+ <Button
112
+ onPress={() => setTheme("system")}
113
+ label="The theme can be changed to system"
114
+ />
104
115
  <Button
105
116
  onPress={() => setTheme("light")}
106
117
  label="The theme can be changed to light"
@@ -163,6 +174,10 @@ regardless of the main application theme. This can be done by setting the
163
174
  `dangerouslyOverrideTheme` prop to `<themeToSet>` on the
164
175
  `AtlantisThemeContextProvider`.
165
176
 
177
+ On mobile, forcing a theme changes the rendered tokens for that subtree, but it
178
+ does not replace the selected theme preference semantics from the nearest
179
+ non-forced provider.
180
+
166
181
  Note: If you're using `buildThemedStyles`, any `useStyles` calls must be done
167
182
  within a *child component* of the `AtlantisThemeContextProvider`, not in the
168
183
  same component that renders the provider. Same goes for `useAtlantisTheme`.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.113.4-implement--94d34f8.2+94d34f81",
3
+ "version": "0.113.4-implement--97f86be.3+97f86be1",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -124,5 +124,5 @@
124
124
  "react-native-screens": ">=4.18.0",
125
125
  "react-native-svg": ">=12.0.0"
126
126
  },
127
- "gitHead": "94d34f81c5579bf7aec109cd129a94ea91aadf56"
127
+ "gitHead": "97f86be1230044c71581dea90ac836ab6a65f580"
128
128
  }
@@ -8,26 +8,34 @@ const lightTokens = Platform.select({
8
8
  default: () => androidTokens,
9
9
  })();
10
10
  const completeDarkTokens = merge({}, lightTokens, darkTokens);
11
- const AtlantisThemeContext = createContext({
11
+ const defaultAtlantisThemeContextValue = {
12
12
  theme: "light",
13
13
  effectiveTheme: "light",
14
14
  tokens: lightTokens,
15
15
  setTheme: () => {
16
16
  console.error("useAtlantisTheme accessed outside of AtlantisThemeContextProvider");
17
17
  },
18
- });
18
+ };
19
+ const AtlantisThemeContext = createContext(undefined);
19
20
  export function AtlantisThemeContextProvider({ children, theme, onThemeChange, dangerouslyOverrideTheme, }) {
20
- var _a;
21
- const [uncontrolledTheme, setUncontrolledTheme] = useState("light");
21
+ var _a, _b;
22
+ const parentThemeContext = useContext(AtlantisThemeContext);
23
+ const [localTheme, setLocalTheme] = useState(undefined);
22
24
  const colorScheme = useColorScheme();
23
25
  const setTheme = useCallback((nextTheme) => {
24
26
  onThemeChange === null || onThemeChange === void 0 ? void 0 : onThemeChange(nextTheme);
25
- if (theme === undefined) {
26
- setUncontrolledTheme(nextTheme);
27
+ if (theme !== undefined)
28
+ return;
29
+ // A forced visual override should preserve the parent subtree's selected
30
+ // theme semantics while still rendering with the forced tokens.
31
+ if (dangerouslyOverrideTheme !== undefined && parentThemeContext) {
32
+ parentThemeContext.setTheme(nextTheme);
33
+ return;
27
34
  }
28
- }, [onThemeChange, theme]);
29
- const currentTheme = (_a = dangerouslyOverrideTheme !== null && dangerouslyOverrideTheme !== void 0 ? dangerouslyOverrideTheme : theme) !== null && _a !== void 0 ? _a : uncontrolledTheme;
30
- const effectiveTheme = resolveEffectiveTheme(currentTheme, colorScheme);
35
+ setLocalTheme(nextTheme);
36
+ }, [dangerouslyOverrideTheme, onThemeChange, parentThemeContext, theme]);
37
+ const currentTheme = (_b = (_a = theme !== null && theme !== void 0 ? theme : localTheme) !== null && _a !== void 0 ? _a : parentThemeContext === null || parentThemeContext === void 0 ? void 0 : parentThemeContext.theme) !== null && _b !== void 0 ? _b : "light";
38
+ const effectiveTheme = dangerouslyOverrideTheme !== null && dangerouslyOverrideTheme !== void 0 ? dangerouslyOverrideTheme : resolveEffectiveTheme(currentTheme, colorScheme);
31
39
  const currentTokens = effectiveTheme === "dark" ? completeDarkTokens : lightTokens;
32
40
  return (React.createElement(AtlantisThemeContext.Provider, { value: {
33
41
  theme: currentTheme,
@@ -37,7 +45,8 @@ export function AtlantisThemeContextProvider({ children, theme, onThemeChange, d
37
45
  } }, children));
38
46
  }
39
47
  export function useAtlantisTheme() {
40
- return useContext(AtlantisThemeContext);
48
+ var _a;
49
+ return (_a = useContext(AtlantisThemeContext)) !== null && _a !== void 0 ? _a : defaultAtlantisThemeContextValue;
41
50
  }
42
51
  function resolveEffectiveTheme(theme, colorScheme) {
43
52
  if (theme !== "system")
@@ -102,14 +102,33 @@ describe("ThemeContext", () => {
102
102
  expect(result.current.tokens).toEqual(expectedLightTokens);
103
103
  }));
104
104
  describe("when theme is forced for provider", () => {
105
- it("ignores updates to the global theme", () => __awaiter(void 0, void 0, void 0, function* () {
105
+ it("preserves the forced effective theme when the selected theme changes", () => __awaiter(void 0, void 0, void 0, function* () {
106
106
  const { result } = renderHook(useAtlantisTheme, {
107
107
  wrapper: (props) => (React.createElement(WrapperWithOverride, Object.assign({}, props, { dangerouslyOverrideTheme: "light" }))),
108
108
  });
109
- // Update the global theme
109
+ // Update the selected theme through the forced subtree.
110
110
  yield act(() => __awaiter(void 0, void 0, void 0, function* () { return result.current.setTheme("dark"); }));
111
- // This hook shouldn't be affected by it because it's set to the light theme
112
- expect(result.current.theme).toBe("light");
111
+ expect(result.current.theme).toBe("dark");
112
+ expect(result.current.effectiveTheme).toBe("light");
113
+ expect(result.current.tokens).toEqual(expectedLightTokens);
114
+ }));
115
+ it("preserves the parent selected theme while forcing the effective theme", () => {
116
+ mockUseColorScheme.mockReturnValue("dark");
117
+ const { result } = renderHook(useAtlantisTheme, {
118
+ wrapper: (props) => (React.createElement(Wrapper, { theme: "system" },
119
+ React.createElement(AtlantisThemeContextProvider, Object.assign({ dangerouslyOverrideTheme: "light" }, props)))),
120
+ });
121
+ expect(result.current.theme).toBe("system");
122
+ expect(result.current.effectiveTheme).toBe("light");
123
+ expect(result.current.tokens).toEqual(expectedLightTokens);
124
+ });
125
+ it("forwards selected theme changes through a forced subtree", () => __awaiter(void 0, void 0, void 0, function* () {
126
+ const { result } = renderHook(useAtlantisTheme, {
127
+ wrapper: (props) => (React.createElement(Wrapper, null,
128
+ React.createElement(AtlantisThemeContextProvider, Object.assign({ dangerouslyOverrideTheme: "light" }, props)))),
129
+ });
130
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () { return result.current.setTheme("dark"); }));
131
+ expect(result.current.theme).toBe("dark");
113
132
  expect(result.current.effectiveTheme).toBe("light");
114
133
  expect(result.current.tokens).toEqual(expectedLightTokens);
115
134
  }));
@@ -131,7 +150,7 @@ describe("ThemeContext", () => {
131
150
  const { result } = renderHook(useAtlantisTheme, {
132
151
  wrapper: (props) => (React.createElement(WrapperWithOverride, Object.assign({}, props, { dangerouslyOverrideTheme: defaultTheme }))),
133
152
  });
134
- expect(result.current.theme).toBe(defaultTheme);
153
+ expect(result.current.theme).toBe("light");
135
154
  expect(result.current.effectiveTheme).toBe(expectedEffectiveTheme);
136
155
  expect(result.current.tokens).toEqual(expectedTokens);
137
156
  });