@jobber/components-native 0.101.11 → 0.102.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/docs/FormatFile/FormatFile.md +28 -2
- package/dist/docs/Icon/Icon.md +1 -0
- package/dist/package.json +3 -3
- package/dist/src/FormatFile/FormatFile.js +2 -2
- package/dist/src/FormatFile/FormatFileThumbnail.js +3 -2
- package/dist/src/FormatFile/FormatFileThumbnail.test.js +23 -0
- package/dist/src/FormatFile/components/FileView/FileView.js +6 -23
- package/dist/src/FormatFile/components/FileView/FileView.style.js +5 -0
- package/dist/src/FormatFile/components/MediaView/MediaView.js +40 -6
- package/dist/src/FormatFile/components/MediaView/MediaView.test.js +69 -0
- package/dist/src/FormatFile/constants.js +60 -0
- package/dist/src/FormatFile/index.js +1 -0
- package/dist/src/FormatFile/utils/getFileCategory.js +62 -0
- package/dist/src/FormatFile/utils/getFileCategory.test.js +83 -0
- package/dist/src/FormatFile/utils/index.js +4 -0
- package/dist/src/FormatFile/utils/mapFileTypeToIconName.js +31 -0
- package/dist/src/FormatFile/utils/mapFileTypeToIconName.test.js +41 -0
- package/dist/src/FormatFile/utils/sanitizeFileName.js +9 -0
- package/dist/src/FormatFile/utils/sanitizeFileName.test.js +18 -0
- package/dist/src/FormatFile/utils/truncateFileNameForLine.js +58 -0
- package/dist/src/FormatFile/utils/truncateFileNameForLine.test.js +40 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/FormatFile/FormatFile.d.ts +11 -3
- package/dist/types/src/FormatFile/FormatFileThumbnail.d.ts +10 -2
- package/dist/types/src/FormatFile/components/FileView/FileView.d.ts +2 -1
- package/dist/types/src/FormatFile/components/FileView/FileView.style.d.ts +2 -0
- package/dist/types/src/FormatFile/components/MediaView/MediaView.d.ts +2 -1
- package/dist/types/src/FormatFile/constants.d.ts +29 -0
- package/dist/types/src/FormatFile/index.d.ts +2 -0
- package/dist/types/src/FormatFile/utils/getFileCategory.d.ts +28 -0
- package/dist/types/src/FormatFile/utils/getFileCategory.test.d.ts +1 -0
- package/dist/types/src/FormatFile/utils/index.d.ts +5 -0
- package/dist/types/src/FormatFile/utils/mapFileTypeToIconName.d.ts +18 -0
- package/dist/types/src/FormatFile/utils/mapFileTypeToIconName.test.d.ts +1 -0
- package/dist/types/src/FormatFile/utils/sanitizeFileName.d.ts +5 -0
- package/dist/types/src/FormatFile/utils/sanitizeFileName.test.d.ts +1 -0
- package/dist/types/src/FormatFile/utils/truncateFileNameForLine.d.ts +24 -0
- package/dist/types/src/FormatFile/utils/truncateFileNameForLine.test.d.ts +1 -0
- package/package.json +3 -3
- package/src/FormatFile/FormatFile.stories.tsx +41 -1
- package/src/FormatFile/FormatFile.tsx +14 -2
- package/src/FormatFile/FormatFileThumbnail.test.tsx +44 -0
- package/src/FormatFile/FormatFileThumbnail.tsx +13 -1
- package/src/FormatFile/components/FileView/FileView.style.ts +5 -0
- package/src/FormatFile/components/FileView/FileView.tsx +12 -29
- package/src/FormatFile/components/MediaView/MediaView.test.tsx +159 -2
- package/src/FormatFile/components/MediaView/MediaView.tsx +114 -5
- package/src/FormatFile/constants.ts +64 -0
- package/src/FormatFile/index.ts +7 -0
- package/src/FormatFile/utils/getFileCategory.test.ts +96 -0
- package/src/FormatFile/utils/getFileCategory.ts +106 -0
- package/src/FormatFile/utils/index.ts +5 -0
- package/src/FormatFile/utils/mapFileTypeToIconName.test.ts +43 -0
- package/src/FormatFile/utils/mapFileTypeToIconName.ts +42 -0
- package/src/FormatFile/utils/sanitizeFileName.test.ts +25 -0
- package/src/FormatFile/utils/sanitizeFileName.ts +9 -0
- package/src/FormatFile/utils/truncateFileNameForLine.test.ts +61 -0
- package/src/FormatFile/utils/truncateFileNameForLine.ts +68 -0
- package/src/ThumbnailList/__snapshots__/ThumbnailList.test.tsx.snap +1 -0
|
@@ -39,7 +39,8 @@ export interface FormatFileProps<T> {
|
|
|
39
39
|
*/
|
|
40
40
|
readonly testID?: string;
|
|
41
41
|
/**
|
|
42
|
-
* Set false to hide the
|
|
42
|
+
* Set false to hide all file type iconography, including the video fallback
|
|
43
|
+
* placeholder icon.
|
|
43
44
|
*/
|
|
44
45
|
readonly showFileTypeIndicator?: boolean;
|
|
45
46
|
readonly createThumbnail?: CreateThumbnail;
|
|
@@ -60,12 +61,19 @@ interface FormatFileContentProps {
|
|
|
60
61
|
*/
|
|
61
62
|
readonly skipContainerStyles?: boolean;
|
|
62
63
|
/**
|
|
63
|
-
*
|
|
64
|
+
* @internal
|
|
64
65
|
* A function to be called when the media has loaded.
|
|
65
66
|
* This is only used in FormatFileThumbnail.
|
|
66
67
|
*/
|
|
67
68
|
readonly onMediaLoadEnd?: () => void;
|
|
69
|
+
/**
|
|
70
|
+
* Optional override for the tile's surface colour, forwarded to the
|
|
71
|
+
* underlying `FileView` or `MediaView`. Only meaningful when
|
|
72
|
+
* `skipContainerStyles` is true and the consumer (e.g.
|
|
73
|
+
* `FormatFileThumbnail`) is wrapping this content directly.
|
|
74
|
+
*/
|
|
75
|
+
readonly surfaceColor?: string;
|
|
68
76
|
}
|
|
69
|
-
export declare function FormatFileContent({ accessibilityLabel, file, showOverlay, styleInGrid, onUploadComplete, isMedia, skipContainerStyles, onMediaLoadEnd, }: FormatFileContentProps): React.JSX.Element;
|
|
77
|
+
export declare function FormatFileContent({ accessibilityLabel, file, showOverlay, styleInGrid, onUploadComplete, isMedia, skipContainerStyles, onMediaLoadEnd, surfaceColor, }: FormatFileContentProps): React.JSX.Element;
|
|
70
78
|
export declare function FormatFile<T extends File | FileUpload>({ file, accessibilityLabel, accessibilityHint, onTap, onRemove, bottomSheetOptionsSuffix, styleInGrid, testID, showFileTypeIndicator, createThumbnail, onPreviewPress, }: FormatFileProps<T>): React.JSX.Element;
|
|
71
79
|
export {};
|
|
@@ -11,7 +11,8 @@ export interface FormatFileThumbnailProps<T> {
|
|
|
11
11
|
*/
|
|
12
12
|
readonly accessibilityLabel?: string;
|
|
13
13
|
/**
|
|
14
|
-
* Set false to hide the
|
|
14
|
+
* Set false to hide all file type iconography, including the video fallback
|
|
15
|
+
* placeholder icon.
|
|
15
16
|
*/
|
|
16
17
|
readonly showFileTypeIndicator?: boolean;
|
|
17
18
|
/**
|
|
@@ -35,6 +36,13 @@ export interface FormatFileThumbnailProps<T> {
|
|
|
35
36
|
* Not to be used for Files
|
|
36
37
|
*/
|
|
37
38
|
readonly onMediaLoadEnd?: () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Optional override for the tile's surface colour. Defaults to the
|
|
41
|
+
* themed `color-surface--background` token. Use this when embedding
|
|
42
|
+
* the thumbnail inside surrounding chrome (e.g. a media gallery) so
|
|
43
|
+
* the tile background matches the surrounding container.
|
|
44
|
+
*/
|
|
45
|
+
readonly surfaceColor?: string;
|
|
38
46
|
}
|
|
39
47
|
/**
|
|
40
48
|
* A lightweight, visual-only file thumbnail component.
|
|
@@ -47,4 +55,4 @@ export interface FormatFileThumbnailProps<T> {
|
|
|
47
55
|
* where you need a single shared BottomSheet rather than one per item, and
|
|
48
56
|
* where the consumer controls the item dimensions via the `size` prop.
|
|
49
57
|
*/
|
|
50
|
-
export declare function FormatFileThumbnail<T extends File | FileUpload>({ file, accessibilityLabel, showFileTypeIndicator, createThumbnail: createThumbnailProp, size, testID, onMediaLoadEnd, }: FormatFileThumbnailProps<T>): React.JSX.Element;
|
|
58
|
+
export declare function FormatFileThumbnail<T extends File | FileUpload>({ file, accessibilityLabel, showFileTypeIndicator, createThumbnail: createThumbnailProp, size, testID, onMediaLoadEnd, surfaceColor, }: FormatFileThumbnailProps<T>): React.JSX.Element;
|
|
@@ -7,6 +7,7 @@ interface FileViewProps {
|
|
|
7
7
|
readonly file: FormattedFile;
|
|
8
8
|
readonly styleInGrid: boolean;
|
|
9
9
|
readonly onUploadComplete: () => void;
|
|
10
|
+
readonly surfaceColor?: string;
|
|
10
11
|
}
|
|
11
|
-
export declare function FileView({ accessibilityLabel, styleInGrid, file, showOverlay, showError, onUploadComplete, }: FileViewProps): React.JSX.Element;
|
|
12
|
+
export declare function FileView({ accessibilityLabel, styleInGrid, file, showOverlay, showError, onUploadComplete, surfaceColor, }: FileViewProps): React.JSX.Element;
|
|
12
13
|
export {};
|
|
@@ -34,12 +34,14 @@ export declare const useStyles: () => {
|
|
|
34
34
|
borderTopWidth: number;
|
|
35
35
|
width: "100%";
|
|
36
36
|
marginTop: number;
|
|
37
|
+
overflow: "hidden";
|
|
37
38
|
};
|
|
38
39
|
fileName: {
|
|
39
40
|
alignItems: "center";
|
|
40
41
|
borderTopColor: string;
|
|
41
42
|
borderTopWidth: number;
|
|
42
43
|
width: "100%";
|
|
44
|
+
overflow: "hidden";
|
|
43
45
|
};
|
|
44
46
|
fileNameGrid: {
|
|
45
47
|
position: "absolute";
|
|
@@ -8,6 +8,7 @@ interface MediaViewProps {
|
|
|
8
8
|
readonly styleInGrid: boolean;
|
|
9
9
|
readonly onUploadComplete: () => void;
|
|
10
10
|
readonly onLoadEnd?: () => void;
|
|
11
|
+
readonly surfaceColor?: string;
|
|
11
12
|
}
|
|
12
|
-
export declare function MediaView({ accessibilityLabel, showOverlay, showError, file, styleInGrid, onUploadComplete, onLoadEnd, }: MediaViewProps): React.JSX.Element;
|
|
13
|
+
export declare function MediaView({ accessibilityLabel, showOverlay, showError, file, styleInGrid, onUploadComplete, onLoadEnd, surfaceColor, }: MediaViewProps): React.JSX.Element;
|
|
13
14
|
export {};
|
|
@@ -4,3 +4,32 @@ export declare const acceptedExtensions: {
|
|
|
4
4
|
export declare const videoExtensions: {
|
|
5
5
|
type: string;
|
|
6
6
|
}[];
|
|
7
|
+
/**
|
|
8
|
+
* Extensions that should be treated as documents (Word-family + OpenDocument
|
|
9
|
+
* Text + RTF + Apple Pages + WordPerfect). Used to route to the document
|
|
10
|
+
* icon and the "document" file category.
|
|
11
|
+
*/
|
|
12
|
+
export declare const documentExtensions: {
|
|
13
|
+
type: string;
|
|
14
|
+
}[];
|
|
15
|
+
/**
|
|
16
|
+
* Extensions that should be treated as spreadsheets (Excel-family +
|
|
17
|
+
* OpenDocument Spreadsheet + Apple Numbers + CSV). Used to route to the
|
|
18
|
+
* spreadsheet icon and the "spreadsheet" file category.
|
|
19
|
+
*/
|
|
20
|
+
export declare const spreadsheetExtensions: {
|
|
21
|
+
type: string;
|
|
22
|
+
}[];
|
|
23
|
+
/**
|
|
24
|
+
* Substring patterns matched against the `Content-Type` MIME string for
|
|
25
|
+
* documents. Substring rather than exact match because real-world MIMEs
|
|
26
|
+
* vary widely (e.g. `application/msword`,
|
|
27
|
+
* `application/vnd.openxmlformats-officedocument.wordprocessingml.document`,
|
|
28
|
+
* `application/vnd.oasis.opendocument.text`).
|
|
29
|
+
*/
|
|
30
|
+
export declare const documentMimePatterns: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Substring patterns matched against the `Content-Type` MIME string for
|
|
33
|
+
* spreadsheets.
|
|
34
|
+
*/
|
|
35
|
+
export declare const spreadsheetMimePatterns: string[];
|
|
@@ -2,5 +2,7 @@ export type { FormatFileProps } from "./FormatFile";
|
|
|
2
2
|
export { FormatFile } from "./FormatFile";
|
|
3
3
|
export type { FormatFileThumbnailProps } from "./FormatFileThumbnail";
|
|
4
4
|
export { FormatFileThumbnail } from "./FormatFileThumbnail";
|
|
5
|
+
export { getFileCategory, mapFileTypeToIconName, sanitizeFileName, truncateFileNameForLine, } from "./utils";
|
|
6
|
+
export type { FileCategory } from "./utils";
|
|
5
7
|
export type { FormattedFile, File, CreateThumbnail, CreateThumbnailResponse, FileUpload, StatusCode, } from "./types";
|
|
6
8
|
export type { UseCreateThumbnail, UseCreateThumbnailResponse, } from "./context/types";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type FileCategory = "document" | "spreadsheet" | "pdf" | "video" | "image" | "other";
|
|
2
|
+
interface GetFileCategoryParams {
|
|
3
|
+
readonly fileName?: string;
|
|
4
|
+
readonly fileType?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Bucket a file into one of the generic categories the design system
|
|
8
|
+
* recognises. Decoupled from icon names so that:
|
|
9
|
+
*
|
|
10
|
+
* 1. Consumers can use generic terminology in copy, accessibility
|
|
11
|
+
* labels, and filter UI ("Document", "Spreadsheet") without
|
|
12
|
+
* coupling to the brand-named icon assets (`word`, `excel`).
|
|
13
|
+
* 2. When `@jobber/design` adds generic-document / generic-spreadsheet
|
|
14
|
+
* icons, the icon mapping can be updated without breaking
|
|
15
|
+
* consumers who already use this categorisation.
|
|
16
|
+
*
|
|
17
|
+
* Detection precedence: `fileType` (MIME) first, then `fileName`
|
|
18
|
+
* extension. The extension lists cover OpenDocument Text / Spreadsheet,
|
|
19
|
+
* RTF, Apple Pages / Numbers, CSV, and WordPerfect alongside the
|
|
20
|
+
* Microsoft Office formats so callers do not have to enumerate them.
|
|
21
|
+
*
|
|
22
|
+
* `fileType` matching is case-insensitive and accepts both full MIME
|
|
23
|
+
* strings (`"video/mp4"`, `"image/jpeg"`) and coarse type strings
|
|
24
|
+
* (`"video"`, `"image"`) since `parseFile` collapses video MIMEs to
|
|
25
|
+
* the bare token `"video"` for external files.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getFileCategory({ fileName, fileType, }: GetFileCategoryParams): FileCategory;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
export { computeA11yLabel } from "./computeA11yLabel";
|
|
2
2
|
export { parseFile, isMediaFile } from "./parseFile";
|
|
3
|
+
export { mapFileTypeToIconName } from "./mapFileTypeToIconName";
|
|
4
|
+
export { getFileCategory } from "./getFileCategory";
|
|
5
|
+
export type { FileCategory } from "./getFileCategory";
|
|
6
|
+
export { sanitizeFileName } from "./sanitizeFileName";
|
|
7
|
+
export { truncateFileNameForLine } from "./truncateFileNameForLine";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IconNames } from "@jobber/design";
|
|
2
|
+
interface FileTypeToIconNameParams {
|
|
3
|
+
readonly fileName?: string;
|
|
4
|
+
readonly fileType?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Pick the file-type icon name for a given file. Today the design system
|
|
8
|
+
* exposes brand-named icons (`word`, `excel`) which double-duty as the
|
|
9
|
+
* generic "document" / "spreadsheet" indicators; when generic icons land
|
|
10
|
+
* in `@jobber/design`, the mapping below is the one place that needs to
|
|
11
|
+
* change.
|
|
12
|
+
*
|
|
13
|
+
* The actual file-type detection lives in `getFileCategory` so consumers
|
|
14
|
+
* that need a generic category name (e.g. for accessibility labels or
|
|
15
|
+
* filter UI) do not have to thread brand vocabulary through their copy.
|
|
16
|
+
*/
|
|
17
|
+
export declare function mapFileTypeToIconName({ fileName, fileType, }: FileTypeToIconNameParams): IconNames;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncate a filename so it fits a single line in the small file-type tile.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: on Android, RN's `Text` with `numberOfLines={1}` does not
|
|
5
|
+
* always enforce single-line layout for URL-shaped strings with no
|
|
6
|
+
* whitespace (e.g. `vertopal.com/converter`). They wrap to a second line
|
|
7
|
+
* which then bleeds outside the rounded tile border. Pre-truncating in JS
|
|
8
|
+
* guarantees the rendered string is short enough that single-line wrapping
|
|
9
|
+
* is never required.
|
|
10
|
+
*
|
|
11
|
+
* The truncation preserves the trailing file extension when one is
|
|
12
|
+
* detectable so the file type stays visible at a glance (examples below
|
|
13
|
+
* use the default 15-character budget):
|
|
14
|
+
* `vertopal.com/converter` → `vertopal.com/c…`
|
|
15
|
+
* `file-sample_2_dx.docx` → `file-samp….docx`
|
|
16
|
+
* `quarterly-budget.numbers` → `quarte….numbers`
|
|
17
|
+
* `report.pdf` → `report.pdf` (unchanged)
|
|
18
|
+
* `embedded\nname.txt` → caller should pass through
|
|
19
|
+
* `sanitizeFileName` first
|
|
20
|
+
*
|
|
21
|
+
* `maxLength` is clamped to a sensible minimum of 2 so a caller passing
|
|
22
|
+
* `0` or a negative value cannot violate the single-line guarantee.
|
|
23
|
+
*/
|
|
24
|
+
export declare function truncateFileNameForLine(name?: string, maxLength?: number): string;
|
|
@@ -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.102.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native implementation of Atlantis",
|
|
6
6
|
"repository": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@babel/runtime": "^7.29.2",
|
|
60
60
|
"@gorhom/bottom-sheet": "^5.2.8",
|
|
61
|
-
"@jobber/design": "0.
|
|
61
|
+
"@jobber/design": "0.101.0",
|
|
62
62
|
"@jobber/hooks": "2.20.1",
|
|
63
63
|
"@react-native-community/datetimepicker": "^8.4.5",
|
|
64
64
|
"@react-native/babel-preset": "^0.82.1",
|
|
@@ -109,5 +109,5 @@
|
|
|
109
109
|
"react-native-screens": ">=4.18.0",
|
|
110
110
|
"react-native-svg": ">=12.0.0"
|
|
111
111
|
},
|
|
112
|
-
"gitHead": "
|
|
112
|
+
"gitHead": "1e7705b3834eb2027601b03d1d47cb7368eafd7d"
|
|
113
113
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ComponentProps } from "react";
|
|
2
|
+
import React from "react";
|
|
2
3
|
import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
3
|
-
import { FormatFile } from "@jobber/components-native";
|
|
4
|
+
import { FormatFile, FormatFileThumbnail } from "@jobber/components-native";
|
|
5
|
+
import type { File, FormatFileThumbnailProps } from "@jobber/components-native";
|
|
4
6
|
import { FormatFileBasic } from "./docs";
|
|
5
7
|
|
|
6
8
|
const meta = {
|
|
@@ -13,6 +15,15 @@ const meta = {
|
|
|
13
15
|
} satisfies Meta<typeof FormatFile>;
|
|
14
16
|
export default meta;
|
|
15
17
|
type Story = StoryObj<Partial<ComponentProps<typeof FormatFile>>>;
|
|
18
|
+
type ThumbnailStory = StoryObj<Partial<FormatFileThumbnailProps<File>>>;
|
|
19
|
+
|
|
20
|
+
const videoWithoutPreviewFile = {
|
|
21
|
+
fileName: "video.mp4",
|
|
22
|
+
contentType: "video/quicktime",
|
|
23
|
+
url: undefined,
|
|
24
|
+
thumbnailUrl: undefined,
|
|
25
|
+
fileSize: 1024,
|
|
26
|
+
} satisfies File;
|
|
16
27
|
|
|
17
28
|
export const Image: Story = {
|
|
18
29
|
render: FormatFileBasic,
|
|
@@ -54,6 +65,13 @@ export const Video: Story = {
|
|
|
54
65
|
},
|
|
55
66
|
};
|
|
56
67
|
|
|
68
|
+
export const VideoWithoutPreview: Story = {
|
|
69
|
+
render: FormatFileBasic,
|
|
70
|
+
args: {
|
|
71
|
+
file: videoWithoutPreviewFile,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
57
75
|
export const PDF: Story = {
|
|
58
76
|
render: FormatFileBasic,
|
|
59
77
|
args: {
|
|
@@ -66,3 +84,25 @@ export const PDF: Story = {
|
|
|
66
84
|
},
|
|
67
85
|
},
|
|
68
86
|
};
|
|
87
|
+
|
|
88
|
+
export const ThumbnailSurfaceColor: ThumbnailStory = {
|
|
89
|
+
render: FormatFileThumbnailBasic,
|
|
90
|
+
args: {
|
|
91
|
+
file: videoWithoutPreviewFile,
|
|
92
|
+
size: { width: 96, height: 96 },
|
|
93
|
+
surfaceColor: "var(--color-surface)",
|
|
94
|
+
accessibilityLabel: "Video file without preview",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function FormatFileThumbnailBasic(
|
|
99
|
+
props: Partial<FormatFileThumbnailProps<File>>,
|
|
100
|
+
) {
|
|
101
|
+
return (
|
|
102
|
+
<FormatFileThumbnail
|
|
103
|
+
file={videoWithoutPreviewFile}
|
|
104
|
+
size={{ width: 96, height: 96 }}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -57,7 +57,8 @@ export interface FormatFileProps<T> {
|
|
|
57
57
|
readonly testID?: string;
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Set false to hide the
|
|
60
|
+
* Set false to hide all file type iconography, including the video fallback
|
|
61
|
+
* placeholder icon.
|
|
61
62
|
*/
|
|
62
63
|
readonly showFileTypeIndicator?: boolean;
|
|
63
64
|
|
|
@@ -90,11 +91,19 @@ interface FormatFileContentProps {
|
|
|
90
91
|
readonly skipContainerStyles?: boolean;
|
|
91
92
|
|
|
92
93
|
/**
|
|
93
|
-
*
|
|
94
|
+
* @internal
|
|
94
95
|
* A function to be called when the media has loaded.
|
|
95
96
|
* This is only used in FormatFileThumbnail.
|
|
96
97
|
*/
|
|
97
98
|
readonly onMediaLoadEnd?: () => void;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Optional override for the tile's surface colour, forwarded to the
|
|
102
|
+
* underlying `FileView` or `MediaView`. Only meaningful when
|
|
103
|
+
* `skipContainerStyles` is true and the consumer (e.g.
|
|
104
|
+
* `FormatFileThumbnail`) is wrapping this content directly.
|
|
105
|
+
*/
|
|
106
|
+
readonly surfaceColor?: string;
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
export function FormatFileContent({
|
|
@@ -106,6 +115,7 @@ export function FormatFileContent({
|
|
|
106
115
|
isMedia,
|
|
107
116
|
skipContainerStyles = false,
|
|
108
117
|
onMediaLoadEnd,
|
|
118
|
+
surfaceColor,
|
|
109
119
|
}: FormatFileContentProps) {
|
|
110
120
|
const styles = useStyles();
|
|
111
121
|
|
|
@@ -118,6 +128,7 @@ export function FormatFileContent({
|
|
|
118
128
|
styleInGrid={styleInGrid}
|
|
119
129
|
onUploadComplete={onUploadComplete}
|
|
120
130
|
onLoadEnd={onMediaLoadEnd}
|
|
131
|
+
surfaceColor={surfaceColor}
|
|
121
132
|
/>
|
|
122
133
|
) : (
|
|
123
134
|
<FileView
|
|
@@ -127,6 +138,7 @@ export function FormatFileContent({
|
|
|
127
138
|
showError={file.error}
|
|
128
139
|
styleInGrid={styleInGrid}
|
|
129
140
|
onUploadComplete={onUploadComplete}
|
|
141
|
+
surfaceColor={surfaceColor}
|
|
130
142
|
/>
|
|
131
143
|
);
|
|
132
144
|
|
|
@@ -290,3 +290,47 @@ function renderThumbnail(file: FileUpload | File) {
|
|
|
290
290
|
/>,
|
|
291
291
|
);
|
|
292
292
|
}
|
|
293
|
+
|
|
294
|
+
describe("FormatFileThumbnail surfaceColor", () => {
|
|
295
|
+
it("applies the provided surfaceColor to the outer container", () => {
|
|
296
|
+
const { getByTestId } = render(
|
|
297
|
+
<FormatFileThumbnail
|
|
298
|
+
file={FILE_MOCK_FILE}
|
|
299
|
+
accessibilityLabel="Custom Label"
|
|
300
|
+
createThumbnail={mockCreateThumbnail}
|
|
301
|
+
size={{ width: 100, height: 100 }}
|
|
302
|
+
surfaceColor="#F4ECD9"
|
|
303
|
+
testID="thumbnail-container"
|
|
304
|
+
/>,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const container = getByTestId("thumbnail-container");
|
|
308
|
+
const styles = Array.isArray(container.props.style)
|
|
309
|
+
? container.props.style.filter(Boolean)
|
|
310
|
+
: [container.props.style];
|
|
311
|
+
const merged = Object.assign({}, ...styles);
|
|
312
|
+
expect(merged.backgroundColor).toBe("#F4ECD9");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("preserves the themed background when surfaceColor is omitted", () => {
|
|
316
|
+
const { getByTestId } = render(
|
|
317
|
+
<FormatFileThumbnail
|
|
318
|
+
file={FILE_MOCK_FILE}
|
|
319
|
+
accessibilityLabel="Custom Label"
|
|
320
|
+
createThumbnail={mockCreateThumbnail}
|
|
321
|
+
size={{ width: 100, height: 100 }}
|
|
322
|
+
testID="thumbnail-container"
|
|
323
|
+
/>,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const container = getByTestId("thumbnail-container");
|
|
327
|
+
const styles = Array.isArray(container.props.style)
|
|
328
|
+
? container.props.style.filter(Boolean)
|
|
329
|
+
: [container.props.style];
|
|
330
|
+
const merged = Object.assign({}, ...styles);
|
|
331
|
+
// The default themed background token resolves to the surface
|
|
332
|
+
// colour, not the consumer-supplied #F4ECD9.
|
|
333
|
+
expect(merged.backgroundColor).not.toBe("#F4ECD9");
|
|
334
|
+
expect(merged.backgroundColor).toBeTruthy();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -21,7 +21,8 @@ export interface FormatFileThumbnailProps<T> {
|
|
|
21
21
|
readonly accessibilityLabel?: string;
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Set false to hide the
|
|
24
|
+
* Set false to hide all file type iconography, including the video fallback
|
|
25
|
+
* placeholder icon.
|
|
25
26
|
*/
|
|
26
27
|
readonly showFileTypeIndicator?: boolean;
|
|
27
28
|
|
|
@@ -49,6 +50,14 @@ export interface FormatFileThumbnailProps<T> {
|
|
|
49
50
|
* Not to be used for Files
|
|
50
51
|
*/
|
|
51
52
|
readonly onMediaLoadEnd?: () => void;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Optional override for the tile's surface colour. Defaults to the
|
|
56
|
+
* themed `color-surface--background` token. Use this when embedding
|
|
57
|
+
* the thumbnail inside surrounding chrome (e.g. a media gallery) so
|
|
58
|
+
* the tile background matches the surrounding container.
|
|
59
|
+
*/
|
|
60
|
+
readonly surfaceColor?: string;
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
/**
|
|
@@ -70,6 +79,7 @@ export function FormatFileThumbnail<T extends File | FileUpload>({
|
|
|
70
79
|
size,
|
|
71
80
|
testID,
|
|
72
81
|
onMediaLoadEnd,
|
|
82
|
+
surfaceColor,
|
|
73
83
|
}: FormatFileThumbnailProps<T>) {
|
|
74
84
|
const formattedFile = useMemo(
|
|
75
85
|
() => parseFile(file, showFileTypeIndicator),
|
|
@@ -95,6 +105,7 @@ export function FormatFileThumbnail<T extends File | FileUpload>({
|
|
|
95
105
|
// that belongs to the consumer, not the thumbnail itself.
|
|
96
106
|
{ marginBottom: 0 },
|
|
97
107
|
size && { width: size.width, height: size.height },
|
|
108
|
+
surfaceColor ? { backgroundColor: surfaceColor } : undefined,
|
|
98
109
|
]}
|
|
99
110
|
testID={testID}
|
|
100
111
|
>
|
|
@@ -107,6 +118,7 @@ export function FormatFileThumbnail<T extends File | FileUpload>({
|
|
|
107
118
|
showOverlay={showOverlay}
|
|
108
119
|
skipContainerStyles={true}
|
|
109
120
|
onMediaLoadEnd={onMediaLoadEnd}
|
|
121
|
+
surfaceColor={surfaceColor}
|
|
110
122
|
/>
|
|
111
123
|
</View>
|
|
112
124
|
</AtlantisFormatFileContext.Provider>
|
|
@@ -37,12 +37,17 @@ export const useStyles = buildThemedStyles(tokens => {
|
|
|
37
37
|
borderTopWidth: tokens["space-minuscule"],
|
|
38
38
|
width: "100%",
|
|
39
39
|
marginTop: tokens["space-base"],
|
|
40
|
+
overflow: "hidden",
|
|
40
41
|
},
|
|
41
42
|
fileName: {
|
|
42
43
|
alignItems: "center",
|
|
43
44
|
borderTopColor: tokens["color-border"],
|
|
44
45
|
borderTopWidth: tokens["space-minuscule"],
|
|
45
46
|
width: "100%",
|
|
47
|
+
// Belt-and-suspenders with `numberOfLines={1}`: on Android the latter
|
|
48
|
+
// does not always enforce single-line layout for URL-shaped strings,
|
|
49
|
+
// so clip any unexpected overflow at the rounded tile border.
|
|
50
|
+
overflow: "hidden",
|
|
46
51
|
},
|
|
47
52
|
fileNameGrid: {
|
|
48
53
|
position: "absolute",
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View } from "react-native";
|
|
3
|
-
import type { IconNames } from "@jobber/design";
|
|
4
3
|
import { useStyles } from "./FileView.style";
|
|
5
4
|
import { Icon } from "../../../Icon";
|
|
6
5
|
import { Text } from "../../../Text";
|
|
7
6
|
import type { FormattedFile } from "../../types";
|
|
8
7
|
import { StatusCode } from "../../types";
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
computeA11yLabel,
|
|
10
|
+
mapFileTypeToIconName,
|
|
11
|
+
sanitizeFileName,
|
|
12
|
+
truncateFileNameForLine,
|
|
13
|
+
} from "../../utils";
|
|
10
14
|
import { ProgressBar } from "../ProgressBar";
|
|
11
15
|
import { ErrorIcon } from "../ErrorIcon";
|
|
12
16
|
import { useAtlantisI18n } from "../../../hooks/useAtlantisI18n";
|
|
@@ -18,6 +22,7 @@ interface FileViewProps {
|
|
|
18
22
|
readonly file: FormattedFile;
|
|
19
23
|
readonly styleInGrid: boolean;
|
|
20
24
|
readonly onUploadComplete: () => void;
|
|
25
|
+
readonly surfaceColor?: string;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
export function FileView({
|
|
@@ -27,6 +32,7 @@ export function FileView({
|
|
|
27
32
|
showOverlay,
|
|
28
33
|
showError,
|
|
29
34
|
onUploadComplete,
|
|
35
|
+
surfaceColor,
|
|
30
36
|
}: FileViewProps) {
|
|
31
37
|
const { t } = useAtlantisI18n();
|
|
32
38
|
const styles = useStyles();
|
|
@@ -46,6 +52,7 @@ export function FileView({
|
|
|
46
52
|
style={[
|
|
47
53
|
styles.fileBackground,
|
|
48
54
|
styleInGrid ? styles.fileBackgroundGrid : styles.fileBackgroundFlat,
|
|
55
|
+
surfaceColor ? { backgroundColor: surfaceColor } : undefined,
|
|
49
56
|
]}
|
|
50
57
|
accessible={true}
|
|
51
58
|
accessibilityLabel={a11yLabel}
|
|
@@ -74,7 +81,9 @@ export function FileView({
|
|
|
74
81
|
]}
|
|
75
82
|
>
|
|
76
83
|
<Text level="textSupporting" variation="subdued" maxLines="single">
|
|
77
|
-
{
|
|
84
|
+
{styleInGrid
|
|
85
|
+
? truncateFileNameForLine(sanitizeFileName(file.name))
|
|
86
|
+
: sanitizeFileName(file.name)}
|
|
78
87
|
</Text>
|
|
79
88
|
</View>
|
|
80
89
|
{!showError && showOverlay && (
|
|
@@ -106,29 +115,3 @@ export function FileView({
|
|
|
106
115
|
</View>
|
|
107
116
|
);
|
|
108
117
|
}
|
|
109
|
-
|
|
110
|
-
interface FileTypeToIconNameParams {
|
|
111
|
-
fileName?: string;
|
|
112
|
-
fileType?: string;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function mapFileTypeToIconName({
|
|
116
|
-
fileName,
|
|
117
|
-
fileType,
|
|
118
|
-
}: FileTypeToIconNameParams): IconNames {
|
|
119
|
-
if (!fileName && !fileType) {
|
|
120
|
-
return "alert";
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (fileType?.includes("pdf") || fileName?.match(/\.pdf$/i)) {
|
|
124
|
-
return "pdf";
|
|
125
|
-
} else if (fileType?.includes("ms-word") || fileName?.match(/\.docx?$/i)) {
|
|
126
|
-
return "word";
|
|
127
|
-
} else if (fileType?.includes("ms-excel") || fileName?.match(/\.xlsx?$/i)) {
|
|
128
|
-
return "excel";
|
|
129
|
-
} else if (fileType?.includes("video") || fileName?.match(/\.mp4$/i)) {
|
|
130
|
-
return "video";
|
|
131
|
-
} else {
|
|
132
|
-
return "file";
|
|
133
|
-
}
|
|
134
|
-
}
|