@unif/react-native-chat 3.2.4 → 3.3.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.
Files changed (114) hide show
  1. package/lib/module/components/Attachments/AttachmentItem.js +3 -2
  2. package/lib/module/components/Attachments/AttachmentItem.js.map +1 -1
  3. package/lib/module/components/Attachments/Attachments.js +1 -0
  4. package/lib/module/components/Attachments/Attachments.js.map +1 -1
  5. package/lib/module/components/Attachments/styles.js +4 -0
  6. package/lib/module/components/Attachments/styles.js.map +1 -1
  7. package/lib/module/components/Composer/Composer.js +13 -9
  8. package/lib/module/components/Composer/Composer.js.map +1 -1
  9. package/lib/module/components/Confirmation/Confirmation.js +29 -13
  10. package/lib/module/components/Confirmation/Confirmation.js.map +1 -1
  11. package/lib/module/components/Confirmation/styles.js +17 -6
  12. package/lib/module/components/Confirmation/styles.js.map +1 -1
  13. package/lib/module/components/Message/MarkdownContent/styles.js +29 -33
  14. package/lib/module/components/Message/MarkdownContent/styles.js.map +1 -1
  15. package/lib/module/components/Message/Message.js +37 -24
  16. package/lib/module/components/Message/Message.js.map +1 -1
  17. package/lib/module/components/Message/MessageWaiting.js +53 -0
  18. package/lib/module/components/Message/MessageWaiting.js.map +1 -0
  19. package/lib/module/components/Message/constants.js +3 -0
  20. package/lib/module/components/Message/constants.js.map +1 -1
  21. package/lib/module/components/Message/styles.js +41 -3
  22. package/lib/module/components/Message/styles.js.map +1 -1
  23. package/lib/module/components/MessageList/MessageList.js +60 -31
  24. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  25. package/lib/module/components/MessageList/MessageListEndButton.js +60 -0
  26. package/lib/module/components/MessageList/MessageListEndButton.js.map +1 -0
  27. package/lib/module/components/MessageList/MessageListRow.js +9 -2
  28. package/lib/module/components/MessageList/MessageListRow.js.map +1 -1
  29. package/lib/module/components/MessageList/styles.js +6 -0
  30. package/lib/module/components/MessageList/styles.js.map +1 -1
  31. package/lib/module/components/MessageList/useMessageListScroll.js +218 -13
  32. package/lib/module/components/MessageList/useMessageListScroll.js.map +1 -1
  33. package/lib/module/components/Process/Process.js +15 -5
  34. package/lib/module/components/Process/Process.js.map +1 -1
  35. package/lib/module/components/Process/ProcessCompactStep.js +21 -12
  36. package/lib/module/components/Process/ProcessCompactStep.js.map +1 -1
  37. package/lib/module/components/Process/ProcessTimelineStep.js +79 -0
  38. package/lib/module/components/Process/ProcessTimelineStep.js.map +1 -0
  39. package/lib/module/components/Process/styles.js +63 -3
  40. package/lib/module/components/Process/styles.js.map +1 -1
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/typescript/src/components/Attachments/AttachmentItem.d.ts +1 -1
  43. package/lib/typescript/src/components/Attachments/AttachmentItem.d.ts.map +1 -1
  44. package/lib/typescript/src/components/Attachments/Attachments.d.ts.map +1 -1
  45. package/lib/typescript/src/components/Attachments/styles.d.ts +4 -0
  46. package/lib/typescript/src/components/Attachments/styles.d.ts.map +1 -1
  47. package/lib/typescript/src/components/Attachments/types.d.ts +2 -1
  48. package/lib/typescript/src/components/Attachments/types.d.ts.map +1 -1
  49. package/lib/typescript/src/components/Composer/Composer.d.ts.map +1 -1
  50. package/lib/typescript/src/components/Confirmation/Confirmation.d.ts.map +1 -1
  51. package/lib/typescript/src/components/Confirmation/styles.d.ts +13 -2
  52. package/lib/typescript/src/components/Confirmation/styles.d.ts.map +1 -1
  53. package/lib/typescript/src/components/Message/MarkdownContent/styles.d.ts +32 -66
  54. package/lib/typescript/src/components/Message/MarkdownContent/styles.d.ts.map +1 -1
  55. package/lib/typescript/src/components/Message/Message.d.ts.map +1 -1
  56. package/lib/typescript/src/components/Message/MessageWaiting.d.ts +3 -0
  57. package/lib/typescript/src/components/Message/MessageWaiting.d.ts.map +1 -0
  58. package/lib/typescript/src/components/Message/constants.d.ts +3 -0
  59. package/lib/typescript/src/components/Message/constants.d.ts.map +1 -1
  60. package/lib/typescript/src/components/Message/styles.d.ts +40 -2
  61. package/lib/typescript/src/components/Message/styles.d.ts.map +1 -1
  62. package/lib/typescript/src/components/Message/types.d.ts +5 -0
  63. package/lib/typescript/src/components/Message/types.d.ts.map +1 -1
  64. package/lib/typescript/src/components/MessageList/MessageList.d.ts.map +1 -1
  65. package/lib/typescript/src/components/MessageList/MessageListEndButton.d.ts +3 -0
  66. package/lib/typescript/src/components/MessageList/MessageListEndButton.d.ts.map +1 -0
  67. package/lib/typescript/src/components/MessageList/MessageListRow.d.ts +2 -8
  68. package/lib/typescript/src/components/MessageList/MessageListRow.d.ts.map +1 -1
  69. package/lib/typescript/src/components/MessageList/index.d.ts +1 -1
  70. package/lib/typescript/src/components/MessageList/index.d.ts.map +1 -1
  71. package/lib/typescript/src/components/MessageList/styles.d.ts +6 -0
  72. package/lib/typescript/src/components/MessageList/styles.d.ts.map +1 -1
  73. package/lib/typescript/src/components/MessageList/types.d.ts +41 -2
  74. package/lib/typescript/src/components/MessageList/types.d.ts.map +1 -1
  75. package/lib/typescript/src/components/MessageList/useMessageListScroll.d.ts +14 -3
  76. package/lib/typescript/src/components/MessageList/useMessageListScroll.d.ts.map +1 -1
  77. package/lib/typescript/src/components/Process/Process.d.ts.map +1 -1
  78. package/lib/typescript/src/components/Process/ProcessCompactStep.d.ts +1 -1
  79. package/lib/typescript/src/components/Process/ProcessCompactStep.d.ts.map +1 -1
  80. package/lib/typescript/src/components/Process/ProcessTimelineStep.d.ts +5 -0
  81. package/lib/typescript/src/components/Process/ProcessTimelineStep.d.ts.map +1 -0
  82. package/lib/typescript/src/components/Process/styles.d.ts +62 -2
  83. package/lib/typescript/src/components/Process/styles.d.ts.map +1 -1
  84. package/lib/typescript/src/components/Process/types.d.ts +7 -2
  85. package/lib/typescript/src/components/Process/types.d.ts.map +1 -1
  86. package/lib/typescript/src/index.d.ts +1 -1
  87. package/lib/typescript/src/index.d.ts.map +1 -1
  88. package/package.json +1 -1
  89. package/src/components/Attachments/AttachmentItem.tsx +11 -3
  90. package/src/components/Attachments/Attachments.tsx +1 -0
  91. package/src/components/Attachments/styles.ts +1 -0
  92. package/src/components/Attachments/types.ts +2 -1
  93. package/src/components/Composer/Composer.tsx +34 -8
  94. package/src/components/Confirmation/Confirmation.tsx +50 -14
  95. package/src/components/Confirmation/styles.ts +13 -6
  96. package/src/components/Message/MarkdownContent/styles.ts +18 -44
  97. package/src/components/Message/Message.tsx +53 -33
  98. package/src/components/Message/MessageWaiting.tsx +68 -0
  99. package/src/components/Message/constants.ts +4 -0
  100. package/src/components/Message/styles.ts +25 -2
  101. package/src/components/Message/types.ts +6 -0
  102. package/src/components/MessageList/MessageList.tsx +107 -35
  103. package/src/components/MessageList/MessageListEndButton.tsx +61 -0
  104. package/src/components/MessageList/MessageListRow.tsx +12 -12
  105. package/src/components/MessageList/index.ts +1 -0
  106. package/src/components/MessageList/styles.ts +6 -0
  107. package/src/components/MessageList/types.ts +52 -2
  108. package/src/components/MessageList/useMessageListScroll.ts +296 -21
  109. package/src/components/Process/Process.tsx +38 -5
  110. package/src/components/Process/ProcessCompactStep.tsx +32 -16
  111. package/src/components/Process/ProcessTimelineStep.tsx +109 -0
  112. package/src/components/Process/styles.ts +48 -3
  113. package/src/components/Process/types.ts +7 -2
  114. package/src/index.tsx +1 -0
@@ -1,5 +1,11 @@
1
1
  import { Text, View } from 'react-native';
2
- import { Button, useThemedStyles } from '@unif/react-native-design';
2
+ import {
3
+ Button,
4
+ Icon,
5
+ Spinner,
6
+ useColors,
7
+ useThemedStyles,
8
+ } from '@unif/react-native-design';
3
9
  import {
4
10
  CONFIRMATION_STATUS_TEXT,
5
11
  DEFAULT_CANCEL_LABEL,
@@ -24,35 +30,65 @@ export function Confirmation({
24
30
  testID,
25
31
  }: ConfirmationProps): React.JSX.Element {
26
32
  const styles = useThemedStyles(createStyles);
33
+ const colors = useColors();
34
+ const tint =
35
+ status === 'confirmed'
36
+ ? colors.success
37
+ : status === 'cancelled'
38
+ ? colors.foregroundSubtle
39
+ : colors.primary;
40
+ const borderColor =
41
+ status === 'cancelled' ? colors.surfaceContainerHighest : tint;
27
42
  const resolvedStatusText =
28
43
  status === 'pending'
29
44
  ? statusText
30
45
  : (statusText ?? CONFIRMATION_STATUS_TEXT[status]);
31
46
 
32
47
  return (
33
- <View style={[styles.root, style]} testID={testID}>
34
- <Text style={styles.title}>{title}</Text>
35
- <View>{children}</View>
48
+ <View style={[styles.root, { borderColor }, style]} testID={testID}>
49
+ <View style={styles.heading}>
50
+ {status === 'processing' ? (
51
+ <Spinner size={14} color={tint} />
52
+ ) : (
53
+ <Icon
54
+ name={
55
+ status === 'confirmed'
56
+ ? 'check'
57
+ : status === 'cancelled'
58
+ ? 'close'
59
+ : 'warning'
60
+ }
61
+ size={14}
62
+ color={tint}
63
+ />
64
+ )}
65
+ <Text
66
+ style={[styles.title, status === 'cancelled' && styles.cancelled]}
67
+ >
68
+ {title}
69
+ </Text>
70
+ </View>
71
+ <View style={styles.body}>{children}</View>
36
72
  {resolvedStatusText ? (
37
73
  <Text style={styles.status}>{resolvedStatusText}</Text>
38
74
  ) : null}
39
75
  {status === 'pending' && (onConfirm || onCancel) ? (
40
76
  <View style={styles.actions}>
41
- {onConfirm ? (
42
- <Button
43
- label={confirmLabel}
44
- onPress={onConfirm}
45
- disabled={confirmDisabled}
46
- size="sm"
47
- />
48
- ) : null}
49
77
  {onCancel ? (
50
78
  <Button
51
79
  label={cancelLabel}
52
80
  onPress={onCancel}
53
81
  disabled={cancelDisabled}
54
- size="sm"
55
- variant="outline"
82
+ block
83
+ variant="secondary"
84
+ />
85
+ ) : null}
86
+ {onConfirm ? (
87
+ <Button
88
+ label={confirmLabel}
89
+ onPress={onConfirm}
90
+ disabled={confirmDisabled}
91
+ block
56
92
  />
57
93
  ) : null}
58
94
  </View>
@@ -10,25 +10,32 @@ import {
10
10
  export const createStyles = (colors: ColorTokens) =>
11
11
  StyleSheet.create({
12
12
  root: {
13
- gap: space[4],
14
13
  padding: space[5],
15
- borderWidth: 1,
14
+ borderWidth: 2,
16
15
  borderColor: colors.outline,
17
- borderRadius: radius.xl,
16
+ borderRadius: radius.lg,
18
17
  backgroundColor: colors.surface,
19
18
  },
19
+ heading: {
20
+ flexDirection: 'row',
21
+ alignItems: 'center',
22
+ gap: space[2],
23
+ marginBottom: space[3],
24
+ },
25
+ body: { marginBottom: space[4] },
26
+ cancelled: { color: colors.foregroundSubtle },
20
27
  title: {
28
+ flex: 1,
21
29
  color: colors.foreground,
22
- fontSize: type.h3,
30
+ fontSize: type.xs,
23
31
  fontWeight: fw.semi,
24
32
  },
25
33
  status: {
26
34
  color: colors.foregroundMuted,
27
- fontSize: type.sm,
35
+ fontSize: type.xxs,
28
36
  },
29
37
  actions: {
30
38
  flexDirection: 'row',
31
- flexWrap: 'wrap',
32
39
  gap: space[3],
33
40
  },
34
41
  footer: {
@@ -1,5 +1,5 @@
1
1
  import { StyleSheet } from 'react-native';
2
- import { type, space, fontMono } from '@unif/react-native-design';
2
+ import { type, fw } from '@unif/react-native-design';
3
3
  import type { ColorTokens } from '@unif/react-native-design';
4
4
 
5
5
  const makeMarkdownStyles = (colors: ColorTokens, outgoing: boolean) => {
@@ -11,58 +11,32 @@ const makeMarkdownStyles = (colors: ColorTokens, outgoing: boolean) => {
11
11
  };
12
12
  return StyleSheet.create({
13
13
  text: body,
14
- em: { ...body, fontStyle: 'italic' },
15
- strong: { ...body, fontWeight: 'bold' },
16
- strikethrough: { ...body, textDecorationLine: 'line-through' },
14
+ em: { color: textColor, fontStyle: 'italic' },
15
+ strong: { color: textColor, fontWeight: 'bold' },
16
+ strikethrough: { color: textColor, textDecorationLine: 'line-through' },
17
17
  link: {
18
- ...body,
19
18
  color: outgoing ? colors.onPrimary : colors.primary,
20
19
  textDecorationLine: 'underline',
21
20
  },
22
- li: body,
21
+ li: { color: textColor, fontSize: type.sm },
23
22
  codespan: {
24
- ...body,
25
- fontFamily: fontMono,
26
23
  color: colors.foreground,
27
24
  backgroundColor: colors.surfaceContainer,
28
25
  },
29
- h1: {
30
- fontSize: type.h1,
31
- lineHeight: type.h1 * 1.4,
32
- color: textColor,
33
- borderBottomColor: colors.outline,
34
- },
35
- h2: {
36
- fontSize: type.h2,
37
- lineHeight: type.h2 * 1.4,
38
- color: textColor,
39
- borderBottomColor: colors.outline,
40
- },
41
- h3: {
42
- fontSize: type.h3,
43
- lineHeight: type.h3 * 1.4,
44
- color: textColor,
45
- },
46
- h4: {
47
- fontSize: type.body,
48
- lineHeight: type.body * 1.4,
49
- color: textColor,
50
- },
51
- h5: {
52
- fontSize: type.sm,
53
- lineHeight: type.sm * 1.4,
54
- color: textColor,
55
- },
56
- h6: {
57
- fontSize: type.xs,
58
- lineHeight: type.xs * 1.4,
59
- color: textColor,
26
+ h1: { fontSize: type.h2, fontWeight: fw.bold, color: textColor },
27
+ h2: { fontSize: type.h3, fontWeight: fw.bold, color: textColor },
28
+ h3: { fontSize: type.body, fontWeight: fw.semi, color: textColor },
29
+ h4: { fontSize: type.sm, fontWeight: fw.semi, color: textColor },
30
+ h5: { fontSize: type.sm, fontWeight: fw.semi, color: textColor },
31
+ h6: { fontSize: type.sm, fontWeight: fw.semi, color: textColor },
32
+ code: {
33
+ backgroundColor: colors.surfaceContainer,
34
+ borderColor: colors.outline,
35
+ borderWidth: 1,
60
36
  },
61
- paragraph: { paddingVertical: space[1] },
62
- code: { padding: space[2], backgroundColor: colors.surfaceContainer },
63
- codeText: { ...body, color: colors.foreground, fontFamily: fontMono },
64
- blockquote: { borderLeftColor: colors.outline },
65
- hr: { borderBottomColor: colors.outline },
37
+ codeText: { color: colors.foreground },
38
+ blockquote: { borderColor: colors.outline, borderLeftWidth: 3 },
39
+ hr: { backgroundColor: colors.outline, height: 1 },
66
40
  table: { borderColor: colors.outline },
67
41
  });
68
42
  };
@@ -2,13 +2,13 @@ import { Pressable, Text, View } from 'react-native';
2
2
  import {
3
3
  Button,
4
4
  IconButton,
5
- Spinner,
6
5
  useThemedStyles,
7
6
  useColors,
8
7
  } from '@unif/react-native-design';
9
8
  import { MarkdownContent } from './MarkdownContent/MarkdownContent';
10
9
  import { createStyles } from './styles';
11
10
  import { isNestedAction } from './isNestedAction';
11
+ import { MessageWaiting } from './MessageWaiting';
12
12
  import type { MessageProps } from './types';
13
13
 
14
14
  export function Message(props: MessageProps) {
@@ -23,6 +23,7 @@ export function Message(props: MessageProps) {
23
23
  header,
24
24
  footer,
25
25
  actions = [],
26
+ failureAction,
26
27
  onPress,
27
28
  onLongPress,
28
29
  style,
@@ -39,6 +40,7 @@ export function Message(props: MessageProps) {
39
40
  (footer !== undefined && footer !== null && footer !== false);
40
41
  const waiting =
41
42
  (status === 'pending' || status === 'streaming') && !hasContent;
43
+ const failedAction = status === 'failed' ? failureAction : undefined;
42
44
  const contentStyle = [
43
45
  styles.content,
44
46
  fullWidth && styles.fullWidth,
@@ -48,6 +50,12 @@ export function Message(props: MessageProps) {
48
50
  ],
49
51
  surface === 'outlined' && styles.outlined,
50
52
  status === 'failed' && styles.failed,
53
+ waiting &&
54
+ surface === 'bubble' &&
55
+ placement === 'start' &&
56
+ styles.waitingBubble,
57
+ failedAction && styles.failureContent,
58
+ failedAction && fullWidth && styles.failureFullWidth,
51
59
  ];
52
60
  const content = (
53
61
  <>
@@ -76,16 +84,8 @@ export function Message(props: MessageProps) {
76
84
  ) : (
77
85
  props.children
78
86
  )}
79
- {waiting ? (
80
- <View
81
- accessible
82
- accessibilityLabel="正在等待回复"
83
- accessibilityState={{ busy: true }}
84
- >
85
- <Spinner />
86
- </View>
87
- ) : null}
88
- {statusText ? (
87
+ {waiting ? <MessageWaiting label={statusText} /> : null}
88
+ {statusText && !waiting ? (
89
89
  <Text style={[styles.status, status === 'failed' && styles.errorText]}>
90
90
  {statusText}
91
91
  </Text>
@@ -93,6 +93,31 @@ export function Message(props: MessageProps) {
93
93
  {footer}
94
94
  </>
95
95
  );
96
+ const bubble =
97
+ onPress || onLongPress ? (
98
+ <Pressable
99
+ style={contentStyle}
100
+ onPress={
101
+ onPress
102
+ ? (event) => {
103
+ if (!isNestedAction(event)) onPress();
104
+ }
105
+ : undefined
106
+ }
107
+ onLongPress={
108
+ onLongPress
109
+ ? (event) => {
110
+ if (!isNestedAction(event)) onLongPress();
111
+ }
112
+ : undefined
113
+ }
114
+ accessible={false}
115
+ >
116
+ {content}
117
+ </Pressable>
118
+ ) : (
119
+ <View style={contentStyle}>{content}</View>
120
+ );
96
121
  return (
97
122
  <View
98
123
  style={[styles.root, placement === 'end' && styles.end, style]}
@@ -107,29 +132,24 @@ export function Message(props: MessageProps) {
107
132
  ]}
108
133
  >
109
134
  {name ? <Text style={styles.name}>{name}</Text> : null}
110
- {onPress || onLongPress ? (
111
- <Pressable
112
- style={contentStyle}
113
- onPress={
114
- onPress
115
- ? (event) => {
116
- if (!isNestedAction(event)) onPress();
117
- }
118
- : undefined
119
- }
120
- onLongPress={
121
- onLongPress
122
- ? (event) => {
123
- if (!isNestedAction(event)) onLongPress();
124
- }
125
- : undefined
126
- }
127
- accessible={false}
128
- >
129
- {content}
130
- </Pressable>
135
+ {failedAction ? (
136
+ <View style={[styles.failureRow, placement === 'end' && styles.end]}>
137
+ {bubble}
138
+ <IconButton
139
+ icon="alert"
140
+ size="sm"
141
+ variant="ghost"
142
+ color={colors.error}
143
+ style={styles.failureAction}
144
+ accessibilityLabel={failedAction.label}
145
+ accessibilityHint={failedAction.accessibilityHint}
146
+ disabled={failedAction.disabled}
147
+ loading={failedAction.loading}
148
+ onPress={failedAction.onPress}
149
+ />
150
+ </View>
131
151
  ) : (
132
- <View style={contentStyle}>{content}</View>
152
+ bubble
133
153
  )}
134
154
  {actions.length ? (
135
155
  <View style={styles.actions}>
@@ -0,0 +1,68 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Animated, Easing, Platform, Text, View } from 'react-native';
3
+ import {
4
+ usePrefersReducedMotion,
5
+ useThemedStyles,
6
+ } from '@unif/react-native-design';
7
+ import {
8
+ MESSAGE_WAITING_DOT_DELAYS,
9
+ MESSAGE_WAITING_DOT_DURATION,
10
+ MESSAGE_WAITING_DOT_MIN_OPACITY,
11
+ } from './constants';
12
+ import { createStyles } from './styles';
13
+ import type { MessageWaitingProps } from './types';
14
+
15
+ export function MessageWaiting({ label }: MessageWaitingProps) {
16
+ const styles = useThemedStyles(createStyles);
17
+ const reducedMotion = usePrefersReducedMotion();
18
+ const [values] = useState(() =>
19
+ MESSAGE_WAITING_DOT_DELAYS.map(() => new Animated.Value(1))
20
+ );
21
+ useEffect(() => {
22
+ const animations = MESSAGE_WAITING_DOT_DELAYS.map((delay, index) => {
23
+ const value = values[index]!;
24
+ value.setValue(reducedMotion ? 1 : MESSAGE_WAITING_DOT_MIN_OPACITY);
25
+ return Animated.sequence([
26
+ Animated.delay(delay),
27
+ Animated.loop(
28
+ Animated.sequence(
29
+ [1, MESSAGE_WAITING_DOT_MIN_OPACITY].map((toValue) =>
30
+ Animated.timing(value, {
31
+ toValue,
32
+ duration: MESSAGE_WAITING_DOT_DURATION,
33
+ easing: Easing.inOut(Easing.ease),
34
+ useNativeDriver: Platform.OS !== 'web',
35
+ })
36
+ )
37
+ )
38
+ ),
39
+ ]);
40
+ });
41
+ if (!reducedMotion) animations.forEach((animation) => animation.start());
42
+ return () => animations.forEach((animation) => animation.stop());
43
+ }, [reducedMotion, values]);
44
+ return (
45
+ <View
46
+ style={styles.waitingRow}
47
+ accessible
48
+ accessibilityLabel={label || '正在等待回复'}
49
+ accessibilityState={{ busy: true }}
50
+ >
51
+ <View
52
+ style={styles.waitingDots}
53
+ pointerEvents="none"
54
+ accessibilityElementsHidden
55
+ importantForAccessibility="no-hide-descendants"
56
+ >
57
+ {values.map((opacity, index) => (
58
+ <Animated.View
59
+ key={index}
60
+ testID={`message-waiting-dot-${index}`}
61
+ style={[styles.waitingDot, { opacity }]}
62
+ />
63
+ ))}
64
+ </View>
65
+ {label ? <Text style={styles.waitingLabel}>{label}</Text> : null}
66
+ </View>
67
+ );
68
+ }
@@ -1,2 +1,6 @@
1
1
  export const INTERACTIVE_CONTENT_SELECTOR =
2
2
  'button,a,input,textarea,select,[role="button"],[role="link"],[role="checkbox"],[role="switch"],[role="menuitem"],[role="tab"]';
3
+
4
+ export const MESSAGE_WAITING_DOT_DELAYS = [0, 150, 300] as const;
5
+ export const MESSAGE_WAITING_DOT_DURATION = 600;
6
+ export const MESSAGE_WAITING_DOT_MIN_OPACITY = 0.3;
@@ -1,5 +1,5 @@
1
1
  import { StyleSheet } from 'react-native';
2
- import { radius, space, type } from '@unif/react-native-design';
2
+ import { r, radius, space, type } from '@unif/react-native-design';
3
3
  import type { ColorTokens } from '@unif/react-native-design';
4
4
 
5
5
  export const createStyles = (colors: ColorTokens) =>
@@ -35,7 +35,30 @@ export const createStyles = (colors: ColorTokens) =>
35
35
  borderWidth: 1,
36
36
  borderColor: colors.outline,
37
37
  },
38
- failed: { borderWidth: 1, borderColor: colors.error },
38
+ failed: { opacity: 0.7 },
39
+ failureRow: {
40
+ flexDirection: 'row',
41
+ alignItems: 'center',
42
+ alignSelf: 'stretch',
43
+ gap: space[2],
44
+ },
45
+ failureContent: { flexShrink: 1 },
46
+ failureAction: { alignSelf: 'center' },
47
+ failureFullWidth: { flex: 1 },
48
+ waitingBubble: { width: r(240), borderTopLeftRadius: 0 },
49
+ waitingRow: { flexDirection: 'row', alignItems: 'center', gap: space[2] },
50
+ waitingDots: { flexDirection: 'row', gap: r(3) },
51
+ waitingDot: {
52
+ width: r(6),
53
+ height: r(6),
54
+ borderRadius: r(3),
55
+ backgroundColor: colors.foregroundSubtle,
56
+ },
57
+ waitingLabel: {
58
+ fontSize: type.xxs,
59
+ color: colors.foregroundSubtle,
60
+ flexShrink: 1,
61
+ },
39
62
  text: {
40
63
  fontSize: type.sm,
41
64
  lineHeight: type.sm * 1.5,
@@ -15,6 +15,8 @@ export interface MessageBaseProps {
15
15
  header?: ReactNode;
16
16
  footer?: ReactNode;
17
17
  actions?: readonly ChatAction[];
18
+ /** 仅 failed 时在气泡旁显示的错误操作;不自动生成重试。 */
19
+ failureAction?: ChatAction;
18
20
  onPress?(): void;
19
21
  onLongPress?(): void;
20
22
  style?: StyleProp<ViewStyle>;
@@ -43,3 +45,7 @@ export interface MessageCustomContent {
43
45
  }
44
46
  export type MessageProps = MessageBaseProps &
45
47
  (MessagePlainContent | MessageMarkdownContent | MessageCustomContent);
48
+
49
+ export interface MessageWaitingProps {
50
+ label?: string;
51
+ }