@unif/react-native-chat 2.2.0 → 3.1.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/lib/module/components/Composer/Composer.js +12 -7
- package/lib/module/components/Composer/Composer.js.map +1 -1
- package/lib/module/components/Composer/ComposerIconAction.js +2 -2
- package/lib/module/components/Composer/ComposerIconAction.js.map +1 -1
- package/lib/module/components/Composer/ComposerVoiceWave.js +44 -0
- package/lib/module/components/Composer/ComposerVoiceWave.js.map +1 -0
- package/lib/module/components/Composer/constants.js +20 -1
- package/lib/module/components/Composer/constants.js.map +1 -1
- package/lib/module/components/Composer/styles.js +30 -3
- package/lib/module/components/Composer/styles.js.map +1 -1
- package/lib/module/components/Message/MarkdownContent/MarkdownContent.js +9 -3
- package/lib/module/components/Message/MarkdownContent/MarkdownContent.js.map +1 -1
- package/lib/module/components/Message/MarkdownContent/styles.js +21 -11
- package/lib/module/components/Message/MarkdownContent/styles.js.map +1 -1
- package/lib/module/components/Message/Message.js +16 -4
- package/lib/module/components/Message/Message.js.map +1 -1
- package/lib/module/components/Message/styles.js +20 -14
- package/lib/module/components/Message/styles.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +29 -8
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/components/MessageList/styles.js +14 -4
- package/lib/module/components/MessageList/styles.js.map +1 -1
- package/lib/typescript/src/components/Composer/Composer.d.ts.map +1 -1
- package/lib/typescript/src/components/Composer/ComposerIconAction.d.ts.map +1 -1
- package/lib/typescript/src/components/Composer/ComposerVoiceWave.d.ts +2 -0
- package/lib/typescript/src/components/Composer/ComposerVoiceWave.d.ts.map +1 -0
- package/lib/typescript/src/components/Composer/constants.d.ts +29 -0
- package/lib/typescript/src/components/Composer/constants.d.ts.map +1 -1
- package/lib/typescript/src/components/Composer/styles.d.ts +27 -0
- package/lib/typescript/src/components/Composer/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/MarkdownContent/MarkdownContent.d.ts +1 -1
- package/lib/typescript/src/components/Message/MarkdownContent/MarkdownContent.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/MarkdownContent/styles.d.ts +104 -0
- package/lib/typescript/src/components/Message/MarkdownContent/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/MarkdownContent/types.d.ts +1 -0
- package/lib/typescript/src/components/Message/MarkdownContent/types.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/Message.d.ts.map +1 -1
- package/lib/typescript/src/components/Message/styles.d.ts +11 -5
- package/lib/typescript/src/components/Message/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageList/MessageList.d.ts +1 -1
- package/lib/typescript/src/components/MessageList/MessageList.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageList/styles.d.ts +26 -1
- package/lib/typescript/src/components/MessageList/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageList/types.d.ts +6 -2
- package/lib/typescript/src/components/MessageList/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/components/Composer/Composer.tsx +21 -14
- package/src/components/Composer/ComposerIconAction.tsx +6 -1
- package/src/components/Composer/ComposerVoiceWave.tsx +67 -0
- package/src/components/Composer/constants.ts +14 -1
- package/src/components/Composer/styles.ts +23 -2
- package/src/components/Message/MarkdownContent/MarkdownContent.tsx +15 -3
- package/src/components/Message/MarkdownContent/styles.ts +23 -11
- package/src/components/Message/MarkdownContent/types.ts +1 -0
- package/src/components/Message/Message.tsx +46 -15
- package/src/components/Message/styles.ts +23 -12
- package/src/components/MessageList/MessageList.tsx +33 -12
- package/src/components/MessageList/styles.ts +22 -6
- package/src/components/MessageList/types.ts +6 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ColorTokens, ShadowTokens } from '@unif/react-native-design';
|
|
2
|
+
export declare const createStyles: (colors: ColorTokens, shadows: ShadowTokens) => Readonly<{
|
|
2
3
|
root: {
|
|
3
4
|
flex: number;
|
|
4
5
|
minHeight: number;
|
|
@@ -11,6 +12,30 @@ export declare const styles: Readonly<{
|
|
|
11
12
|
position: "absolute";
|
|
12
13
|
bottom: number;
|
|
13
14
|
alignSelf: "center";
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
alignItems: "center";
|
|
18
|
+
justifyContent: "center";
|
|
19
|
+
};
|
|
20
|
+
returnProgress: Readonly<{
|
|
21
|
+
position: "absolute";
|
|
22
|
+
left: 0;
|
|
23
|
+
right: 0;
|
|
24
|
+
top: 0;
|
|
25
|
+
bottom: 0;
|
|
26
|
+
}>;
|
|
27
|
+
returnButton: {
|
|
28
|
+
shadowColor: string;
|
|
29
|
+
shadowOffset: {
|
|
30
|
+
readonly width: number;
|
|
31
|
+
readonly height: number;
|
|
32
|
+
};
|
|
33
|
+
shadowOpacity: number;
|
|
34
|
+
shadowRadius: number;
|
|
35
|
+
elevation: number;
|
|
36
|
+
alignSelf: "center";
|
|
37
|
+
borderRadius: number;
|
|
38
|
+
backgroundColor: string;
|
|
14
39
|
};
|
|
15
40
|
}>;
|
|
16
41
|
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../../../src/components/MessageList/styles.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../../../src/components/MessageList/styles.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC3E,eAAO,MAAM,YAAY,GAAI,QAAQ,WAAW,EAAE,SAAS,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoBnE,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import type { ScrollViewProps, StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
export interface MessageListScrollOptions {
|
|
4
4
|
animated?: boolean;
|
|
5
5
|
}
|
|
@@ -19,11 +19,15 @@ export interface MessageListProps<T> {
|
|
|
19
19
|
followOutput?: 'whenAtEnd' | 'never';
|
|
20
20
|
endThreshold?: number;
|
|
21
21
|
showScrollToEnd?: boolean;
|
|
22
|
+
/** 外部仍在生成内容时显示返回入口的进度外环,不影响滚动操作。 */
|
|
23
|
+
scrollToEndBusy?: boolean;
|
|
22
24
|
onAtEndChange?(atEnd: boolean): void;
|
|
23
25
|
hasEarlier?: boolean;
|
|
24
26
|
loadingEarlier?: boolean;
|
|
25
27
|
onRequestEarlier?(): void;
|
|
26
|
-
keyboardDismissMode?: '
|
|
28
|
+
keyboardDismissMode?: ScrollViewProps['keyboardDismissMode'];
|
|
29
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
30
|
+
showsVerticalScrollIndicator?: boolean;
|
|
27
31
|
style?: StyleProp<ViewStyle>;
|
|
28
32
|
testID?: string;
|
|
29
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/components/MessageList/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/components/MessageList/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AACD,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAAC;CACvD;AACD,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IACpB,YAAY,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC;IAC9B,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,eAAe,CAAC,CAAC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC;IAClD,eAAe,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAClC,YAAY,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oCAAoC;IACpC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,IAAI,IAAI,CAAC;IAC1B,mBAAmB,CAAC,EAAE,eAAe,CAAC,qBAAqB,CAAC,CAAC;IAC7D,qBAAqB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7C,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AACD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,CAAC;CACzB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unif/react-native-chat",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Unif React Native 聊天组件:消息、输入、附件和交互展示",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@babel/core": "^7.29.7",
|
|
58
|
+
"@callstack/liquid-glass": "0.8.2",
|
|
58
59
|
"@commitlint/config-conventional": "^21.2.0",
|
|
59
60
|
"@eslint/compat": "^2.1.0",
|
|
60
61
|
"@eslint/eslintrc": "^3.3.6",
|
|
@@ -65,10 +66,9 @@
|
|
|
65
66
|
"@react-native/jest-preset": "0.86.3",
|
|
66
67
|
"@react-native/metro-config": "0.86.3",
|
|
67
68
|
"@release-it/conventional-changelog": "^12.0.0",
|
|
68
|
-
"@sbaiahmed1/react-native-blur": "6.0.1",
|
|
69
69
|
"@testing-library/react-native": "13.3.3",
|
|
70
70
|
"@types/react": "^19.2.0",
|
|
71
|
-
"@unif/react-native-design": "0.
|
|
71
|
+
"@unif/react-native-design": "0.36.0",
|
|
72
72
|
"commitlint": "^21.2.1",
|
|
73
73
|
"del-cli": "^7.0.0",
|
|
74
74
|
"eslint": "^9.39.5",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"typescript": "^6.0.3"
|
|
97
97
|
},
|
|
98
98
|
"peerDependencies": {
|
|
99
|
-
"@unif/react-native-design": "^0.
|
|
99
|
+
"@unif/react-native-design": "^0.36.0",
|
|
100
100
|
"react": ">=19.2.3 <20.0.0",
|
|
101
101
|
"react-native": ">=0.86.0",
|
|
102
102
|
"react-native-svg": ">=15"
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from './constants';
|
|
23
23
|
import { ComposerIconAction } from './ComposerIconAction';
|
|
24
24
|
import { ComposerMenuItem } from './ComposerMenuItem';
|
|
25
|
+
import { ComposerVoiceWave } from './ComposerVoiceWave';
|
|
25
26
|
import { createStyles } from './styles';
|
|
26
27
|
import type { ComposerHandle, ComposerProps } from './types';
|
|
27
28
|
|
|
@@ -122,7 +123,9 @@ export const Composer = forwardRef<ComposerHandle, ComposerProps>(
|
|
|
122
123
|
}
|
|
123
124
|
}}
|
|
124
125
|
>
|
|
125
|
-
{header != null ?
|
|
126
|
+
{header != null ? (
|
|
127
|
+
<View style={[styles.accessory, styles.header]}>{header}</View>
|
|
128
|
+
) : null}
|
|
126
129
|
{menuOpen && !disabled && !voiceActive && actions.length > 0 ? (
|
|
127
130
|
<>
|
|
128
131
|
<Pressable
|
|
@@ -175,6 +178,7 @@ export const Composer = forwardRef<ComposerHandle, ComposerProps>(
|
|
|
175
178
|
<Textarea
|
|
176
179
|
ref={inputRef}
|
|
177
180
|
surface="plain"
|
|
181
|
+
contentAlignment="center"
|
|
178
182
|
value={value}
|
|
179
183
|
onChangeText={onChangeText}
|
|
180
184
|
editable={editable}
|
|
@@ -247,19 +251,22 @@ export const Composer = forwardRef<ComposerHandle, ComposerProps>(
|
|
|
247
251
|
disabled={disabled || voice.disabled}
|
|
248
252
|
onPress={voice.onCancel}
|
|
249
253
|
/>
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
254
|
+
<View style={styles.voiceBody}>
|
|
255
|
+
{voice.status !== 'starting' ? <ComposerVoiceWave /> : null}
|
|
256
|
+
<Text
|
|
257
|
+
numberOfLines={1}
|
|
258
|
+
ellipsizeMode="head"
|
|
259
|
+
style={[
|
|
260
|
+
styles.transcript,
|
|
261
|
+
!voice.transcript && styles.transcriptPlaceholder,
|
|
262
|
+
]}
|
|
263
|
+
accessibilityLiveRegion="polite"
|
|
264
|
+
>
|
|
265
|
+
{voice.status === 'starting'
|
|
266
|
+
? VOICE_LABELS.starting
|
|
267
|
+
: voice.transcript || VOICE_LABELS[voice.status]}
|
|
268
|
+
</Text>
|
|
269
|
+
</View>
|
|
263
270
|
{voice.status === 'listening' ? (
|
|
264
271
|
<ComposerIconAction
|
|
265
272
|
icon="stop"
|
|
@@ -3,6 +3,7 @@ import { Icon, useColors, useThemedStyles } from '@unif/react-native-design';
|
|
|
3
3
|
import {
|
|
4
4
|
COMPOSER_ACTION_ICON_SIZE,
|
|
5
5
|
COMPOSER_PRIMARY_ICON_SIZE,
|
|
6
|
+
COMPOSER_CANCEL_ICON_SIZE,
|
|
6
7
|
} from './constants';
|
|
7
8
|
import { createStyles } from './styles';
|
|
8
9
|
import { useMousePress } from './useMousePress';
|
|
@@ -56,7 +57,11 @@ export function ComposerIconAction({
|
|
|
56
57
|
<Icon
|
|
57
58
|
name={icon}
|
|
58
59
|
size={
|
|
59
|
-
primary
|
|
60
|
+
primary
|
|
61
|
+
? COMPOSER_PRIMARY_ICON_SIZE
|
|
62
|
+
: visual === 'cancel'
|
|
63
|
+
? COMPOSER_CANCEL_ICON_SIZE
|
|
64
|
+
: COMPOSER_ACTION_ICON_SIZE
|
|
60
65
|
}
|
|
61
66
|
color={color}
|
|
62
67
|
style={
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Animated, Easing, Platform, View } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
usePrefersReducedMotion,
|
|
5
|
+
useThemedStyles,
|
|
6
|
+
} from '@unif/react-native-design';
|
|
7
|
+
import {
|
|
8
|
+
COMPOSER_VOICE_WAVE_PATTERN,
|
|
9
|
+
COMPOSER_VOICE_WAVE_STAGGER_MS,
|
|
10
|
+
} from './constants';
|
|
11
|
+
import { createStyles } from './styles';
|
|
12
|
+
|
|
13
|
+
export function ComposerVoiceWave() {
|
|
14
|
+
const styles = useThemedStyles(createStyles);
|
|
15
|
+
const reducedMotion = usePrefersReducedMotion();
|
|
16
|
+
const [values] = useState(() =>
|
|
17
|
+
COMPOSER_VOICE_WAVE_PATTERN.map(
|
|
18
|
+
(group) => new Animated.Value(group.from / Math.max(group.from, group.to))
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const animations = COMPOSER_VOICE_WAVE_PATTERN.map((group, index) => {
|
|
23
|
+
const value = values[index]!;
|
|
24
|
+
const maximum = Math.max(group.from, group.to);
|
|
25
|
+
value.setValue((reducedMotion ? group.to : group.from) / maximum);
|
|
26
|
+
return Animated.sequence([
|
|
27
|
+
Animated.delay(index * COMPOSER_VOICE_WAVE_STAGGER_MS),
|
|
28
|
+
Animated.loop(
|
|
29
|
+
Animated.sequence(
|
|
30
|
+
[group.to, group.from].map((height) =>
|
|
31
|
+
Animated.timing(value, {
|
|
32
|
+
toValue: height / maximum,
|
|
33
|
+
duration: group.duration / 2,
|
|
34
|
+
easing: Easing.inOut(Easing.ease),
|
|
35
|
+
useNativeDriver: Platform.OS !== 'web',
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
),
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
if (!reducedMotion) animations.forEach((animation) => animation.start());
|
|
43
|
+
return () => animations.forEach((animation) => animation.stop());
|
|
44
|
+
// 波形规格固定,输入文字更新不重启装饰动画。
|
|
45
|
+
}, [reducedMotion, values]);
|
|
46
|
+
return (
|
|
47
|
+
<View
|
|
48
|
+
style={styles.wave}
|
|
49
|
+
pointerEvents="none"
|
|
50
|
+
accessibilityElementsHidden
|
|
51
|
+
importantForAccessibility="no-hide-descendants"
|
|
52
|
+
>
|
|
53
|
+
{COMPOSER_VOICE_WAVE_PATTERN.map((group, index) => (
|
|
54
|
+
<Animated.View
|
|
55
|
+
key={index}
|
|
56
|
+
style={[
|
|
57
|
+
styles.waveBar,
|
|
58
|
+
{
|
|
59
|
+
height: Math.max(group.from, group.to),
|
|
60
|
+
transform: [{ scaleY: values[index]! }],
|
|
61
|
+
},
|
|
62
|
+
]}
|
|
63
|
+
/>
|
|
64
|
+
))}
|
|
65
|
+
</View>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -2,10 +2,23 @@ import { space, type } from '@unif/react-native-design';
|
|
|
2
2
|
|
|
3
3
|
export const COMPOSER_INPUT_LINE_HEIGHT = Math.round(type.body * 1.4);
|
|
4
4
|
export const COMPOSER_INPUT_MAX_LINES = 4;
|
|
5
|
-
export const COMPOSER_PLAIN_VERTICAL_INSET = 2 * space[
|
|
5
|
+
export const COMPOSER_PLAIN_VERTICAL_INSET = 2 * space[3];
|
|
6
6
|
export const COMPOSER_ACTION_VISUAL_SIZE = 34;
|
|
7
7
|
export const COMPOSER_ACTION_ICON_SIZE = 22;
|
|
8
8
|
export const COMPOSER_PRIMARY_ICON_SIZE = 18;
|
|
9
|
+
export const COMPOSER_CANCEL_ICON_SIZE = 20;
|
|
10
|
+
export const COMPOSER_VOICE_WAVE_COUNT = 5;
|
|
11
|
+
export const COMPOSER_VOICE_WAVE_STAGGER_MS = 90;
|
|
12
|
+
export const COMPOSER_VOICE_WAVE_GROUPS = [
|
|
13
|
+
{ from: 6, to: 20, duration: 1100 },
|
|
14
|
+
{ from: 14, to: 5, duration: 1000 },
|
|
15
|
+
{ from: 8, to: 18, duration: 900 },
|
|
16
|
+
] as const;
|
|
17
|
+
export const COMPOSER_VOICE_WAVE_PATTERN = Array.from(
|
|
18
|
+
{ length: COMPOSER_VOICE_WAVE_COUNT },
|
|
19
|
+
(_, index) =>
|
|
20
|
+
COMPOSER_VOICE_WAVE_GROUPS[index % COMPOSER_VOICE_WAVE_GROUPS.length]!
|
|
21
|
+
);
|
|
9
22
|
export const COMPOSER_MENU_ICON_CONTAINER_SIZE = 40;
|
|
10
23
|
export const COMPOSER_MENU_ICON_SIZE = 20;
|
|
11
24
|
export const PRIMARY_LABELS = {
|
|
@@ -17,6 +17,10 @@ export const createStyles = (colors: ColorTokens, shadows: ShadowTokens) =>
|
|
|
17
17
|
...shadows.card,
|
|
18
18
|
},
|
|
19
19
|
accessory: { padding: space[3] },
|
|
20
|
+
header: {
|
|
21
|
+
borderBottomWidth: 0.5,
|
|
22
|
+
borderBottomColor: colors.outlineVariant,
|
|
23
|
+
},
|
|
20
24
|
regular: {
|
|
21
25
|
flexDirection: 'row',
|
|
22
26
|
alignItems: 'flex-end',
|
|
@@ -26,7 +30,12 @@ export const createStyles = (colors: ColorTokens, shadows: ShadowTokens) =>
|
|
|
26
30
|
},
|
|
27
31
|
expanded: { flexDirection: 'column', alignItems: 'stretch', gap: 0 },
|
|
28
32
|
hidden: { display: 'none' },
|
|
29
|
-
input: {
|
|
33
|
+
input: {
|
|
34
|
+
flex: 1,
|
|
35
|
+
minWidth: 0,
|
|
36
|
+
alignSelf: 'stretch',
|
|
37
|
+
justifyContent: 'center',
|
|
38
|
+
},
|
|
30
39
|
inputExpanded: { flex: 0, width: '100%' },
|
|
31
40
|
// 保持原生堆叠特征稳定,避免聚焦展开时重排输入所在的原生视图树。
|
|
32
41
|
more: { width: fixed.hitTarget, height: fixed.hitTarget, zIndex: 1 },
|
|
@@ -119,11 +128,13 @@ export const createStyles = (colors: ColorTokens, shadows: ShadowTokens) =>
|
|
|
119
128
|
voice: {
|
|
120
129
|
flexDirection: 'row',
|
|
121
130
|
alignItems: 'center',
|
|
122
|
-
gap: space[
|
|
131
|
+
gap: space[3],
|
|
123
132
|
paddingVertical: space[1],
|
|
124
133
|
paddingHorizontal: space[2],
|
|
125
134
|
borderRadius: radius['3xl'],
|
|
126
135
|
backgroundColor: colors.primaryContainerSubtle,
|
|
136
|
+
borderWidth: 0.5,
|
|
137
|
+
borderColor: colors.brandTint14,
|
|
127
138
|
},
|
|
128
139
|
transcript: {
|
|
129
140
|
flex: 1,
|
|
@@ -131,5 +142,15 @@ export const createStyles = (colors: ColorTokens, shadows: ShadowTokens) =>
|
|
|
131
142
|
color: colors.foreground,
|
|
132
143
|
fontSize: type.body,
|
|
133
144
|
},
|
|
145
|
+
voiceBody: {
|
|
146
|
+
flex: 1,
|
|
147
|
+
minWidth: 0,
|
|
148
|
+
flexDirection: 'row',
|
|
149
|
+
alignItems: 'center',
|
|
150
|
+
gap: space[4],
|
|
151
|
+
minHeight: r(34),
|
|
152
|
+
},
|
|
153
|
+
wave: { flexDirection: 'row', alignItems: 'center', gap: 3, height: r(20) },
|
|
154
|
+
waveBar: { width: 2.5, borderRadius: 1.5, backgroundColor: colors.primary },
|
|
134
155
|
transcriptPlaceholder: { color: colors.foregroundSubtle },
|
|
135
156
|
});
|
|
@@ -8,15 +8,27 @@ import {
|
|
|
8
8
|
import { Pressable, Text } from 'react-native';
|
|
9
9
|
import { Renderer, useMarkdown } from 'react-native-marked';
|
|
10
10
|
import { useTheme, useThemedStyles } from '@unif/react-native-design';
|
|
11
|
-
import { createStyles } from './styles';
|
|
11
|
+
import { createStyles, createOutgoingStyles } from './styles';
|
|
12
12
|
import type { MarkdownContentProps, MarkdownSvgLabelProps } from './types';
|
|
13
13
|
import { MarkdownImage } from './MarkdownImage/MarkdownImage';
|
|
14
14
|
|
|
15
|
-
export function MarkdownContent({
|
|
15
|
+
export function MarkdownContent({
|
|
16
|
+
text,
|
|
17
|
+
onLinkPress,
|
|
18
|
+
outgoing = false,
|
|
19
|
+
}: MarkdownContentProps) {
|
|
16
20
|
const { scheme } = useTheme();
|
|
17
|
-
const styles = useThemedStyles(
|
|
21
|
+
const styles = useThemedStyles(
|
|
22
|
+
outgoing ? createOutgoingStyles : createStyles
|
|
23
|
+
);
|
|
18
24
|
const markdown = useMemo(() => {
|
|
19
25
|
const instance = new Renderer();
|
|
26
|
+
const renderCode = instance.code.bind(instance);
|
|
27
|
+
instance.code = (value, language, containerStyle, textStyle) =>
|
|
28
|
+
renderCode(value, language, containerStyle, {
|
|
29
|
+
...textStyle,
|
|
30
|
+
...styles.codeText,
|
|
31
|
+
});
|
|
20
32
|
// 上游 table 节点未提供 key,保留其渲染并补齐稳定的节点身份。
|
|
21
33
|
const renderTable = instance.table.bind(instance);
|
|
22
34
|
instance.table = (...args) => (
|
|
@@ -2,60 +2,72 @@ import { StyleSheet } from 'react-native';
|
|
|
2
2
|
import { type, space, fontMono } 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) => {
|
|
6
|
+
const textColor = outgoing ? colors.onPrimary : colors.foreground;
|
|
6
7
|
const body = {
|
|
7
|
-
fontSize: type.
|
|
8
|
-
lineHeight: type.
|
|
9
|
-
color:
|
|
8
|
+
fontSize: type.sm,
|
|
9
|
+
lineHeight: type.sm * 1.5,
|
|
10
|
+
color: textColor,
|
|
10
11
|
};
|
|
11
12
|
return StyleSheet.create({
|
|
12
13
|
text: body,
|
|
13
14
|
em: { ...body, fontStyle: 'italic' },
|
|
14
15
|
strong: { ...body, fontWeight: 'bold' },
|
|
15
16
|
strikethrough: { ...body, textDecorationLine: 'line-through' },
|
|
16
|
-
link: {
|
|
17
|
+
link: {
|
|
18
|
+
...body,
|
|
19
|
+
color: outgoing ? colors.onPrimary : colors.primary,
|
|
20
|
+
textDecorationLine: 'underline',
|
|
21
|
+
},
|
|
17
22
|
li: body,
|
|
18
23
|
codespan: {
|
|
19
24
|
...body,
|
|
20
25
|
fontFamily: fontMono,
|
|
26
|
+
color: colors.foreground,
|
|
21
27
|
backgroundColor: colors.surfaceContainer,
|
|
22
28
|
},
|
|
23
29
|
h1: {
|
|
24
30
|
fontSize: type.h1,
|
|
25
31
|
lineHeight: type.h1 * 1.4,
|
|
26
|
-
color:
|
|
32
|
+
color: textColor,
|
|
27
33
|
borderBottomColor: colors.outline,
|
|
28
34
|
},
|
|
29
35
|
h2: {
|
|
30
36
|
fontSize: type.h2,
|
|
31
37
|
lineHeight: type.h2 * 1.4,
|
|
32
|
-
color:
|
|
38
|
+
color: textColor,
|
|
33
39
|
borderBottomColor: colors.outline,
|
|
34
40
|
},
|
|
35
41
|
h3: {
|
|
36
42
|
fontSize: type.h3,
|
|
37
43
|
lineHeight: type.h3 * 1.4,
|
|
38
|
-
color:
|
|
44
|
+
color: textColor,
|
|
39
45
|
},
|
|
40
46
|
h4: {
|
|
41
47
|
fontSize: type.body,
|
|
42
48
|
lineHeight: type.body * 1.4,
|
|
43
|
-
color:
|
|
49
|
+
color: textColor,
|
|
44
50
|
},
|
|
45
51
|
h5: {
|
|
46
52
|
fontSize: type.sm,
|
|
47
53
|
lineHeight: type.sm * 1.4,
|
|
48
|
-
color:
|
|
54
|
+
color: textColor,
|
|
49
55
|
},
|
|
50
56
|
h6: {
|
|
51
57
|
fontSize: type.xs,
|
|
52
58
|
lineHeight: type.xs * 1.4,
|
|
53
|
-
color:
|
|
59
|
+
color: textColor,
|
|
54
60
|
},
|
|
55
61
|
paragraph: { paddingVertical: space[1] },
|
|
56
62
|
code: { padding: space[2], backgroundColor: colors.surfaceContainer },
|
|
63
|
+
codeText: { ...body, color: colors.foreground, fontFamily: fontMono },
|
|
57
64
|
blockquote: { borderLeftColor: colors.outline },
|
|
58
65
|
hr: { borderBottomColor: colors.outline },
|
|
59
66
|
table: { borderColor: colors.outline },
|
|
60
67
|
});
|
|
61
68
|
};
|
|
69
|
+
|
|
70
|
+
export const createStyles = (colors: ColorTokens) =>
|
|
71
|
+
makeMarkdownStyles(colors, false);
|
|
72
|
+
export const createOutgoingStyles = (colors: ColorTokens) =>
|
|
73
|
+
makeMarkdownStyles(colors, true);
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Pressable, Text, View } from 'react-native';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
IconButton,
|
|
5
|
+
Spinner,
|
|
6
|
+
useThemedStyles,
|
|
7
|
+
useColors,
|
|
8
|
+
} from '@unif/react-native-design';
|
|
3
9
|
import { MarkdownContent } from './MarkdownContent/MarkdownContent';
|
|
4
10
|
import { createStyles } from './styles';
|
|
5
11
|
import { isNestedAction } from './isNestedAction';
|
|
@@ -23,6 +29,7 @@ export function Message(props: MessageProps) {
|
|
|
23
29
|
testID,
|
|
24
30
|
} = props;
|
|
25
31
|
const styles = useThemedStyles(createStyles);
|
|
32
|
+
const colors = useColors();
|
|
26
33
|
const hasContent =
|
|
27
34
|
Boolean(props.text?.length) ||
|
|
28
35
|
(props.children !== undefined &&
|
|
@@ -47,11 +54,20 @@ export function Message(props: MessageProps) {
|
|
|
47
54
|
{header}
|
|
48
55
|
{props.text !== undefined ? (
|
|
49
56
|
props.format === 'markdown' ? (
|
|
50
|
-
<MarkdownContent
|
|
57
|
+
<MarkdownContent
|
|
58
|
+
text={props.text}
|
|
59
|
+
onLinkPress={props.onLinkPress}
|
|
60
|
+
outgoing={surface === 'bubble' && placement === 'end'}
|
|
61
|
+
/>
|
|
51
62
|
) : (
|
|
52
63
|
<Text
|
|
53
64
|
selectable
|
|
54
|
-
style={
|
|
65
|
+
style={[
|
|
66
|
+
styles.text,
|
|
67
|
+
surface === 'bubble' &&
|
|
68
|
+
placement === 'end' &&
|
|
69
|
+
styles.outgoingText,
|
|
70
|
+
]}
|
|
55
71
|
numberOfLines={props.numberOfLines}
|
|
56
72
|
>
|
|
57
73
|
{props.text}
|
|
@@ -117,18 +133,33 @@ export function Message(props: MessageProps) {
|
|
|
117
133
|
)}
|
|
118
134
|
{actions.length ? (
|
|
119
135
|
<View style={styles.actions}>
|
|
120
|
-
{actions.map((action) =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
{actions.map((action) =>
|
|
137
|
+
action.icon ? (
|
|
138
|
+
<IconButton
|
|
139
|
+
key={action.id}
|
|
140
|
+
icon={action.icon}
|
|
141
|
+
size="sm"
|
|
142
|
+
variant="ghost"
|
|
143
|
+
color={colors.foregroundSubtle}
|
|
144
|
+
accessibilityLabel={action.label}
|
|
145
|
+
accessibilityHint={action.accessibilityHint}
|
|
146
|
+
disabled={action.disabled}
|
|
147
|
+
loading={action.loading}
|
|
148
|
+
onPress={action.onPress}
|
|
149
|
+
/>
|
|
150
|
+
) : (
|
|
151
|
+
<Button
|
|
152
|
+
key={action.id}
|
|
153
|
+
label={action.label}
|
|
154
|
+
leftIcon={action.icon}
|
|
155
|
+
disabled={action.disabled}
|
|
156
|
+
loading={action.loading}
|
|
157
|
+
accessibilityHint={action.accessibilityHint}
|
|
158
|
+
variant="text"
|
|
159
|
+
onPress={action.onPress}
|
|
160
|
+
/>
|
|
161
|
+
)
|
|
162
|
+
)}
|
|
132
163
|
</View>
|
|
133
164
|
) : null}
|
|
134
165
|
</View>
|
|
@@ -7,36 +7,47 @@ export const createStyles = (colors: ColorTokens) =>
|
|
|
7
7
|
root: {
|
|
8
8
|
flexDirection: 'row',
|
|
9
9
|
alignItems: 'flex-start',
|
|
10
|
-
gap: space[
|
|
10
|
+
gap: space[3],
|
|
11
11
|
minWidth: 0,
|
|
12
|
-
paddingVertical: space[2],
|
|
13
12
|
},
|
|
14
13
|
end: { flexDirection: 'row-reverse' },
|
|
15
14
|
column: {
|
|
16
|
-
|
|
15
|
+
flex: 1,
|
|
17
16
|
maxWidth: '100%',
|
|
18
17
|
gap: space[1],
|
|
19
18
|
alignItems: 'flex-start',
|
|
20
19
|
},
|
|
21
20
|
columnEnd: { alignItems: 'flex-end' },
|
|
22
|
-
fullWidth: {
|
|
23
|
-
content: { minWidth: 0, maxWidth: '
|
|
21
|
+
fullWidth: { alignSelf: 'stretch', maxWidth: '100%' },
|
|
22
|
+
content: { minWidth: 0, maxWidth: '78%', gap: space[2] },
|
|
24
23
|
bubble: {
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
paddingHorizontal: space[5],
|
|
25
|
+
paddingVertical: space[4],
|
|
26
|
+
borderRadius: radius['2xl'],
|
|
27
27
|
backgroundColor: colors.surface,
|
|
28
28
|
},
|
|
29
|
-
outgoing: { backgroundColor: colors.
|
|
29
|
+
outgoing: { backgroundColor: colors.primary },
|
|
30
|
+
outgoingText: { color: colors.onPrimary },
|
|
30
31
|
outlined: {
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
paddingHorizontal: space[5],
|
|
33
|
+
paddingVertical: space[4],
|
|
34
|
+
borderRadius: radius['2xl'],
|
|
33
35
|
borderWidth: 1,
|
|
34
36
|
borderColor: colors.outline,
|
|
35
37
|
},
|
|
36
38
|
failed: { borderWidth: 1, borderColor: colors.error },
|
|
37
|
-
text: {
|
|
39
|
+
text: {
|
|
40
|
+
fontSize: type.sm,
|
|
41
|
+
lineHeight: type.sm * 1.5,
|
|
42
|
+
color: colors.foreground,
|
|
43
|
+
},
|
|
38
44
|
name: { fontSize: type.sm, color: colors.foregroundMuted },
|
|
39
45
|
status: { fontSize: type.sm, color: colors.foregroundMuted },
|
|
40
46
|
errorText: { color: colors.error },
|
|
41
|
-
actions: {
|
|
47
|
+
actions: {
|
|
48
|
+
flexDirection: 'row',
|
|
49
|
+
flexWrap: 'wrap',
|
|
50
|
+
gap: space[1],
|
|
51
|
+
marginTop: space[1],
|
|
52
|
+
},
|
|
42
53
|
});
|