@jobber/components-native 0.40.1 → 0.41.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/FormatFile/FormatFile.js +114 -0
- package/dist/src/FormatFile/FormatFile.style.js +16 -0
- package/dist/src/FormatFile/components/ErrorIcon/ErrorIcon.js +8 -0
- package/dist/src/FormatFile/components/ErrorIcon/ErrorIcon.style.js +10 -0
- package/dist/src/FormatFile/components/ErrorIcon/index.js +1 -0
- package/dist/src/FormatFile/components/FileView/FileView.js +67 -0
- package/dist/src/FormatFile/components/FileView/FileView.style.js +64 -0
- package/dist/src/FormatFile/components/FileView/index.js +1 -0
- package/dist/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.js +22 -0
- package/dist/src/FormatFile/components/FormatFileBottomSheet/index.js +1 -0
- package/dist/src/FormatFile/components/FormatFileBottomSheet/messages.js +13 -0
- package/dist/src/FormatFile/components/MediaView/MediaView.js +56 -0
- package/dist/src/FormatFile/components/MediaView/MediaView.style.js +27 -0
- package/dist/src/FormatFile/components/MediaView/index.js +1 -0
- package/dist/src/FormatFile/components/ProgressBar/ProgressBar.js +29 -0
- package/dist/src/FormatFile/components/ProgressBar/ProgressBar.style.js +15 -0
- package/dist/src/FormatFile/components/ProgressBar/index.js +1 -0
- package/dist/src/FormatFile/components/_mocks/mockFiles.js +78 -0
- package/dist/src/FormatFile/constants.js +14 -0
- package/dist/src/FormatFile/context/FormatFileContext.js +8 -0
- package/dist/src/FormatFile/context/types.js +1 -0
- package/dist/src/FormatFile/index.js +1 -0
- package/dist/src/FormatFile/messages.js +23 -0
- package/dist/src/FormatFile/types.js +8 -0
- package/dist/src/FormatFile/utils/computeA11yLabel.js +12 -0
- package/dist/src/FormatFile/utils/createUseCreateThumbnail.js +22 -0
- package/dist/src/FormatFile/utils/index.js +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/utils/test/wait.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/Form/components/FormMessage/FormMessage.d.ts +1 -0
- package/dist/types/src/FormatFile/FormatFile.d.ts +47 -0
- package/dist/types/src/FormatFile/FormatFile.style.d.ts +14 -0
- package/dist/types/src/FormatFile/components/ErrorIcon/ErrorIcon.d.ts +2 -0
- package/dist/types/src/FormatFile/components/ErrorIcon/ErrorIcon.style.d.ts +8 -0
- package/dist/types/src/FormatFile/components/ErrorIcon/index.d.ts +1 -0
- package/dist/types/src/FormatFile/components/FileView/FileView.d.ts +12 -0
- package/dist/types/src/FormatFile/components/FileView/FileView.style.d.ts +62 -0
- package/dist/types/src/FormatFile/components/FileView/index.d.ts +1 -0
- package/dist/types/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.d.ts +11 -0
- package/dist/types/src/FormatFile/components/FormatFileBottomSheet/index.d.ts +2 -0
- package/dist/types/src/FormatFile/components/FormatFileBottomSheet/messages.d.ts +12 -0
- package/dist/types/src/FormatFile/components/MediaView/MediaView.d.ts +12 -0
- package/dist/types/src/FormatFile/components/MediaView/MediaView.style.d.ts +25 -0
- package/dist/types/src/FormatFile/components/MediaView/index.d.ts +1 -0
- package/dist/types/src/FormatFile/components/ProgressBar/ProgressBar.d.ts +19 -0
- package/dist/types/src/FormatFile/components/ProgressBar/ProgressBar.style.d.ts +13 -0
- package/dist/types/src/FormatFile/components/ProgressBar/index.d.ts +1 -0
- package/dist/types/src/FormatFile/components/_mocks/mockFiles.d.ts +18 -0
- package/dist/types/src/FormatFile/constants.d.ts +6 -0
- package/dist/types/src/FormatFile/context/FormatFileContext.d.ts +10 -0
- package/dist/types/src/FormatFile/context/types.d.ts +9 -0
- package/dist/types/src/FormatFile/index.d.ts +3 -0
- package/dist/types/src/FormatFile/messages.d.ts +22 -0
- package/dist/types/src/FormatFile/types.d.ts +105 -0
- package/dist/types/src/FormatFile/utils/computeA11yLabel.d.ts +9 -0
- package/dist/types/src/FormatFile/utils/createUseCreateThumbnail.d.ts +5 -0
- package/dist/types/src/FormatFile/utils/index.d.ts +1 -0
- package/dist/types/src/InputCurrency/InputCurrency.d.ts +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/package.json +3 -2
- package/src/FormatFile/FormatFile.style.ts +17 -0
- package/src/FormatFile/FormatFile.test.tsx +333 -0
- package/src/FormatFile/FormatFile.tsx +300 -0
- package/src/FormatFile/components/ErrorIcon/ErrorIcon.style.ts +11 -0
- package/src/FormatFile/components/ErrorIcon/ErrorIcon.tsx +12 -0
- package/src/FormatFile/components/ErrorIcon/index.ts +1 -0
- package/src/FormatFile/components/FileView/FileView.style.ts +65 -0
- package/src/FormatFile/components/FileView/FileView.tsx +134 -0
- package/src/FormatFile/components/FileView/index.ts +1 -0
- package/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.test.tsx +108 -0
- package/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.tsx +56 -0
- package/src/FormatFile/components/FormatFileBottomSheet/index.ts +2 -0
- package/src/FormatFile/components/FormatFileBottomSheet/messages.ts +14 -0
- package/src/FormatFile/components/MediaView/MediaView.style.ts +28 -0
- package/src/FormatFile/components/MediaView/MediaView.tsx +145 -0
- package/src/FormatFile/components/MediaView/index.ts +1 -0
- package/src/FormatFile/components/ProgressBar/ProgressBar.style.tsx +16 -0
- package/src/FormatFile/components/ProgressBar/ProgressBar.tsx +57 -0
- package/src/FormatFile/components/ProgressBar/index.ts +1 -0
- package/src/FormatFile/components/_mocks/mockFiles.ts +105 -0
- package/src/FormatFile/constants.ts +15 -0
- package/src/FormatFile/context/FormatFileContext.ts +13 -0
- package/src/FormatFile/context/types.ts +12 -0
- package/src/FormatFile/index.ts +13 -0
- package/src/FormatFile/messages.ts +24 -0
- package/src/FormatFile/types.ts +126 -0
- package/src/FormatFile/utils/computeA11yLabel.ts +26 -0
- package/src/FormatFile/utils/createUseCreateThumbnail.ts +33 -0
- package/src/FormatFile/utils/index.ts +1 -0
- package/src/InputCurrency/InputCurrency.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/utils/test/wait.ts +3 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../../../utils/design";
|
|
3
|
+
|
|
4
|
+
export const styles = StyleSheet.create({
|
|
5
|
+
overlay: {
|
|
6
|
+
backgroundColor: tokens["color-overlay--dimmed"],
|
|
7
|
+
},
|
|
8
|
+
overlayError: {
|
|
9
|
+
backgroundColor: tokens["color-overlay--dimmed"],
|
|
10
|
+
borderColor: tokens["color-critical"],
|
|
11
|
+
borderWidth: tokens["border-base"],
|
|
12
|
+
},
|
|
13
|
+
iconCenter: {
|
|
14
|
+
marginBottom: tokens["space-large"],
|
|
15
|
+
},
|
|
16
|
+
fileBackground: {
|
|
17
|
+
width: "100%",
|
|
18
|
+
display: "flex",
|
|
19
|
+
alignItems: "center",
|
|
20
|
+
},
|
|
21
|
+
fileBackgroundGrid: {
|
|
22
|
+
height: "100%",
|
|
23
|
+
},
|
|
24
|
+
fileBackgroundFlat: {
|
|
25
|
+
justifyContent: "center",
|
|
26
|
+
height: tokens["space-extravagant"],
|
|
27
|
+
},
|
|
28
|
+
fileIconGrid: {
|
|
29
|
+
top: tokens["space-small"],
|
|
30
|
+
},
|
|
31
|
+
fileIconFlat: {
|
|
32
|
+
top: tokens["space-smaller"],
|
|
33
|
+
},
|
|
34
|
+
fileNameError: {
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
borderTopColor: tokens["color-border"],
|
|
37
|
+
borderTopWidth: tokens["space-minuscule"],
|
|
38
|
+
width: "100%",
|
|
39
|
+
marginTop: tokens["space-base"],
|
|
40
|
+
},
|
|
41
|
+
fileName: {
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
borderTopColor: tokens["color-border"],
|
|
44
|
+
borderTopWidth: tokens["space-minuscule"],
|
|
45
|
+
width: "100%",
|
|
46
|
+
},
|
|
47
|
+
fileNameGrid: {
|
|
48
|
+
position: "absolute",
|
|
49
|
+
right: 0,
|
|
50
|
+
bottom: 0,
|
|
51
|
+
left: 0,
|
|
52
|
+
},
|
|
53
|
+
fileNameFlat: {
|
|
54
|
+
top: tokens["space-small"],
|
|
55
|
+
},
|
|
56
|
+
fileOverlay: {
|
|
57
|
+
position: "absolute",
|
|
58
|
+
justifyContent: "center",
|
|
59
|
+
right: 0,
|
|
60
|
+
bottom: 0,
|
|
61
|
+
left: 0,
|
|
62
|
+
height: "100%",
|
|
63
|
+
paddingHorizontal: tokens["space-small"],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import { IconNames } from "@jobber/design";
|
|
5
|
+
import { styles } from "./FileView.style";
|
|
6
|
+
import { Icon } from "../../../Icon";
|
|
7
|
+
import { Text } from "../../../Text";
|
|
8
|
+
import { FormattedFile, StatusCode } from "../../types";
|
|
9
|
+
import { computeA11yLabel } from "../../utils";
|
|
10
|
+
import { ProgressBar } from "../ProgressBar";
|
|
11
|
+
import { ErrorIcon } from "../ErrorIcon";
|
|
12
|
+
|
|
13
|
+
interface FileViewProps {
|
|
14
|
+
accessibilityLabel?: string;
|
|
15
|
+
showOverlay: boolean;
|
|
16
|
+
showError: boolean;
|
|
17
|
+
file: FormattedFile;
|
|
18
|
+
styleInGrid: boolean;
|
|
19
|
+
onUploadComplete: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function FileView({
|
|
23
|
+
accessibilityLabel,
|
|
24
|
+
styleInGrid,
|
|
25
|
+
file,
|
|
26
|
+
showOverlay,
|
|
27
|
+
showError,
|
|
28
|
+
onUploadComplete,
|
|
29
|
+
}: FileViewProps): JSX.Element {
|
|
30
|
+
const { formatMessage } = useIntl();
|
|
31
|
+
const a11yLabel = computeA11yLabel({
|
|
32
|
+
accessibilityLabel,
|
|
33
|
+
showOverlay,
|
|
34
|
+
showError,
|
|
35
|
+
formatMessage,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const freezeProgressBar =
|
|
39
|
+
file.status !== StatusCode.Completed && file.progress >= 0.9;
|
|
40
|
+
return (
|
|
41
|
+
<View
|
|
42
|
+
style={[
|
|
43
|
+
styles.fileBackground,
|
|
44
|
+
styleInGrid ? styles.fileBackgroundGrid : styles.fileBackgroundFlat,
|
|
45
|
+
]}
|
|
46
|
+
accessible={true}
|
|
47
|
+
accessibilityLabel={a11yLabel}
|
|
48
|
+
>
|
|
49
|
+
<View
|
|
50
|
+
style={[
|
|
51
|
+
styles.fileBackground,
|
|
52
|
+
styleInGrid ? styles.fileIconGrid : styles.fileIconFlat,
|
|
53
|
+
]}
|
|
54
|
+
testID={"test-file"}
|
|
55
|
+
>
|
|
56
|
+
{(styleInGrid || !showError) && file.showFileTypeIndicator && (
|
|
57
|
+
<Icon
|
|
58
|
+
size={"large"}
|
|
59
|
+
name={mapFileTypeToIconName({
|
|
60
|
+
fileName: file.name,
|
|
61
|
+
fileType: file.type,
|
|
62
|
+
})}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
</View>
|
|
66
|
+
<View
|
|
67
|
+
style={[
|
|
68
|
+
showError && styleInGrid ? styles.fileNameError : styles.fileName,
|
|
69
|
+
styleInGrid ? styles.fileNameGrid : styles.fileNameFlat,
|
|
70
|
+
]}
|
|
71
|
+
>
|
|
72
|
+
<Text level="textSupporting" variation="subdued" maxLines="single">
|
|
73
|
+
{file.name}
|
|
74
|
+
</Text>
|
|
75
|
+
</View>
|
|
76
|
+
{!showError && showOverlay && (
|
|
77
|
+
<View
|
|
78
|
+
style={[styles.fileBackground, styles.overlay, styles.fileOverlay]}
|
|
79
|
+
testID="format-file-progress-bar-container"
|
|
80
|
+
>
|
|
81
|
+
<ProgressBar
|
|
82
|
+
status={file.status}
|
|
83
|
+
progress={freezeProgressBar ? 0.9 : file.progress}
|
|
84
|
+
onComplete={onUploadComplete}
|
|
85
|
+
/>
|
|
86
|
+
</View>
|
|
87
|
+
)}
|
|
88
|
+
{showError && (
|
|
89
|
+
<View
|
|
90
|
+
style={[
|
|
91
|
+
styles.fileBackground,
|
|
92
|
+
styles.overlayError,
|
|
93
|
+
styles.fileOverlay,
|
|
94
|
+
]}
|
|
95
|
+
testID="format-file-error-container"
|
|
96
|
+
>
|
|
97
|
+
<View style={!styleInGrid ? styles.iconCenter : undefined}>
|
|
98
|
+
<ErrorIcon />
|
|
99
|
+
</View>
|
|
100
|
+
</View>
|
|
101
|
+
)}
|
|
102
|
+
</View>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface FileTypeToIconNameParams {
|
|
107
|
+
fileName?: string;
|
|
108
|
+
fileType?: string;
|
|
109
|
+
}
|
|
110
|
+
function mapFileTypeToIconName({
|
|
111
|
+
fileName,
|
|
112
|
+
fileType,
|
|
113
|
+
}: FileTypeToIconNameParams): IconNames {
|
|
114
|
+
if (!fileName && !fileType) {
|
|
115
|
+
return "alert";
|
|
116
|
+
}
|
|
117
|
+
if (fileType?.includes("pdf") || fileName?.match(/~*.pdf$/)) {
|
|
118
|
+
return "pdf";
|
|
119
|
+
} else if (
|
|
120
|
+
fileType?.includes("ms-word") ||
|
|
121
|
+
fileName?.match(/~*.doc$|docx$/)
|
|
122
|
+
) {
|
|
123
|
+
return "word";
|
|
124
|
+
} else if (
|
|
125
|
+
fileType?.includes("ms-excel") ||
|
|
126
|
+
fileName?.match(/~*.xls$|xlsx$/)
|
|
127
|
+
) {
|
|
128
|
+
return "excel";
|
|
129
|
+
} else if (fileType?.includes("video") || fileName?.match(/~*.mp4$|mp4$/)) {
|
|
130
|
+
return "video";
|
|
131
|
+
} else {
|
|
132
|
+
return "file";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileView } from "./FileView";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, { createRef } from "react";
|
|
2
|
+
import { RenderAPI, fireEvent, render } from "@testing-library/react-native";
|
|
3
|
+
import { Host } from "react-native-portalize";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { act } from "react-test-renderer";
|
|
6
|
+
import {
|
|
7
|
+
BottomSheetOptionsSuffix,
|
|
8
|
+
FormatFileBottomSheet,
|
|
9
|
+
} from "./FormatFileBottomSheet";
|
|
10
|
+
import { messages } from "./messages";
|
|
11
|
+
import { BottomSheetRef } from "../../../BottomSheet/BottomSheet";
|
|
12
|
+
|
|
13
|
+
let Platform: { OS: "ios" | "android" };
|
|
14
|
+
const onRemove = jest.fn();
|
|
15
|
+
const onPreview = jest.fn();
|
|
16
|
+
const bottomSheetRef = createRef<BottomSheetRef>();
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
Platform = require("react-native").Platform;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const renderBottomSheet = (
|
|
23
|
+
bottomSheetOptionsSuffix: BottomSheetOptionsSuffix,
|
|
24
|
+
) => {
|
|
25
|
+
return render(
|
|
26
|
+
<Host>
|
|
27
|
+
<FormatFileBottomSheet
|
|
28
|
+
onPreviewPress={onPreview}
|
|
29
|
+
onRemovePress={onRemove}
|
|
30
|
+
bottomSheetRef={bottomSheetRef}
|
|
31
|
+
bottomSheetOptionsSuffix={bottomSheetOptionsSuffix}
|
|
32
|
+
/>
|
|
33
|
+
</Host>,
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const basicRenderTestWithValue = () => {
|
|
38
|
+
describe.each([["image"], ["receipt"], ["file"], ["video"]])(
|
|
39
|
+
"when FormatFileBottomSheet for %s is opened",
|
|
40
|
+
bottomSheetOptionsSuffix => {
|
|
41
|
+
const { formatMessage } = useIntl();
|
|
42
|
+
const previewLabel = formatMessage(messages.lightBoxPreviewButton, {
|
|
43
|
+
bottomSheetOptionsSuffix,
|
|
44
|
+
});
|
|
45
|
+
const removeLabel = formatMessage(messages.removeButton, {
|
|
46
|
+
bottomSheetOptionsSuffix,
|
|
47
|
+
});
|
|
48
|
+
let tree: RenderAPI;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
tree = renderBottomSheet(
|
|
52
|
+
bottomSheetOptionsSuffix as BottomSheetOptionsSuffix,
|
|
53
|
+
);
|
|
54
|
+
act(() => {
|
|
55
|
+
bottomSheetRef.current?.open();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
jest.clearAllMocks();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("onPreviewPress", () => {
|
|
63
|
+
it("renders the preview option", () => {
|
|
64
|
+
const { getByLabelText } = tree;
|
|
65
|
+
|
|
66
|
+
expect(getByLabelText(previewLabel)).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("is called when pressed", () => {
|
|
70
|
+
const { getByLabelText } = tree;
|
|
71
|
+
fireEvent.press(getByLabelText(previewLabel));
|
|
72
|
+
expect(onPreview).toHaveBeenCalledTimes(1);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("onRemovePress", () => {
|
|
77
|
+
it("renders the remove option", () => {
|
|
78
|
+
const { getByLabelText } = tree;
|
|
79
|
+
|
|
80
|
+
expect(getByLabelText(removeLabel)).toBeDefined();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("is called when pressed", () => {
|
|
84
|
+
const { getByLabelText } = tree;
|
|
85
|
+
fireEvent.press(getByLabelText(removeLabel));
|
|
86
|
+
|
|
87
|
+
expect(onRemove).toHaveBeenCalledTimes(1);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
describe("ios", () => {
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
Platform.OS = "ios";
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
basicRenderTestWithValue();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("android", () => {
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
Platform.OS = "android";
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
basicRenderTestWithValue();
|
|
108
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, { RefObject } from "react";
|
|
2
|
+
import { Portal } from "react-native-portalize";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import { messages } from "./messages";
|
|
5
|
+
import { BottomSheet, BottomSheetRef } from "../../../BottomSheet/BottomSheet";
|
|
6
|
+
import { BottomSheetOption } from "../../../BottomSheet/components/BottomSheetOption";
|
|
7
|
+
|
|
8
|
+
export type BottomSheetOptionsSuffix = "receipt" | "image" | "file" | "video";
|
|
9
|
+
|
|
10
|
+
interface FormatFileBottomSheetProps {
|
|
11
|
+
bottomSheetRef: RefObject<BottomSheetRef>;
|
|
12
|
+
onPreviewPress?: () => void;
|
|
13
|
+
onRemovePress?: () => void;
|
|
14
|
+
bottomSheetOptionsSuffix?: BottomSheetOptionsSuffix;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const FormatFileBottomSheet = ({
|
|
18
|
+
bottomSheetRef,
|
|
19
|
+
onPreviewPress,
|
|
20
|
+
onRemovePress,
|
|
21
|
+
bottomSheetOptionsSuffix,
|
|
22
|
+
}: FormatFileBottomSheetProps): JSX.Element => {
|
|
23
|
+
const { formatMessage } = useIntl();
|
|
24
|
+
|
|
25
|
+
const handlePress = (onPressAction: () => void) => {
|
|
26
|
+
onPressAction();
|
|
27
|
+
|
|
28
|
+
bottomSheetRef.current?.close();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Portal>
|
|
33
|
+
<BottomSheet ref={bottomSheetRef}>
|
|
34
|
+
{onPreviewPress ? (
|
|
35
|
+
<BottomSheetOption
|
|
36
|
+
icon={"eye"}
|
|
37
|
+
text={formatMessage(messages.lightBoxPreviewButton, {
|
|
38
|
+
bottomSheetOptionsSuffix,
|
|
39
|
+
})}
|
|
40
|
+
onPress={() => handlePress(onPreviewPress)}
|
|
41
|
+
/>
|
|
42
|
+
) : undefined}
|
|
43
|
+
{onRemovePress ? (
|
|
44
|
+
<BottomSheetOption
|
|
45
|
+
icon={"trash"}
|
|
46
|
+
destructive={true}
|
|
47
|
+
text={formatMessage(messages.removeButton, {
|
|
48
|
+
bottomSheetOptionsSuffix,
|
|
49
|
+
})}
|
|
50
|
+
onPress={() => handlePress(onRemovePress)}
|
|
51
|
+
/>
|
|
52
|
+
) : undefined}
|
|
53
|
+
</BottomSheet>
|
|
54
|
+
</Portal>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineMessages } from "react-intl";
|
|
2
|
+
|
|
3
|
+
export const messages = defineMessages({
|
|
4
|
+
lightBoxPreviewButton: {
|
|
5
|
+
id: "lightBoxPreviewButton",
|
|
6
|
+
defaultMessage: "Preview {bottomSheetOptionsSuffix}",
|
|
7
|
+
description: "Label for button when preview the file",
|
|
8
|
+
},
|
|
9
|
+
removeButton: {
|
|
10
|
+
id: "removeButton",
|
|
11
|
+
defaultMessage: "Remove {bottomSheetOptionsSuffix}",
|
|
12
|
+
description: "Label for button when remove the file",
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
|
+
import { tokens } from "../../../utils/design";
|
|
3
|
+
|
|
4
|
+
export const styles = StyleSheet.create({
|
|
5
|
+
imageBackground: {
|
|
6
|
+
width: "100%",
|
|
7
|
+
justifyContent: "center",
|
|
8
|
+
alignItems: "center",
|
|
9
|
+
},
|
|
10
|
+
imageBackgroundFlat: {
|
|
11
|
+
aspectRatio: 375 / 150,
|
|
12
|
+
},
|
|
13
|
+
imageBackgroundGrid: {
|
|
14
|
+
height: "100%",
|
|
15
|
+
},
|
|
16
|
+
overlay: {
|
|
17
|
+
height: "100%",
|
|
18
|
+
backgroundColor: tokens["color-overlay--dimmed"],
|
|
19
|
+
paddingHorizontal: tokens["space-small"],
|
|
20
|
+
},
|
|
21
|
+
overlayError: {
|
|
22
|
+
height: "100%",
|
|
23
|
+
backgroundColor: tokens["color-overlay--dimmed"],
|
|
24
|
+
paddingHorizontal: tokens["space-small"],
|
|
25
|
+
borderColor: tokens["color-critical"],
|
|
26
|
+
borderWidth: tokens["border-base"],
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { ImageBackground, View } from "react-native";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
import { styles } from "./MediaView.style";
|
|
5
|
+
import { FormattedFile, StatusCode } from "../../types";
|
|
6
|
+
import { computeA11yLabel } from "../../utils";
|
|
7
|
+
import { ActivityIndicator } from "../../../ActivityIndicator";
|
|
8
|
+
import { Icon } from "../../../Icon";
|
|
9
|
+
import { ProgressBar } from "../ProgressBar";
|
|
10
|
+
import { ErrorIcon } from "../ErrorIcon";
|
|
11
|
+
import { useAtlantisFormatFileContext } from "../../context/FormatFileContext";
|
|
12
|
+
|
|
13
|
+
interface MediaViewProps {
|
|
14
|
+
accessibilityLabel?: string;
|
|
15
|
+
showOverlay: boolean;
|
|
16
|
+
showError: boolean;
|
|
17
|
+
file: FormattedFile;
|
|
18
|
+
styleInGrid: boolean;
|
|
19
|
+
onUploadComplete: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function MediaView({
|
|
23
|
+
accessibilityLabel,
|
|
24
|
+
showOverlay,
|
|
25
|
+
showError,
|
|
26
|
+
file,
|
|
27
|
+
styleInGrid,
|
|
28
|
+
onUploadComplete,
|
|
29
|
+
}: MediaViewProps): JSX.Element {
|
|
30
|
+
const { formatMessage } = useIntl();
|
|
31
|
+
const { useCreateThumbnail } = useAtlantisFormatFileContext();
|
|
32
|
+
const { thumbnail, error } = useCreateThumbnail(file);
|
|
33
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
34
|
+
|
|
35
|
+
const a11yLabel = computeA11yLabel({
|
|
36
|
+
accessibilityLabel,
|
|
37
|
+
showOverlay,
|
|
38
|
+
showError,
|
|
39
|
+
formatMessage,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const hasError = showError || error;
|
|
43
|
+
const uri = thumbnail || file.thumbnailUrl || file.source;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View accessible={true} accessibilityLabel={a11yLabel}>
|
|
47
|
+
<ImageBackground
|
|
48
|
+
style={[
|
|
49
|
+
styles.imageBackground,
|
|
50
|
+
styleInGrid ? styles.imageBackgroundGrid : styles.imageBackgroundFlat,
|
|
51
|
+
]}
|
|
52
|
+
resizeMode={styleInGrid ? "cover" : "contain"}
|
|
53
|
+
source={{ uri }}
|
|
54
|
+
testID={"test-image"}
|
|
55
|
+
onLoadStart={() => setIsLoading(true)}
|
|
56
|
+
onLoadEnd={() => setIsLoading(false)}
|
|
57
|
+
>
|
|
58
|
+
<Overlay
|
|
59
|
+
isLoading={isLoading}
|
|
60
|
+
showOverlay={showOverlay}
|
|
61
|
+
hasError={hasError}
|
|
62
|
+
file={file}
|
|
63
|
+
onUploadComplete={onUploadComplete}
|
|
64
|
+
/>
|
|
65
|
+
</ImageBackground>
|
|
66
|
+
</View>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface OverlayProps {
|
|
71
|
+
isLoading: boolean;
|
|
72
|
+
showOverlay: boolean;
|
|
73
|
+
hasError: boolean;
|
|
74
|
+
file: FormattedFile;
|
|
75
|
+
onUploadComplete: () => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Overlay({
|
|
79
|
+
isLoading,
|
|
80
|
+
showOverlay,
|
|
81
|
+
hasError,
|
|
82
|
+
file,
|
|
83
|
+
onUploadComplete,
|
|
84
|
+
}: OverlayProps) {
|
|
85
|
+
if (isLoading) return <ActivityIndicator />;
|
|
86
|
+
|
|
87
|
+
if (hasError) return <ErrorOverlay />;
|
|
88
|
+
|
|
89
|
+
if (showOverlay) {
|
|
90
|
+
return (
|
|
91
|
+
<ProgressOverlay
|
|
92
|
+
status={file.status}
|
|
93
|
+
progress={file.progress}
|
|
94
|
+
onUploadComplete={onUploadComplete}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (isVideo(file.type) && file.showFileTypeIndicator) {
|
|
100
|
+
return <Icon name="video" color="white" />;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return <></>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface ProgressOverlayProps {
|
|
107
|
+
status: StatusCode;
|
|
108
|
+
progress: number;
|
|
109
|
+
onUploadComplete: () => void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ProgressOverlay({
|
|
113
|
+
status,
|
|
114
|
+
progress,
|
|
115
|
+
onUploadComplete,
|
|
116
|
+
}: ProgressOverlayProps) {
|
|
117
|
+
const freezeProgressBar = status !== StatusCode.Completed && progress >= 0.9;
|
|
118
|
+
return (
|
|
119
|
+
<View
|
|
120
|
+
style={[styles.imageBackground, styles.overlay]}
|
|
121
|
+
testID="format-file-progress-bar-container"
|
|
122
|
+
>
|
|
123
|
+
<ProgressBar
|
|
124
|
+
status={status}
|
|
125
|
+
progress={freezeProgressBar ? 0.9 : progress}
|
|
126
|
+
onComplete={onUploadComplete}
|
|
127
|
+
/>
|
|
128
|
+
</View>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function ErrorOverlay() {
|
|
133
|
+
return (
|
|
134
|
+
<View
|
|
135
|
+
style={[styles.imageBackground, styles.overlayError]}
|
|
136
|
+
testID="format-file-error-container"
|
|
137
|
+
>
|
|
138
|
+
<ErrorIcon />
|
|
139
|
+
</View>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isVideo(fileType = ""): boolean {
|
|
144
|
+
return fileType.includes("video");
|
|
145
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MediaView } from "./MediaView";
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
height: 8,
|
|
8
|
+
borderRadius: tokens["radius-circle"],
|
|
9
|
+
overflow: "hidden",
|
|
10
|
+
backgroundColor: tokens["color-surface--background"],
|
|
11
|
+
},
|
|
12
|
+
progress: {
|
|
13
|
+
height: "100%",
|
|
14
|
+
backgroundColor: tokens["color-green"],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
|
+
import { Animated, View } from "react-native";
|
|
3
|
+
import { styles } from "./ProgressBar.style";
|
|
4
|
+
import { StatusCode } from "../../types";
|
|
5
|
+
|
|
6
|
+
interface ProgressBarProps {
|
|
7
|
+
/**
|
|
8
|
+
* Upload progress value from 0 to 1
|
|
9
|
+
*/
|
|
10
|
+
progress: number;
|
|
11
|
+
/**
|
|
12
|
+
* Upload status
|
|
13
|
+
*/
|
|
14
|
+
status: StatusCode;
|
|
15
|
+
/**
|
|
16
|
+
* Function to be called when the progress is finished
|
|
17
|
+
*/
|
|
18
|
+
onComplete?: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const ProgressBar = ({
|
|
22
|
+
progress,
|
|
23
|
+
status,
|
|
24
|
+
onComplete,
|
|
25
|
+
}: ProgressBarProps): JSX.Element => {
|
|
26
|
+
const barWidth = useRef(new Animated.Value(0)).current;
|
|
27
|
+
const progressPercentage = barWidth.interpolate({
|
|
28
|
+
inputRange: [0, 1],
|
|
29
|
+
outputRange: ["0%", `100%`],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
Animated.timing(barWidth, {
|
|
34
|
+
toValue: progress,
|
|
35
|
+
duration: 500,
|
|
36
|
+
useNativeDriver: false,
|
|
37
|
+
}).start(({ finished }) => {
|
|
38
|
+
if (status === StatusCode.Completed && finished && onComplete) {
|
|
39
|
+
onComplete();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}, [progress, barWidth, onComplete, status]);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<View testID="format-file-progress-bar" style={styles.container}>
|
|
46
|
+
<Animated.View
|
|
47
|
+
testID={"format-file-inner-progress-bar"}
|
|
48
|
+
style={[
|
|
49
|
+
styles.progress,
|
|
50
|
+
{
|
|
51
|
+
width: progressPercentage,
|
|
52
|
+
},
|
|
53
|
+
]}
|
|
54
|
+
/>
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ProgressBar } from "./ProgressBar";
|