@jobber/components-native 0.14.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/index.js +1 -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/index.d.ts +1 -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/index.ts +1 -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";
|
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
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import { Pressable, View } from "react-native";
|
|
3
|
+
import { styles } from "./ActionItemContainer.style";
|
|
4
|
+
import { styles as actionItemStyles } from "../ActionItem.style";
|
|
5
|
+
|
|
6
|
+
interface ActionItemContainerProps {
|
|
7
|
+
readonly children: ReactNode;
|
|
8
|
+
readonly title?: string;
|
|
9
|
+
readonly onPress?: () => void;
|
|
10
|
+
readonly testID?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ActionItemContainer({
|
|
14
|
+
onPress,
|
|
15
|
+
title,
|
|
16
|
+
children,
|
|
17
|
+
testID,
|
|
18
|
+
}: ActionItemContainerProps): JSX.Element {
|
|
19
|
+
if (onPress) {
|
|
20
|
+
return (
|
|
21
|
+
<Pressable
|
|
22
|
+
onPress={onPress}
|
|
23
|
+
style={({ pressed }) => [
|
|
24
|
+
styles.container,
|
|
25
|
+
actionItemStyles.actionItemHorizontalOffset,
|
|
26
|
+
pressed && styles.pressed,
|
|
27
|
+
]}
|
|
28
|
+
accessibilityRole="button"
|
|
29
|
+
accessibilityLabel={title}
|
|
30
|
+
testID={testID}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</Pressable>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<View
|
|
39
|
+
style={[styles.container, actionItemStyles.actionItemHorizontalOffset]}
|
|
40
|
+
testID={testID}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
</View>
|
|
44
|
+
);
|
|
45
|
+
}
|
package/src/Button/Button.tsx
CHANGED
|
@@ -3,7 +3,6 @@ import { TouchableHighlight, View } from "react-native";
|
|
|
3
3
|
import { IconColorNames, IconNames } from "@jobber/design";
|
|
4
4
|
import { XOR } from "ts-xor";
|
|
5
5
|
import { styles } from "./Button.style";
|
|
6
|
-
// eslint-disable-next-line import/no-internal-modules
|
|
7
6
|
import { InternalButtonLoading } from "./components/InternalButtonLoading";
|
|
8
7
|
import { ButtonSize, ButtonType, ButtonVariation } from "./types";
|
|
9
8
|
import { ActionLabel, ActionLabelVariation } from "../ActionLabel";
|
package/src/Card/Card.tsx
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
import { IconNames } from "@jobber/design";
|
|
9
9
|
import { XOR } from "ts-xor";
|
|
10
10
|
import { styles } from "./Card.style";
|
|
11
|
-
// eslint-disable-next-line import/no-internal-modules
|
|
12
11
|
import { InternalCardHeader } from "./components/InternalCardHeader";
|
|
13
12
|
import { ErrorMessageWrapper } from "../ErrorMessageWrapper";
|
|
14
13
|
import { ActionLabel } from "../ActionLabel";
|
|
@@ -49,7 +48,7 @@ type HeaderActionProps =
|
|
|
49
48
|
}
|
|
50
49
|
| {
|
|
51
50
|
readonly onPress: () => void;
|
|
52
|
-
readonly actionItem:
|
|
51
|
+
readonly actionItem: CardAction;
|
|
53
52
|
};
|
|
54
53
|
|
|
55
54
|
interface IconAction {
|
|
@@ -60,7 +59,7 @@ interface ButtonAction {
|
|
|
60
59
|
readonly label: string;
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
export type
|
|
62
|
+
export type CardAction = XOR<IconAction, ButtonAction>;
|
|
64
63
|
|
|
65
64
|
function getElevationStyle(elevation: elevationProp) {
|
|
66
65
|
if (elevation === "none") {
|
package/src/Card/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Card } from "./Card";
|
|
2
|
-
export type {
|
|
2
|
+
export type { CardAction, HeaderProps } from "./Card";
|
package/src/index.ts
CHANGED