@jobber/components-native 0.86.1 → 0.86.2-JOB-89949-ec4cb2a.11

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,11 +1,12 @@
1
+ import React from "react";
1
2
  import type { TextAlign, TextColor } from "../Typography";
2
3
  export type ActionLabelVariation = Extract<TextColor, "interactive" | "destructive" | "learning" | "subtle" | "onPrimary">;
3
4
  type ActionLabelType = "default" | "cardTitle";
4
5
  interface ActionLabelProps {
5
6
  /**
6
- * Text to display
7
+ * Text to display. Supports nesting text elements.
7
8
  */
8
- readonly children?: string;
9
+ readonly children?: React.ReactNode;
9
10
  /**
10
11
  * Set the display text to disabled color
11
12
  */
@@ -1,11 +1,12 @@
1
+ import React from "react";
1
2
  import type { TextAlign, TextColor, TruncateLength, TypographyProps } from "../Typography";
2
3
  type HeadingColor = Extract<TextColor, "text" | "subdued" | "heading">;
3
4
  export type HeadingLevel = "title" | "subtitle" | "heading" | "subHeading";
4
5
  interface HeadingProps<T extends HeadingLevel> extends Pick<TypographyProps<"base">, "selectable"> {
5
6
  /**
6
- * Text to display.
7
+ * Text to display. Supports nesting text elements.
7
8
  */
8
- readonly children: string;
9
+ readonly children: React.ReactNode;
9
10
  /**
10
11
  * The type of heading, e.g., "Title"
11
12
  */
@@ -1,3 +1,4 @@
1
+ import React from "react";
1
2
  import type { OnTextLayoutEvent, TextAlign, TextVariation, TruncateLength, TypographyProps } from "../Typography";
2
3
  import type { TypographyUnsafeStyle } from "../Typography/Typography";
3
4
  export interface TextProps extends Pick<TypographyProps<"base">, "maxFontScaleSize" | "selectable"> {
@@ -31,9 +32,9 @@ export interface TextProps extends Pick<TypographyProps<"base">, "maxFontScaleSi
31
32
  */
32
33
  readonly align?: TextAlign;
33
34
  /**
34
- * Text to display
35
+ * Text to display. Supports nesting text elements.
35
36
  */
36
- readonly children?: string;
37
+ readonly children?: React.ReactNode;
37
38
  /**
38
39
  * Reverse theme for better display on dark background
39
40
  */
@@ -24,9 +24,9 @@ export interface TypographyProps<T extends FontFamily> {
24
24
  */
25
25
  readonly size?: TextSize;
26
26
  /**
27
- * Text to display
27
+ * Text to display. Supports nesting text elements.
28
28
  */
29
- readonly children?: string;
29
+ readonly children?: React.ReactNode;
30
30
  /**
31
31
  * The maximum amount of lines the text can occupy before being truncated with "...".
32
32
  * Uses predefined string values that correspond to a doubling scale for the amount of lines.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.86.1",
3
+ "version": "0.86.2-JOB-89949-ec4cb2a.11+ec4cb2ad1",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -95,5 +95,5 @@
95
95
  "react-native-safe-area-context": "^5.4.0",
96
96
  "react-native-svg": ">=12.0.0"
97
97
  },
98
- "gitHead": "ca472a375fdf3c6b8e8596c3c26a3958893093ec"
98
+ "gitHead": "ec4cb2ad125a2793e88b405dc0d53041a4c1dab6"
99
99
  }
@@ -117,6 +117,18 @@ describe("ActionLabel", () => {
117
117
  });
118
118
  });
119
119
  });
120
+
121
+ it("supports nested inline content", () => {
122
+ const { Text } = require("../Text");
123
+ const { getByText, toJSON } = render(
124
+ <ActionLabel>
125
+ Before <Text variation="interactive">Inner</Text> After
126
+ </ActionLabel>,
127
+ );
128
+
129
+ expect(getByText("Inner")).toBeDefined();
130
+ expect(toJSON()).toMatchSnapshot();
131
+ });
120
132
  });
121
133
 
122
134
  function getStyleObject(el: ReactTestInstance) {
@@ -12,9 +12,9 @@ type ActionLabelType = "default" | "cardTitle";
12
12
 
13
13
  interface ActionLabelProps {
14
14
  /**
15
- * Text to display
15
+ * Text to display. Supports nesting text elements.
16
16
  */
17
- readonly children?: string;
17
+ readonly children?: React.ReactNode;
18
18
 
19
19
  /**
20
20
  * Set the display text to disabled color
@@ -0,0 +1,66 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ActionLabel supports nested inline content 1`] = `
4
+ <Text
5
+ accessibilityRole="text"
6
+ adjustsFontSizeToFit={false}
7
+ allowFontScaling={true}
8
+ maxFontSizeMultiplier={1.125}
9
+ selectable={false}
10
+ selectionColor="hsl(86, 100%, 46%)"
11
+ style={
12
+ [
13
+ {
14
+ "fontFamily": "inter-semibold",
15
+ },
16
+ {
17
+ "color": "hsl(107, 58%, 33%)",
18
+ },
19
+ {
20
+ "textAlign": "center",
21
+ },
22
+ {
23
+ "fontSize": 16,
24
+ "lineHeight": 18,
25
+ },
26
+ {
27
+ "letterSpacing": 0,
28
+ },
29
+ ]
30
+ }
31
+ >
32
+ Before
33
+ <Text
34
+ accessibilityRole="text"
35
+ adjustsFontSizeToFit={false}
36
+ allowFontScaling={true}
37
+ collapsable={false}
38
+ maxFontSizeMultiplier={3.125}
39
+ selectable={true}
40
+ selectionColor="hsl(86, 100%, 46%)"
41
+ style={
42
+ [
43
+ {
44
+ "fontFamily": "inter-regular",
45
+ },
46
+ {
47
+ "color": "hsl(107, 58%, 33%)",
48
+ },
49
+ {
50
+ "textAlign": "left",
51
+ },
52
+ {
53
+ "fontSize": 16,
54
+ "lineHeight": 20,
55
+ },
56
+ {
57
+ "letterSpacing": 0,
58
+ },
59
+ ]
60
+ }
61
+ >
62
+ Inner
63
+ </Text>
64
+ After
65
+ </Text>
66
+ `;
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { render } from "@testing-library/react-native";
3
3
  import { Heading } from "./Heading";
4
+ import { Text } from "../Text";
4
5
 
5
6
  describe("when Heading called with text as the only prop", () => {
6
7
  it("should match snapshot", () => {
@@ -81,3 +82,15 @@ describe("when Heading called with maxLines", () => {
81
82
  expect(view).toMatchSnapshot();
82
83
  });
83
84
  });
85
+
86
+ describe("when Heading contains nested inline text", () => {
87
+ it("renders nested Text within Heading", () => {
88
+ const view = render(
89
+ <Heading>
90
+ Heading before <Text emphasis="strong">Inner</Text> after
91
+ </Heading>,
92
+ ).toJSON();
93
+
94
+ expect(view).toMatchSnapshot();
95
+ });
96
+ });
@@ -19,9 +19,9 @@ export type HeadingLevel = "title" | "subtitle" | "heading" | "subHeading";
19
19
  interface HeadingProps<T extends HeadingLevel>
20
20
  extends Pick<TypographyProps<"base">, "selectable"> {
21
21
  /**
22
- * Text to display.
22
+ * Text to display. Supports nesting text elements.
23
23
  */
24
- readonly children: string;
24
+ readonly children: React.ReactNode;
25
25
 
26
26
  /**
27
27
  * The type of heading, e.g., "Title"
@@ -268,3 +268,68 @@ exports[`when Heading called with title variation should match snapshot 1`] = `
268
268
  Title Heading
269
269
  </Text>
270
270
  `;
271
+
272
+ exports[`when Heading contains nested inline text renders nested Text within Heading 1`] = `
273
+ <Text
274
+ accessibilityRole="header"
275
+ adjustsFontSizeToFit={false}
276
+ allowFontScaling={true}
277
+ collapsable={false}
278
+ selectable={true}
279
+ selectionColor="hsl(86, 100%, 46%)"
280
+ style={
281
+ [
282
+ {
283
+ "fontFamily": "inter-bold",
284
+ },
285
+ {
286
+ "color": "hsl(197, 90%, 12%)",
287
+ },
288
+ {
289
+ "textAlign": "left",
290
+ },
291
+ {
292
+ "fontSize": 20,
293
+ "lineHeight": 22,
294
+ },
295
+ {
296
+ "letterSpacing": 0,
297
+ },
298
+ ]
299
+ }
300
+ >
301
+ Heading before
302
+ <Text
303
+ accessibilityRole="text"
304
+ adjustsFontSizeToFit={false}
305
+ allowFontScaling={true}
306
+ collapsable={false}
307
+ maxFontSizeMultiplier={3.125}
308
+ selectable={true}
309
+ selectionColor="hsl(86, 100%, 46%)"
310
+ style={
311
+ [
312
+ {
313
+ "fontFamily": "inter-semibold",
314
+ },
315
+ {
316
+ "color": "hsl(198, 35%, 21%)",
317
+ },
318
+ {
319
+ "textAlign": "left",
320
+ },
321
+ {
322
+ "fontSize": 16,
323
+ "lineHeight": 20,
324
+ },
325
+ {
326
+ "letterSpacing": 0,
327
+ },
328
+ ]
329
+ }
330
+ >
331
+ Inner
332
+ </Text>
333
+ after
334
+ </Text>
335
+ `;
@@ -151,6 +151,16 @@ it("renders text with underline styling", () => {
151
151
  expect(text.toJSON()).toMatchSnapshot();
152
152
  });
153
153
 
154
+ it("supports nested Text children with mixed styles", () => {
155
+ const { getByText, toJSON } = render(
156
+ <Text>
157
+ Hello <Text emphasis="strong">World</Text>!
158
+ </Text>,
159
+ );
160
+ expect(getByText("World")).toBeDefined();
161
+ expect(toJSON()).toMatchSnapshot();
162
+ });
163
+
154
164
  describe("UNSAFE_style", () => {
155
165
  it("applies custom styles via UNSAFE_style prop", () => {
156
166
  const customStyle = {
package/src/Text/Text.tsx CHANGED
@@ -53,9 +53,9 @@ export interface TextProps
53
53
  readonly align?: TextAlign;
54
54
 
55
55
  /**
56
- * Text to display
56
+ * Text to display. Supports nesting text elements.
57
57
  */
58
- readonly children?: string;
58
+ readonly children?: React.ReactNode;
59
59
 
60
60
  /**
61
61
  * Reverse theme for better display on dark background
@@ -694,3 +694,69 @@ exports[`renders with strikethrough styling 1`] = `
694
694
  Test Text
695
695
  </Text>
696
696
  `;
697
+
698
+ exports[`supports nested Text children with mixed styles 1`] = `
699
+ <Text
700
+ accessibilityRole="text"
701
+ adjustsFontSizeToFit={false}
702
+ allowFontScaling={true}
703
+ collapsable={false}
704
+ maxFontSizeMultiplier={3.125}
705
+ selectable={true}
706
+ selectionColor="hsl(86, 100%, 46%)"
707
+ style={
708
+ [
709
+ {
710
+ "fontFamily": "inter-regular",
711
+ },
712
+ {
713
+ "color": "hsl(198, 35%, 21%)",
714
+ },
715
+ {
716
+ "textAlign": "left",
717
+ },
718
+ {
719
+ "fontSize": 16,
720
+ "lineHeight": 20,
721
+ },
722
+ {
723
+ "letterSpacing": 0,
724
+ },
725
+ ]
726
+ }
727
+ >
728
+ Hello
729
+ <Text
730
+ accessibilityRole="text"
731
+ adjustsFontSizeToFit={false}
732
+ allowFontScaling={true}
733
+ collapsable={false}
734
+ maxFontSizeMultiplier={3.125}
735
+ selectable={true}
736
+ selectionColor="hsl(86, 100%, 46%)"
737
+ style={
738
+ [
739
+ {
740
+ "fontFamily": "inter-semibold",
741
+ },
742
+ {
743
+ "color": "hsl(198, 35%, 21%)",
744
+ },
745
+ {
746
+ "textAlign": "left",
747
+ },
748
+ {
749
+ "fontSize": 16,
750
+ "lineHeight": 20,
751
+ },
752
+ {
753
+ "letterSpacing": 0,
754
+ },
755
+ ]
756
+ }
757
+ >
758
+ World
759
+ </Text>
760
+ !
761
+ </Text>
762
+ `;
@@ -79,6 +79,67 @@ it("renders text with lowercase transform", () => {
79
79
  expect(typography.toJSON()).toMatchSnapshot();
80
80
  });
81
81
 
82
+ it("supports nested children and applies transform only to string children", () => {
83
+ const typography = render(
84
+ <Typography transform="uppercase">
85
+ before <Typography fontWeight="bold">Inner</Typography> after
86
+ </Typography>,
87
+ );
88
+ expect(typography.toJSON()).toMatchSnapshot();
89
+ });
90
+
91
+ it("allows child Typography to control its own transform", () => {
92
+ const view = render(
93
+ <Typography transform="uppercase">
94
+ {"before "}
95
+ <Typography fontWeight="bold" transform="lowercase">
96
+ Inner
97
+ </Typography>
98
+ {" after"}
99
+ </Typography>,
100
+ ).toJSON();
101
+
102
+ expect(view).toMatchSnapshot();
103
+ });
104
+
105
+ it("supports multi-level nesting across Typography and Text", () => {
106
+ const view = render(
107
+ <Typography transform="uppercase">
108
+ {"level1 "}
109
+ <Typography>
110
+ and <Typography transform="lowercase">INNER</Typography>
111
+ </Typography>
112
+ {" end"}
113
+ </Typography>,
114
+ ).toJSON();
115
+
116
+ expect(view).toMatchSnapshot();
117
+ });
118
+
119
+ it("applies transform to parent strings only", () => {
120
+ const { getByText } = render(
121
+ <Typography transform="uppercase">
122
+ {"test "}
123
+ <Typography>inner</Typography>
124
+ </Typography>,
125
+ );
126
+
127
+ expect(getByText(/TEST/)).toBeDefined();
128
+ expect(getByText("inner")).toBeDefined();
129
+ });
130
+
131
+ it("allows child transform to override parent transform", () => {
132
+ const { getByText } = render(
133
+ <Typography transform="uppercase">
134
+ before <Typography transform="lowercase">INNER</Typography> after
135
+ </Typography>,
136
+ );
137
+
138
+ expect(getByText(/BEFORE/)).toBeDefined();
139
+ expect(getByText("inner")).toBeDefined();
140
+ expect(getByText(/AFTER/)).toBeDefined();
141
+ });
142
+
82
143
  it("renders text with white color", () => {
83
144
  const typography = render(<Typography color="white">Test Text</Typography>);
84
145
  expect(typography.toJSON()).toMatchSnapshot();
@@ -41,9 +41,9 @@ export interface TypographyProps<T extends FontFamily> {
41
41
  readonly size?: TextSize;
42
42
 
43
43
  /**
44
- * Text to display
44
+ * Text to display. Supports nesting text elements.
45
45
  */
46
- readonly children?: string;
46
+ readonly children?: React.ReactNode;
47
47
 
48
48
  /**
49
49
  * The maximum amount of lines the text can occupy before being truncated with "...".
@@ -204,7 +204,7 @@ function InternalTypography<T extends FontFamily = "base">({
204
204
 
205
205
  const numberOfLinesForNativeText = maxNumberOfLines[maxLines];
206
206
 
207
- const text = getTransformedText(children, transform);
207
+ const content = transformChildren(children, transform);
208
208
  const accessibilityProps: AccessibilityProps = hideFromScreenReader
209
209
  ? {
210
210
  accessibilityRole: "none",
@@ -232,7 +232,7 @@ function InternalTypography<T extends FontFamily = "base">({
232
232
  selectionColor={tokens["color-brand--highlight"]}
233
233
  onTextLayout={onTextLayout}
234
234
  >
235
- {text}
235
+ {content}
236
236
  </Text>
237
237
  );
238
238
 
@@ -273,19 +273,35 @@ function getFontStyle(
273
273
  }
274
274
  }
275
275
 
276
- function getTransformedText(text?: string, transform?: TextTransform) {
276
+ function getTransformedText(text: string, transform?: TextTransform) {
277
277
  switch (transform) {
278
278
  case "lowercase":
279
- return text?.toLocaleLowerCase();
279
+ return text.toLocaleLowerCase();
280
280
  case "uppercase":
281
- return text?.toLocaleUpperCase();
281
+ return text.toLocaleUpperCase();
282
282
  case "capitalize":
283
- return capitalize(text || "");
283
+ return capitalize(text);
284
284
  default:
285
285
  return text;
286
286
  }
287
287
  }
288
288
 
289
+ function transformChildren(
290
+ children: React.ReactNode,
291
+ transform?: TextTransform,
292
+ ): React.ReactNode {
293
+ if (children == null || !transform || transform === "none") return children;
294
+
295
+ return React.Children.map(children, child => {
296
+ if (typeof child === "string") {
297
+ return getTransformedText(child, transform);
298
+ }
299
+
300
+ // Keep non-string children (numbers, elements, fragments) unchanged
301
+ return child;
302
+ });
303
+ }
304
+
289
305
  function getColorStyle(
290
306
  styles: ReturnType<typeof useTypographyStyles>,
291
307
  color?: TextColor,