@jobber/components-native 0.103.1 → 0.104.1
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/Form/Form.md +2 -2
- package/dist/package.json +3 -3
- package/dist/src/primitives/HelperText/HelperText.js +13 -0
- package/dist/src/primitives/HelperText/HelperText.style.js +19 -0
- package/dist/src/primitives/HelperText/HelperText.test.js +55 -0
- package/dist/src/primitives/HelperText/index.js +1 -0
- package/dist/src/primitives/index.js +4 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/primitives/HelperText/HelperText.d.ts +15 -0
- package/dist/types/src/primitives/HelperText/HelperText.style.d.ts +16 -0
- package/dist/types/src/primitives/HelperText/HelperText.test.d.ts +1 -0
- package/dist/types/src/primitives/HelperText/index.d.ts +2 -0
- package/dist/types/src/primitives/index.d.ts +1 -1
- package/package.json +3 -3
- package/src/primitives/HelperText/HelperText.stories.tsx +36 -0
- package/src/primitives/HelperText/HelperText.style.ts +21 -0
- package/src/primitives/HelperText/HelperText.test.tsx +80 -0
- package/src/primitives/HelperText/HelperText.tsx +43 -0
- package/src/primitives/HelperText/docs/HelperTextCriticalExample.tsx +9 -0
- package/src/primitives/HelperText/docs/HelperTextNeutralExample.tsx +9 -0
- package/src/primitives/HelperText/docs/index.ts +2 -0
- package/src/primitives/HelperText/index.ts +2 -0
- package/src/primitives/index.ts +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface HelperTextProps {
|
|
3
|
+
/**
|
|
4
|
+
* The helper message.
|
|
5
|
+
*/
|
|
6
|
+
readonly message: string;
|
|
7
|
+
/**
|
|
8
|
+
* `neutral` renders subdued supporting text. `critical` renders an `alert`
|
|
9
|
+
* icon followed by error-coloured supporting text.
|
|
10
|
+
*
|
|
11
|
+
* @default "neutral"
|
|
12
|
+
*/
|
|
13
|
+
readonly status?: "neutral" | "critical";
|
|
14
|
+
}
|
|
15
|
+
export declare function HelperText({ message, status }: HelperTextProps): React.JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const useStyles: () => {
|
|
2
|
+
row: {
|
|
3
|
+
flexDirection: "row";
|
|
4
|
+
alignItems: "flex-start";
|
|
5
|
+
};
|
|
6
|
+
rowNeutral: {
|
|
7
|
+
gap: number;
|
|
8
|
+
};
|
|
9
|
+
rowCritical: {
|
|
10
|
+
gap: number;
|
|
11
|
+
};
|
|
12
|
+
iconContainer: {
|
|
13
|
+
height: number;
|
|
14
|
+
justifyContent: "center";
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.104.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native implementation of Atlantis",
|
|
6
6
|
"repository": {
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"@babel/runtime": "^7.29.2",
|
|
73
73
|
"@gorhom/bottom-sheet": "^5.2.8",
|
|
74
74
|
"@jobber/design": "0.101.1",
|
|
75
|
-
"@jobber/hooks": "2.
|
|
75
|
+
"@jobber/hooks": "2.21.0",
|
|
76
76
|
"@react-native-community/datetimepicker": "^8.4.5",
|
|
77
77
|
"@react-native/babel-preset": "^0.82.1",
|
|
78
78
|
"@storybook/addon-a11y": "10.3.5",
|
|
@@ -122,5 +122,5 @@
|
|
|
122
122
|
"react-native-screens": ">=4.18.0",
|
|
123
123
|
"react-native-svg": ">=12.0.0"
|
|
124
124
|
},
|
|
125
|
-
"gitHead": "
|
|
125
|
+
"gitHead": "60b75a0d6a64b67dcb6607c474ed7aa469474f98"
|
|
126
126
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
2
|
+
import { HelperText } from "./HelperText";
|
|
3
|
+
import { HelperTextCriticalExample, HelperTextNeutralExample } from "./docs";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/Primitives/HelperText",
|
|
7
|
+
component: HelperText,
|
|
8
|
+
parameters: {
|
|
9
|
+
viewport: { defaultViewport: "mobile1" },
|
|
10
|
+
},
|
|
11
|
+
argTypes: {
|
|
12
|
+
status: {
|
|
13
|
+
control: "select",
|
|
14
|
+
options: ["neutral", "critical"],
|
|
15
|
+
},
|
|
16
|
+
message: { control: "text" },
|
|
17
|
+
},
|
|
18
|
+
} satisfies Meta<typeof HelperText>;
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof meta>;
|
|
21
|
+
|
|
22
|
+
export const Neutral: Story = {
|
|
23
|
+
render: HelperTextNeutralExample,
|
|
24
|
+
args: {
|
|
25
|
+
message:
|
|
26
|
+
"This is some helper text to aid users. It should be short and to the point.",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const Critical: Story = {
|
|
31
|
+
render: HelperTextCriticalExample,
|
|
32
|
+
args: {
|
|
33
|
+
message:
|
|
34
|
+
"This is some helper text to aid users. It should be short and to the point.",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { tokens as staticTokens } from "../../utils/design";
|
|
2
|
+
import { buildThemedStyles } from "../../AtlantisThemeContext";
|
|
3
|
+
|
|
4
|
+
const tightLineHeight = staticTokens["typography--lineHeight-tight"];
|
|
5
|
+
|
|
6
|
+
export const useStyles = buildThemedStyles(tokens => ({
|
|
7
|
+
row: {
|
|
8
|
+
flexDirection: "row",
|
|
9
|
+
alignItems: "flex-start",
|
|
10
|
+
},
|
|
11
|
+
rowNeutral: {
|
|
12
|
+
gap: 0,
|
|
13
|
+
},
|
|
14
|
+
rowCritical: {
|
|
15
|
+
gap: tokens["space-smaller"],
|
|
16
|
+
},
|
|
17
|
+
iconContainer: {
|
|
18
|
+
height: tightLineHeight,
|
|
19
|
+
justifyContent: "center",
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@testing-library/react-native";
|
|
3
|
+
import { AccessibilityInfo } from "react-native";
|
|
4
|
+
import { HelperText } from ".";
|
|
5
|
+
import { Icon } from "../../Icon";
|
|
6
|
+
|
|
7
|
+
describe("HelperText", () => {
|
|
8
|
+
describe("neutral status (default)", () => {
|
|
9
|
+
it("renders the message as subdued supporting text without an alert icon or role", () => {
|
|
10
|
+
const { getByText, queryByRole } = render(
|
|
11
|
+
<HelperText message="Pick a status" />,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(getByText("Pick a status")).toBeDefined();
|
|
15
|
+
expect(queryByRole("alert")).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("renders identically when status is explicitly neutral", () => {
|
|
19
|
+
const { getByText, queryByRole } = render(
|
|
20
|
+
<HelperText status="neutral" message="Pick a status" />,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(getByText("Pick a status")).toBeDefined();
|
|
24
|
+
expect(queryByRole("alert")).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("critical status", () => {
|
|
29
|
+
it("renders the alert icon, error text, alert role, and assertive live region", () => {
|
|
30
|
+
const { getByText, getByRole, UNSAFE_getByType } = render(
|
|
31
|
+
<HelperText
|
|
32
|
+
status="critical"
|
|
33
|
+
message="Pick a status before continuing"
|
|
34
|
+
/>,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(getByText("Pick a status before continuing")).toBeDefined();
|
|
38
|
+
|
|
39
|
+
const alertWrapper = getByRole("alert");
|
|
40
|
+
expect(alertWrapper.props.accessibilityLiveRegion).toBe("assertive");
|
|
41
|
+
|
|
42
|
+
// Confirms the alert icon is present alongside the message.
|
|
43
|
+
const icon = UNSAFE_getByType(Icon);
|
|
44
|
+
expect(icon.props.name).toBe("alert");
|
|
45
|
+
expect(icon.props.size).toBe("small");
|
|
46
|
+
expect(icon.props.color).toBe("critical");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("leaf invariants", () => {
|
|
51
|
+
it("does not fire imperative accessibility announcements", () => {
|
|
52
|
+
jest.spyOn(AccessibilityInfo, "announceForAccessibility");
|
|
53
|
+
|
|
54
|
+
render(<HelperText status="neutral" message="Neutral" />);
|
|
55
|
+
render(<HelperText status="critical" message="Critical" />);
|
|
56
|
+
|
|
57
|
+
expect(AccessibilityInfo.announceForAccessibility).not.toHaveBeenCalled();
|
|
58
|
+
|
|
59
|
+
jest.mocked(AccessibilityInfo.announceForAccessibility).mockRestore();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("renders successfully outside of any Atlantis context", () => {
|
|
63
|
+
// Renders bare (no providers) and asserts no warning or throw.
|
|
64
|
+
const warn = jest.spyOn(console, "warn").mockImplementation(() => {
|
|
65
|
+
// swallow warnings; we'll inspect the spy below
|
|
66
|
+
});
|
|
67
|
+
const error = jest.spyOn(console, "error").mockImplementation(() => {
|
|
68
|
+
// swallow errors; we'll inspect the spy below
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(() => render(<HelperText message="Bare render" />)).not.toThrow();
|
|
72
|
+
|
|
73
|
+
expect(warn).not.toHaveBeenCalled();
|
|
74
|
+
expect(error).not.toHaveBeenCalled();
|
|
75
|
+
|
|
76
|
+
warn.mockRestore();
|
|
77
|
+
error.mockRestore();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useStyles } from "./HelperText.style";
|
|
4
|
+
import { Text } from "../../Text";
|
|
5
|
+
import { Icon } from "../../Icon";
|
|
6
|
+
|
|
7
|
+
export interface HelperTextProps {
|
|
8
|
+
/**
|
|
9
|
+
* The helper message.
|
|
10
|
+
*/
|
|
11
|
+
readonly message: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `neutral` renders subdued supporting text. `critical` renders an `alert`
|
|
15
|
+
* icon followed by error-coloured supporting text.
|
|
16
|
+
*
|
|
17
|
+
* @default "neutral"
|
|
18
|
+
*/
|
|
19
|
+
readonly status?: "neutral" | "critical";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function HelperText({ message, status = "neutral" }: HelperTextProps) {
|
|
23
|
+
const styles = useStyles();
|
|
24
|
+
const isCritical = status === "critical";
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<View
|
|
28
|
+
style={[styles.row, isCritical ? styles.rowCritical : styles.rowNeutral]}
|
|
29
|
+
accessible={isCritical || undefined}
|
|
30
|
+
accessibilityRole={isCritical ? "alert" : undefined}
|
|
31
|
+
accessibilityLiveRegion={isCritical ? "assertive" : undefined}
|
|
32
|
+
>
|
|
33
|
+
{isCritical && (
|
|
34
|
+
<View style={styles.iconContainer}>
|
|
35
|
+
<Icon name="alert" size="small" color="critical" />
|
|
36
|
+
</View>
|
|
37
|
+
)}
|
|
38
|
+
<Text level="textSupporting" variation={isCritical ? "error" : "subdued"}>
|
|
39
|
+
{message}
|
|
40
|
+
</Text>
|
|
41
|
+
</View>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { HelperText } from "../HelperText";
|
|
3
|
+
import type { HelperTextProps } from "../HelperText";
|
|
4
|
+
|
|
5
|
+
export function HelperTextCriticalExample({
|
|
6
|
+
message = "This is some helper text to aid users. It should be short and to the point.",
|
|
7
|
+
}: Partial<HelperTextProps>) {
|
|
8
|
+
return <HelperText status="critical" message={message} />;
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { HelperText } from "../HelperText";
|
|
3
|
+
import type { HelperTextProps } from "../HelperText";
|
|
4
|
+
|
|
5
|
+
export function HelperTextNeutralExample({
|
|
6
|
+
message = "This is some helper text to aid users. It should be short and to the point.",
|
|
7
|
+
}: Partial<HelperTextProps>) {
|
|
8
|
+
return <HelperText status="neutral" message={message} />;
|
|
9
|
+
}
|
package/src/primitives/index.ts
CHANGED