@unif/react-native-chat 0.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/package.json +45 -0
- package/src/bubble/Bubble.tsx +160 -0
- package/src/bubble/BubbleList.tsx +84 -0
- package/src/bubble/index.md +111 -0
- package/src/card-wrapper/CardWrapper.tsx +76 -0
- package/src/card-wrapper/index.md +78 -0
- package/src/conversations/Conversations.tsx +236 -0
- package/src/conversations/index.md +90 -0
- package/src/index.tsx +41 -0
- package/src/prompts/Prompts.tsx +123 -0
- package/src/prompts/index.md +81 -0
- package/src/sender/Sender.tsx +435 -0
- package/src/sender/index.md +131 -0
- package/src/theme/tokens.ts +61 -0
- package/src/welcome/Welcome.tsx +208 -0
- package/src/welcome/index.md +95 -0
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unif/react-native-chat",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "UNIF React Native Chat — AI 聊天 UI 组件(Bubble、Sender、Conversations 等)",
|
|
5
|
+
"main": "./src/index.tsx",
|
|
6
|
+
"types": "./src/index.tsx",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"source": "./src/index.tsx",
|
|
10
|
+
"types": "./src/index.tsx",
|
|
11
|
+
"default": "./src/index.tsx"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"!**/__tests__",
|
|
19
|
+
"!**/__mocks__",
|
|
20
|
+
"!**/.*"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "bob build",
|
|
24
|
+
"clean": "del-cli lib",
|
|
25
|
+
"typecheck": "tsc"
|
|
26
|
+
},
|
|
27
|
+
"keywords": ["react-native", "chat", "ui", "bubble", "sender"],
|
|
28
|
+
"author": "qq382724935 <liulijun@pec.com.cn>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"registry": "https://registry.npmjs.org/"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": "*",
|
|
35
|
+
"react-native": "*"
|
|
36
|
+
},
|
|
37
|
+
"react-native-builder-bob": {
|
|
38
|
+
"source": "src",
|
|
39
|
+
"output": "lib",
|
|
40
|
+
"targets": [
|
|
41
|
+
["module", { "esm": true }],
|
|
42
|
+
["typescript", { "project": "tsconfig.build.json" }]
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bubble — 消息气泡容器
|
|
3
|
+
*
|
|
4
|
+
* 根据 role 自动镜像布局:
|
|
5
|
+
* - user → 右对齐浅色气泡 + 不对称圆角
|
|
6
|
+
* - assistant → 左侧 Avatar + 名称 + 内容
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import {
|
|
11
|
+
View,
|
|
12
|
+
Text,
|
|
13
|
+
StyleSheet,
|
|
14
|
+
type ViewStyle,
|
|
15
|
+
type TextStyle,
|
|
16
|
+
} from 'react-native';
|
|
17
|
+
import {chatTokens} from '../theme/tokens';
|
|
18
|
+
|
|
19
|
+
export interface BubbleSemanticStyles {
|
|
20
|
+
root?: ViewStyle;
|
|
21
|
+
avatar?: ViewStyle;
|
|
22
|
+
name?: TextStyle;
|
|
23
|
+
content?: ViewStyle;
|
|
24
|
+
footer?: ViewStyle;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BubbleProps {
|
|
28
|
+
role: 'user' | 'assistant' | 'system';
|
|
29
|
+
avatar?: React.ReactNode;
|
|
30
|
+
name?: string;
|
|
31
|
+
children: React.ReactNode;
|
|
32
|
+
footer?: React.ReactNode;
|
|
33
|
+
style?: ViewStyle;
|
|
34
|
+
styles?: Partial<BubbleSemanticStyles>;
|
|
35
|
+
testID?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const Bubble: React.FC<BubbleProps> = ({
|
|
39
|
+
role,
|
|
40
|
+
avatar,
|
|
41
|
+
name,
|
|
42
|
+
children,
|
|
43
|
+
footer,
|
|
44
|
+
style,
|
|
45
|
+
styles: semanticStyles,
|
|
46
|
+
testID,
|
|
47
|
+
}) => {
|
|
48
|
+
if (role === 'user') {
|
|
49
|
+
return (
|
|
50
|
+
<View
|
|
51
|
+
style={[defaultStyles.userRow, semanticStyles?.root, style]}
|
|
52
|
+
testID={testID ?? 'bubble-user'}>
|
|
53
|
+
<View style={defaultStyles.userBubble}>
|
|
54
|
+
{typeof children === 'string' ? (
|
|
55
|
+
<Text style={defaultStyles.userText}>{children}</Text>
|
|
56
|
+
) : (
|
|
57
|
+
children
|
|
58
|
+
)}
|
|
59
|
+
</View>
|
|
60
|
+
{footer && (
|
|
61
|
+
<View style={[defaultStyles.footer, semanticStyles?.footer]}>
|
|
62
|
+
{footer}
|
|
63
|
+
</View>
|
|
64
|
+
)}
|
|
65
|
+
</View>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// assistant / system
|
|
70
|
+
return (
|
|
71
|
+
<View
|
|
72
|
+
style={[defaultStyles.assistantRow, semanticStyles?.root, style]}
|
|
73
|
+
testID={testID ?? 'bubble-assistant'}>
|
|
74
|
+
<View style={defaultStyles.headerRow}>
|
|
75
|
+
{avatar && (
|
|
76
|
+
<View style={[defaultStyles.avatarContainer, semanticStyles?.avatar]}>
|
|
77
|
+
{avatar}
|
|
78
|
+
</View>
|
|
79
|
+
)}
|
|
80
|
+
{name && (
|
|
81
|
+
<Text style={[defaultStyles.assistantName, semanticStyles?.name]}>
|
|
82
|
+
{name}
|
|
83
|
+
</Text>
|
|
84
|
+
)}
|
|
85
|
+
</View>
|
|
86
|
+
<View style={[defaultStyles.contentArea, semanticStyles?.content]}>
|
|
87
|
+
{children}
|
|
88
|
+
</View>
|
|
89
|
+
{footer && (
|
|
90
|
+
<View style={[defaultStyles.footer, semanticStyles?.footer]}>
|
|
91
|
+
{footer}
|
|
92
|
+
</View>
|
|
93
|
+
)}
|
|
94
|
+
<View style={defaultStyles.separator} />
|
|
95
|
+
</View>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
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
|
+
export default React.memo(Bubble);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bubble.List — 消息列表
|
|
3
|
+
* 封装 inverted FlatList,自动滚动到底部
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, {useCallback} from 'react';
|
|
7
|
+
import {FlatList, View, StyleSheet, type ViewStyle} from 'react-native';
|
|
8
|
+
|
|
9
|
+
export interface BubbleListProps<T = unknown> {
|
|
10
|
+
items: T[];
|
|
11
|
+
renderBubble: (item: T) => React.ReactNode;
|
|
12
|
+
keyExtractor?: (item: T) => string;
|
|
13
|
+
onEndReached?: () => void;
|
|
14
|
+
header?: React.ReactNode;
|
|
15
|
+
footer?: React.ReactNode;
|
|
16
|
+
style?: ViewStyle;
|
|
17
|
+
testID?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function BubbleListInner<T>(props: BubbleListProps<T>) {
|
|
21
|
+
const {
|
|
22
|
+
items,
|
|
23
|
+
renderBubble,
|
|
24
|
+
keyExtractor,
|
|
25
|
+
onEndReached,
|
|
26
|
+
header,
|
|
27
|
+
footer,
|
|
28
|
+
style,
|
|
29
|
+
testID = 'bubble-list',
|
|
30
|
+
} = props;
|
|
31
|
+
|
|
32
|
+
const renderItem = useCallback(
|
|
33
|
+
({item}: {item: T}) => <>{renderBubble(item)}</>,
|
|
34
|
+
[renderBubble]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const defaultKeyExtractor = useCallback(
|
|
38
|
+
(item: T, index: number) => {
|
|
39
|
+
if (keyExtractor) return keyExtractor(item);
|
|
40
|
+
const record = item as Record<string, unknown>;
|
|
41
|
+
return (record['id'] as string) ?? String(index);
|
|
42
|
+
},
|
|
43
|
+
[keyExtractor]
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (items.length === 0 && header) {
|
|
47
|
+
return (
|
|
48
|
+
<View style={[defaultStyles.container, style]} testID={testID}>
|
|
49
|
+
{header}
|
|
50
|
+
</View>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<FlatList
|
|
56
|
+
data={items}
|
|
57
|
+
renderItem={renderItem}
|
|
58
|
+
keyExtractor={defaultKeyExtractor}
|
|
59
|
+
inverted
|
|
60
|
+
style={[defaultStyles.list, style]}
|
|
61
|
+
contentContainerStyle={defaultStyles.contentContainer}
|
|
62
|
+
onEndReached={onEndReached}
|
|
63
|
+
onEndReachedThreshold={0.3}
|
|
64
|
+
ListFooterComponent={footer ? <>{footer}</> : null}
|
|
65
|
+
showsVerticalScrollIndicator={false}
|
|
66
|
+
testID={testID}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const defaultStyles = StyleSheet.create({
|
|
72
|
+
container: {
|
|
73
|
+
flex: 1,
|
|
74
|
+
},
|
|
75
|
+
list: {
|
|
76
|
+
flex: 1,
|
|
77
|
+
},
|
|
78
|
+
contentContainer: {
|
|
79
|
+
paddingVertical: 8,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const BubbleList = React.memo(BubbleListInner) as typeof BubbleListInner;
|
|
84
|
+
export default BubbleList;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Bubble 消息气泡
|
|
3
|
+
nav:
|
|
4
|
+
title: 组件
|
|
5
|
+
path: /components
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Bubble 消息气泡
|
|
9
|
+
|
|
10
|
+
消息气泡容器,根据 `role` 自动镜像布局。
|
|
11
|
+
|
|
12
|
+
## 何时使用
|
|
13
|
+
|
|
14
|
+
- 展示聊天消息(用户消息、AI 回复、系统消息)
|
|
15
|
+
- 需要根据角色自动区分左右布局
|
|
16
|
+
|
|
17
|
+
## 代码示例
|
|
18
|
+
|
|
19
|
+
### 基本用法
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { Bubble } from '@unif/react-native-chat';
|
|
23
|
+
|
|
24
|
+
// 用户消息(右对齐)
|
|
25
|
+
<Bubble role="user">你好,请帮我查询订单</Bubble>
|
|
26
|
+
|
|
27
|
+
// AI 回复(左对齐)
|
|
28
|
+
<Bubble role="assistant" name="小U" avatar={<Avatar name="AI" />}>
|
|
29
|
+
好的,正在为您查询...
|
|
30
|
+
</Bubble>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 自定义底部区域
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<Bubble role="assistant" footer={<Text>14:30</Text>}>
|
|
37
|
+
这是消息内容
|
|
38
|
+
</Bubble>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 语义样式覆盖
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<Bubble
|
|
45
|
+
role="user"
|
|
46
|
+
styles={{
|
|
47
|
+
root: { paddingHorizontal: 20 },
|
|
48
|
+
content: { backgroundColor: '#E8F0FE' },
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
自定义样式的消息
|
|
52
|
+
</Bubble>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
### BubbleProps
|
|
58
|
+
|
|
59
|
+
| 属性 | 说明 | 类型 | 默认值 |
|
|
60
|
+
|------|------|------|--------|
|
|
61
|
+
| role | 消息角色 | `'user' \| 'assistant' \| 'system'` | - |
|
|
62
|
+
| avatar | 头像(assistant/system 角色显示) | `ReactNode` | - |
|
|
63
|
+
| name | 发送者名称 | `string` | - |
|
|
64
|
+
| children | 消息内容 | `ReactNode` | - |
|
|
65
|
+
| footer | 底部区域(时间戳、操作等) | `ReactNode` | - |
|
|
66
|
+
| style | 外层容器样式 | `ViewStyle` | - |
|
|
67
|
+
| styles | 语义样式覆盖 | `Partial<BubbleSemanticStyles>` | - |
|
|
68
|
+
| testID | 测试标识 | `string` | - |
|
|
69
|
+
|
|
70
|
+
### BubbleSemanticStyles
|
|
71
|
+
|
|
72
|
+
| 属性 | 说明 | 类型 |
|
|
73
|
+
|------|------|------|
|
|
74
|
+
| root | 外层容器 | `ViewStyle` |
|
|
75
|
+
| avatar | 头像容器 | `ViewStyle` |
|
|
76
|
+
| name | 名称文字 | `TextStyle` |
|
|
77
|
+
| content | 内容区域 | `ViewStyle` |
|
|
78
|
+
| footer | 底部区域 | `ViewStyle` |
|
|
79
|
+
|
|
80
|
+
## Bubble.List 消息列表
|
|
81
|
+
|
|
82
|
+
封装 inverted FlatList,消息从底部向上排列,支持泛型。
|
|
83
|
+
|
|
84
|
+
### 基本用法
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { Bubble } from '@unif/react-native-chat';
|
|
88
|
+
|
|
89
|
+
<Bubble.List
|
|
90
|
+
items={messages}
|
|
91
|
+
header={<Welcome />}
|
|
92
|
+
renderBubble={(msg) => (
|
|
93
|
+
<Bubble role={msg.message.role}>
|
|
94
|
+
{msg.message.text}
|
|
95
|
+
</Bubble>
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### BubbleListProps
|
|
101
|
+
|
|
102
|
+
| 属性 | 说明 | 类型 | 默认值 |
|
|
103
|
+
|------|------|------|--------|
|
|
104
|
+
| items | 消息数据 | `T[]` | - |
|
|
105
|
+
| renderBubble | 渲染单条消息 | `(item: T) => ReactNode` | - |
|
|
106
|
+
| keyExtractor | 自定义 key 提取 | `(item: T) => string` | 使用 `item.id` |
|
|
107
|
+
| onEndReached | 滚动到底部回调(加载更多) | `() => void` | - |
|
|
108
|
+
| header | 列表为空时的顶部占位 | `ReactNode` | - |
|
|
109
|
+
| footer | 列表底部内容 | `ReactNode` | - |
|
|
110
|
+
| style | 容器样式 | `ViewStyle` | - |
|
|
111
|
+
| testID | 测试标识 | `string` | `'bubble-list'` |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CardWrapper — 通用卡片路由容器
|
|
3
|
+
* registry 通过 props 注入(而非全局 import),更灵活
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import {View, Text, StyleSheet, type ViewStyle} from 'react-native';
|
|
8
|
+
import {chatTokens} from '../theme/tokens';
|
|
9
|
+
|
|
10
|
+
export type CardComponentType = React.ComponentType<{
|
|
11
|
+
data: Record<string, unknown>;
|
|
12
|
+
actions: string[];
|
|
13
|
+
}>;
|
|
14
|
+
|
|
15
|
+
export interface CardWrapperProps {
|
|
16
|
+
cardType: string;
|
|
17
|
+
data: Record<string, unknown>;
|
|
18
|
+
actions: string[];
|
|
19
|
+
registry?: Record<string, CardComponentType>;
|
|
20
|
+
fallback?: React.ReactNode;
|
|
21
|
+
style?: ViewStyle;
|
|
22
|
+
testID?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const CardWrapper: React.FC<CardWrapperProps> = ({
|
|
26
|
+
cardType,
|
|
27
|
+
data,
|
|
28
|
+
actions,
|
|
29
|
+
registry = {},
|
|
30
|
+
fallback,
|
|
31
|
+
style,
|
|
32
|
+
testID,
|
|
33
|
+
}) => {
|
|
34
|
+
const CardComponent = registry[cardType];
|
|
35
|
+
|
|
36
|
+
if (!CardComponent) {
|
|
37
|
+
return (
|
|
38
|
+
<View style={[defaultStyles.container, style]} testID={testID}>
|
|
39
|
+
{fallback ?? (
|
|
40
|
+
<Text style={defaultStyles.fallback}>
|
|
41
|
+
不支持的卡片类型: {cardType}
|
|
42
|
+
</Text>
|
|
43
|
+
)}
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<View
|
|
50
|
+
style={[defaultStyles.container, style]}
|
|
51
|
+
testID={testID ?? `card-${cardType}`}>
|
|
52
|
+
<CardComponent data={data} actions={actions} />
|
|
53
|
+
</View>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
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
|
+
export default React.memo(CardWrapper);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: CardWrapper 卡片容器
|
|
3
|
+
nav:
|
|
4
|
+
title: 组件
|
|
5
|
+
path: /components
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# CardWrapper 卡片容器
|
|
9
|
+
|
|
10
|
+
通用卡片路由容器,通过 `registry` props 注入实现卡片类型到组件的映射。
|
|
11
|
+
|
|
12
|
+
## 何时使用
|
|
13
|
+
|
|
14
|
+
- AI 回复中包含结构化卡片内容(产品列表、表单、图表等)
|
|
15
|
+
- 需要根据 `cardType` 动态渲染不同业务组件
|
|
16
|
+
|
|
17
|
+
## 代码示例
|
|
18
|
+
|
|
19
|
+
### 基本用法
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { CardWrapper, type CardComponentType } from '@unif/react-native-chat';
|
|
23
|
+
|
|
24
|
+
// 定义业务卡片
|
|
25
|
+
const ProductCard: CardComponentType = ({ data, actions }) => (
|
|
26
|
+
<View>
|
|
27
|
+
<Text>{data.productName as string}</Text>
|
|
28
|
+
<Text>{data.price as string}</Text>
|
|
29
|
+
</View>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// 注册卡片
|
|
33
|
+
const registry = {
|
|
34
|
+
product_list: ProductCard,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
<CardWrapper
|
|
38
|
+
cardType="product_list"
|
|
39
|
+
data={{ productName: '基金A', price: '1.2345' }}
|
|
40
|
+
actions={['buy', 'detail']}
|
|
41
|
+
registry={registry}
|
|
42
|
+
/>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 自定义 fallback
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<CardWrapper
|
|
49
|
+
cardType="unknown_type"
|
|
50
|
+
data={{}}
|
|
51
|
+
actions={[]}
|
|
52
|
+
registry={registry}
|
|
53
|
+
fallback={<Text>暂不支持此类型</Text>}
|
|
54
|
+
/>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### CardWrapperProps
|
|
60
|
+
|
|
61
|
+
| 属性 | 说明 | 类型 | 默认值 |
|
|
62
|
+
|------|------|------|--------|
|
|
63
|
+
| cardType | 卡片类型标识 | `string` | - |
|
|
64
|
+
| data | 卡片数据 | `Record<string, unknown>` | - |
|
|
65
|
+
| actions | 可用操作列表 | `string[]` | - |
|
|
66
|
+
| registry | 卡片注册表 | `Record<string, CardComponentType>` | `{}` |
|
|
67
|
+
| fallback | 未知卡片类型的 fallback | `ReactNode` | 默认错误提示 |
|
|
68
|
+
| style | 容器样式 | `ViewStyle` | - |
|
|
69
|
+
| testID | 测试标识 | `string` | - |
|
|
70
|
+
|
|
71
|
+
### CardComponentType
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
type CardComponentType = React.ComponentType<{
|
|
75
|
+
data: Record<string, unknown>;
|
|
76
|
+
actions: string[];
|
|
77
|
+
}>;
|
|
78
|
+
```
|