@jobber/components-native 0.115.4 → 0.115.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.115.4",
3
+ "version": "0.115.5",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -73,11 +73,12 @@
73
73
  "devDependencies": {
74
74
  "@babel/runtime": "^7.29.2",
75
75
  "@gorhom/bottom-sheet": "^5.2.8",
76
- "@jobber/design": "0.121.0",
76
+ "@jobber/design": "0.121.1",
77
77
  "@jobber/hooks": "2.21.1",
78
78
  "@react-native-community/datetimepicker": "^8.4.5",
79
79
  "@react-native/babel-preset": "^0.82.1",
80
80
  "@storybook/addon-a11y": "10.3.5",
81
+ "@storybook/addon-docs": "10.3.5",
81
82
  "@storybook/addon-mcp": "^0.4.2",
82
83
  "@storybook/react-native-web-vite": "10.3.5",
83
84
  "@testing-library/react-native": "^13.3.3",
@@ -123,5 +124,5 @@
123
124
  "react-native-screens": ">=4.18.0",
124
125
  "react-native-svg": ">=12.0.0"
125
126
  },
126
- "gitHead": "8efe7ed48c2c677ef66802b64d945c259f22a343"
127
+ "gitHead": "9b27100be34c36aa1d4717f298cea4388ab9277d"
127
128
  }
@@ -35,13 +35,21 @@ Consumers that need to style non-Atlantis surfaces should usually use
35
35
  ## Platform token composition
36
36
 
37
37
  Native themes start with the current platform's iOS or Android token set and
38
- then apply dark theme overrides. An override must preserve the native token's
39
- runtime shape. If a dark override is a primitive but the platform token is an
40
- object, the provider keeps the platform value.
38
+ then apply that platform's dark token set (`darkIosTokens` or
39
+ `darkAndroidTokens` from `@jobber/design`). Each dark platform set is the web
40
+ dark token set with dark platform overrides layered on top, defined in
41
+ `packages/design/src/tokens/darkPlatformOverride.tokens.json`.
41
42
 
42
43
  This matters for shadows: web dark shadow tokens are CSS strings, while native
43
- shadow tokens are React Native style objects. Keeping the platform object means
44
- consumers can safely continue to spread `tokens["shadow-base"]` in both themes.
44
+ shadow tokens are React Native style objects. The dark platform overrides
45
+ replace the dark shadow strings with native shadow objects tuned for dark
46
+ surfaces, so consumers can safely continue to spread `tokens["shadow-base"]` in
47
+ both themes.
48
+
49
+ An override must preserve the native token's runtime shape. If a dark token is a
50
+ primitive but the platform token is an object, the provider keeps the platform
51
+ value. Add a dark platform override when a token needs a different native value
52
+ in dark mode.
45
53
 
46
54
  ## Controlled vs uncontrolled usage
47
55
 
@@ -1,12 +1,23 @@
1
1
  import React from "react";
2
2
  import { act, renderHook } from "@testing-library/react-native";
3
- import { darkTokens, iosTokens } from "@jobber/design";
3
+ import type * as TestingLibrary from "@testing-library/react-native";
4
+ import {
5
+ androidTokens,
6
+ darkAndroidTokens,
7
+ darkIosTokens,
8
+ iosTokens,
9
+ } from "@jobber/design";
4
10
  import merge from "lodash/merge";
5
- import { type ColorSchemeName, useColorScheme } from "react-native";
11
+ import {
12
+ type ColorSchemeName,
13
+ type Platform as PlatformModule,
14
+ useColorScheme,
15
+ } from "react-native";
6
16
  import {
7
17
  AtlantisThemeContextProvider,
8
18
  useAtlantisTheme,
9
19
  } from "./AtlantisThemeContext";
20
+ import type * as ThemeContextModule from "./AtlantisThemeContext";
10
21
  import type {
11
22
  AtlantisThemeContextProviderProps,
12
23
  EffectiveTheme,
@@ -23,12 +34,7 @@ jest.mock("react-native", () => {
23
34
  return actual;
24
35
  });
25
36
 
26
- const expectedDarkTokens = {
27
- ...merge({}, iosTokens, darkTokens),
28
- "shadow-low": iosTokens["shadow-low"],
29
- "shadow-base": iosTokens["shadow-base"],
30
- "shadow-high": iosTokens["shadow-high"],
31
- };
37
+ const expectedDarkTokens = merge({}, iosTokens, darkIosTokens);
32
38
  const expectedLightTokens = iosTokens;
33
39
  const mockUseColorScheme = useColorScheme as jest.MockedFunction<
34
40
  typeof useColorScheme
@@ -102,7 +108,7 @@ describe("ThemeContext", () => {
102
108
  });
103
109
 
104
110
  it.each(["shadow-low", "shadow-base", "shadow-high"] as const)(
105
- "keeps the platform-native %s token in dark mode",
111
+ "uses the dark platform-native %s token in dark mode",
106
112
  async shadowToken => {
107
113
  const { result } = renderHook(useAtlantisTheme, {
108
114
  wrapper: (props: AtlantisThemeContextProviderProps) => (
@@ -113,12 +119,58 @@ describe("ThemeContext", () => {
113
119
  await act(async () => result.current.setTheme("dark"));
114
120
 
115
121
  expect(result.current.tokens[shadowToken]).toEqual(
122
+ darkIosTokens[shadowToken],
123
+ );
124
+ expect(result.current.tokens[shadowToken]).not.toEqual(
116
125
  iosTokens[shadowToken],
117
126
  );
118
127
  expect(typeof result.current.tokens[shadowToken]).toBe("object");
119
128
  },
120
129
  );
121
130
 
131
+ describe("on android", () => {
132
+ let android!: typeof TestingLibrary & typeof ThemeContextModule;
133
+
134
+ // The provider picks platform tokens at import time, so it is loaded in an
135
+ // isolated registry after Platform is switched to android. This runs while
136
+ // collecting tests because the testing library registers cleanup hooks.
137
+ jest.isolateModules(() => {
138
+ const {
139
+ Platform,
140
+ }: { Platform: typeof PlatformModule } = require("react-native");
141
+ jest.replaceProperty(Platform, "OS", "android");
142
+ jest.spyOn(Platform, "select").mockImplementation(spec => spec.android);
143
+
144
+ android = {
145
+ ...require("@testing-library/react-native"),
146
+ ...require("./AtlantisThemeContext"),
147
+ };
148
+ });
149
+
150
+ it.each(["shadow-low", "shadow-base", "shadow-high"] as const)(
151
+ "uses the android %s token in light and dark mode",
152
+ async shadowToken => {
153
+ const { result } = android.renderHook(android.useAtlantisTheme, {
154
+ wrapper: ({ children }: AtlantisThemeContextProviderProps) => (
155
+ <android.AtlantisThemeContextProvider>
156
+ {children}
157
+ </android.AtlantisThemeContextProvider>
158
+ ),
159
+ });
160
+
161
+ expect(result.current.tokens[shadowToken]).toEqual(
162
+ androidTokens[shadowToken],
163
+ );
164
+
165
+ await android.act(async () => result.current.setTheme("dark"));
166
+
167
+ expect(result.current.tokens[shadowToken]).toEqual(
168
+ darkAndroidTokens[shadowToken],
169
+ );
170
+ },
171
+ );
172
+ });
173
+
122
174
  it("resolves the system theme from the OS color scheme", async () => {
123
175
  mockUseColorScheme.mockReturnValue("dark");
124
176
 
@@ -1,4 +1,9 @@
1
- import { androidTokens, darkTokens, iosTokens } from "@jobber/design";
1
+ import {
2
+ androidTokens,
3
+ darkAndroidTokens,
4
+ darkIosTokens,
5
+ iosTokens,
6
+ } from "@jobber/design";
2
7
  import React, { createContext, useCallback, useContext, useState } from "react";
3
8
  import mergeWith from "lodash/mergeWith";
4
9
  import { Platform, useColorScheme } from "react-native";
@@ -15,10 +20,16 @@ const lightTokens = Platform.select({
15
20
  default: () => androidTokens,
16
21
  })();
17
22
 
23
+ const darkPlatformTokens = Platform.select({
24
+ ios: () => darkIosTokens,
25
+ android: () => darkAndroidTokens,
26
+ default: () => darkAndroidTokens,
27
+ })();
28
+
18
29
  const completeDarkTokens = mergeWith(
19
30
  {},
20
31
  lightTokens,
21
- darkTokens,
32
+ darkPlatformTokens,
22
33
  (platformValue, darkValue) => {
23
34
  const platformValueIsObject =
24
35
  typeof platformValue === "object" && platformValue !== null;