@unif/react-native-chat 0.2.0 → 0.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 (63) hide show
  1. package/lib/commonjs/bubble/Bubble.js +74 -72
  2. package/lib/commonjs/bubble/Bubble.js.map +1 -1
  3. package/lib/commonjs/card-wrapper/CardWrapper.js +25 -24
  4. package/lib/commonjs/card-wrapper/CardWrapper.js.map +1 -1
  5. package/lib/commonjs/conversations/Conversations.js +135 -84
  6. package/lib/commonjs/conversations/Conversations.js.map +1 -1
  7. package/lib/commonjs/index.js +0 -6
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/prompts/Prompts.js +38 -37
  10. package/lib/commonjs/prompts/Prompts.js.map +1 -1
  11. package/lib/commonjs/sender/Sender.js +120 -122
  12. package/lib/commonjs/sender/Sender.js.map +1 -1
  13. package/lib/commonjs/theme/tokens.js +2 -56
  14. package/lib/commonjs/theme/tokens.js.map +1 -1
  15. package/lib/commonjs/welcome/Welcome.js +89 -90
  16. package/lib/commonjs/welcome/Welcome.js.map +1 -1
  17. package/lib/module/bubble/Bubble.js +73 -71
  18. package/lib/module/bubble/Bubble.js.map +1 -1
  19. package/lib/module/card-wrapper/CardWrapper.js +24 -23
  20. package/lib/module/card-wrapper/CardWrapper.js.map +1 -1
  21. package/lib/module/conversations/Conversations.js +137 -86
  22. package/lib/module/conversations/Conversations.js.map +1 -1
  23. package/lib/module/index.js +3 -1
  24. package/lib/module/index.js.map +1 -1
  25. package/lib/module/prompts/Prompts.js +37 -36
  26. package/lib/module/prompts/Prompts.js.map +1 -1
  27. package/lib/module/sender/Sender.js +121 -123
  28. package/lib/module/sender/Sender.js.map +1 -1
  29. package/lib/module/theme/tokens.js +2 -55
  30. package/lib/module/theme/tokens.js.map +1 -1
  31. package/lib/module/welcome/Welcome.js +88 -89
  32. package/lib/module/welcome/Welcome.js.map +1 -1
  33. package/lib/typescript/commonjs/bubble/Bubble.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/card-wrapper/CardWrapper.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/conversations/Conversations.d.ts +4 -2
  36. package/lib/typescript/commonjs/conversations/Conversations.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/index.d.ts +2 -1
  38. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/prompts/Prompts.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/sender/Sender.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/theme/tokens.d.ts +1 -45
  42. package/lib/typescript/commonjs/theme/tokens.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/welcome/Welcome.d.ts.map +1 -1
  44. package/lib/typescript/module/bubble/Bubble.d.ts.map +1 -1
  45. package/lib/typescript/module/card-wrapper/CardWrapper.d.ts.map +1 -1
  46. package/lib/typescript/module/conversations/Conversations.d.ts +4 -2
  47. package/lib/typescript/module/conversations/Conversations.d.ts.map +1 -1
  48. package/lib/typescript/module/index.d.ts +2 -1
  49. package/lib/typescript/module/index.d.ts.map +1 -1
  50. package/lib/typescript/module/prompts/Prompts.d.ts.map +1 -1
  51. package/lib/typescript/module/sender/Sender.d.ts.map +1 -1
  52. package/lib/typescript/module/theme/tokens.d.ts +1 -45
  53. package/lib/typescript/module/theme/tokens.d.ts.map +1 -1
  54. package/lib/typescript/module/welcome/Welcome.d.ts.map +1 -1
  55. package/package.json +2 -1
  56. package/src/bubble/Bubble.tsx +85 -80
  57. package/src/card-wrapper/CardWrapper.tsx +31 -29
  58. package/src/conversations/Conversations.tsx +170 -100
  59. package/src/index.tsx +4 -1
  60. package/src/prompts/Prompts.tsx +51 -41
  61. package/src/sender/Sender.tsx +125 -138
  62. package/src/theme/tokens.ts +1 -56
  63. package/src/welcome/Welcome.tsx +109 -108
@@ -6,7 +6,7 @@
6
6
  * - assistant → 左侧 Avatar + 名称 + 内容
7
7
  */
8
8
 
9
- import React from 'react';
9
+ import React, { useMemo } from 'react';
10
10
  import {
11
11
  View,
12
12
  Text,
@@ -14,7 +14,8 @@ import {
14
14
  type ViewStyle,
15
15
  type TextStyle,
16
16
  } from 'react-native';
17
- import {chatTokens} from '../theme/tokens';
17
+ import { useTokens } from '@unif/react-native-ui';
18
+ import { chatTokens } from '../theme/tokens';
18
19
 
19
20
  export interface BubbleSemanticStyles {
20
21
  root?: ViewStyle;
@@ -45,22 +46,88 @@ const Bubble: React.FC<BubbleProps> = ({
45
46
  styles: semanticStyles,
46
47
  testID,
47
48
  }) => {
49
+ const t = useTokens();
50
+
51
+ const styles = useMemo(
52
+ () =>
53
+ StyleSheet.create({
54
+ // Assistant
55
+ assistantRow: {
56
+ paddingHorizontal: t.spaceMd,
57
+ paddingVertical: t.spaceSm,
58
+ marginBottom: 12,
59
+ },
60
+ headerRow: {
61
+ flexDirection: 'row',
62
+ alignItems: 'center',
63
+ marginBottom: 6,
64
+ },
65
+ avatarContainer: {
66
+ marginRight: 8,
67
+ },
68
+ assistantName: {
69
+ fontSize: 13,
70
+ fontWeight: '500',
71
+ color: t.colorTextSecondary,
72
+ },
73
+ contentArea: {
74
+ paddingLeft: 36,
75
+ },
76
+ separator: {
77
+ marginTop: 12,
78
+ marginLeft: 36,
79
+ height: StyleSheet.hairlineWidth,
80
+ backgroundColor: t.colorBorder,
81
+ opacity: 0.5,
82
+ },
83
+ footer: {
84
+ paddingLeft: 36,
85
+ marginTop: 4,
86
+ },
87
+
88
+ // User
89
+ userRow: {
90
+ flexDirection: 'row',
91
+ justifyContent: 'flex-end',
92
+ flexWrap: 'wrap',
93
+ paddingHorizontal: t.spaceMd,
94
+ paddingVertical: t.spaceSm,
95
+ marginBottom: t.spaceSm,
96
+ },
97
+ userBubble: {
98
+ backgroundColor: chatTokens.colorBgUserMsg,
99
+ borderTopLeftRadius: 20,
100
+ borderTopRightRadius: 20,
101
+ borderBottomLeftRadius: 20,
102
+ borderBottomRightRadius: 6,
103
+ paddingHorizontal: t.spaceMd,
104
+ paddingVertical: t.space,
105
+ maxWidth: '75%',
106
+ },
107
+ userText: {
108
+ fontSize: t.fontSize,
109
+ color: t.colorText,
110
+ lineHeight: t.lineHeight,
111
+ },
112
+ }),
113
+ [t]
114
+ );
115
+
48
116
  if (role === 'user') {
49
117
  return (
50
118
  <View
51
- style={[defaultStyles.userRow, semanticStyles?.root, style]}
52
- testID={testID ?? 'bubble-user'}>
53
- <View style={defaultStyles.userBubble}>
119
+ style={[styles.userRow, semanticStyles?.root, style]}
120
+ testID={testID ?? 'bubble-user'}
121
+ >
122
+ <View style={styles.userBubble}>
54
123
  {typeof children === 'string' ? (
55
- <Text style={defaultStyles.userText}>{children}</Text>
124
+ <Text style={styles.userText}>{children}</Text>
56
125
  ) : (
57
126
  children
58
127
  )}
59
128
  </View>
60
129
  {footer && (
61
- <View style={[defaultStyles.footer, semanticStyles?.footer]}>
62
- {footer}
63
- </View>
130
+ <View style={[styles.footer, semanticStyles?.footer]}>{footer}</View>
64
131
  )}
65
132
  </View>
66
133
  );
@@ -69,92 +136,30 @@ const Bubble: React.FC<BubbleProps> = ({
69
136
  // assistant / system
70
137
  return (
71
138
  <View
72
- style={[defaultStyles.assistantRow, semanticStyles?.root, style]}
73
- testID={testID ?? 'bubble-assistant'}>
74
- <View style={defaultStyles.headerRow}>
139
+ style={[styles.assistantRow, semanticStyles?.root, style]}
140
+ testID={testID ?? 'bubble-assistant'}
141
+ >
142
+ <View style={styles.headerRow}>
75
143
  {avatar && (
76
- <View style={[defaultStyles.avatarContainer, semanticStyles?.avatar]}>
144
+ <View style={[styles.avatarContainer, semanticStyles?.avatar]}>
77
145
  {avatar}
78
146
  </View>
79
147
  )}
80
148
  {name && (
81
- <Text style={[defaultStyles.assistantName, semanticStyles?.name]}>
149
+ <Text style={[styles.assistantName, semanticStyles?.name]}>
82
150
  {name}
83
151
  </Text>
84
152
  )}
85
153
  </View>
86
- <View style={[defaultStyles.contentArea, semanticStyles?.content]}>
154
+ <View style={[styles.contentArea, semanticStyles?.content]}>
87
155
  {children}
88
156
  </View>
89
157
  {footer && (
90
- <View style={[defaultStyles.footer, semanticStyles?.footer]}>
91
- {footer}
92
- </View>
158
+ <View style={[styles.footer, semanticStyles?.footer]}>{footer}</View>
93
159
  )}
94
- <View style={defaultStyles.separator} />
160
+ <View style={styles.separator} />
95
161
  </View>
96
162
  );
97
163
  };
98
164
 
99
- const defaultStyles = StyleSheet.create({
100
- // Assistant
101
- assistantRow: {
102
- paddingHorizontal: chatTokens.spaceMd,
103
- paddingVertical: chatTokens.spaceSm,
104
- marginBottom: 12,
105
- },
106
- headerRow: {
107
- flexDirection: 'row',
108
- alignItems: 'center',
109
- marginBottom: 6,
110
- },
111
- avatarContainer: {
112
- marginRight: 8,
113
- },
114
- assistantName: {
115
- fontSize: 13,
116
- fontWeight: '500',
117
- color: chatTokens.colorTextSecondary,
118
- },
119
- contentArea: {
120
- paddingLeft: 36,
121
- },
122
- separator: {
123
- marginTop: 12,
124
- marginLeft: 36,
125
- height: StyleSheet.hairlineWidth,
126
- backgroundColor: chatTokens.colorBorder,
127
- opacity: 0.5,
128
- },
129
- footer: {
130
- paddingLeft: 36,
131
- marginTop: 4,
132
- },
133
-
134
- // User
135
- userRow: {
136
- flexDirection: 'row',
137
- justifyContent: 'flex-end',
138
- flexWrap: 'wrap',
139
- paddingHorizontal: chatTokens.spaceMd,
140
- paddingVertical: chatTokens.spaceSm,
141
- marginBottom: chatTokens.spaceSm,
142
- },
143
- userBubble: {
144
- backgroundColor: chatTokens.colorBgUserMsg,
145
- borderTopLeftRadius: 20,
146
- borderTopRightRadius: 20,
147
- borderBottomLeftRadius: 20,
148
- borderBottomRightRadius: 6,
149
- paddingHorizontal: chatTokens.spaceMd,
150
- paddingVertical: chatTokens.space,
151
- maxWidth: '75%',
152
- },
153
- userText: {
154
- fontSize: chatTokens.fontSize,
155
- color: chatTokens.colorText,
156
- lineHeight: chatTokens.lineHeight,
157
- },
158
- });
159
-
160
165
  export default React.memo(Bubble);
@@ -3,9 +3,9 @@
3
3
  * registry 通过 props 注入(而非全局 import),更灵活
4
4
  */
5
5
 
6
- import React from 'react';
7
- import {View, Text, StyleSheet, type ViewStyle} from 'react-native';
8
- import {chatTokens} from '../theme/tokens';
6
+ import React, { useMemo } from 'react';
7
+ import { View, Text, StyleSheet, type ViewStyle } from 'react-native';
8
+ import { useTokens } from '@unif/react-native-ui';
9
9
 
10
10
  export type CardComponentType = React.ComponentType<{
11
11
  data: Record<string, unknown>;
@@ -31,46 +31,48 @@ const CardWrapper: React.FC<CardWrapperProps> = ({
31
31
  style,
32
32
  testID,
33
33
  }) => {
34
+ const t = useTokens();
35
+
36
+ const ds = useMemo(
37
+ () =>
38
+ StyleSheet.create({
39
+ container: {
40
+ backgroundColor: t.colorBgElevated,
41
+ borderRadius: 12,
42
+ borderWidth: 1,
43
+ borderColor: t.colorBorder,
44
+ marginLeft: 8,
45
+ marginRight: 40,
46
+ marginBottom: 4,
47
+ overflow: 'hidden',
48
+ maxWidth: '85%',
49
+ },
50
+ fallback: {
51
+ padding: 12,
52
+ fontSize: 13,
53
+ color: t.colorTextSecondary,
54
+ },
55
+ }),
56
+ [t]
57
+ );
58
+
34
59
  const CardComponent = registry[cardType];
35
60
 
36
61
  if (!CardComponent) {
37
62
  return (
38
- <View style={[defaultStyles.container, style]} testID={testID}>
63
+ <View style={[ds.container, style]} testID={testID}>
39
64
  {fallback ?? (
40
- <Text style={defaultStyles.fallback}>
41
- 不支持的卡片类型: {cardType}
42
- </Text>
65
+ <Text style={ds.fallback}>不支持的卡片类型: {cardType}</Text>
43
66
  )}
44
67
  </View>
45
68
  );
46
69
  }
47
70
 
48
71
  return (
49
- <View
50
- style={[defaultStyles.container, style]}
51
- testID={testID ?? `card-${cardType}`}>
72
+ <View style={[ds.container, style]} testID={testID ?? `card-${cardType}`}>
52
73
  <CardComponent data={data} actions={actions} />
53
74
  </View>
54
75
  );
55
76
  };
56
77
 
57
- const defaultStyles = StyleSheet.create({
58
- container: {
59
- backgroundColor: chatTokens.colorBgElevated,
60
- borderRadius: 12,
61
- borderWidth: 1,
62
- borderColor: chatTokens.colorBorder,
63
- marginLeft: 8,
64
- marginRight: 40,
65
- marginBottom: 4,
66
- overflow: 'hidden',
67
- maxWidth: '85%',
68
- },
69
- fallback: {
70
- padding: 12,
71
- fontSize: 13,
72
- color: chatTokens.colorTextSecondary,
73
- },
74
- });
75
-
76
78
  export default React.memo(CardWrapper);
@@ -2,21 +2,21 @@
2
2
  * Conversations — 会话列表
3
3
  *
4
4
  * SectionList + 日期分组(今天/昨天/更早)
5
- * 基于 ListItem 模式渲染
5
+ * 长按弹出 Menu 菜单
6
6
  */
7
7
 
8
- import React, {useCallback, useMemo} from 'react';
8
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
9
9
  import {
10
10
  View,
11
11
  Text,
12
12
  TouchableOpacity,
13
- Alert,
14
13
  SectionList,
15
14
  StyleSheet,
16
15
  type ViewStyle,
17
16
  type TextStyle,
18
17
  } from 'react-native';
19
- import {chatTokens} from '../theme/tokens';
18
+ import { useTokens, Menu } from '@unif/react-native-ui';
19
+ import type { Action } from '@unif/react-native-ui';
20
20
 
21
21
  export interface ConversationItem {
22
22
  id: string;
@@ -38,16 +38,17 @@ export interface ConversationsProps {
38
38
  items: ConversationItem[];
39
39
  activeId?: string;
40
40
  onSelect: (id: string) => void;
41
- onDelete?: (id: string) => void;
42
41
  onNew?: () => void;
43
42
  groupByDate?: boolean;
44
43
  header?: React.ReactNode;
44
+ menus?: Action[] | ((item: ConversationItem) => Action[]);
45
+ onMenuAction?: (action: Action, item: ConversationItem) => void;
45
46
  style?: ViewStyle;
46
47
  styles?: Partial<ConversationsSemanticStyles>;
47
48
  testID?: string;
48
49
  }
49
50
 
50
- function groupByDate(sessions: ConversationItem[]) {
51
+ function groupByDateFn(sessions: ConversationItem[]) {
51
52
  const now = new Date();
52
53
  const todayStart = new Date(
53
54
  now.getFullYear(),
@@ -78,93 +79,202 @@ function groupByDate(sessions: ConversationItem[]) {
78
79
  return groups;
79
80
  }
80
81
 
82
+ interface MenuState {
83
+ visible: boolean;
84
+ item: ConversationItem | null;
85
+ layout: { x: number; y: number; width: number; height: number };
86
+ }
87
+
88
+ const initialMenuState: MenuState = {
89
+ visible: false,
90
+ item: null,
91
+ layout: { x: 0, y: 0, width: 0, height: 0 },
92
+ };
93
+
81
94
  const Conversations: React.FC<ConversationsProps> = ({
82
95
  items,
83
96
  activeId,
84
97
  onSelect,
85
- onDelete,
86
98
  onNew,
87
99
  groupByDate: shouldGroup = true,
88
100
  header,
101
+ menus,
102
+ onMenuAction,
89
103
  style,
90
104
  styles: semanticStyles,
91
105
  testID = 'conversations',
92
106
  }) => {
107
+ const t = useTokens();
108
+ const [menuState, setMenuState] = useState<MenuState>(initialMenuState);
109
+ const itemRefs = useRef<Record<string, View | null>>({});
110
+
93
111
  const sections = useMemo(() => {
94
112
  if (shouldGroup) {
95
- return groupByDate(items);
113
+ return groupByDateFn(items);
96
114
  }
97
115
  return [{ title: '', data: items }];
98
116
  }, [items, shouldGroup]);
99
117
 
100
- const handleDelete = useCallback(
118
+ const closeMenu = useCallback(() => {
119
+ setMenuState(initialMenuState);
120
+ }, []);
121
+
122
+ const handleLongPress = useCallback(
101
123
  (item: ConversationItem) => {
102
- if (!onDelete) return;
103
- Alert.alert('删除会话', `确定删除"${item.title}"?`, [
104
- { text: '取消', style: 'cancel' },
105
- {
106
- text: '删除',
107
- style: 'destructive',
108
- onPress: () => onDelete(item.id),
109
- },
110
- ]);
124
+ if (!menus) return;
125
+ const ref = itemRefs.current[item.id];
126
+ if (ref) {
127
+ ref.measureInWindow((x, y, width, height) => {
128
+ setMenuState({
129
+ visible: true,
130
+ item,
131
+ layout: { x, y, width, height },
132
+ });
133
+ });
134
+ }
135
+ },
136
+ [menus]
137
+ );
138
+
139
+ const handleMenuAction = useCallback(
140
+ (action: Action) => {
141
+ const item = menuState.item;
142
+ if (item && onMenuAction) {
143
+ onMenuAction(action, item);
144
+ }
145
+ },
146
+ [menuState.item, onMenuAction]
147
+ );
148
+
149
+ const setItemRef = useCallback(
150
+ (id: string) => (ref: View | null) => {
151
+ itemRefs.current[id] = ref;
111
152
  },
112
- [onDelete]
153
+ []
154
+ );
155
+
156
+ const menuActions = useMemo(() => {
157
+ if (!menus || !menuState.item) return [];
158
+ if (typeof menus === 'function') {
159
+ return menus(menuState.item);
160
+ }
161
+ return menus;
162
+ }, [menus, menuState.item]);
163
+
164
+ const ds = useMemo(
165
+ () =>
166
+ StyleSheet.create({
167
+ container: {
168
+ flex: 1,
169
+ },
170
+ newButtonWrapper: {
171
+ alignItems: 'center',
172
+ paddingVertical: 12,
173
+ paddingHorizontal: 20,
174
+ },
175
+ newButton: {
176
+ flexDirection: 'row',
177
+ alignItems: 'center',
178
+ justifyContent: 'center',
179
+ width: '80%',
180
+ paddingVertical: 10,
181
+ borderRadius: t.radiusFull,
182
+ borderWidth: 1,
183
+ borderColor: t.colorPrimary,
184
+ },
185
+ newButtonText: {
186
+ fontSize: 14,
187
+ fontWeight: '500',
188
+ color: t.colorPrimary,
189
+ },
190
+ sectionHeader: {
191
+ fontSize: 12,
192
+ color: t.colorTextSecondary,
193
+ paddingHorizontal: 20,
194
+ paddingTop: 16,
195
+ paddingBottom: 6,
196
+ },
197
+ sessionItem: {
198
+ flexDirection: 'row',
199
+ alignItems: 'center',
200
+ paddingHorizontal: 20,
201
+ paddingVertical: 12,
202
+ },
203
+ sessionItemActive: {
204
+ backgroundColor: '#F3F4F6',
205
+ },
206
+ sessionTitle: {
207
+ flex: 1,
208
+ fontSize: 14,
209
+ fontWeight: '500',
210
+ color: t.colorText,
211
+ },
212
+ list: {
213
+ flex: 1,
214
+ },
215
+ }),
216
+ [t]
113
217
  );
114
218
 
115
219
  const renderItem = useCallback(
116
220
  ({ item }: { item: ConversationItem }) => {
117
221
  const isActive = item.id === activeId;
118
222
  return (
119
- <TouchableOpacity
120
- style={[
121
- defaultStyles.sessionItem,
122
- semanticStyles?.item,
123
- isActive && defaultStyles.sessionItemActive,
124
- isActive && semanticStyles?.itemActive,
125
- ]}
126
- onPress={() => onSelect(item.id)}
127
- onLongPress={() => handleDelete(item)}
128
- activeOpacity={0.7}
129
- testID={`${testID}-item-${item.id}`}>
130
- <Text style={defaultStyles.sessionTitle} numberOfLines={1}>
131
- {item.title}
132
- </Text>
133
- </TouchableOpacity>
223
+ <View ref={setItemRef(item.id)} collapsable={false}>
224
+ <TouchableOpacity
225
+ style={[
226
+ ds.sessionItem,
227
+ semanticStyles?.item,
228
+ isActive && ds.sessionItemActive,
229
+ isActive && semanticStyles?.itemActive,
230
+ ]}
231
+ onPress={() => onSelect(item.id)}
232
+ onLongPress={() => handleLongPress(item)}
233
+ activeOpacity={0.7}
234
+ testID={`${testID}-item-${item.id}`}
235
+ >
236
+ <Text style={ds.sessionTitle} numberOfLines={1}>
237
+ {item.title}
238
+ </Text>
239
+ </TouchableOpacity>
240
+ </View>
134
241
  );
135
242
  },
136
- [activeId, onSelect, handleDelete, semanticStyles, testID]
243
+ [
244
+ activeId,
245
+ onSelect,
246
+ handleLongPress,
247
+ semanticStyles,
248
+ testID,
249
+ setItemRef,
250
+ ds,
251
+ ]
137
252
  );
138
253
 
139
254
  const renderSectionHeader = useCallback(
140
255
  ({ section }: { section: { title: string } }) => {
141
256
  if (!section.title) return null;
142
257
  return (
143
- <Text
144
- style={[
145
- defaultStyles.sectionHeader,
146
- semanticStyles?.sectionHeader,
147
- ]}>
258
+ <Text style={[ds.sectionHeader, semanticStyles?.sectionHeader]}>
148
259
  {section.title}
149
260
  </Text>
150
261
  );
151
262
  },
152
- [semanticStyles]
263
+ [semanticStyles, ds]
153
264
  );
154
265
 
155
266
  return (
156
- <View
157
- style={[defaultStyles.container, semanticStyles?.root, style]}
158
- testID={testID}>
267
+ <View style={[ds.container, semanticStyles?.root, style]} testID={testID}>
159
268
  {header}
160
269
 
161
270
  {onNew && (
162
- <View style={defaultStyles.newButtonWrapper}>
271
+ <View style={ds.newButtonWrapper}>
163
272
  <TouchableOpacity
164
- style={[defaultStyles.newButton, semanticStyles?.newButton]}
273
+ style={[ds.newButton, semanticStyles?.newButton]}
165
274
  onPress={onNew}
166
- testID={`${testID}-new`}>
167
- <Text style={defaultStyles.newButtonText}>+ 新建会话</Text>
275
+ testID={`${testID}-new`}
276
+ >
277
+ <Text style={ds.newButtonText}>+ 新建会话</Text>
168
278
  </TouchableOpacity>
169
279
  </View>
170
280
  )}
@@ -174,63 +284,23 @@ const Conversations: React.FC<ConversationsProps> = ({
174
284
  keyExtractor={(item) => item.id}
175
285
  renderItem={renderItem}
176
286
  renderSectionHeader={renderSectionHeader}
177
- style={defaultStyles.list}
287
+ style={ds.list}
178
288
  showsVerticalScrollIndicator={false}
179
289
  stickySectionHeadersEnabled={false}
180
290
  />
291
+
292
+ {menus && (
293
+ <Menu
294
+ visible={menuState.visible}
295
+ onClose={closeMenu}
296
+ anchorLayout={menuState.layout}
297
+ actions={menuActions}
298
+ onAction={handleMenuAction}
299
+ testID={`${testID}-menu`}
300
+ />
301
+ )}
181
302
  </View>
182
303
  );
183
304
  };
184
305
 
185
- const defaultStyles = StyleSheet.create({
186
- container: {
187
- flex: 1,
188
- },
189
- newButtonWrapper: {
190
- alignItems: 'center',
191
- paddingVertical: 12,
192
- paddingHorizontal: 20,
193
- },
194
- newButton: {
195
- flexDirection: 'row',
196
- alignItems: 'center',
197
- justifyContent: 'center',
198
- width: '80%',
199
- paddingVertical: 10,
200
- borderRadius: chatTokens.radiusFull,
201
- borderWidth: 1,
202
- borderColor: chatTokens.colorPrimary,
203
- },
204
- newButtonText: {
205
- fontSize: 14,
206
- fontWeight: '500',
207
- color: chatTokens.colorPrimary,
208
- },
209
- sectionHeader: {
210
- fontSize: 12,
211
- color: chatTokens.colorTextSecondary,
212
- paddingHorizontal: 20,
213
- paddingTop: 16,
214
- paddingBottom: 6,
215
- },
216
- sessionItem: {
217
- flexDirection: 'row',
218
- alignItems: 'center',
219
- paddingHorizontal: 20,
220
- paddingVertical: 12,
221
- },
222
- sessionItemActive: {
223
- backgroundColor: '#F3F4F6',
224
- },
225
- sessionTitle: {
226
- flex: 1,
227
- fontSize: 14,
228
- fontWeight: '500',
229
- color: chatTokens.colorText,
230
- },
231
- list: {
232
- flex: 1,
233
- },
234
- });
235
-
236
306
  export default React.memo(Conversations);
package/src/index.tsx CHANGED
@@ -49,4 +49,7 @@ export type {
49
49
  } from './card-wrapper/CardWrapper';
50
50
 
51
51
  // Theme
52
- export { chatTokens, configure } from './theme/tokens';
52
+ export { chatTokens } from './theme/tokens';
53
+
54
+ // Re-export Menu types from UI lib
55
+ export type { Action } from '@unif/react-native-ui';