@unif/react-native-chat 2.0.0 → 2.1.1
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/README.md +39 -30
- package/lib/module/components/Composer/Composer.js +131 -83
- package/lib/module/components/Composer/Composer.js.map +1 -1
- package/lib/module/components/Composer/ComposerIconAction.js +60 -0
- package/lib/module/components/Composer/ComposerIconAction.js.map +1 -0
- package/lib/module/components/Composer/ComposerMenuItem.js +57 -0
- package/lib/module/components/Composer/ComposerMenuItem.js.map +1 -0
- package/lib/module/components/Composer/constants.js +9 -1
- package/lib/module/components/Composer/constants.js.map +1 -1
- package/lib/module/components/Composer/styles.js +144 -13
- package/lib/module/components/Composer/styles.js.map +1 -1
- package/lib/module/components/Composer/useMousePress.js +28 -0
- package/lib/module/components/Composer/useMousePress.js.map +1 -0
- package/lib/module/components/Suggestions/Suggestions.js +6 -2
- package/lib/module/components/Suggestions/Suggestions.js.map +1 -1
- package/lib/module/components/Suggestions/constants.js +4 -0
- package/lib/module/components/Suggestions/constants.js.map +1 -0
- package/lib/typescript/src/components/Composer/Composer.d.ts.map +1 -1
- package/lib/typescript/src/components/Composer/ComposerIconAction.d.ts +3 -0
- package/lib/typescript/src/components/Composer/ComposerIconAction.d.ts.map +1 -0
- package/lib/typescript/src/components/Composer/ComposerMenuItem.d.ts +3 -0
- package/lib/typescript/src/components/Composer/ComposerMenuItem.d.ts.map +1 -0
- package/lib/typescript/src/components/Composer/constants.d.ts +8 -1
- package/lib/typescript/src/components/Composer/constants.d.ts.map +1 -1
- package/lib/typescript/src/components/Composer/styles.d.ts +150 -7
- package/lib/typescript/src/components/Composer/styles.d.ts.map +1 -1
- package/lib/typescript/src/components/Composer/types.d.ts +19 -1
- package/lib/typescript/src/components/Composer/types.d.ts.map +1 -1
- package/lib/typescript/src/components/Composer/useMousePress.d.ts +9 -0
- package/lib/typescript/src/components/Composer/useMousePress.d.ts.map +1 -0
- package/lib/typescript/src/components/Suggestions/Suggestions.d.ts.map +1 -1
- package/lib/typescript/src/components/Suggestions/constants.d.ts +2 -0
- package/lib/typescript/src/components/Suggestions/constants.d.ts.map +1 -0
- package/lib/typescript/src/components/Suggestions/types.d.ts +1 -0
- package/lib/typescript/src/components/Suggestions/types.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/components/Composer/Composer.tsx +165 -95
- package/src/components/Composer/ComposerIconAction.tsx +71 -0
- package/src/components/Composer/ComposerMenuItem.tsx +56 -0
- package/src/components/Composer/constants.ts +10 -1
- package/src/components/Composer/styles.ts +123 -13
- package/src/components/Composer/types.ts +21 -1
- package/src/components/Composer/useMousePress.ts +32 -0
- package/src/components/Suggestions/Suggestions.tsx +12 -2
- package/src/components/Suggestions/constants.ts +1 -0
- package/src/components/Suggestions/types.ts +1 -0
|
@@ -1,25 +1,135 @@
|
|
|
1
1
|
import { StyleSheet } from 'react-native';
|
|
2
|
-
import { radius, space, type } from '@unif/react-native-design';
|
|
3
|
-
import type { ColorTokens } from '@unif/react-native-design';
|
|
2
|
+
import { fixed, r, radius, space, type } from '@unif/react-native-design';
|
|
3
|
+
import type { ColorTokens, ShadowTokens } from '@unif/react-native-design';
|
|
4
|
+
import {
|
|
5
|
+
COMPOSER_ACTION_VISUAL_SIZE,
|
|
6
|
+
COMPOSER_MENU_ICON_CONTAINER_SIZE,
|
|
7
|
+
} from './constants';
|
|
4
8
|
|
|
5
|
-
export const createStyles = (colors: ColorTokens) =>
|
|
9
|
+
export const createStyles = (colors: ColorTokens, shadows: ShadowTokens) =>
|
|
6
10
|
StyleSheet.create({
|
|
7
|
-
root: {
|
|
11
|
+
root: { position: 'relative', minWidth: 0 },
|
|
8
12
|
card: {
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
borderRadius: r(20),
|
|
14
|
+
borderWidth: 0.5,
|
|
15
|
+
borderColor: colors.outline,
|
|
11
16
|
backgroundColor: colors.surface,
|
|
17
|
+
...shadows.card,
|
|
12
18
|
},
|
|
13
|
-
|
|
19
|
+
accessory: { padding: space[3] },
|
|
20
|
+
regular: {
|
|
21
|
+
flexDirection: 'row',
|
|
22
|
+
alignItems: 'flex-end',
|
|
23
|
+
gap: space[1],
|
|
24
|
+
paddingVertical: space[1],
|
|
25
|
+
paddingHorizontal: space[2],
|
|
26
|
+
},
|
|
27
|
+
expanded: { flexDirection: 'column', alignItems: 'stretch', gap: 0 },
|
|
14
28
|
hidden: { display: 'none' },
|
|
15
|
-
|
|
29
|
+
input: { flex: 1, minWidth: 0 },
|
|
30
|
+
inputExpanded: { flex: 0, width: '100%' },
|
|
31
|
+
// 保持原生堆叠特征稳定,避免聚焦展开时重排输入所在的原生视图树。
|
|
32
|
+
more: { width: fixed.hitTarget, height: fixed.hitTarget, zIndex: 1 },
|
|
33
|
+
moreExpanded: {
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
left: space[2],
|
|
36
|
+
bottom: space[1],
|
|
37
|
+
},
|
|
38
|
+
toolbar: { flexDirection: 'row', alignItems: 'center' },
|
|
39
|
+
toolbarExpanded: {
|
|
40
|
+
width: '100%',
|
|
41
|
+
minHeight: fixed.hitTarget,
|
|
42
|
+
justifyContent: 'flex-end',
|
|
43
|
+
},
|
|
44
|
+
toolbarWithMore: { paddingStart: fixed.hitTarget + space[1] },
|
|
45
|
+
trailingActions: {
|
|
46
|
+
flexDirection: 'row',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
gap: space[1],
|
|
49
|
+
marginStart: 'auto',
|
|
50
|
+
},
|
|
51
|
+
action: {
|
|
52
|
+
width: fixed.hitTarget,
|
|
53
|
+
height: fixed.hitTarget,
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
justifyContent: 'center',
|
|
56
|
+
},
|
|
57
|
+
actionVisual: {
|
|
58
|
+
width: COMPOSER_ACTION_VISUAL_SIZE,
|
|
59
|
+
height: COMPOSER_ACTION_VISUAL_SIZE,
|
|
60
|
+
borderRadius: COMPOSER_ACTION_VISUAL_SIZE / 2,
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
justifyContent: 'center',
|
|
63
|
+
},
|
|
64
|
+
actionPressed: { backgroundColor: colors.surfaceContainerHigh },
|
|
65
|
+
actionDisabled: { opacity: 0.4 },
|
|
66
|
+
primary: { backgroundColor: colors.primary },
|
|
67
|
+
primaryPressed: { backgroundColor: colors.primaryPressed },
|
|
68
|
+
primaryDisabled: { backgroundColor: colors.surfaceContainerHighest },
|
|
69
|
+
cancel: { backgroundColor: colors.surfaceContainer },
|
|
70
|
+
backdrop: {
|
|
71
|
+
position: 'absolute',
|
|
72
|
+
bottom: '100%',
|
|
73
|
+
left: 0,
|
|
74
|
+
right: 0,
|
|
75
|
+
zIndex: 1,
|
|
76
|
+
},
|
|
77
|
+
menu: {
|
|
78
|
+
position: 'absolute',
|
|
79
|
+
bottom: '100%',
|
|
80
|
+
left: space[6],
|
|
81
|
+
width: r(200),
|
|
82
|
+
maxWidth: '100%',
|
|
83
|
+
marginBottom: space[2],
|
|
84
|
+
borderRadius: r(20),
|
|
85
|
+
borderWidth: 0.5,
|
|
86
|
+
borderColor: colors.outline,
|
|
87
|
+
backgroundColor: colors.surface,
|
|
88
|
+
zIndex: 2,
|
|
89
|
+
...shadows.card,
|
|
90
|
+
},
|
|
91
|
+
menuClip: {
|
|
92
|
+
borderRadius: r(20),
|
|
93
|
+
overflow: 'hidden',
|
|
94
|
+
paddingVertical: space[2],
|
|
95
|
+
},
|
|
96
|
+
menuRow: {
|
|
97
|
+
minHeight: fixed.hitTarget,
|
|
98
|
+
flexDirection: 'row',
|
|
99
|
+
alignItems: 'center',
|
|
100
|
+
gap: space[5],
|
|
101
|
+
paddingVertical: space[3],
|
|
102
|
+
paddingHorizontal: space[6],
|
|
103
|
+
},
|
|
104
|
+
menuRowPressed: { backgroundColor: colors.surfaceContainer },
|
|
105
|
+
menuIcon: {
|
|
106
|
+
width: COMPOSER_MENU_ICON_CONTAINER_SIZE,
|
|
107
|
+
height: COMPOSER_MENU_ICON_CONTAINER_SIZE,
|
|
108
|
+
borderRadius: COMPOSER_MENU_ICON_CONTAINER_SIZE / 2,
|
|
109
|
+
alignItems: 'center',
|
|
110
|
+
justifyContent: 'center',
|
|
111
|
+
backgroundColor: colors.surfaceContainer,
|
|
112
|
+
},
|
|
113
|
+
menuLabel: {
|
|
114
|
+
flex: 1,
|
|
115
|
+
minWidth: 0,
|
|
116
|
+
fontSize: type.body,
|
|
117
|
+
color: colors.foreground,
|
|
118
|
+
},
|
|
119
|
+
voice: {
|
|
16
120
|
flexDirection: 'row',
|
|
17
|
-
flexWrap: 'wrap',
|
|
18
121
|
alignItems: 'center',
|
|
19
122
|
gap: space[2],
|
|
123
|
+
paddingVertical: space[1],
|
|
124
|
+
paddingHorizontal: space[2],
|
|
125
|
+
borderRadius: radius['3xl'],
|
|
126
|
+
backgroundColor: colors.primaryContainerSubtle,
|
|
127
|
+
},
|
|
128
|
+
transcript: {
|
|
129
|
+
flex: 1,
|
|
130
|
+
minWidth: 0,
|
|
131
|
+
color: colors.foreground,
|
|
132
|
+
fontSize: type.body,
|
|
20
133
|
},
|
|
21
|
-
|
|
22
|
-
menu: { flexDirection: 'row', flexWrap: 'wrap', gap: space[2] },
|
|
23
|
-
voice: { gap: space[3] },
|
|
24
|
-
transcript: { color: colors.foreground, fontSize: type.body },
|
|
134
|
+
transcriptPlaceholder: { color: colors.foregroundSubtle },
|
|
25
135
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
-
import type { TextFieldHandle } from '@unif/react-native-design';
|
|
3
|
+
import type { IconName, TextFieldHandle } from '@unif/react-native-design';
|
|
4
4
|
import type { ChatAction } from '../../actions';
|
|
5
5
|
|
|
6
6
|
export interface ComposerSendAction {
|
|
@@ -8,6 +8,7 @@ export interface ComposerSendAction {
|
|
|
8
8
|
onPress(value: string): void;
|
|
9
9
|
allowEmpty?: boolean;
|
|
10
10
|
disabled?: boolean;
|
|
11
|
+
/** 图标按钮的可访问名称,默认“发送”。 */
|
|
11
12
|
label?: string;
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -49,8 +50,11 @@ export interface ComposerProps {
|
|
|
49
50
|
voice?: ComposerVoiceControl;
|
|
50
51
|
header?: ReactNode;
|
|
51
52
|
footer?: ReactNode;
|
|
53
|
+
/** card 提供统一表面;plain 由外层提供表面,内部 Textarea 均不单独绘制表面。 */
|
|
52
54
|
surface?: 'card' | 'plain';
|
|
55
|
+
/** Textarea 整体最小高度(含内间距),默认 44,不低于最小触达高度。 */
|
|
53
56
|
minInputHeight?: number;
|
|
57
|
+
/** Textarea 整体最大高度;默认四行文字预算加 plain 内间距,随应用字号变化。 */
|
|
54
58
|
maxInputHeight?: number;
|
|
55
59
|
onFocusChange?(focused: boolean): void;
|
|
56
60
|
onHeightChange?(height: number): void;
|
|
@@ -59,3 +63,19 @@ export interface ComposerProps {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
export type ComposerHandle = TextFieldHandle;
|
|
66
|
+
|
|
67
|
+
export interface ComposerIconActionProps {
|
|
68
|
+
icon: IconName;
|
|
69
|
+
label: string;
|
|
70
|
+
onPress?(): void;
|
|
71
|
+
disabled?: boolean;
|
|
72
|
+
busy?: boolean;
|
|
73
|
+
expanded?: boolean;
|
|
74
|
+
visual?: 'icon' | 'primary' | 'cancel';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ComposerMenuItemProps {
|
|
78
|
+
action: ChatAction;
|
|
79
|
+
disabled: boolean;
|
|
80
|
+
onPress(): void;
|
|
81
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import type { PointerEvent } from 'react-native';
|
|
4
|
+
|
|
5
|
+
export function useMousePress(disabled: boolean) {
|
|
6
|
+
const [pressed, setPressed] = useState(false);
|
|
7
|
+
const enabled = Platform.OS === 'web' && !disabled;
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (disabled) setPressed(false);
|
|
10
|
+
}, [disabled]);
|
|
11
|
+
|
|
12
|
+
function begin(event: PointerEvent) {
|
|
13
|
+
if (
|
|
14
|
+
event.nativeEvent.pointerType !== 'mouse' ||
|
|
15
|
+
event.nativeEvent.button !== 0
|
|
16
|
+
)
|
|
17
|
+
return;
|
|
18
|
+
// 阻止默认聚焦后不会产生兼容 mousedown,局部投影按住状态;点击仍交给 SDK。
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
setPressed(true);
|
|
21
|
+
}
|
|
22
|
+
function end(event: PointerEvent) {
|
|
23
|
+
if (event.nativeEvent.pointerType === 'mouse') setPressed(false);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
mousePressed: enabled && pressed,
|
|
27
|
+
onPointerDown: enabled ? begin : undefined,
|
|
28
|
+
onPointerUp: enabled ? end : undefined,
|
|
29
|
+
onPointerCancel: enabled ? end : undefined,
|
|
30
|
+
onPointerLeave: enabled ? end : undefined,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { View } from 'react-native';
|
|
2
|
-
import { Chip, Icon } from '@unif/react-native-design';
|
|
2
|
+
import { Chip, Icon, useColors } from '@unif/react-native-design';
|
|
3
|
+
import { SUGGESTION_ICON_SIZE } from './constants';
|
|
3
4
|
import { styles } from './styles';
|
|
4
5
|
import type { SuggestionsProps } from './types';
|
|
5
6
|
|
|
@@ -10,6 +11,7 @@ export function Suggestions({
|
|
|
10
11
|
style,
|
|
11
12
|
testID,
|
|
12
13
|
}: SuggestionsProps): React.JSX.Element | null {
|
|
14
|
+
const colors = useColors();
|
|
13
15
|
if (items.length === 0) return null;
|
|
14
16
|
|
|
15
17
|
return (
|
|
@@ -25,7 +27,15 @@ export function Suggestions({
|
|
|
25
27
|
selected={item.selected}
|
|
26
28
|
busy={item.loading}
|
|
27
29
|
disabled={item.disabled}
|
|
28
|
-
leading={
|
|
30
|
+
leading={
|
|
31
|
+
item.icon ? (
|
|
32
|
+
<Icon
|
|
33
|
+
name={item.icon}
|
|
34
|
+
size={SUGGESTION_ICON_SIZE}
|
|
35
|
+
color={colors.primary}
|
|
36
|
+
/>
|
|
37
|
+
) : undefined
|
|
38
|
+
}
|
|
29
39
|
onPress={onSelect ? () => onSelect(item) : undefined}
|
|
30
40
|
/>
|
|
31
41
|
))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SUGGESTION_ICON_SIZE = 14;
|