@jobber/components-native 0.113.4-implement--94d34f8.2 → 0.113.5

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.
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
2
  import type { AtlantisThemeContextProviderProps, AtlantisThemeContextValue } from "./types";
3
- export declare function AtlantisThemeContextProvider({ children, theme, onThemeChange, dangerouslyOverrideTheme, }: AtlantisThemeContextProviderProps): React.JSX.Element;
3
+ export declare function AtlantisThemeContextProvider({ children, dangerouslyOverrideTheme, }: AtlantisThemeContextProviderProps): React.JSX.Element;
4
4
  export declare function useAtlantisTheme(): AtlantisThemeContextValue;
@@ -1,3 +1,3 @@
1
1
  export { AtlantisThemeContextProvider, useAtlantisTheme, } from "./AtlantisThemeContext";
2
- export type { EffectiveTheme, Theme, AtlantisThemeContextProviderProps, AtlantisThemeContextValue, } from "./types";
2
+ export type { Theme, AtlantisThemeContextProviderProps, AtlantisThemeContextValue, } from "./types";
3
3
  export { buildThemedStyles } from "./buildThemedStyles";
@@ -1,13 +1,9 @@
1
1
  import type { iosTokens } from "@jobber/design";
2
2
  export interface AtlantisThemeContextValue {
3
3
  /**
4
- * The selected theme.
4
+ * The active theme.
5
5
  */
6
6
  readonly theme: Theme;
7
- /**
8
- * The resolved theme used to select tokens.
9
- */
10
- readonly effectiveTheme: EffectiveTheme;
11
7
  /**
12
8
  * The design tokens for the current theme.
13
9
  */
@@ -22,19 +18,10 @@ export interface AtlantisThemeContextProviderProps {
22
18
  * The children to render.
23
19
  */
24
20
  readonly children: React.ReactNode;
25
- /**
26
- * Control the selected theme for this provider.
27
- */
28
- readonly theme?: Theme;
29
- /**
30
- * Called when children request a selected theme change through setTheme.
31
- */
32
- readonly onThemeChange?: (theme: Theme) => void;
33
21
  /**
34
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.
35
23
  * This is dangerous because the children in this provider will not be able to change the theme.
36
24
  */
37
- readonly dangerouslyOverrideTheme?: EffectiveTheme;
25
+ readonly dangerouslyOverrideTheme?: Theme;
38
26
  }
39
- export type Theme = "system" | EffectiveTheme;
40
- export type EffectiveTheme = "light" | "dark";
27
+ export type Theme = "light" | "dark";
package/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.5",
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": "a39de5621289f06a4d21d021130ff191937cd851"
128
128
  }
@@ -84,8 +84,8 @@ function OnSurfacesExample() {
84
84
  * Rendering pane → "Emulate CSS media feature prefers-reduced-motion"
85
85
  *
86
86
  * The component will hide the rocking arcs and render a single static
87
- * ring in `color-icon` whose opacity pulses between 1.0 and 0.35 over a
88
- * 2-second alternate cycle.
87
+ * ring in `color-text--secondary` whose opacity pulses between 1.0 and 0.35
88
+ * over a 2-second alternate cycle.
89
89
  */
90
90
  export const ReducedMotion: Story = {
91
91
  render: () => <ActivityIndicator />,
@@ -160,7 +160,7 @@ export function ActivityIndicator({
160
160
  <ReducedMotionRing
161
161
  pixelSize={pixelSize}
162
162
  strokeWidth={strokeWidth}
163
- ringColor={themeTokens["color-icon"]}
163
+ ringColor={themeTokens["color-text--secondary"]}
164
164
  style={style}
165
165
  testID={testID}
166
166
  a11yProps={a11yProps}
@@ -173,7 +173,7 @@ export function ActivityIndicator({
173
173
  size={resolvedSize}
174
174
  pixelSize={pixelSize}
175
175
  strokeWidth={strokeWidth}
176
- arcColor={themeTokens["color-icon"]}
176
+ arcColor={themeTokens["color-text--secondary"]}
177
177
  trackColor={themeTokens["color-surface--active"]}
178
178
  style={style}
179
179
  testID={testID}
@@ -49,8 +49,8 @@ interface ActivityIndicatorProps {
49
49
  The previous wrapper accepted React Native's full `ActivityIndicatorProps` via
50
50
  spread. The new public API drops the following intentionally:
51
51
 
52
- - `color` — replaced by semantic theme tokens (`color-icon` for the arcs,
53
- `color-surface--active` for the track). The single known consumer of
52
+ - `color` — replaced by semantic theme tokens (`color-text--secondary` for the
53
+ arcs, `color-surface--active` for the track). The single known consumer of
54
54
  `color="white"` (`jobber-mobile/GalleryHeader`) is migrated as part of the
55
55
  `migrate-activityindicator` change in the jobber-mobile repository.
56
56
  - numeric `size` — sizing is constrained to the two design-system sizes (28px /
@@ -161,7 +161,9 @@ duration is a CSS literal on web and a matching constant on native (`1000ms`).
161
161
  When the operating system reports reduce-motion is enabled, the component:
162
162
 
163
163
  - Skips rendering the rotating layers and rocking arcs.
164
- - Renders a single static `<Circle>` ring in `tokens["color-icon"]`.
164
+ - Renders a single static `<Circle>` ring in `tokens["color-text--secondary"]`.
165
+ The ring stands in for the hidden arcs, so it deliberately tracks the arc
166
+ colour.
165
167
  - Animates the ring's opacity between 1.0 and 0.35 over a 4-second ease-in-out
166
168
  alternate cycle (2s down, 2s up) so the indicator still reads as "busy"
167
169
  without rotational motion.
@@ -203,8 +205,8 @@ Both components share:
203
205
  - Sizes (28 px small, 44 px base)
204
206
  - Motion durations (`timing-indicator-arc`, `timing-indicator-cycle`,
205
207
  `timing-indicator-linear-rotate`) from the same `@jobber/design` source
206
- - Color tokens (`color-icon` for arcs, `color-surface--active` for track) from
207
- the same `@jobber/design` source
208
+ - Color tokens (`color-text--secondary` for arcs, `color-surface--active` for
209
+ track) from the same `@jobber/design` source
208
210
  - Reduced-motion fallback (opacity 1.0 ↔ 0.35, 2 s ease-in-out alternate)
209
211
 
210
212
  Documented divergences:
@@ -2,43 +2,21 @@ import React from "react";
2
2
  import { act, renderHook } from "@testing-library/react-native";
3
3
  import { darkTokens, iosTokens } from "@jobber/design";
4
4
  import merge from "lodash/merge";
5
- import { type ColorSchemeName, useColorScheme } from "react-native";
6
5
  import {
7
6
  AtlantisThemeContextProvider,
8
7
  useAtlantisTheme,
9
8
  } from "./AtlantisThemeContext";
10
- import type {
11
- AtlantisThemeContextProviderProps,
12
- EffectiveTheme,
13
- } from "./types";
14
-
15
- jest.mock("react-native", () => {
16
- const actual = jest.requireActual("react-native");
17
-
18
- Object.defineProperty(actual, "useColorScheme", {
19
- configurable: true,
20
- value: jest.fn(),
21
- });
22
-
23
- return actual;
24
- });
9
+ import type { AtlantisThemeContextProviderProps, Theme } from "./types";
25
10
 
26
11
  const expectedDarkTokens = merge({}, iosTokens, darkTokens);
27
12
  const expectedLightTokens = iosTokens;
28
- const mockUseColorScheme = useColorScheme as jest.MockedFunction<
29
- typeof useColorScheme
30
- >;
31
13
 
32
14
  function Wrapper({
33
15
  children,
34
- theme,
35
- onThemeChange,
36
16
  dangerouslyOverrideTheme,
37
17
  }: AtlantisThemeContextProviderProps) {
38
18
  return (
39
19
  <AtlantisThemeContextProvider
40
- theme={theme}
41
- onThemeChange={onThemeChange}
42
20
  dangerouslyOverrideTheme={dangerouslyOverrideTheme}
43
21
  >
44
22
  {children}
@@ -62,10 +40,6 @@ function WrapperWithOverride({
62
40
  }
63
41
 
64
42
  describe("ThemeContext", () => {
65
- beforeEach(() => {
66
- mockUseColorScheme.mockReturnValue("light");
67
- });
68
-
69
43
  it("defaults to the light theme", () => {
70
44
  const { result } = renderHook(useAtlantisTheme, {
71
45
  wrapper: (props: AtlantisThemeContextProviderProps) => (
@@ -74,7 +48,6 @@ describe("ThemeContext", () => {
74
48
  });
75
49
 
76
50
  expect(result.current.theme).toBe("light");
77
- expect(result.current.effectiveTheme).toBe("light");
78
51
  expect(result.current.tokens).toEqual(expectedLightTokens);
79
52
  });
80
53
 
@@ -87,81 +60,10 @@ describe("ThemeContext", () => {
87
60
 
88
61
  await act(async () => result.current.setTheme("dark"));
89
62
  expect(result.current.theme).toBe("dark");
90
- expect(result.current.effectiveTheme).toBe("dark");
91
63
  expect(result.current.tokens).toEqual(expectedDarkTokens);
92
64
 
93
65
  await act(async () => result.current.setTheme("light"));
94
66
  expect(result.current.theme).toBe("light");
95
- expect(result.current.effectiveTheme).toBe("light");
96
- expect(result.current.tokens).toEqual(expectedLightTokens);
97
- });
98
-
99
- it("resolves the system theme from the OS color scheme", async () => {
100
- mockUseColorScheme.mockReturnValue("dark");
101
-
102
- const { result, rerender } = renderHook(useAtlantisTheme, {
103
- wrapper: (props: AtlantisThemeContextProviderProps) => (
104
- <Wrapper {...props} />
105
- ),
106
- });
107
-
108
- await act(async () => result.current.setTheme("system"));
109
- expect(result.current.theme).toBe("system");
110
- expect(result.current.effectiveTheme).toBe("dark");
111
- expect(result.current.tokens).toEqual(expectedDarkTokens);
112
-
113
- mockUseColorScheme.mockReturnValue("light");
114
- rerender({});
115
-
116
- expect(result.current.theme).toBe("system");
117
- expect(result.current.effectiveTheme).toBe("light");
118
- expect(result.current.tokens).toEqual(expectedLightTokens);
119
- });
120
-
121
- it("uses a controlled selected theme", () => {
122
- mockUseColorScheme.mockReturnValue("dark");
123
-
124
- const { result } = renderHook(useAtlantisTheme, {
125
- wrapper: (props: AtlantisThemeContextProviderProps) => (
126
- <Wrapper {...props} theme="system" />
127
- ),
128
- });
129
-
130
- expect(result.current.theme).toBe("system");
131
- expect(result.current.effectiveTheme).toBe("dark");
132
- expect(result.current.tokens).toEqual(expectedDarkTokens);
133
- });
134
-
135
- it("reports selected theme changes from a controlled provider", async () => {
136
- const onThemeChange = jest.fn();
137
- const { result } = renderHook(useAtlantisTheme, {
138
- wrapper: (props: AtlantisThemeContextProviderProps) => (
139
- <Wrapper {...props} theme="light" onThemeChange={onThemeChange} />
140
- ),
141
- });
142
-
143
- await act(async () => result.current.setTheme("system"));
144
-
145
- expect(onThemeChange).toHaveBeenCalledWith("system");
146
- expect(result.current.theme).toBe("light");
147
- expect(result.current.effectiveTheme).toBe("light");
148
- expect(result.current.tokens).toEqual(expectedLightTokens);
149
- });
150
-
151
- it("falls back to light when system color scheme is unavailable", async () => {
152
- mockUseColorScheme.mockReturnValue(
153
- null as unknown as ReturnType<typeof useColorScheme>,
154
- );
155
-
156
- const { result } = renderHook(useAtlantisTheme, {
157
- wrapper: (props: AtlantisThemeContextProviderProps) => (
158
- <Wrapper {...props} />
159
- ),
160
- });
161
-
162
- await act(async () => result.current.setTheme("system"));
163
- expect(result.current.theme).toBe("system");
164
- expect(result.current.effectiveTheme).toBe("light");
165
67
  expect(result.current.tokens).toEqual(expectedLightTokens);
166
68
  });
167
69
 
@@ -178,38 +80,15 @@ describe("ThemeContext", () => {
178
80
 
179
81
  // This hook shouldn't be affected by it because it's set to the light theme
180
82
  expect(result.current.theme).toBe("light");
181
- expect(result.current.effectiveTheme).toBe("light");
182
83
  expect(result.current.tokens).toEqual(expectedLightTokens);
183
84
  });
184
85
 
185
86
  it.each([
186
- {
187
- defaultTheme: "light",
188
- colorScheme: "dark",
189
- expectedEffectiveTheme: "light",
190
- expectedTokens: expectedLightTokens,
191
- },
192
- {
193
- defaultTheme: "dark",
194
- colorScheme: "light",
195
- expectedEffectiveTheme: "dark",
196
- expectedTokens: expectedDarkTokens,
197
- },
198
- ] as {
199
- defaultTheme: EffectiveTheme;
200
- colorScheme: ColorSchemeName;
201
- expectedEffectiveTheme: EffectiveTheme;
202
- expectedTokens: typeof iosTokens;
203
- }[])(
87
+ { defaultTheme: "light", expectedTokens: expectedLightTokens },
88
+ { defaultTheme: "dark", expectedTokens: expectedDarkTokens },
89
+ ] as { defaultTheme: Theme; expectedTokens: typeof iosTokens }[])(
204
90
  "provides the dangerouslyOverrideTheme $defaultTheme tokens",
205
- ({
206
- defaultTheme,
207
- colorScheme,
208
- expectedEffectiveTheme,
209
- expectedTokens,
210
- }) => {
211
- mockUseColorScheme.mockReturnValue(colorScheme);
212
-
91
+ ({ defaultTheme, expectedTokens }) => {
213
92
  const { result } = renderHook(useAtlantisTheme, {
214
93
  wrapper: (props: AtlantisThemeContextProviderProps) => (
215
94
  <WrapperWithOverride
@@ -220,7 +99,6 @@ describe("ThemeContext", () => {
220
99
  });
221
100
 
222
101
  expect(result.current.theme).toBe(defaultTheme);
223
- expect(result.current.effectiveTheme).toBe(expectedEffectiveTheme);
224
102
  expect(result.current.tokens).toEqual(expectedTokens);
225
103
  },
226
104
  );
@@ -1,11 +1,10 @@
1
1
  import { androidTokens, darkTokens, iosTokens } from "@jobber/design";
2
- import React, { createContext, useCallback, useContext, useState } from "react";
2
+ import React, { createContext, useContext, useState } from "react";
3
3
  import merge from "lodash/merge";
4
- import { Platform, useColorScheme } from "react-native";
4
+ import { Platform } from "react-native";
5
5
  import type {
6
6
  AtlantisThemeContextProviderProps,
7
7
  AtlantisThemeContextValue,
8
- EffectiveTheme,
9
8
  Theme,
10
9
  } from "./types";
11
10
 
@@ -19,7 +18,6 @@ const completeDarkTokens = merge({}, lightTokens, darkTokens);
19
18
 
20
19
  const AtlantisThemeContext = createContext<AtlantisThemeContextValue>({
21
20
  theme: "light",
22
- effectiveTheme: "light",
23
21
  tokens: lightTokens,
24
22
  setTheme: () => {
25
23
  console.error(
@@ -30,36 +28,22 @@ const AtlantisThemeContext = createContext<AtlantisThemeContextValue>({
30
28
 
31
29
  export function AtlantisThemeContextProvider({
32
30
  children,
33
- theme,
34
- onThemeChange,
35
31
  dangerouslyOverrideTheme,
36
32
  }: AtlantisThemeContextProviderProps) {
37
- const [uncontrolledTheme, setUncontrolledTheme] = useState<Theme>("light");
38
- const colorScheme = useColorScheme();
33
+ // TODO: check last saved theme from local/device storage
34
+ const initialTheme: Theme = "light";
35
+ const [globalTheme, setGlobalTheme] = useState<Theme>(initialTheme);
39
36
 
40
- const setTheme = useCallback(
41
- (nextTheme: Theme) => {
42
- onThemeChange?.(nextTheme);
43
-
44
- if (theme === undefined) {
45
- setUncontrolledTheme(nextTheme);
46
- }
47
- },
48
- [onThemeChange, theme],
49
- );
50
-
51
- const currentTheme = dangerouslyOverrideTheme ?? theme ?? uncontrolledTheme;
52
- const effectiveTheme = resolveEffectiveTheme(currentTheme, colorScheme);
37
+ const currentTheme = dangerouslyOverrideTheme ?? globalTheme;
53
38
  const currentTokens =
54
- effectiveTheme === "dark" ? completeDarkTokens : lightTokens;
39
+ currentTheme === "dark" ? completeDarkTokens : lightTokens;
55
40
 
56
41
  return (
57
42
  <AtlantisThemeContext.Provider
58
43
  value={{
59
44
  theme: currentTheme,
60
- effectiveTheme,
61
45
  tokens: currentTokens,
62
- setTheme,
46
+ setTheme: setGlobalTheme,
63
47
  }}
64
48
  >
65
49
  {children}
@@ -70,12 +54,3 @@ export function AtlantisThemeContextProvider({
70
54
  export function useAtlantisTheme() {
71
55
  return useContext(AtlantisThemeContext);
72
56
  }
73
-
74
- function resolveEffectiveTheme(
75
- theme: Theme,
76
- colorScheme: ReturnType<typeof useColorScheme>,
77
- ): EffectiveTheme {
78
- if (theme !== "system") return theme;
79
-
80
- return colorScheme === "dark" ? "dark" : "light";
81
- }
@@ -14,17 +14,15 @@ function ChildrenComponent({
14
14
  }: {
15
15
  readonly message?: string;
16
16
  }) {
17
- const { theme, effectiveTheme, tokens, setTheme } = useAtlantisTheme();
17
+ const { theme, tokens, setTheme } = useAtlantisTheme();
18
18
 
19
19
  return (
20
20
  <View style={{ backgroundColor: tokens["color-surface"] }}>
21
21
  <Content>
22
22
  <Text>{message}</Text>
23
- <Text>{`Selected theme: ${theme}`}</Text>
24
- <Text>{`Effective theme: ${effectiveTheme}`}</Text>
23
+ <Text>{`Current theme: ${theme}`}</Text>
25
24
  <Text>Tokens can be accessed using tokens[token-name]</Text>
26
25
  <Text>{`For example color-surface: ${tokens["color-surface"]}`}</Text>
27
- <Button label="Set system theme" onPress={() => setTheme("system")} />
28
26
  <Button label="Set dark theme" onPress={() => setTheme("dark")} />
29
27
  <Button label="Set light theme" onPress={() => setTheme("light")} />
30
28
  </Content>
@@ -14,17 +14,15 @@ function ChildrenComponent({
14
14
  }: {
15
15
  readonly message?: string;
16
16
  }) {
17
- const { theme, effectiveTheme, tokens, setTheme } = useAtlantisTheme();
17
+ const { theme, tokens, setTheme } = useAtlantisTheme();
18
18
 
19
19
  return (
20
20
  <View style={{ backgroundColor: tokens["color-surface"] }}>
21
21
  <Content>
22
22
  <Text>{message}</Text>
23
- <Text>{`Selected theme: ${theme}`}</Text>
24
- <Text>{`Effective theme: ${effectiveTheme}`}</Text>
23
+ <Text>{`Current theme: ${theme}`}</Text>
25
24
  <Text>Tokens can be accessed using tokens[token-name]</Text>
26
25
  <Text>{`For example color-surface: ${tokens["color-surface"]}`}</Text>
27
- <Button label="Set system theme" onPress={() => setTheme("system")} />
28
26
  <Button label="Set dark theme" onPress={() => setTheme("dark")} />
29
27
  <Button label="Set light theme" onPress={() => setTheme("light")} />
30
28
  </Content>
@@ -3,7 +3,6 @@ export {
3
3
  useAtlantisTheme,
4
4
  } from "./AtlantisThemeContext";
5
5
  export type {
6
- EffectiveTheme,
7
6
  Theme,
8
7
  AtlantisThemeContextProviderProps,
9
8
  AtlantisThemeContextValue,
@@ -2,15 +2,10 @@ import type { iosTokens } from "@jobber/design";
2
2
 
3
3
  export interface AtlantisThemeContextValue {
4
4
  /**
5
- * The selected theme.
5
+ * The active theme.
6
6
  */
7
7
  readonly theme: Theme;
8
8
 
9
- /**
10
- * The resolved theme used to select tokens.
11
- */
12
- readonly effectiveTheme: EffectiveTheme;
13
-
14
9
  /**
15
10
  * The design tokens for the current theme.
16
11
  */
@@ -28,23 +23,11 @@ export interface AtlantisThemeContextProviderProps {
28
23
  */
29
24
  readonly children: React.ReactNode;
30
25
 
31
- /**
32
- * Control the selected theme for this provider.
33
- */
34
- readonly theme?: Theme;
35
-
36
- /**
37
- * Called when children request a selected theme change through setTheme.
38
- */
39
- readonly onThemeChange?: (theme: Theme) => void;
40
-
41
26
  /**
42
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.
43
28
  * This is dangerous because the children in this provider will not be able to change the theme.
44
29
  */
45
- readonly dangerouslyOverrideTheme?: EffectiveTheme;
30
+ readonly dangerouslyOverrideTheme?: Theme;
46
31
  }
47
32
 
48
- export type Theme = "system" | EffectiveTheme;
49
-
50
- export type EffectiveTheme = "light" | "dark";
33
+ export type Theme = "light" | "dark";
@@ -231,7 +231,7 @@ function getIconColorVariation(
231
231
  }
232
232
 
233
233
  if (type === "primary" && variation !== "cancel") {
234
- return "white";
234
+ return "surface"; // This should be a text color token instead, but the action label also uses this
235
235
  }
236
236
 
237
237
  switch (variation) {
@@ -99,7 +99,7 @@ export function ContentOverlay({
99
99
 
100
100
  const styles = useStyles();
101
101
  const { t } = useAtlantisI18n();
102
- const { effectiveTheme, tokens } = useAtlantisTheme();
102
+ const { theme, tokens } = useAtlantisTheme();
103
103
  const isScreenReaderEnabled = useIsScreenReaderEnabled();
104
104
 
105
105
  const behavior = computeContentOverlayBehavior(
@@ -327,7 +327,7 @@ export function ContentOverlay({
327
327
  the active theme here so descendants (e.g. the header title) resolve the
328
328
  correct themed tokens.
329
329
  */}
330
- <AtlantisThemeContextProvider dangerouslyOverrideTheme={effectiveTheme}>
330
+ <AtlantisThemeContextProvider dangerouslyOverrideTheme={theme}>
331
331
  <ContentOverlayKeyboardContext.Provider value={scrollEnabled}>
332
332
  {scrollEnabled ? (
333
333
  <BottomSheetKeyboardAwareScrollView