@unif/react-native-chat 2.0.0 → 2.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.
Files changed (46) hide show
  1. package/README.md +39 -30
  2. package/lib/module/components/Composer/Composer.js +131 -83
  3. package/lib/module/components/Composer/Composer.js.map +1 -1
  4. package/lib/module/components/Composer/ComposerIconAction.js +60 -0
  5. package/lib/module/components/Composer/ComposerIconAction.js.map +1 -0
  6. package/lib/module/components/Composer/ComposerMenuItem.js +57 -0
  7. package/lib/module/components/Composer/ComposerMenuItem.js.map +1 -0
  8. package/lib/module/components/Composer/constants.js +9 -1
  9. package/lib/module/components/Composer/constants.js.map +1 -1
  10. package/lib/module/components/Composer/styles.js +143 -13
  11. package/lib/module/components/Composer/styles.js.map +1 -1
  12. package/lib/module/components/Composer/useMousePress.js +28 -0
  13. package/lib/module/components/Composer/useMousePress.js.map +1 -0
  14. package/lib/module/components/Suggestions/Suggestions.js +6 -2
  15. package/lib/module/components/Suggestions/Suggestions.js.map +1 -1
  16. package/lib/module/components/Suggestions/constants.js +4 -0
  17. package/lib/module/components/Suggestions/constants.js.map +1 -0
  18. package/lib/typescript/src/components/Composer/Composer.d.ts.map +1 -1
  19. package/lib/typescript/src/components/Composer/ComposerIconAction.d.ts +3 -0
  20. package/lib/typescript/src/components/Composer/ComposerIconAction.d.ts.map +1 -0
  21. package/lib/typescript/src/components/Composer/ComposerMenuItem.d.ts +3 -0
  22. package/lib/typescript/src/components/Composer/ComposerMenuItem.d.ts.map +1 -0
  23. package/lib/typescript/src/components/Composer/constants.d.ts +8 -1
  24. package/lib/typescript/src/components/Composer/constants.d.ts.map +1 -1
  25. package/lib/typescript/src/components/Composer/styles.d.ts +150 -7
  26. package/lib/typescript/src/components/Composer/styles.d.ts.map +1 -1
  27. package/lib/typescript/src/components/Composer/types.d.ts +19 -1
  28. package/lib/typescript/src/components/Composer/types.d.ts.map +1 -1
  29. package/lib/typescript/src/components/Composer/useMousePress.d.ts +9 -0
  30. package/lib/typescript/src/components/Composer/useMousePress.d.ts.map +1 -0
  31. package/lib/typescript/src/components/Suggestions/Suggestions.d.ts.map +1 -1
  32. package/lib/typescript/src/components/Suggestions/constants.d.ts +2 -0
  33. package/lib/typescript/src/components/Suggestions/constants.d.ts.map +1 -0
  34. package/lib/typescript/src/components/Suggestions/types.d.ts +1 -0
  35. package/lib/typescript/src/components/Suggestions/types.d.ts.map +1 -1
  36. package/package.json +10 -10
  37. package/src/components/Composer/Composer.tsx +165 -95
  38. package/src/components/Composer/ComposerIconAction.tsx +71 -0
  39. package/src/components/Composer/ComposerMenuItem.tsx +56 -0
  40. package/src/components/Composer/constants.ts +10 -1
  41. package/src/components/Composer/styles.ts +123 -13
  42. package/src/components/Composer/types.ts +21 -1
  43. package/src/components/Composer/useMousePress.ts +32 -0
  44. package/src/components/Suggestions/Suggestions.tsx +12 -2
  45. package/src/components/Suggestions/constants.ts +1 -0
  46. 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: { gap: space[2], minWidth: 0 },
11
+ root: { position: 'relative', minWidth: 0 },
8
12
  card: {
9
- padding: space[3],
10
- borderRadius: radius.lg,
13
+ borderRadius: r(20),
14
+ borderWidth: 0.5,
15
+ borderColor: colors.outline,
11
16
  backgroundColor: colors.surface,
17
+ ...shadows.card,
12
18
  },
13
- regular: { gap: space[2] },
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
- toolbar: {
29
+ input: { flex: 1, minWidth: 0 },
30
+ inputExpanded: { flex: 0, width: '100%' },
31
+ more: { width: fixed.hitTarget, height: fixed.hitTarget },
32
+ moreExpanded: {
33
+ position: 'absolute',
34
+ left: space[2],
35
+ bottom: space[1],
36
+ zIndex: 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
- primary: { marginStart: 'auto' },
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={item.icon ? <Icon name={item.icon} /> : undefined}
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;
@@ -4,6 +4,7 @@ import type { StyleProp, ViewStyle } from 'react-native';
4
4
  export interface SuggestionItem {
5
5
  id: string;
6
6
  label: string;
7
+ /** 14 布局单位的主题主色图标,普通与选中状态保持相同图标样式。 */
7
8
  icon?: IconName;
8
9
  selected?: boolean;
9
10
  loading?: boolean;