@jobber/components-native 0.54.4 → 0.55.0

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,19 +1,15 @@
1
1
  export declare const styles: {
2
2
  container: {
3
3
  width: string;
4
- };
5
- error: {
6
- backgroundColor: string;
7
- };
8
- warning: {
9
- backgroundColor: string;
10
- };
11
- notice: {
4
+ borderColor: string;
5
+ borderStyle: "solid";
6
+ borderBottomWidth: number;
12
7
  backgroundColor: string;
13
8
  };
14
9
  bannerContent: {
15
10
  flexDirection: "row";
16
11
  alignItems: "flex-start";
12
+ gap: number;
17
13
  };
18
14
  contentContainer: {
19
15
  flex: number;
@@ -24,7 +20,13 @@ export declare const styles: {
24
20
  textContainer: {
25
21
  marginTop: number;
26
22
  };
27
- bannerIcon: {
28
- paddingRight: number;
23
+ fullWidth: {
24
+ width: string;
25
+ };
26
+ bannerChildrenContent: {
27
+ marginBottom: number;
28
+ };
29
+ contentSpacing: {
30
+ gap: number;
29
31
  };
30
32
  };
@@ -1,6 +1,8 @@
1
1
  /// <reference types="react" />
2
2
  import { IconNames } from "@jobber/design";
3
+ import { BannerTypes } from "../../types";
3
4
  export interface BannerIconProps {
4
- icon: IconNames;
5
+ readonly icon: IconNames;
6
+ readonly type: BannerTypes;
5
7
  }
6
- export declare function BannerIcon({ icon }: BannerIconProps): JSX.Element;
8
+ export declare function BannerIcon({ icon, type }: BannerIconProps): JSX.Element;
@@ -0,0 +1,15 @@
1
+ export declare const styles: {
2
+ bannerIcon: {
3
+ borderRadius: number;
4
+ padding: number;
5
+ };
6
+ error: {
7
+ backgroundColor: string;
8
+ };
9
+ warning: {
10
+ backgroundColor: string;
11
+ };
12
+ notice: {
13
+ backgroundColor: string;
14
+ };
15
+ };
@@ -28,12 +28,12 @@ export interface BannerProps {
28
28
  */
29
29
  readonly children?: ReactElement | ReactElement[] | string;
30
30
  /**
31
- * **Deprecated**: Use `children` with a `<TextList level="textSupporting" />` instead
31
+ * **Deprecated**: Use `children` with a `<TextList level="text" />` instead
32
32
  * @deprecated
33
33
  */
34
34
  readonly details?: string[];
35
35
  /**
36
- * **Deprecated**: Use either `children` with a `<Text level="textSupporting" />` or a `string` instead
36
+ * **Deprecated**: Use `children` with a `<Text level="text" />`
37
37
  * @deprecated
38
38
  */
39
39
  readonly text?: string;
@@ -1,7 +1,7 @@
1
1
  export declare const styles: {
2
2
  details: {
3
+ width: string;
3
4
  flexDirection: "column";
4
- marginTop: number;
5
5
  };
6
6
  detail: {
7
7
  flexDirection: "row";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.54.4",
3
+ "version": "0.55.0",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -84,5 +84,5 @@
84
84
  "react-native-reanimated": "^2.17.0",
85
85
  "react-native-safe-area-context": "^4.5.2"
86
86
  },
87
- "gitHead": "4780cde621bb770d24f6bd324ee868b302afc579"
87
+ "gitHead": "825421391c3feb7c30550303949e6f6f0c36535a"
88
88
  }
@@ -4,19 +4,15 @@ import { tokens } from "../utils/design";
4
4
  export const styles = StyleSheet.create({
5
5
  container: {
6
6
  width: "100%",
7
- },
8
- error: {
9
- backgroundColor: tokens["color-critical--surface"],
10
- },
11
- warning: {
12
- backgroundColor: tokens["color-warning--surface"],
13
- },
14
- notice: {
15
- backgroundColor: tokens["color-informative--surface"],
7
+ borderColor: tokens["color-border"],
8
+ borderStyle: "solid",
9
+ borderBottomWidth: tokens["border-base"],
10
+ backgroundColor: tokens["color-surface"],
16
11
  },
17
12
  bannerContent: {
18
13
  flexDirection: "row",
19
14
  alignItems: "flex-start",
15
+ gap: tokens["space-small"] + tokens["space-smaller"],
20
16
  },
21
17
  contentContainer: {
22
18
  flex: 1,
@@ -27,7 +23,13 @@ export const styles = StyleSheet.create({
27
23
  textContainer: {
28
24
  marginTop: tokens["space-minuscule"],
29
25
  },
30
- bannerIcon: {
31
- paddingRight: tokens["space-small"],
26
+ fullWidth: {
27
+ width: "100%",
28
+ },
29
+ bannerChildrenContent: {
30
+ marginBottom: tokens["space-small"],
31
+ },
32
+ contentSpacing: {
33
+ gap: tokens["space-small"],
32
34
  },
33
35
  });
@@ -1,51 +1,159 @@
1
1
  import React from "react";
2
2
  import { render } from "@testing-library/react-native";
3
3
  import { Banner } from ".";
4
- import { tokens } from "../utils/design";
4
+ import { Text } from "../Text";
5
+ import { TextList } from "../TextList";
5
6
 
6
7
  describe("Banner", () => {
7
- it("renders an error Banner", () => {
8
- const { getByText, getByRole } = render(
9
- <Banner type="error" text="An error happened" />,
10
- );
11
- const alertPressable = getByRole("alert");
12
- expect(getByText("An error happened")).toBeDefined();
13
- expect(alertPressable.props.style).toContainEqual({
14
- backgroundColor: tokens["color-critical--surface"],
8
+ describe("Type", () => {
9
+ it("renders an error Banner", () => {
10
+ const { getByText, getByTestId } = render(
11
+ <Banner type="error" text="An error happened" />,
12
+ );
13
+
14
+ const icon = getByTestId("ATL-Banner-Icon");
15
+ expect(getByText("An error happened")).toBeDefined();
16
+ expect(icon.props.children.props.name).toBe("alert");
15
17
  });
16
- });
17
18
 
18
- it("renders a warning Banner", () => {
19
- const { getByText, getByRole } = render(
20
- <Banner type="warning" text="Here is a warning" />,
21
- );
19
+ it("renders a warning Banner", () => {
20
+ const { getByText, getByTestId } = render(
21
+ <Banner type="warning" text="Here is a warning" />,
22
+ );
22
23
 
23
- const alertPressable = getByRole("alert");
24
- expect(getByText("Here is a warning")).toBeDefined();
25
- expect(alertPressable.props.style).toContainEqual({
26
- backgroundColor: tokens["color-warning--surface"],
24
+ const icon = getByTestId("ATL-Banner-Icon");
25
+ expect(getByText("Here is a warning")).toBeDefined();
26
+ expect(icon.props.children.props.name).toBe("help");
27
27
  });
28
- });
29
28
 
30
- it("renders a Banner with details", () => {
31
- const tree = render(
32
- <Banner type="error" text="You are disconnected" details={["details"]} />,
33
- );
29
+ it("renders a notice Banner", () => {
30
+ const { getByText, getByTestId } = render(
31
+ <Banner type="notice" text="Notice me" />,
32
+ );
34
33
 
35
- expect(tree.getByText("You are disconnected")).toBeDefined();
36
- expect(tree.getByText("details")).toBeDefined();
34
+ const icon = getByTestId("ATL-Banner-Icon");
35
+ expect(getByText("Notice me")).toBeDefined();
36
+ expect(icon.props.children.props.name).toBe("starburst");
37
+ });
37
38
  });
38
39
 
39
- it("renders a Banner with multiple details", () => {
40
- const tree = render(
41
- <Banner
42
- type="error"
43
- text="You are disconnected"
44
- details={["details", "etc"]}
45
- />,
46
- );
47
-
48
- expect(tree.getByText("details")).toBeDefined();
49
- expect(tree.getByText("etc")).toBeDefined();
40
+ describe("Children", () => {
41
+ describe("should flow", () => {
42
+ it("renders using RNText when there is a text prop", () => {
43
+ const { getByText, queryByTestId } = render(
44
+ <Banner type="notice" text="Notice me" />,
45
+ );
46
+
47
+ expect(getByText("Notice me")).toBeDefined();
48
+ expect(queryByTestId("ATL-Banner-RNText")).toBeDefined();
49
+ });
50
+ it("renders using RNText when there is a Text element", () => {
51
+ const { getByText, queryByTestId } = render(
52
+ <Banner type="notice">
53
+ <Text>Notice me</Text>
54
+ </Banner>,
55
+ );
56
+
57
+ expect(getByText("Notice me")).toBeDefined();
58
+ expect(queryByTestId("ATL-Banner-RNText")).toBeDefined();
59
+ });
60
+ });
61
+
62
+ describe("should stack", () => {
63
+ it("renders using View when there is a details prop", () => {
64
+ const { getByText, queryByTestId } = render(
65
+ <Banner type="notice" details={["details"]} />,
66
+ );
67
+
68
+ expect(getByText("details")).toBeDefined();
69
+ expect(queryByTestId("ATL-View-Container")).toBeDefined();
70
+ });
71
+
72
+ it("renders using View when there is more than one child", () => {
73
+ const { getByText, queryByTestId } = render(
74
+ <Banner type="notice">
75
+ <Text>Notice me</Text>
76
+ <TextList items={["Detail One", "Detail Two", "Detail Three"]} />
77
+ </Banner>,
78
+ );
79
+
80
+ expect(getByText("Notice me")).toBeDefined();
81
+ expect(getByText("Detail Three")).toBeDefined();
82
+ expect(queryByTestId("ATL-View-Container")).toBeDefined();
83
+ });
84
+
85
+ it("renders using View when there is a details prop and a text prop", () => {
86
+ const { getByText, queryByTestId } = render(
87
+ <Banner type="notice" text="Notice me" details={["details"]} />,
88
+ );
89
+
90
+ expect(getByText("Notice me")).toBeDefined();
91
+ expect(getByText("details")).toBeDefined();
92
+ expect(queryByTestId("ATL-View-Container")).toBeDefined();
93
+ });
94
+
95
+ it("renders using View when there are text and details props, as well as a child", () => {
96
+ const { getByText, queryByTestId } = render(
97
+ <Banner type="notice" text="Notice me" details={["details"]}>
98
+ <Text>I am a text child!</Text>
99
+ </Banner>,
100
+ );
101
+
102
+ expect(getByText("Notice me")).toBeDefined();
103
+ expect(getByText("I am a text child!")).toBeDefined();
104
+ expect(queryByTestId("ATL-View-Container")).toBeDefined();
105
+ });
106
+ });
107
+
108
+ it("should render multiple details when using the details prop", () => {
109
+ const tree = render(
110
+ <Banner
111
+ type="error"
112
+ text="You are disconnected"
113
+ details={["details", "etc"]}
114
+ />,
115
+ );
116
+
117
+ expect(tree.getByText("details")).toBeDefined();
118
+ expect(tree.getByText("etc")).toBeDefined();
119
+ });
120
+
121
+ it("should render multiple details when using the TextList component", () => {
122
+ const tree = render(
123
+ <Banner type="error" text="You are disconnected">
124
+ <TextList items={["details", "etc"]} />
125
+ </Banner>,
126
+ );
127
+
128
+ expect(tree.getByText("details")).toBeDefined();
129
+ expect(tree.getByText("etc")).toBeDefined();
130
+ });
131
+
132
+ it("should render a Typography bar when there is an action prop but no details", () => {
133
+ const { getByText } = render(
134
+ <Banner
135
+ type="error"
136
+ text="You are disconnected"
137
+ action={{ label: "Reconnect", onPress: jest.fn() }}
138
+ />,
139
+ );
140
+
141
+ expect(getByText("Reconnect")).toBeDefined();
142
+ expect(getByText("|")).toBeDefined();
143
+ });
144
+
145
+ it("should not render a Typography bar when there are action and details props", () => {
146
+ const { getByText, queryByText } = render(
147
+ <Banner
148
+ type="error"
149
+ text="You are disconnected"
150
+ details={["details"]}
151
+ action={{ label: "Reconnect", onPress: jest.fn() }}
152
+ />,
153
+ );
154
+
155
+ expect(getByText("Reconnect")).toBeDefined();
156
+ expect(queryByText("|")).toBeNull();
157
+ });
50
158
  });
51
159
  });
@@ -1,13 +1,14 @@
1
- import React, { ReactElement } from "react";
2
- import { Pressable, View } from "react-native";
3
- import { BannerProps } from "./types";
4
- import { BannerTypeStyles } from "./constants";
1
+ import React, { PropsWithChildren, ReactElement } from "react";
2
+ import { Pressable, Text as RNText, View } from "react-native";
3
+ import { IconNames } from "@jobber/design";
4
+ import { BannerProps, BannerTypes } from "./types";
5
5
  import { styles } from "./Banner.style";
6
6
  import { BannerIcon } from "./components/BannerIcon/BannerIcon";
7
7
  import { Content } from "../Content";
8
8
  import { Text } from "../Text";
9
9
  import { TextList } from "../TextList";
10
10
  import { ActionLabel } from "../ActionLabel";
11
+ import { Typography } from "../Typography";
11
12
 
12
13
  export function Banner({
13
14
  action,
@@ -17,43 +18,79 @@ export function Banner({
17
18
  children,
18
19
  icon,
19
20
  }: BannerProps): JSX.Element {
21
+ const bannerIcon = icon || getBannerIcon(type);
22
+
23
+ const shouldFlow =
24
+ Boolean(React.Children.count(children) === 1 && !text && !details) ||
25
+ Boolean(text && !details && !children);
26
+
20
27
  return (
21
28
  <Pressable
22
- style={[styles.container, BannerTypeStyles[type].styles]}
29
+ style={[styles.container]}
23
30
  accessibilityRole="alert"
24
31
  onPress={action?.onPress}
25
32
  >
26
33
  <Content childSpacing="small">
27
34
  <View style={styles.bannerContent}>
28
- {icon && <BannerIcon icon={icon} />}
35
+ {bannerIcon && <BannerIcon icon={bannerIcon} type={type} />}
29
36
  <View style={styles.contentContainer}>
30
37
  <View style={styles.childrenContainer}>
31
- <BannerChildren>{children}</BannerChildren>
38
+ <WrappingElement shouldFlow={shouldFlow}>
39
+ <BannerChildren>{children}</BannerChildren>
40
+
41
+ {text && <Text level="text">{text}</Text>}
42
+
43
+ {details && <TextList items={details} level="text" />}
44
+
45
+ {action && (
46
+ <RNText>
47
+ {shouldFlow && <Typography color="subdued"> | </Typography>}
48
+ <ActionLabel align="start">{action.label}</ActionLabel>
49
+ </RNText>
50
+ )}
51
+ </WrappingElement>
32
52
  </View>
33
- {text && (
34
- <View style={styles.textContainer}>
35
- <Text level="textSupporting">{text}</Text>
36
- </View>
37
- )}
38
- {details && <TextList items={details} level="textSupporting" />}
39
53
  </View>
40
54
  </View>
41
- {action && <ActionLabel align="start">{action.label}</ActionLabel>}
42
55
  </Content>
43
56
  </Pressable>
44
57
  );
45
58
  }
46
59
 
47
- function BannerChildren({
48
- children,
49
- }: {
50
- children?: ReactElement | ReactElement[] | string;
51
- }): JSX.Element {
60
+ function BannerChildren({ children }: PropsWithChildren): JSX.Element {
52
61
  if (!children) return <></>;
53
62
 
54
63
  if (children && typeof children === "string") {
55
- return <Text level="textSupporting">{children}</Text>;
64
+ return <Text level="text">{children}</Text>;
56
65
  }
57
66
 
58
67
  return <>{children}</>;
59
68
  }
69
+
70
+ function WrappingElement({
71
+ shouldFlow,
72
+ children,
73
+ }: PropsWithChildren<{
74
+ readonly shouldFlow: boolean;
75
+ }>): ReactElement {
76
+ if (shouldFlow) {
77
+ return <RNText testID="ATL-Banner-RNText">{children}</RNText>;
78
+ }
79
+
80
+ return (
81
+ <View testID="ATL-Banner-View" style={styles.contentSpacing}>
82
+ {children}
83
+ </View>
84
+ );
85
+ }
86
+
87
+ function getBannerIcon(type: BannerTypes): IconNames | undefined {
88
+ switch (type) {
89
+ case "notice":
90
+ return "starburst";
91
+ case "warning":
92
+ return "help";
93
+ case "error":
94
+ return "alert";
95
+ }
96
+ }
@@ -0,0 +1,18 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../../../utils/design";
3
+
4
+ export const styles = StyleSheet.create({
5
+ bannerIcon: {
6
+ borderRadius: tokens["radius-circle"],
7
+ padding: tokens["space-smaller"],
8
+ },
9
+ error: {
10
+ backgroundColor: tokens["color-destructive"],
11
+ },
12
+ warning: {
13
+ backgroundColor: tokens["color-warning"],
14
+ },
15
+ notice: {
16
+ backgroundColor: tokens["color-informative"],
17
+ },
18
+ });
@@ -1,17 +1,19 @@
1
1
  import React from "react";
2
- import { IconNames, tokens } from "@jobber/design";
2
+ import { IconNames } from "@jobber/design";
3
3
  import { View } from "react-native";
4
+ import { styles } from "./BannerIcon.style";
4
5
  import { Icon } from "../../../Icon";
5
- import { styles } from "../../Banner.style";
6
+ import { BannerTypes } from "../../types";
6
7
 
7
8
  export interface BannerIconProps {
8
- icon: IconNames;
9
+ readonly icon: IconNames;
10
+ readonly type: BannerTypes;
9
11
  }
10
12
 
11
- export function BannerIcon({ icon }: BannerIconProps): JSX.Element {
13
+ export function BannerIcon({ icon, type }: BannerIconProps): JSX.Element {
12
14
  return (
13
- <View style={styles.bannerIcon}>
14
- <Icon name={icon} customColor={tokens["color-text"]} />
15
+ <View style={[styles.bannerIcon, styles[type]]} testID="ATL-Banner-Icon">
16
+ <Icon name={icon} color="white" size="small" />
15
17
  </View>
16
18
  );
17
19
  }
@@ -35,13 +35,13 @@ export interface BannerProps {
35
35
  readonly children?: ReactElement | ReactElement[] | string;
36
36
 
37
37
  /**
38
- * **Deprecated**: Use `children` with a `<TextList level="textSupporting" />` instead
38
+ * **Deprecated**: Use `children` with a `<TextList level="text" />` instead
39
39
  * @deprecated
40
40
  */
41
41
  readonly details?: string[];
42
42
 
43
43
  /**
44
- * **Deprecated**: Use either `children` with a `<Text level="textSupporting" />` or a `string` instead
44
+ * **Deprecated**: Use `children` with a `<Text level="text" />`
45
45
  * @deprecated
46
46
  */
47
47
  readonly text?: string;
@@ -3,8 +3,8 @@ import { tokens } from "../utils/design";
3
3
 
4
4
  export const styles = StyleSheet.create({
5
5
  details: {
6
+ width: "100%",
6
7
  flexDirection: "column",
7
- marginTop: tokens["space-small"],
8
8
  },
9
9
  detail: {
10
10
  flexDirection: "row",
@@ -5,7 +5,7 @@ exports[`TextList when the bulletItems is provided displays the text list 1`] =
5
5
  style={
6
6
  {
7
7
  "flexDirection": "column",
8
- "marginTop": 8,
8
+ "width": "100%",
9
9
  }
10
10
  }
11
11
  >
@@ -1,12 +0,0 @@
1
- import { styles } from "./Banner.style";
2
- export const BannerTypeStyles = {
3
- error: {
4
- styles: styles.error,
5
- },
6
- warning: {
7
- styles: styles.warning,
8
- },
9
- notice: {
10
- styles: styles.notice,
11
- },
12
- };
@@ -1,2 +0,0 @@
1
- import { BannerStyleProps } from "./types";
2
- export declare const BannerTypeStyles: Record<string, BannerStyleProps>;
@@ -1,14 +0,0 @@
1
- import { styles } from "./Banner.style";
2
- import { BannerStyleProps } from "./types";
3
-
4
- export const BannerTypeStyles: Record<string, BannerStyleProps> = {
5
- error: {
6
- styles: styles.error,
7
- },
8
- warning: {
9
- styles: styles.warning,
10
- },
11
- notice: {
12
- styles: styles.notice,
13
- },
14
- };