@jobber/components-native 0.114.16 → 0.114.17-JOB-90457--dc056e7.4
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/dist/docs/Banner/Banner.md +10 -0
- package/dist/package.json +3 -3
- package/dist/src/Banner/Banner.js +37 -15
- package/dist/src/Banner/Banner.style.js +13 -0
- package/dist/src/Banner/Banner.test.js +47 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/Banner/Banner.d.ts +1 -1
- package/dist/types/src/Banner/Banner.style.d.ts +13 -0
- package/dist/types/src/Banner/types.d.ts +14 -0
- package/package.json +3 -3
- package/src/Banner/Banner.stories.tsx +23 -0
- package/src/Banner/Banner.style.ts +13 -0
- package/src/Banner/Banner.test.tsx +87 -1
- package/src/Banner/Banner.tsx +84 -27
- package/src/Banner/docs/BannerDismissibleActionExample.tsx +23 -0
- package/src/Banner/docs/BannerDismissibleExample.tsx +19 -0
- package/src/Banner/docs/index.ts +2 -0
- package/src/Banner/types.ts +17 -0
|
@@ -166,6 +166,13 @@ See
|
|
|
166
166
|
[Actions in Banners](/storybook/mobile/?path=/story/components-status-and-feedback-banner--actions-in-banners)
|
|
167
167
|
for an example.
|
|
168
168
|
|
|
169
|
+
### Dismissal (mobile)
|
|
170
|
+
|
|
171
|
+
Set `dismissible` to render a dismiss button in the top-right corner of the
|
|
172
|
+
Banner. The dismiss button hides the Banner and calls `onDismiss` when pressed.
|
|
173
|
+
If the Banner also has an `action`, pressing the dismiss button does not trigger
|
|
174
|
+
the action.
|
|
175
|
+
|
|
169
176
|
## Component customization
|
|
170
177
|
|
|
171
178
|
### Composable usage
|
|
@@ -386,5 +393,8 @@ Use `UNSAFE_style` to apply inline custom styles to the Banner.
|
|
|
386
393
|
| `action` | `ActionProps` | No | — | The function that should be performed when the Banner is pressed |
|
|
387
394
|
| `children` | `string | ReactElement<unknown, string | JSXElementConstructor<any>> | ReactElement<unknown, string | JSXElementConstructor<any>>[]` | No | — | Custom content to be displayed in the banner |
|
|
388
395
|
| `details` | `string[]` | No | — | **Deprecated**: Use `children` with a `<TextList level="text" />` instead @deprecated |
|
|
396
|
+
| `dismissAccessibilityLabel` | `string` | No | — | Accessibility label for the dismiss button. |
|
|
397
|
+
| `dismissible` | `boolean` | No | `false` | Set to true to show a dismiss button |
|
|
389
398
|
| `icon` | `IconNames` | No | — | Adds an icon to the left of the banner content |
|
|
399
|
+
| `onDismiss` | `() => void` | No | — | Callback to be called when the Banner is dismissed. |
|
|
390
400
|
| `text` | `string` | No | — | **Deprecated**: Use `children` with a `<Text level="text" />` @deprecated |
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.114.
|
|
3
|
+
"version": "0.114.17-JOB-90457--dc056e7.4+dc056e7c",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native implementation of Atlantis",
|
|
6
6
|
"repository": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@rn-primitives/select": "^1.4.0",
|
|
63
63
|
"autolinker": "^4.0.0",
|
|
64
64
|
"deepmerge": "^4.2.2",
|
|
65
|
-
"lodash": "^4.
|
|
65
|
+
"lodash": "^4.18.1",
|
|
66
66
|
"react-hook-form": "^7.52.0",
|
|
67
67
|
"react-intl": "^7.1.11",
|
|
68
68
|
"react-native-keyboard-controller": "^1.20.7",
|
|
@@ -123,5 +123,5 @@
|
|
|
123
123
|
"react-native-screens": ">=4.18.0",
|
|
124
124
|
"react-native-svg": ">=12.0.0"
|
|
125
125
|
},
|
|
126
|
-
"gitHead": "
|
|
126
|
+
"gitHead": "dc056e7c9219e48e31c2bb326c4551437bf75dbc"
|
|
127
127
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useState } from "react";
|
|
2
2
|
import { Pressable, Text as RNText, View } from "react-native";
|
|
3
3
|
import { useStyles } from "./Banner.style";
|
|
4
4
|
import { BannerIcon } from "./components/BannerIcon/BannerIcon";
|
|
@@ -7,32 +7,54 @@ import { Text } from "../Text";
|
|
|
7
7
|
import { TextList } from "../TextList";
|
|
8
8
|
import { ActionLabel } from "../ActionLabel";
|
|
9
9
|
import { Typography } from "../Typography";
|
|
10
|
-
|
|
10
|
+
import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
|
|
11
|
+
import { Icon } from "../Icon";
|
|
12
|
+
export function Banner({ action, details, dismissAccessibilityLabel, dismissible = false, text, type, children, icon, onDismiss, }) {
|
|
11
13
|
const bannerIcon = icon || getBannerIcon(type);
|
|
14
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
15
|
+
const { t } = useAtlantisI18n();
|
|
12
16
|
const shouldFlow = Boolean(React.Children.count(children) === 1 && !text && !details) ||
|
|
13
17
|
Boolean(text && !details && !children);
|
|
14
18
|
const styles = useStyles();
|
|
15
|
-
|
|
19
|
+
if (!isVisible)
|
|
20
|
+
return null;
|
|
21
|
+
const handleDismiss = (event) => {
|
|
22
|
+
event === null || event === void 0 ? void 0 : event.stopPropagation();
|
|
23
|
+
setIsVisible(false);
|
|
24
|
+
onDismiss === null || onDismiss === void 0 ? void 0 : onDismiss();
|
|
25
|
+
};
|
|
26
|
+
const bannerContent = (React.createElement(React.Fragment, null,
|
|
27
|
+
bannerIcon && React.createElement(BannerIcon, { icon: bannerIcon, type: type }),
|
|
28
|
+
React.createElement(View, { style: styles.contentContainer },
|
|
29
|
+
React.createElement(View, { style: styles.childrenContainer },
|
|
30
|
+
React.createElement(WrappingElement, { shouldFlow: shouldFlow, styles: styles },
|
|
31
|
+
React.createElement(BannerChildren, null, children),
|
|
32
|
+
text && React.createElement(Text, { level: "text" }, text),
|
|
33
|
+
details && React.createElement(TextList, { items: details, level: "text" }),
|
|
34
|
+
action && (React.createElement(RNText, null,
|
|
35
|
+
shouldFlow && React.createElement(Typography, { color: "subdued" }, " | "),
|
|
36
|
+
React.createElement(ActionLabel, { align: "start" }, action.label))))))));
|
|
37
|
+
const renderBannerMainContent = () => {
|
|
38
|
+
if (!action) {
|
|
39
|
+
return (React.createElement(View, { style: styles.bannerMainContent, accessibilityRole: "alert", accessible: true, testID: "ATL-Banner-Content" }, bannerContent));
|
|
40
|
+
}
|
|
41
|
+
return (React.createElement(Pressable, { style: styles.bannerMainContent, accessibilityRole: "alert", onPress: action.onPress, testID: "ATL-Banner-Pressable" }, bannerContent));
|
|
42
|
+
};
|
|
43
|
+
return (React.createElement(View, { style: [styles.container] },
|
|
16
44
|
React.createElement(Content, { childSpacing: "small" },
|
|
17
45
|
React.createElement(View, { style: styles.bannerContent },
|
|
18
|
-
|
|
19
|
-
React.createElement(View,
|
|
20
|
-
React.createElement(
|
|
21
|
-
React.createElement(
|
|
22
|
-
React.createElement(BannerChildren, null, children),
|
|
23
|
-
text && React.createElement(Text, { level: "text" }, text),
|
|
24
|
-
details && React.createElement(TextList, { items: details, level: "text" }),
|
|
25
|
-
action && (React.createElement(RNText, null,
|
|
26
|
-
shouldFlow && React.createElement(Typography, { color: "subdued" }, " | "),
|
|
27
|
-
React.createElement(ActionLabel, { align: "start" }, action.label))))))))));
|
|
46
|
+
renderBannerMainContent(),
|
|
47
|
+
dismissible && (React.createElement(View, null,
|
|
48
|
+
React.createElement(Pressable, { style: styles.dismissButton, onPress: handleDismiss, accessibilityLabel: dismissAccessibilityLabel !== null && dismissAccessibilityLabel !== void 0 ? dismissAccessibilityLabel : t("dismiss"), accessibilityRole: "button", testID: "ATL-Banner-DismissButton" },
|
|
49
|
+
React.createElement(Icon, { name: "cross" }))))))));
|
|
28
50
|
}
|
|
29
51
|
function BannerChildren({ children }) {
|
|
30
52
|
if (!children)
|
|
31
|
-
return
|
|
53
|
+
return null;
|
|
32
54
|
if (children && typeof children === "string") {
|
|
33
55
|
return React.createElement(Text, { level: "text" }, children);
|
|
34
56
|
}
|
|
35
|
-
return
|
|
57
|
+
return children;
|
|
36
58
|
}
|
|
37
59
|
function WrappingElement({ shouldFlow, children, styles, }) {
|
|
38
60
|
if (shouldFlow) {
|
|
@@ -13,9 +13,22 @@ export const useStyles = buildThemedStyles(tokens => {
|
|
|
13
13
|
alignItems: "flex-start",
|
|
14
14
|
gap: tokens["space-small"] + tokens["space-smaller"],
|
|
15
15
|
},
|
|
16
|
+
bannerMainContent: {
|
|
17
|
+
flex: 1,
|
|
18
|
+
flexDirection: "row",
|
|
19
|
+
alignItems: "flex-start",
|
|
20
|
+
gap: tokens["space-small"] + tokens["space-smaller"],
|
|
21
|
+
},
|
|
16
22
|
contentContainer: {
|
|
17
23
|
flex: 1,
|
|
18
24
|
},
|
|
25
|
+
dismissButton: {
|
|
26
|
+
alignSelf: "flex-start",
|
|
27
|
+
alignItems: "center",
|
|
28
|
+
height: tokens["space-large"],
|
|
29
|
+
justifyContent: "center",
|
|
30
|
+
width: tokens["space-large"],
|
|
31
|
+
},
|
|
19
32
|
childrenContainer: {
|
|
20
33
|
marginTop: tokens["space-smallest"],
|
|
21
34
|
},
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import React from "react";
|
|
2
|
-
import { render } from "@testing-library/react-native";
|
|
11
|
+
import { render, userEvent } from "@testing-library/react-native";
|
|
3
12
|
import { Banner } from ".";
|
|
4
13
|
import { Text } from "../Text";
|
|
5
14
|
import { TextList } from "../TextList";
|
|
15
|
+
const user = userEvent.setup();
|
|
6
16
|
describe("Banner", () => {
|
|
7
17
|
describe("Type", () => {
|
|
8
18
|
it("renders an error Banner", () => {
|
|
@@ -95,4 +105,40 @@ describe("Banner", () => {
|
|
|
95
105
|
expect(queryByText("|")).toBeNull();
|
|
96
106
|
});
|
|
97
107
|
});
|
|
108
|
+
describe("Dismissal", () => {
|
|
109
|
+
it("does not render a dismiss button by default", () => {
|
|
110
|
+
const { queryByTestId } = render(React.createElement(Banner, { type: "notice" }, "Notice me"));
|
|
111
|
+
expect(queryByTestId("ATL-Banner-DismissButton")).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
it("renders a dismiss button when dismissible", () => {
|
|
114
|
+
const { getByTestId } = render(React.createElement(Banner, { type: "notice", dismissible: true }, "Notice me"));
|
|
115
|
+
expect(getByTestId("ATL-Banner-DismissButton")).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
it("keeps a non-action banner accessible as an alert", () => {
|
|
118
|
+
const { getByTestId } = render(React.createElement(Banner, { type: "notice", dismissible: true }, "Notice me"));
|
|
119
|
+
expect(getByTestId("ATL-Banner-Content").props.accessible).toBe(true);
|
|
120
|
+
expect(getByTestId("ATL-Banner-Content").props.accessibilityRole).toBe("alert");
|
|
121
|
+
});
|
|
122
|
+
it("dismisses the banner and calls onDismiss when the dismiss button is pressed", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
|
+
const onDismiss = jest.fn();
|
|
124
|
+
const { getByTestId, queryByText } = render(React.createElement(Banner, { type: "notice", dismissible: true, onDismiss: onDismiss }, "Notice me"));
|
|
125
|
+
yield user.press(getByTestId("ATL-Banner-DismissButton"));
|
|
126
|
+
expect(onDismiss).toHaveBeenCalledTimes(1);
|
|
127
|
+
expect(queryByText("Notice me")).toBeNull();
|
|
128
|
+
}));
|
|
129
|
+
it("does not call the banner action when the dismiss button is pressed", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
130
|
+
const onDismiss = jest.fn();
|
|
131
|
+
const onPress = jest.fn();
|
|
132
|
+
const { getByTestId } = render(React.createElement(Banner, { type: "notice", dismissible: true, onDismiss: onDismiss, action: { label: "View Plans", onPress } }, "Notice me"));
|
|
133
|
+
yield user.press(getByTestId("ATL-Banner-DismissButton"));
|
|
134
|
+
expect(onDismiss).toHaveBeenCalledTimes(1);
|
|
135
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
136
|
+
}));
|
|
137
|
+
it("calls the banner action when the main banner content is pressed", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
138
|
+
const onPress = jest.fn();
|
|
139
|
+
const { getByTestId } = render(React.createElement(Banner, { type: "notice", dismissible: true, action: { label: "View Plans", onPress } }, "Notice me"));
|
|
140
|
+
yield user.press(getByTestId("ATL-Banner-Pressable"));
|
|
141
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
142
|
+
}));
|
|
143
|
+
});
|
|
98
144
|
});
|