@unif/react-native-chat 3.2.1 → 3.2.2

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.
Files changed (23) hide show
  1. package/lib/module/components/Attachments/AttachmentItem.js +203 -72
  2. package/lib/module/components/Attachments/AttachmentItem.js.map +1 -1
  3. package/lib/module/components/Attachments/Attachments.js +6 -18
  4. package/lib/module/components/Attachments/Attachments.js.map +1 -1
  5. package/lib/module/components/Attachments/constants.js +6 -2
  6. package/lib/module/components/Attachments/constants.js.map +1 -1
  7. package/lib/module/components/Attachments/styles.js +116 -23
  8. package/lib/module/components/Attachments/styles.js.map +1 -1
  9. package/lib/typescript/src/components/Attachments/AttachmentItem.d.ts +1 -1
  10. package/lib/typescript/src/components/Attachments/AttachmentItem.d.ts.map +1 -1
  11. package/lib/typescript/src/components/Attachments/Attachments.d.ts.map +1 -1
  12. package/lib/typescript/src/components/Attachments/constants.d.ts +5 -2
  13. package/lib/typescript/src/components/Attachments/constants.d.ts.map +1 -1
  14. package/lib/typescript/src/components/Attachments/styles.d.ts +103 -11
  15. package/lib/typescript/src/components/Attachments/styles.d.ts.map +1 -1
  16. package/lib/typescript/src/components/Attachments/types.d.ts +1 -0
  17. package/lib/typescript/src/components/Attachments/types.d.ts.map +1 -1
  18. package/package.json +1 -1
  19. package/src/components/Attachments/AttachmentItem.tsx +267 -76
  20. package/src/components/Attachments/Attachments.tsx +10 -20
  21. package/src/components/Attachments/constants.ts +9 -2
  22. package/src/components/Attachments/styles.ts +114 -20
  23. package/src/components/Attachments/types.ts +1 -0
@@ -1,3 +1,3 @@
1
1
  import type { AttachmentItemProps } from './types.js';
2
- export declare function AttachmentItem({ item, width, imageSize, row, showProgressLabel, onPreview, onRemove, }: AttachmentItemProps): import("react").JSX.Element;
2
+ export declare function AttachmentItem({ item, width, imageSize, row, mixed, showProgressLabel, onPreview, onRemove, }: AttachmentItemProps): import("react").JSX.Element;
3
3
  //# sourceMappingURL=AttachmentItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AttachmentItem.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/AttachmentItem.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAS,CAAC;AAEnD,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,KAAK,EACL,SAAS,EACT,GAAG,EACH,iBAAyB,EACzB,SAAS,EACT,QAAQ,GACT,EAAE,mBAAmB,+BA8GrB"}
1
+ {"version":3,"file":"AttachmentItem.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/AttachmentItem.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAS,CAAC;AAEnD,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,KAAK,EACL,SAAS,EACT,GAAG,EACH,KAAa,EACb,iBAAyB,EACzB,SAAS,EACT,QAAQ,GACT,EAAE,mBAAmB,+BAsSrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"Attachments.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/Attachments.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAS,CAAC;AAEhD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,MAAe,EACf,iBAAyB,EACzB,SAAS,EACT,QAAQ,EACR,KAAK,EACL,MAAM,GACP,EAAE,gBAAgB,+BAuDlB"}
1
+ {"version":3,"file":"Attachments.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/Attachments.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAS,CAAC;AAEhD,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,MAAe,EACf,iBAAyB,EACzB,SAAS,EACT,QAAQ,EACR,KAAK,EACL,MAAM,GACP,EAAE,gBAAgB,+BA8ClB"}
@@ -1,7 +1,10 @@
1
1
  import type { IconName } from '@unif/react-native-design';
2
2
  import type { ChatAttachmentItem } from './types.js';
3
- export declare const CARD_WIDTH = 144;
4
- export declare const ROW_IMAGE_SIZE = 56;
3
+ export declare const CARD_WIDTH: number;
4
+ export declare const ROW_IMAGE_SIZE: number;
5
+ export declare const MIXED_IMAGE_SIZE: number;
6
+ export declare const ATTACHMENT_STATUS_SIZE: number;
7
+ export declare const ATTACHMENT_STATUS_OWNER_SIZE: number;
5
8
  export declare const ATTACHMENT_ICONS: Record<NonNullable<ChatAttachmentItem['kind']>, IconName>;
6
9
  export declare const STATUS_LABELS: {
7
10
  readonly idle: "";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAS,CAAC;AAElD,eAAO,MAAM,UAAU,MAAM,CAAC;AAC9B,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,gBAAgB,EAAE,MAAM,CACnC,WAAW,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,EACvC,QAAQ,CAMT,CAAC;AACF,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/constants.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAS,CAAC;AAElD,eAAO,MAAM,UAAU,QAAQ,CAAC;AAChC,eAAO,MAAM,cAAc,QAAU,CAAC;AACtC,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AACtC,eAAO,MAAM,sBAAsB,QAAQ,CAAC;AAC5C,eAAO,MAAM,4BAA4B,QAGxC,CAAC;AACF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CACnC,WAAW,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,EACvC,QAAQ,CAMT,CAAC;AACF,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAC"}
@@ -13,6 +13,31 @@ export declare const createStyles: (colors: ColorTokens) => Readonly<{
13
13
  gap: number;
14
14
  alignItems: "flex-start";
15
15
  };
16
+ tileOwner: {
17
+ position: "relative";
18
+ minWidth: number;
19
+ };
20
+ tile: {
21
+ borderWidth: number;
22
+ borderColor: string;
23
+ borderRadius: number;
24
+ backgroundColor: string;
25
+ overflow: "hidden";
26
+ };
27
+ tileFailed: {
28
+ borderColor: string;
29
+ borderWidth: number;
30
+ };
31
+ documentName: {
32
+ position: "absolute";
33
+ left: number;
34
+ right: number;
35
+ bottom: number;
36
+ fontSize: number;
37
+ color: string;
38
+ textAlign: "center";
39
+ backgroundColor: string;
40
+ };
16
41
  item: {
17
42
  gap: number;
18
43
  padding: number;
@@ -26,10 +51,26 @@ export declare const createStyles: (colors: ColorTokens) => Readonly<{
26
51
  flexDirection: "row";
27
52
  alignItems: "center";
28
53
  };
54
+ rowBody: {
55
+ flex: number;
56
+ minWidth: number;
57
+ flexDirection: "row";
58
+ alignItems: "center";
59
+ gap: number;
60
+ };
61
+ mixedRowBody: {
62
+ gap: number;
63
+ };
64
+ mixedRow: {
65
+ height: number;
66
+ paddingVertical: number;
67
+ paddingHorizontal: number;
68
+ borderWidth: number;
69
+ borderRadius: number;
70
+ };
29
71
  content: {
30
72
  minWidth: number;
31
73
  flexShrink: number;
32
- gap: number;
33
74
  };
34
75
  rowContent: {
35
76
  flex: number;
@@ -37,33 +78,84 @@ export declare const createStyles: (colors: ColorTokens) => Readonly<{
37
78
  media: {
38
79
  alignItems: "center";
39
80
  justifyContent: "center";
40
- alignSelf: "center";
81
+ position: "relative";
41
82
  };
42
- imageLayer: {
43
- position: "absolute";
83
+ placeholder: {
84
+ backgroundColor: string;
85
+ borderRadius: number;
44
86
  alignItems: "center";
45
87
  justifyContent: "center";
88
+ };
89
+ statusOwner: {
90
+ position: "absolute";
46
91
  top: number;
47
- bottom: number;
48
92
  left: number;
49
- right: number;
93
+ width: number;
94
+ height: number;
95
+ alignItems: "center";
96
+ justifyContent: "center";
50
97
  };
51
- placeholder: {
98
+ retryOwner: {
99
+ zIndex: number;
100
+ elevation: number;
101
+ };
102
+ statusVisual: {
103
+ width: number;
104
+ height: number;
105
+ borderRadius: 999;
52
106
  backgroundColor: string;
53
- borderRadius: number;
107
+ alignItems: "center";
108
+ justifyContent: "center";
54
109
  };
55
- progress: {
56
- padding: number;
57
- borderRadius: number;
110
+ removeOwner: {
111
+ position: "absolute";
112
+ top: number;
113
+ width: number;
114
+ height: number;
115
+ paddingTop: number;
116
+ zIndex: number;
117
+ elevation: number;
118
+ };
119
+ removeVisual: {
120
+ width: number;
121
+ height: number;
122
+ borderRadius: 999;
58
123
  backgroundColor: string;
124
+ alignItems: "center";
125
+ justifyContent: "center";
126
+ };
127
+ removeFailed: {
128
+ backgroundColor: string;
129
+ };
130
+ caption: {
131
+ flexDirection: "row";
132
+ alignItems: "center";
133
+ justifyContent: "center";
134
+ gap: number;
135
+ paddingTop: number;
136
+ width: number;
137
+ };
138
+ captionText: {
139
+ flexShrink: number;
140
+ fontSize: number;
141
+ color: string;
142
+ };
143
+ previewAction: {
144
+ width: number;
145
+ alignItems: "center";
59
146
  };
60
147
  name: {
61
148
  color: string;
62
149
  fontSize: number;
150
+ fontWeight: "500";
63
151
  };
64
152
  meta: {
65
153
  color: string;
66
154
  fontSize: number;
155
+ marginTop: number;
156
+ };
157
+ fileMeta: {
158
+ fontFamily: string;
67
159
  };
68
160
  failed: {
69
161
  color: string;
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/styles.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,eAAO,MAAM,YAAY,GAAI,QAAQ,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiD5C,CAAC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/styles.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAO7D,eAAO,MAAM,YAAY,GAAI,QAAQ,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkI5C,CAAC"}
@@ -30,5 +30,6 @@ export interface AttachmentItemProps extends Pick<AttachmentsProps, 'showProgres
30
30
  width?: number;
31
31
  imageSize: number;
32
32
  row: boolean;
33
+ mixed?: boolean;
33
34
  }
34
35
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAe,CAAC;AAEhD,MAAM,MAAM,oBAAoB,GAC9B,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC3D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC5C,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CACjC;AACD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IAChD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AACD,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAC/C,gBAAgB,EAChB,mBAAmB,GAAG,WAAW,GAAG,UAAU,CAC/C;IACC,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,OAAO,CAAC;CACd"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/components/Attachments/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAe,CAAC;AAEhD,MAAM,MAAM,oBAAoB,GAC9B,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC3D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC5C,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CACjC;AACD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IAChD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AACD,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAC/C,gBAAgB,EAChB,mBAAmB,GAAG,WAAW,GAAG,UAAU,CAC/C;IACC,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unif/react-native-chat",
3
- "version": "3.2.1",
3
+ "version": "3.2.2",
4
4
  "description": "Unif React Native 聊天组件:消息、输入、附件和交互展示",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -1,4 +1,4 @@
1
- import { Text, View } from 'react-native';
1
+ import { Pressable, Text, View } from 'react-native';
2
2
  import {
3
3
  Button,
4
4
  CircularProgress,
@@ -6,11 +6,17 @@ import {
6
6
  IconButton,
7
7
  radius,
8
8
  Spinner,
9
+ Thumbnail,
10
+ useColors,
9
11
  useThemedStyles,
10
12
  } from '@unif/react-native-design';
11
- import { ATTACHMENT_ICONS, STATUS_LABELS } from './constants';
13
+ import {
14
+ ATTACHMENT_ICONS,
15
+ ATTACHMENT_STATUS_OWNER_SIZE,
16
+ ROW_IMAGE_SIZE,
17
+ STATUS_LABELS,
18
+ } from './constants';
12
19
  import { createStyles } from './styles';
13
- import { Thumbnail } from '@unif/react-native-design';
14
20
  import type { AttachmentItemProps } from './types';
15
21
 
16
22
  export function AttachmentItem({
@@ -18,15 +24,18 @@ export function AttachmentItem({
18
24
  width,
19
25
  imageSize,
20
26
  row,
27
+ mixed = false,
21
28
  showProgressLabel = false,
22
29
  onPreview,
23
30
  onRemove,
24
31
  }: AttachmentItemProps) {
25
32
  const styles = useThemedStyles(createStyles);
33
+ const colors = useColors();
26
34
  const name = item.name || '未命名附件';
27
35
  const status = item.status ?? 'idle';
28
- const loadingVisual = item.loadingVisual ?? 'center';
29
36
  const busy = status === 'uploading' || status === 'processing';
37
+ const failed = status === 'failed';
38
+ const caption = item.loadingVisual === 'caption';
30
39
  const progress =
31
40
  status === 'uploading' &&
32
41
  typeof item.progress === 'number' &&
@@ -36,99 +45,281 @@ export function AttachmentItem({
36
45
  ? item.progress
37
46
  : undefined;
38
47
  const statusText = item.statusText ?? STATUS_LABELS[status];
39
- const icon = <Icon name={ATTACHMENT_ICONS[item.kind ?? 'file']} size={28} />;
40
- const media = (
41
- <View style={[styles.media, { minWidth: imageSize, minHeight: imageSize }]}>
42
- <View pointerEvents="none" style={styles.imageLayer}>
43
- {item.thumbnail ? (
44
- <Thumbnail
45
- source={item.thumbnail}
46
- size={{
47
- width: imageSize,
48
- height: imageSize,
49
- borderRadius: radius.sm,
50
- }}
51
- fallback={icon}
48
+ const previewable = item.previewable && onPreview;
49
+ const separatePreview = previewable && (busy || failed);
50
+ const removable = item.removable && onRemove;
51
+ const singleAction = item.actions?.length === 1 ? item.actions[0] : undefined;
52
+ const retry =
53
+ failed &&
54
+ !caption &&
55
+ singleAction &&
56
+ (singleAction.id === 'retry' ||
57
+ singleAction.icon === 'retry' ||
58
+ singleAction.icon === 'refresh')
59
+ ? singleAction
60
+ : undefined;
61
+ const icon = (
62
+ <Icon name={ATTACHMENT_ICONS[item.kind ?? 'file']} size={ROW_IMAGE_SIZE} />
63
+ );
64
+ const thumbnail = item.thumbnail ? (
65
+ <Thumbnail
66
+ source={item.thumbnail}
67
+ size={{ width: imageSize, height: imageSize, borderRadius: radius.sm }}
68
+ fallback={icon}
69
+ />
70
+ ) : (
71
+ <View style={[styles.placeholder, { width: imageSize, height: imageSize }]}>
72
+ {icon}
73
+ </View>
74
+ );
75
+ const statusVisual =
76
+ busy || failed ? (
77
+ <View style={styles.statusVisual}>
78
+ {failed ? (
79
+ <Icon
80
+ name={retry?.icon ?? 'refresh'}
81
+ size={16}
82
+ color={colors.onPrimary}
83
+ />
84
+ ) : progress !== undefined ? (
85
+ <CircularProgress
86
+ value={progress}
87
+ size={36}
88
+ showLabel={showProgressLabel}
89
+ color={colors.onPrimary}
90
+ accessibilityLabel={`${name}上传进度`}
52
91
  />
53
92
  ) : (
54
- <View
55
- style={[
56
- styles.media,
57
- styles.placeholder,
58
- { width: imageSize, height: imageSize },
59
- ]}
60
- >
61
- {icon}
62
- </View>
93
+ <Spinner size={16} color={colors.onPrimary} />
63
94
  )}
64
95
  </View>
65
- {busy && loadingVisual === 'center' ? (
66
- <View pointerEvents="none" style={styles.progress}>
67
- {progress !== undefined ? (
68
- <CircularProgress
69
- value={progress}
70
- showLabel={showProgressLabel}
71
- accessibilityLabel={`${name}上传进度`}
72
- />
73
- ) : (
74
- <Spinner />
75
- )}
96
+ ) : null;
97
+ const statusOwner =
98
+ !caption && statusVisual ? (
99
+ retry ? (
100
+ <Pressable
101
+ style={[
102
+ styles.statusOwner,
103
+ {
104
+ top: (imageSize - ATTACHMENT_STATUS_OWNER_SIZE) / 2,
105
+ left: (imageSize - ATTACHMENT_STATUS_OWNER_SIZE) / 2,
106
+ },
107
+ styles.retryOwner,
108
+ ]}
109
+ accessibilityRole="button"
110
+ accessibilityLabel={retry.label}
111
+ accessibilityHint={retry.accessibilityHint}
112
+ accessibilityState={{
113
+ disabled: !!(retry.disabled || retry.loading),
114
+ busy: !!retry.loading,
115
+ }}
116
+ disabled={retry.disabled || retry.loading}
117
+ onPress={() => retry.onPress()}
118
+ >
119
+ {statusVisual}
120
+ </Pressable>
121
+ ) : (
122
+ <View
123
+ style={[
124
+ styles.statusOwner,
125
+ {
126
+ top: (imageSize - ATTACHMENT_STATUS_OWNER_SIZE) / 2,
127
+ left: (imageSize - ATTACHMENT_STATUS_OWNER_SIZE) / 2,
128
+ },
129
+ ]}
130
+ accessible
131
+ accessibilityLabel={statusText}
132
+ accessibilityState={{ busy }}
133
+ >
134
+ {statusVisual}
76
135
  </View>
77
- ) : null}
78
- </View>
79
- );
136
+ )
137
+ ) : null;
138
+ const actions = item.actions
139
+ ?.filter((action) => action !== retry)
140
+ .map((action) => (
141
+ <Button
142
+ key={action.id}
143
+ label={action.label}
144
+ leftIcon={action.icon}
145
+ variant="secondary"
146
+ style={styles.action}
147
+ size="sm"
148
+ disabled={action.disabled}
149
+ loading={action.loading}
150
+ accessibilityHint={action.accessibilityHint}
151
+ onPress={action.onPress}
152
+ />
153
+ ));
80
154
 
81
- return (
82
- <View style={[styles.item, row ? styles.row : { width }]}>
83
- {media}
84
- <View style={[styles.content, row && styles.rowContent]}>
85
- <Text style={styles.name}>{name}</Text>
86
- {item.meta ? <Text style={styles.meta}>{item.meta}</Text> : null}
87
- {statusText ? (
88
- <Text
89
- style={[styles.meta, status === 'failed' && styles.failed]}
90
- accessibilityLiveRegion="polite"
91
- >
92
- {statusText}
155
+ if (row) {
156
+ const content = (
157
+ <>
158
+ <View style={styles.media}>
159
+ {thumbnail}
160
+ {statusOwner}
161
+ </View>
162
+ <View style={[styles.content, styles.rowContent]}>
163
+ <Text style={styles.name} numberOfLines={1} ellipsizeMode="middle">
164
+ {name}
93
165
  </Text>
94
- ) : null}
95
- {busy &&
96
- loadingVisual === 'caption' &&
97
- showProgressLabel &&
98
- progress !== undefined ? (
99
- <Text style={styles.meta}>{`${Math.round(progress * 100)}%`}</Text>
100
- ) : null}
166
+ {item.meta ? (
167
+ <Text
168
+ style={[styles.meta, !mixed && styles.fileMeta]}
169
+ numberOfLines={1}
170
+ >
171
+ {item.meta}
172
+ </Text>
173
+ ) : null}
174
+ {statusText ? (
175
+ <Text
176
+ style={[styles.meta, failed && styles.failed]}
177
+ accessibilityLiveRegion="polite"
178
+ >
179
+ {statusText}
180
+ </Text>
181
+ ) : null}
182
+ {busy && caption && showProgressLabel && progress !== undefined ? (
183
+ <Text style={styles.meta}>{`${Math.round(progress * 100)}%`}</Text>
184
+ ) : null}
185
+ </View>
186
+ </>
187
+ );
188
+ return (
189
+ <View style={[styles.item, styles.row, mixed && styles.mixedRow]}>
190
+ {previewable && !retry ? (
191
+ <Pressable
192
+ style={[styles.rowBody, mixed && styles.mixedRowBody]}
193
+ accessibilityRole="button"
194
+ accessibilityLabel={`预览${name}`}
195
+ onPress={() => onPreview?.(item)}
196
+ >
197
+ {content}
198
+ </Pressable>
199
+ ) : (
200
+ <View style={[styles.rowBody, mixed && styles.mixedRowBody]}>
201
+ {content}
202
+ </View>
203
+ )}
101
204
  <View style={styles.actions}>
102
- {item.previewable && onPreview ? (
205
+ {previewable && retry ? (
103
206
  <IconButton
104
207
  icon="eye"
208
+ size="sm"
105
209
  accessibilityLabel={`预览${name}`}
106
- onPress={() => onPreview(item)}
210
+ onPress={() => onPreview?.(item)}
107
211
  />
108
212
  ) : null}
109
- {item.removable && onRemove ? (
213
+ {removable ? (
110
214
  <IconButton
111
215
  icon="close"
216
+ size="sm"
112
217
  accessibilityLabel={`移除${name}`}
113
218
  disabled={item.removeDisabled}
114
- onPress={() => onRemove(item)}
219
+ onPress={() => onRemove?.(item)}
115
220
  />
116
221
  ) : null}
117
- {item.actions?.map((action) => (
118
- <Button
119
- key={action.id}
120
- label={action.label}
121
- leftIcon={action.icon}
122
- variant="secondary"
123
- style={styles.action}
124
- disabled={action.disabled}
125
- loading={action.loading}
126
- accessibilityHint={action.accessibilityHint}
127
- onPress={action.onPress}
128
- />
129
- ))}
222
+ {actions}
130
223
  </View>
131
224
  </View>
225
+ );
226
+ }
227
+
228
+ const removeLeft =
229
+ previewable && !separatePreview
230
+ ? width!
231
+ : (busy && !caption) || failed
232
+ ? (imageSize - ATTACHMENT_STATUS_OWNER_SIZE) / 2 +
233
+ ATTACHMENT_STATUS_OWNER_SIZE
234
+ : imageSize - ATTACHMENT_STATUS_OWNER_SIZE;
235
+ const ownerWidth = removable
236
+ ? Math.max(width!, removeLeft + ATTACHMENT_STATUS_OWNER_SIZE)
237
+ : width;
238
+ const picture = (
239
+ <>
240
+ {thumbnail}
241
+ {item.kind !== 'image' ? (
242
+ <Text style={styles.documentName} numberOfLines={2}>
243
+ {name}
244
+ </Text>
245
+ ) : null}
246
+ </>
247
+ );
248
+ return (
249
+ <View style={[styles.tileOwner, { width: ownerWidth }]}>
250
+ {previewable && !separatePreview ? (
251
+ <Pressable
252
+ style={[
253
+ styles.tile,
254
+ { width, height: imageSize },
255
+ failed && styles.tileFailed,
256
+ ]}
257
+ accessibilityRole="button"
258
+ accessibilityLabel={`预览${name}`}
259
+ onPress={() => onPreview?.(item)}
260
+ >
261
+ {picture}
262
+ </Pressable>
263
+ ) : (
264
+ <View
265
+ style={[
266
+ styles.tile,
267
+ { width, height: imageSize },
268
+ failed && styles.tileFailed,
269
+ ]}
270
+ accessible={!statusOwner}
271
+ accessibilityLabel={name}
272
+ >
273
+ {picture}
274
+ </View>
275
+ )}
276
+ {statusOwner}
277
+ {removable ? (
278
+ <Pressable
279
+ style={[styles.removeOwner, { left: removeLeft }]}
280
+ accessibilityRole="button"
281
+ accessibilityLabel={`移除${name}`}
282
+ disabled={item.removeDisabled}
283
+ accessibilityState={{ disabled: !!item.removeDisabled }}
284
+ onPress={() => onRemove?.(item)}
285
+ >
286
+ <View style={[styles.removeVisual, failed && styles.removeFailed]}>
287
+ <Icon
288
+ name="close"
289
+ size={10}
290
+ color={colors.onPrimary}
291
+ strokeWidth={2.5}
292
+ />
293
+ </View>
294
+ </Pressable>
295
+ ) : null}
296
+ {caption && (busy || failed) ? (
297
+ <View style={styles.caption} accessibilityLiveRegion="polite">
298
+ {status === 'processing' ? <Spinner size={10} /> : null}
299
+ <Text
300
+ style={[styles.captionText, failed && styles.failed]}
301
+ numberOfLines={1}
302
+ >
303
+ {statusText}
304
+ </Text>
305
+ {showProgressLabel && progress !== undefined ? (
306
+ <Text
307
+ style={styles.captionText}
308
+ >{`${Math.round(progress * 100)}%`}</Text>
309
+ ) : null}
310
+ </View>
311
+ ) : null}
312
+ {separatePreview ? (
313
+ <View style={styles.previewAction}>
314
+ <IconButton
315
+ icon="eye"
316
+ size="sm"
317
+ accessibilityLabel={`预览${name}`}
318
+ onPress={() => onPreview?.(item)}
319
+ />
320
+ </View>
321
+ ) : null}
322
+ {actions?.length ? <View style={styles.actions}>{actions}</View> : null}
132
323
  </View>
133
324
  );
134
325
  }