@jobber/components-native 0.13.0 → 0.15.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.
- package/dist/src/ActionItem/ActionItem.js +37 -0
- package/dist/src/ActionItem/ActionItem.style.js +23 -0
- package/dist/src/ActionItem/ActionItemGroup.js +19 -0
- package/dist/src/ActionItem/components/ActionItemContainer.js +14 -0
- package/dist/src/ActionItem/components/ActionItemContainer.style.js +12 -0
- package/dist/src/ActionItem/index.js +2 -0
- package/dist/src/Button/Button.js +0 -1
- package/dist/src/Card/Card.js +0 -1
- package/dist/src/ProgressBar/ProgressBar.js +21 -0
- package/dist/src/ProgressBar/ProgressBar.style.js +24 -0
- package/dist/src/ProgressBar/ProgressBarInner.js +29 -0
- package/dist/src/ProgressBar/index.js +1 -0
- package/dist/src/ProgressBar/messages.js +13 -0
- package/dist/src/ProgressBar/types.js +1 -0
- package/dist/src/index.js +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/ActionItem/ActionItem.d.ts +41 -0
- package/dist/types/src/ActionItem/ActionItem.style.d.ts +21 -0
- package/dist/types/src/ActionItem/ActionItemGroup.d.ts +11 -0
- package/dist/types/src/ActionItem/components/ActionItemContainer.d.ts +9 -0
- package/dist/types/src/ActionItem/components/ActionItemContainer.style.d.ts +10 -0
- package/dist/types/src/ActionItem/index.d.ts +2 -0
- package/dist/types/src/Card/Card.d.ts +2 -2
- package/dist/types/src/Card/index.d.ts +1 -1
- package/dist/types/src/ProgressBar/ProgressBar.d.ts +3 -0
- package/dist/types/src/ProgressBar/ProgressBar.style.d.ts +22 -0
- package/dist/types/src/ProgressBar/ProgressBarInner.d.ts +9 -0
- package/dist/types/src/ProgressBar/index.d.ts +2 -0
- package/dist/types/src/ProgressBar/messages.d.ts +12 -0
- package/dist/types/src/ProgressBar/types.d.ts +28 -0
- package/dist/types/src/index.d.ts +2 -0
- package/package.json +2 -2
- package/src/ActionItem/ActionItem.style.ts +29 -0
- package/src/ActionItem/ActionItem.test.tsx +121 -0
- package/src/ActionItem/ActionItem.tsx +131 -0
- package/src/ActionItem/ActionItemGroup.test.tsx +34 -0
- package/src/ActionItem/ActionItemGroup.tsx +43 -0
- package/src/ActionItem/components/ActionItemContainer.style.ts +14 -0
- package/src/ActionItem/components/ActionItemContainer.test.tsx +37 -0
- package/src/ActionItem/components/ActionItemContainer.tsx +45 -0
- package/src/ActionItem/index.ts +2 -0
- package/src/Button/Button.tsx +0 -1
- package/src/Card/Card.tsx +2 -3
- package/src/Card/index.ts +1 -1
- package/src/ProgressBar/ProgressBar.style.ts +25 -0
- package/src/ProgressBar/ProgressBar.test.tsx +41 -0
- package/src/ProgressBar/ProgressBar.tsx +58 -0
- package/src/ProgressBar/ProgressBarInner.tsx +47 -0
- package/src/ProgressBar/__snapshots__/ProgressBar.test.tsx.snap +138 -0
- package/src/ProgressBar/index.tsx +2 -0
- package/src/ProgressBar/messages.ts +14 -0
- package/src/ProgressBar/types.ts +34 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { IconColorNames, IconNames } from "@jobber/design";
|
|
3
|
+
export interface ActionItemProps {
|
|
4
|
+
/**
|
|
5
|
+
* Title of the Action Item
|
|
6
|
+
*/
|
|
7
|
+
readonly title?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Name of the icon to display before content
|
|
10
|
+
*/
|
|
11
|
+
readonly icon?: IconNames;
|
|
12
|
+
/**
|
|
13
|
+
* Colour of the icon displayed before content
|
|
14
|
+
*/
|
|
15
|
+
readonly iconColor?: IconColorNames;
|
|
16
|
+
/**
|
|
17
|
+
* Content to display.
|
|
18
|
+
*/
|
|
19
|
+
readonly children?: ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Action icon to display
|
|
22
|
+
*/
|
|
23
|
+
readonly actionIcon?: ActionIconNames;
|
|
24
|
+
/**
|
|
25
|
+
* Colour of the action icon
|
|
26
|
+
*/
|
|
27
|
+
readonly actionIconColour?: ActionIconColour;
|
|
28
|
+
/**
|
|
29
|
+
* Alignment of action icon
|
|
30
|
+
*/
|
|
31
|
+
readonly actionIconAlignment?: "flex-start" | "flex-end" | "center";
|
|
32
|
+
/**
|
|
33
|
+
* Press handler
|
|
34
|
+
*/
|
|
35
|
+
readonly onPress?: () => void;
|
|
36
|
+
readonly testID?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function ActionItem({ title, icon, iconColor, children, actionIcon, actionIconColour, actionIconAlignment, onPress, testID, }: ActionItemProps): JSX.Element;
|
|
39
|
+
type ActionIconColour = "interactive" | "destructive" | "informative" | "subtle";
|
|
40
|
+
type ActionIconNames = IconNames | "editpencil";
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const styles: {
|
|
2
|
+
actionItemHorizontalOffset: {
|
|
3
|
+
paddingHorizontal: number;
|
|
4
|
+
};
|
|
5
|
+
icon: {
|
|
6
|
+
justifyContent: "flex-start";
|
|
7
|
+
paddingRight: number;
|
|
8
|
+
};
|
|
9
|
+
titlePadding: {
|
|
10
|
+
paddingBottom: number;
|
|
11
|
+
};
|
|
12
|
+
content: {
|
|
13
|
+
flex: number;
|
|
14
|
+
};
|
|
15
|
+
offsetForIcons: {
|
|
16
|
+
paddingTop: number;
|
|
17
|
+
};
|
|
18
|
+
actionIcon: {
|
|
19
|
+
paddingLeft: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { ActionItem, ActionItemProps } from "./ActionItem";
|
|
3
|
+
export type ActionItemElement = ReactElement<ActionItemProps, typeof ActionItem> | boolean | null | undefined;
|
|
4
|
+
interface ActionItemGroupProps {
|
|
5
|
+
/**
|
|
6
|
+
* Action Items
|
|
7
|
+
*/
|
|
8
|
+
readonly children: ActionItemElement | ActionItemElement[];
|
|
9
|
+
}
|
|
10
|
+
export declare function ActionItemGroup({ children, }: ActionItemGroupProps): JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
interface ActionItemContainerProps {
|
|
3
|
+
readonly children: ReactNode;
|
|
4
|
+
readonly title?: string;
|
|
5
|
+
readonly onPress?: () => void;
|
|
6
|
+
readonly testID?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function ActionItemContainer({ onPress, title, children, testID, }: ActionItemContainerProps): JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -27,7 +27,7 @@ type HeaderActionProps = {
|
|
|
27
27
|
readonly actionItem?: never;
|
|
28
28
|
} | {
|
|
29
29
|
readonly onPress: () => void;
|
|
30
|
-
readonly actionItem:
|
|
30
|
+
readonly actionItem: CardAction;
|
|
31
31
|
};
|
|
32
32
|
interface IconAction {
|
|
33
33
|
readonly iconName: IconNames;
|
|
@@ -35,6 +35,6 @@ interface IconAction {
|
|
|
35
35
|
interface ButtonAction {
|
|
36
36
|
readonly label: string;
|
|
37
37
|
}
|
|
38
|
-
export type
|
|
38
|
+
export type CardAction = XOR<IconAction, ButtonAction>;
|
|
39
39
|
export declare function Card({ header, footer, children, reportCardHeight: onCardHeightChange, testID, error, elevation, }: CardProps): JSX.Element;
|
|
40
40
|
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Card } from "./Card";
|
|
2
|
-
export type {
|
|
2
|
+
export type { CardAction, HeaderProps } from "./Card";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const styles: {
|
|
2
|
+
progressBarContainer: {
|
|
3
|
+
marginTop: number;
|
|
4
|
+
marginBottom: number;
|
|
5
|
+
height: number;
|
|
6
|
+
position: "relative";
|
|
7
|
+
flexDirection: "row";
|
|
8
|
+
};
|
|
9
|
+
progressBarInner: {
|
|
10
|
+
backgroundColor: string;
|
|
11
|
+
width: string;
|
|
12
|
+
height: string;
|
|
13
|
+
position: "absolute";
|
|
14
|
+
left: number;
|
|
15
|
+
top: number;
|
|
16
|
+
borderRadius: number;
|
|
17
|
+
};
|
|
18
|
+
progressInner: {
|
|
19
|
+
height: string;
|
|
20
|
+
borderRadius: number;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
interface ProgressBarInnerProps {
|
|
3
|
+
readonly width: number;
|
|
4
|
+
readonly animationDuration?: number;
|
|
5
|
+
readonly color?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function ProgressBarInner({ width, color, }: ProgressBarInnerProps): JSX.Element;
|
|
8
|
+
export declare function calculateWidth(total: number, current: number): number;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
export interface ProgressBarProps {
|
|
3
|
+
/**
|
|
4
|
+
* The total number of items to be completed
|
|
5
|
+
*/
|
|
6
|
+
readonly total: number;
|
|
7
|
+
/**
|
|
8
|
+
* The number of items that are currently completed
|
|
9
|
+
*/
|
|
10
|
+
readonly current: number;
|
|
11
|
+
/**
|
|
12
|
+
* The number of items in progress (not completed, but to be less than the total)
|
|
13
|
+
*/
|
|
14
|
+
readonly inProgress?: number;
|
|
15
|
+
/**
|
|
16
|
+
* If the progress bar is loading, the progress indicators aren't rendered on the screen
|
|
17
|
+
*/
|
|
18
|
+
readonly loading?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* If the amountFormatted and totalAmountFormatted text needs to appear more visibile because of the
|
|
21
|
+
* background, for example
|
|
22
|
+
*/
|
|
23
|
+
readonly reverseTheme?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Component to render above the progress bar.
|
|
26
|
+
*/
|
|
27
|
+
readonly header?: ReactNode;
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"module": "dist/src/index.js",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"react": "^18",
|
|
48
48
|
"react-native": ">=0.69.2"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "3bb3bd6837e653ed965570d404324e625e2339ee"
|
|
51
51
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../utils/design";
|
|
3
|
+
|
|
4
|
+
export const styles = StyleSheet.create({
|
|
5
|
+
actionItemHorizontalOffset: {
|
|
6
|
+
paddingHorizontal: tokens["space-base"],
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
icon: {
|
|
10
|
+
justifyContent: "flex-start",
|
|
11
|
+
paddingRight: tokens["space-small"],
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
titlePadding: {
|
|
15
|
+
paddingBottom: tokens["space-smaller"],
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
content: {
|
|
19
|
+
flex: 1,
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
offsetForIcons: {
|
|
23
|
+
paddingTop: tokens["space-smallest"],
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
actionIcon: {
|
|
27
|
+
paddingLeft: tokens["space-small"],
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { fireEvent, render } from "@testing-library/react-native";
|
|
3
|
+
import { ActionItem } from "./ActionItem";
|
|
4
|
+
import { Button } from "../Button";
|
|
5
|
+
import { Text } from "../Text";
|
|
6
|
+
|
|
7
|
+
describe("ActionItem", () => {
|
|
8
|
+
const pressHandler = jest.fn();
|
|
9
|
+
const defaultActionIcon = "arrowRight";
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.resetAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should call onPress when ActionItem is pressed", () => {
|
|
16
|
+
const text = "Get out of my swamp";
|
|
17
|
+
const { getByText } = render(
|
|
18
|
+
<ActionItem onPress={pressHandler}>
|
|
19
|
+
<Text>{text}</Text>
|
|
20
|
+
</ActionItem>,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
fireEvent.press(getByText(text));
|
|
24
|
+
expect(pressHandler).toHaveBeenCalled();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should should display title when provided", () => {
|
|
28
|
+
const title = "Profitttt 💰";
|
|
29
|
+
const { getByText } = render(<ActionItem title={title} />);
|
|
30
|
+
|
|
31
|
+
expect(getByText(title)).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should display icon when provided", () => {
|
|
35
|
+
const iconName = "calendar";
|
|
36
|
+
const { getByTestId } = render(
|
|
37
|
+
<ActionItem onPress={pressHandler} icon={iconName} />,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(getByTestId(iconName)).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("Action Icon", () => {
|
|
44
|
+
it("should display default action icon when custom action icon is not provided", () => {
|
|
45
|
+
const { getByTestId } = render(<ActionItem onPress={pressHandler} />);
|
|
46
|
+
|
|
47
|
+
expect(getByTestId(defaultActionIcon)).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should display custom action icon when provided", () => {
|
|
51
|
+
const iconName = "calendar";
|
|
52
|
+
const { getByTestId, queryByTestId } = render(
|
|
53
|
+
<ActionItem onPress={pressHandler} actionIcon={iconName} />,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(getByTestId(iconName)).toBeDefined();
|
|
57
|
+
expect(queryByTestId(defaultActionIcon)).toBeFalsy();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should not display action icon when onPress does not exist", () => {
|
|
61
|
+
const { queryByTestId } = render(<ActionItem title="Some title" />);
|
|
62
|
+
|
|
63
|
+
expect(queryByTestId(defaultActionIcon)).toBeFalsy();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should display arrowRight icon when edit name is used", () => {
|
|
67
|
+
const iconName = "edit";
|
|
68
|
+
const { getByTestId } = render(
|
|
69
|
+
<ActionItem onPress={pressHandler} actionIcon={iconName} />,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(getByTestId(defaultActionIcon)).toBeDefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should display actual edit icon when editpencil name is used", () => {
|
|
76
|
+
const iconName = "editpencil";
|
|
77
|
+
const { getByTestId } = render(
|
|
78
|
+
<ActionItem onPress={pressHandler} actionIcon={iconName} />,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(getByTestId("edit")).toBeDefined();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("When child has onPress event", () => {
|
|
86
|
+
const childPressHandler = jest.fn();
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
childPressHandler.mockClear();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should only call child onPress when child is pressed", () => {
|
|
92
|
+
const buttonLabel = "I am button label";
|
|
93
|
+
const text = "Some other text chillin";
|
|
94
|
+
const { getByText } = render(
|
|
95
|
+
<ActionItem onPress={pressHandler}>
|
|
96
|
+
<Button label={buttonLabel} onPress={childPressHandler} />
|
|
97
|
+
<Text>{text}</Text>
|
|
98
|
+
</ActionItem>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
fireEvent.press(getByText(buttonLabel));
|
|
102
|
+
expect(childPressHandler).toHaveBeenCalledTimes(1);
|
|
103
|
+
expect(pressHandler).toHaveBeenCalledTimes(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should only call parent onPress when the rest of Parent is pressed", () => {
|
|
107
|
+
const buttonLabel = "I am button label";
|
|
108
|
+
const text = "Some other text chillin";
|
|
109
|
+
const { getByText } = render(
|
|
110
|
+
<ActionItem onPress={pressHandler}>
|
|
111
|
+
<Button label={buttonLabel} onPress={childPressHandler} />
|
|
112
|
+
<Text>{text}</Text>
|
|
113
|
+
</ActionItem>,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
fireEvent.press(getByText(text));
|
|
117
|
+
expect(pressHandler).toHaveBeenCalledTimes(1);
|
|
118
|
+
expect(childPressHandler).toHaveBeenCalledTimes(0);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { IconColorNames, IconNames } from "@jobber/design";
|
|
4
|
+
import { styles } from "./ActionItem.style";
|
|
5
|
+
import { ActionItemContainer } from "./components/ActionItemContainer";
|
|
6
|
+
import { Typography } from "../Typography";
|
|
7
|
+
import { Icon } from "../Icon";
|
|
8
|
+
|
|
9
|
+
export interface ActionItemProps {
|
|
10
|
+
/**
|
|
11
|
+
* Title of the Action Item
|
|
12
|
+
*/
|
|
13
|
+
readonly title?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Name of the icon to display before content
|
|
17
|
+
*/
|
|
18
|
+
readonly icon?: IconNames;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Colour of the icon displayed before content
|
|
22
|
+
*/
|
|
23
|
+
readonly iconColor?: IconColorNames;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Content to display.
|
|
27
|
+
*/
|
|
28
|
+
readonly children?: ReactNode;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Action icon to display
|
|
32
|
+
*/
|
|
33
|
+
readonly actionIcon?: ActionIconNames;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Colour of the action icon
|
|
37
|
+
*/
|
|
38
|
+
readonly actionIconColour?: ActionIconColour;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Alignment of action icon
|
|
42
|
+
*/
|
|
43
|
+
readonly actionIconAlignment?: "flex-start" | "flex-end" | "center";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Press handler
|
|
47
|
+
*/
|
|
48
|
+
readonly onPress?: () => void;
|
|
49
|
+
|
|
50
|
+
readonly testID?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function ActionItem({
|
|
54
|
+
title,
|
|
55
|
+
icon,
|
|
56
|
+
iconColor,
|
|
57
|
+
children,
|
|
58
|
+
actionIcon = "edit",
|
|
59
|
+
actionIconColour = "interactive",
|
|
60
|
+
actionIconAlignment = "center",
|
|
61
|
+
onPress,
|
|
62
|
+
testID = "actionItem",
|
|
63
|
+
}: ActionItemProps): JSX.Element {
|
|
64
|
+
const actionIconStyle = {
|
|
65
|
+
justifyContent: actionIconAlignment,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const addIconOffset = icon || onPress ? styles.offsetForIcons : undefined;
|
|
69
|
+
const titlePadding = children ? styles.titlePadding : undefined;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<ActionItemContainer onPress={onPress} testID={testID} title={title}>
|
|
73
|
+
{icon && (
|
|
74
|
+
<View style={styles.icon}>
|
|
75
|
+
<Icon name={icon} color={iconColor} />
|
|
76
|
+
</View>
|
|
77
|
+
)}
|
|
78
|
+
<View style={[styles.content, addIconOffset]}>
|
|
79
|
+
{title && (
|
|
80
|
+
<View style={titlePadding}>
|
|
81
|
+
<Typography
|
|
82
|
+
color="heading"
|
|
83
|
+
fontFamily="base"
|
|
84
|
+
fontWeight="bold"
|
|
85
|
+
size="default"
|
|
86
|
+
lineHeight="base"
|
|
87
|
+
accessibilityRole="header"
|
|
88
|
+
>
|
|
89
|
+
{title}
|
|
90
|
+
</Typography>
|
|
91
|
+
</View>
|
|
92
|
+
)}
|
|
93
|
+
{children}
|
|
94
|
+
</View>
|
|
95
|
+
{onPress && (
|
|
96
|
+
<View style={[actionIconStyle, styles.actionIcon]}>
|
|
97
|
+
<Icon
|
|
98
|
+
name={getActionIcon(actionIcon)}
|
|
99
|
+
color={getActionIconColour(actionIconColour)}
|
|
100
|
+
/>
|
|
101
|
+
</View>
|
|
102
|
+
)}
|
|
103
|
+
</ActionItemContainer>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type ActionIconColour =
|
|
108
|
+
| "interactive"
|
|
109
|
+
| "destructive"
|
|
110
|
+
| "informative"
|
|
111
|
+
| "subtle";
|
|
112
|
+
|
|
113
|
+
function getActionIconColour(
|
|
114
|
+
actionIconColour: ActionIconColour,
|
|
115
|
+
): IconColorNames {
|
|
116
|
+
if (actionIconColour === "subtle") {
|
|
117
|
+
return "interactiveSubtle";
|
|
118
|
+
}
|
|
119
|
+
return actionIconColour;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
type ActionIconNames = IconNames | "editpencil";
|
|
123
|
+
|
|
124
|
+
function getActionIcon(icon: ActionIconNames): IconNames {
|
|
125
|
+
if (icon === "edit") {
|
|
126
|
+
return "arrowRight";
|
|
127
|
+
} else if (icon === "editpencil") {
|
|
128
|
+
return "edit";
|
|
129
|
+
}
|
|
130
|
+
return icon;
|
|
131
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@testing-library/react-native";
|
|
3
|
+
import { ActionItemGroup } from "./ActionItemGroup";
|
|
4
|
+
import { Text } from "../Text";
|
|
5
|
+
|
|
6
|
+
describe("ActionItemGroup", () => {
|
|
7
|
+
const dividerTestId = "Divider";
|
|
8
|
+
|
|
9
|
+
describe("Separate By Dividers", () => {
|
|
10
|
+
it("should display dividers between children", () => {
|
|
11
|
+
const text = "Get out of my swamp";
|
|
12
|
+
const { queryAllByTestId } = render(
|
|
13
|
+
<ActionItemGroup>
|
|
14
|
+
<Text>{text}</Text>
|
|
15
|
+
<Text>{text}</Text>
|
|
16
|
+
<Text>{text}</Text>
|
|
17
|
+
</ActionItemGroup>,
|
|
18
|
+
);
|
|
19
|
+
const dividers = queryAllByTestId(dividerTestId);
|
|
20
|
+
expect(dividers).toHaveLength(2);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should not display divider when only 1 child", () => {
|
|
24
|
+
const text = "Get out of my swamp";
|
|
25
|
+
const { queryByTestId } = render(
|
|
26
|
+
<ActionItemGroup>
|
|
27
|
+
<Text>{text}</Text>
|
|
28
|
+
</ActionItemGroup>,
|
|
29
|
+
);
|
|
30
|
+
const dividers = queryByTestId(dividerTestId);
|
|
31
|
+
expect(dividers).toBeFalsy();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { ReactElement } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { ActionItem, ActionItemProps } from "./ActionItem";
|
|
4
|
+
import { styles } from "./ActionItem.style";
|
|
5
|
+
import { Divider } from "../Divider";
|
|
6
|
+
|
|
7
|
+
export type ActionItemElement =
|
|
8
|
+
| ReactElement<ActionItemProps, typeof ActionItem>
|
|
9
|
+
| boolean
|
|
10
|
+
| null
|
|
11
|
+
| undefined;
|
|
12
|
+
|
|
13
|
+
interface ActionItemGroupProps {
|
|
14
|
+
/**
|
|
15
|
+
* Action Items
|
|
16
|
+
*/
|
|
17
|
+
readonly children: ActionItemElement | ActionItemElement[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ActionItemGroup({
|
|
21
|
+
children,
|
|
22
|
+
}: ActionItemGroupProps): JSX.Element {
|
|
23
|
+
return <View>{renderChildren(children)}</View>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderChildren(children: ActionItemElement | ActionItemElement[]) {
|
|
27
|
+
const childArray = React.Children.toArray(children);
|
|
28
|
+
if (childArray.length === 1) return children;
|
|
29
|
+
|
|
30
|
+
return childArray.map((child, index) => {
|
|
31
|
+
const isSubsequentChild = index !== 0;
|
|
32
|
+
return (
|
|
33
|
+
<View key={index}>
|
|
34
|
+
{isSubsequentChild && (
|
|
35
|
+
<View style={styles.actionItemHorizontalOffset}>
|
|
36
|
+
<Divider />
|
|
37
|
+
</View>
|
|
38
|
+
)}
|
|
39
|
+
{child}
|
|
40
|
+
</View>
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../../utils/design";
|
|
3
|
+
|
|
4
|
+
export const styles = StyleSheet.create({
|
|
5
|
+
container: {
|
|
6
|
+
width: "100%",
|
|
7
|
+
flexDirection: "row",
|
|
8
|
+
paddingVertical: tokens["space-base"],
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
pressed: {
|
|
12
|
+
opacity: tokens["opacity-pressed"],
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { fireEvent, render } from "@testing-library/react-native";
|
|
3
|
+
import { ActionItemContainer } from "./ActionItemContainer";
|
|
4
|
+
import { Text } from "../../Text";
|
|
5
|
+
|
|
6
|
+
describe("ActionItemContainer", () => {
|
|
7
|
+
const testID = "actionItemContainer";
|
|
8
|
+
const text = "some text";
|
|
9
|
+
|
|
10
|
+
it("should render a pressable header", () => {
|
|
11
|
+
const handlePress = jest.fn();
|
|
12
|
+
|
|
13
|
+
const { getByTestId } = render(
|
|
14
|
+
<ActionItemContainer onPress={handlePress} testID={testID}>
|
|
15
|
+
<Text>{text}</Text>
|
|
16
|
+
</ActionItemContainer>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const container = getByTestId(testID);
|
|
20
|
+
expect(container.props.accessibilityRole).toBe("button");
|
|
21
|
+
|
|
22
|
+
fireEvent.press(container);
|
|
23
|
+
expect(handlePress).toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should render an un-pressable header", () => {
|
|
27
|
+
const { getByTestId } = render(
|
|
28
|
+
<ActionItemContainer testID={testID}>
|
|
29
|
+
<Text>{text}</Text>
|
|
30
|
+
</ActionItemContainer>,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const container = getByTestId(testID);
|
|
34
|
+
expect(container.props.accessibilityRole).not.toBe("button");
|
|
35
|
+
expect(container.props.onPress).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
});
|