@jobber/components-native 0.115.1 → 0.115.2-codexjob--9905233.7

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.1",
3
+ "version": "0.115.2-codexjob--9905233.7+9905233c",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -123,5 +123,5 @@
123
123
  "react-native-screens": ">=4.18.0",
124
124
  "react-native-svg": ">=12.0.0"
125
125
  },
126
- "gitHead": "df5ca5cd691c56be40fad67fae7ea644724656cc"
126
+ "gitHead": "9905233c18ce012134f57ba140fabba5225e18a6"
127
127
  }
@@ -1,6 +1,11 @@
1
1
  import React from "react";
2
+ import { allColors, darkTokens } from "@jobber/design";
2
3
  import { fireEvent, render } from "@testing-library/react-native";
4
+ import { Path } from "react-native-svg";
3
5
  import { Chip } from "./Chip";
6
+ import type { ChipProps } from "./Chip";
7
+ import { AtlantisThemeContextProvider } from "../AtlantisThemeContext";
8
+ import type { EffectiveTheme } from "../AtlantisThemeContext";
4
9
  import { tokens } from "../utils/design";
5
10
 
6
11
  it("renders an active Chip", () => {
@@ -131,3 +136,110 @@ describe("accent", () => {
131
136
  });
132
137
  });
133
138
  });
139
+
140
+ describe("selection color transitions", () => {
141
+ function example(theme: EffectiveTheme, props: Partial<ChipProps> = {}) {
142
+ return (
143
+ <AtlantisThemeContextProvider theme={theme}>
144
+ <Chip label="Object" icon="request" isActive {...props} />
145
+ </AtlantisThemeContextProvider>
146
+ );
147
+ }
148
+
149
+ function expectColors(
150
+ view: ReturnType<typeof render>,
151
+ background: string,
152
+ text: string,
153
+ icon = text,
154
+ ) {
155
+ expect(view.getByTestId("chipTest")).toHaveStyle({
156
+ backgroundColor: background,
157
+ });
158
+ expect(view.getByText("Object")).toHaveStyle({ color: text });
159
+ const paths = view.UNSAFE_root.findAllByType(Path);
160
+ expect(paths.length).toBeGreaterThan(0);
161
+
162
+ for (const path of paths) {
163
+ expect(path.props.fill).toBe(icon);
164
+ }
165
+ }
166
+
167
+ it.each(["request", "quote", "job", "invoice"] as const)(
168
+ "keeps the selected %s fill and white content when the theme changes",
169
+ accent => {
170
+ expect.hasAssertions();
171
+ const props = { accent, icon: accent };
172
+ const view = render(example("light", props));
173
+
174
+ for (const theme of ["light", "dark", "light"] as const) {
175
+ view.rerender(example(theme, props));
176
+ expectColors(
177
+ view,
178
+ allColors[`color-${accent}`],
179
+ allColors["color-text--reverse"],
180
+ );
181
+ }
182
+ },
183
+ );
184
+
185
+ it("changes the selected client fill and foreground together with the theme", () => {
186
+ expect.hasAssertions();
187
+ const props = { accent: "client", icon: "clients" } as const;
188
+ const view = render(example("light", props));
189
+ expectColors(
190
+ view,
191
+ allColors["color-client"],
192
+ allColors["color-text--reverse"],
193
+ );
194
+
195
+ view.rerender(example("dark", props));
196
+ expectColors(view, darkTokens["color-client"], allColors["color-text"]);
197
+
198
+ view.rerender(example("light", props));
199
+ expectColors(
200
+ view,
201
+ allColors["color-client"],
202
+ allColors["color-text--reverse"],
203
+ );
204
+ });
205
+
206
+ it.each(["request", "client"] as const)(
207
+ "restores the theme's normal colors when %s is deselected",
208
+ accent => {
209
+ const view = render(example("dark", { accent }));
210
+
211
+ view.rerender(example("dark", { accent, isActive: false }));
212
+
213
+ expectColors(
214
+ view,
215
+ darkTokens["color-interactive--background"],
216
+ darkTokens["color-text"],
217
+ darkTokens[`color-${accent}`],
218
+ );
219
+ expect(
220
+ view.getByTestId("chipTest").props.accessibilityState.checked,
221
+ ).toBe(false);
222
+ },
223
+ );
224
+
225
+ it("preserves default selected colors when an accent is removed and the theme changes", () => {
226
+ expect.hasAssertions();
227
+ const view = render(example("dark", { accent: "request" }));
228
+
229
+ view.rerender(example("dark"));
230
+ expectColors(
231
+ view,
232
+ darkTokens["color-surface--reverse"],
233
+ darkTokens["color-text--reverse"],
234
+ darkTokens["color-surface"],
235
+ );
236
+
237
+ view.rerender(example("light"));
238
+ expectColors(
239
+ view,
240
+ allColors["color-surface--reverse"],
241
+ allColors["color-text--reverse"],
242
+ allColors["color-surface"],
243
+ );
244
+ });
245
+ });
package/src/Chip/Chip.tsx CHANGED
@@ -2,9 +2,11 @@ import React, { useMemo } from "react";
2
2
  import type { AccessibilityRole } from "react-native";
3
3
  import { Pressable, View } from "react-native";
4
4
  import type { IconNames } from "@jobber/design";
5
+ import { allColors } from "@jobber/design";
5
6
  import { useStyles } from "./Chip.style";
6
7
  import { Icon } from "../Icon";
7
8
  import { Typography } from "../Typography";
9
+ import type { TypographyProps } from "../Typography";
8
10
  import type { AtlantisThemeContextValue } from "../AtlantisThemeContext";
9
11
  import { useAtlantisTheme } from "../AtlantisThemeContext";
10
12
 
@@ -71,34 +73,58 @@ export function Chip({
71
73
  accent,
72
74
  }: ChipProps) {
73
75
  const styles = useStyles();
74
- const { tokens } = useAtlantisTheme();
75
- const defaultAccentColor = tokens["color-surface--reverse"];
76
-
77
- const { chipStyle, iconCustomColor, dismissColor } = useMemo(() => {
78
- const accentColor = accent ? tokens[`color-${accent}`] : defaultAccentColor;
79
-
80
- const iconColor = isActive ? tokens["color-surface"] : accentColor;
81
- const chip = [
82
- styles.container,
83
- {
84
- backgroundColor:
85
- inactiveBackgroundColor === "surface"
86
- ? tokens["color-surface"]
87
- : tokens["color-interactive--background"],
88
- },
89
- isActive && { backgroundColor: accentColor },
90
- getBorderStyle(inactiveBackgroundColor, tokens),
91
- ];
92
- const dismiss =
93
- (isActive || inactiveBackgroundColor === "surface") &&
94
- styles.activeDismissIcon;
95
-
96
- return {
97
- chipStyle: chip,
98
- iconCustomColor: iconColor,
99
- dismissColor: dismiss,
100
- };
101
- }, [accent, isActive, inactiveBackgroundColor, getBorderStyle, styles]);
76
+ const { tokens, effectiveTheme } = useAtlantisTheme();
77
+ const { chipStyle, iconCustomColor, labelStyle, dismissColor } =
78
+ useMemo(() => {
79
+ const accentColor = accent
80
+ ? tokens[`color-${accent}`]
81
+ : tokens["color-surface--reverse"];
82
+ let backgroundColor = accentColor;
83
+ let iconColor = isActive ? tokens["color-surface"] : accentColor;
84
+ let selectedLabelStyle: TypographyProps<"base">["UNSAFE_style"];
85
+
86
+ if (isActive && accent) {
87
+ const selected = getSelectedAccentColors(accent, {
88
+ tokens,
89
+ effectiveTheme,
90
+ });
91
+
92
+ backgroundColor = selected.background;
93
+ iconColor = selected.foreground;
94
+ selectedLabelStyle = {
95
+ textStyle: { color: selected.foreground },
96
+ };
97
+ }
98
+
99
+ const chip = [
100
+ styles.container,
101
+ {
102
+ backgroundColor:
103
+ inactiveBackgroundColor === "surface"
104
+ ? tokens["color-surface"]
105
+ : tokens["color-interactive--background"],
106
+ },
107
+ isActive && { backgroundColor },
108
+ getBorderStyle(inactiveBackgroundColor, tokens),
109
+ ];
110
+ const dismiss =
111
+ (isActive || inactiveBackgroundColor === "surface") &&
112
+ styles.activeDismissIcon;
113
+
114
+ return {
115
+ chipStyle: chip,
116
+ iconCustomColor: iconColor,
117
+ labelStyle: selectedLabelStyle,
118
+ dismissColor: dismiss,
119
+ };
120
+ }, [
121
+ accent,
122
+ isActive,
123
+ inactiveBackgroundColor,
124
+ styles,
125
+ tokens,
126
+ effectiveTheme,
127
+ ]);
102
128
 
103
129
  const accessibilityState = useMemo(() => {
104
130
  const checkableRoles = ["radio", "switch", "togglebutton", "checkbox"];
@@ -133,7 +159,8 @@ export function Chip({
133
159
  fontWeight="medium"
134
160
  maxFontScaleSize={tokens["typography--fontSize-large"]}
135
161
  maxLines="single"
136
- reverseTheme={isActive}
162
+ reverseTheme={isActive && !accent}
163
+ UNSAFE_style={labelStyle}
137
164
  >
138
165
  {label}
139
166
  </Typography>
@@ -148,6 +175,30 @@ export function Chip({
148
175
  );
149
176
  }
150
177
 
178
+ // Object labels and leading icons share the selected foreground.
179
+ function getSelectedAccentColors(
180
+ accent: AccentType,
181
+ {
182
+ tokens,
183
+ effectiveTheme,
184
+ }: Pick<AtlantisThemeContextValue, "tokens" | "effectiveTheme">,
185
+ ) {
186
+ const colors = {
187
+ background: allColors[`color-${accent}`],
188
+ foreground: allColors["color-text--reverse"],
189
+ };
190
+
191
+ if (accent === "client") {
192
+ colors.background = tokens["color-client"];
193
+
194
+ if (effectiveTheme === "dark") {
195
+ colors.foreground = allColors["color-text"];
196
+ }
197
+ }
198
+
199
+ return colors;
200
+ }
201
+
151
202
  function getBorderStyle(
152
203
  inactiveBackgroundColor: "surface" | "background",
153
204
  tokens: AtlantisThemeContextValue["tokens"],