@bm-fe/react-native-ui-components 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 @bm-fe/react-native-multi-bundle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # @bm-fe/react-native-multi-bundle
2
+
3
+ React Native 多 Bundle 系统 - 支持模块按需加载和独立更新
4
+
5
+ ## 特性
6
+
7
+ - ✅ **模块按需加载**:支持懒加载,减少初始包体积
8
+ - ✅ **模块依赖管理**:自动处理模块间的依赖关系
9
+ - ✅ **模块状态管理**:完整的生命周期管理(idle/loading/loaded/failed)
10
+ - ✅ **路由加载器**:`createModuleRouteLoader`,完美支持 React Navigation `getComponent` API
11
+ - ✅ **错误处理与重试**:自动错误处理,支持重试机制和自定义错误组件
12
+ - ✅ **预加载支持**:`preloadModule` 支持关键模块预加载
13
+ - ✅ **开发环境 Mock**:开发环境无需 Native 模块即可运行
14
+ - ✅ **TypeScript 支持**:完整的类型定义
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ npm install @bm-fe/react-native-multi-bundle
20
+ # 或
21
+ yarn add @bm-fe/react-native-multi-bundle
22
+ ```
23
+
24
+ ## 快速开始
25
+
26
+ ### 1. 初始化多 Bundle 系统
27
+
28
+ 在应用入口(通常是 `App.tsx`)中调用 `initMultiBundle`:
29
+
30
+ ```typescript
31
+ import { initMultiBundle, LocalBundleManager } from '@bm-fe/react-native-multi-bundle';
32
+ import { Platform } from 'react-native';
33
+
34
+ function App() {
35
+ const [ready, setReady] = useState(false);
36
+
37
+ useEffect(() => {
38
+ async function bootstrap() {
39
+ const result = await initMultiBundle({
40
+ modulePaths: ['src/modules/**'],
41
+ sharedDependencies: [
42
+ 'node_modules/@bm-fe/react-native-multi-bundle/**',
43
+ 'src/navigation/**'
44
+ ],
45
+ manifestProvider: async () => {
46
+ return await LocalBundleManager.getCurrentBundleManifest();
47
+ },
48
+ preloadModules: ['settings'],
49
+ devServer: {
50
+ host: Platform.OS === 'android' ? '10.0.2.2' : 'localhost',
51
+ port: 8081,
52
+ },
53
+ });
54
+
55
+ if (result.success) {
56
+ setReady(true);
57
+ }
58
+ }
59
+
60
+ bootstrap();
61
+ }, []);
62
+
63
+ if (!ready) {
64
+ return <LoadingScreen />;
65
+ }
66
+
67
+ // ... 其余代码
68
+ }
69
+ ```
70
+
71
+ ### 2. 使用模块路由
72
+
73
+ ```typescript
74
+ import { createModuleRouteLoader } from '@bm-fe/react-native-multi-bundle';
75
+
76
+ // 创建路由加载器
77
+ export const createHomeScreen = createModuleRouteLoader('home', 'Home');
78
+
79
+ // 在 React Navigation 中使用
80
+ <Stack.Screen name="Home" getComponent={createHomeScreen} />
81
+ ```
82
+
83
+ ### 3. 自定义错误处理(可选)
84
+
85
+ ```typescript
86
+ import { createModuleLoader, type ErrorFallbackProps } from '@bm-fe/react-native-multi-bundle';
87
+
88
+ // 自定义错误组件
89
+ function MyErrorFallback({ moduleId, error, onRetry }: ErrorFallbackProps) {
90
+ return (
91
+ <View>
92
+ <Text>模块 {moduleId} 加载失败</Text>
93
+ <Button title="重试" onPress={onRetry} />
94
+ </View>
95
+ );
96
+ }
97
+
98
+ // 使用自定义错误组件
99
+ const createHomeScreen = createModuleLoader(
100
+ 'home',
101
+ (exports) => exports.routes.Home,
102
+ {
103
+ ErrorFallback: MyErrorFallback,
104
+ onError: (error) => console.error('加载失败:', error),
105
+ }
106
+ );
107
+ ```
108
+
109
+ ## 完整集成指南
110
+
111
+ 详细的集成步骤请参考:[INTEGRATION.md](./INTEGRATION.md)
112
+
113
+ 包括:
114
+ - Metro 配置
115
+ - Native 模块集成(Android/iOS)
116
+ - 模块配置
117
+ - 构建多 Bundle
118
+
119
+ ## API 文档
120
+
121
+ ### initMultiBundle
122
+
123
+ 初始化多 Bundle 系统。
124
+
125
+ ```typescript
126
+ function initMultiBundle(config?: MultiBundleConfig): Promise<InitResult>
127
+ ```
128
+
129
+ ### createModuleRouteLoader
130
+
131
+ 创建模块路由加载器函数(推荐,用于 React Navigation `getComponent` API)。
132
+
133
+ ```typescript
134
+ function createModuleRouteLoader(
135
+ moduleId: string,
136
+ routeKey: string
137
+ ): () => React.ComponentType<any>
138
+ ```
139
+
140
+ ### preloadModule
141
+
142
+ 预加载模块。
143
+
144
+ ```typescript
145
+ function preloadModule(moduleId: string): Promise<void>
146
+ ```
147
+
148
+ 更多 API 文档请参考:[src/multi-bundle/README.md](./src/multi-bundle/README.md)
149
+
150
+ ## 示例应用
151
+
152
+ 查看完整示例:[examples/demo-app](./examples/demo-app/README.md)
153
+
154
+ ## 版本管理
155
+
156
+ 本项目使用 [standard-version](https://github.com/conventional-changelog/standard-version) 进行版本管理,遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范。
157
+
158
+ ### 版本命令
159
+
160
+ ```bash
161
+ # 自动判断版本类型
162
+ npm run version
163
+
164
+ # 指定版本类型
165
+ npm run version:patch # 1.0.0 -> 1.0.1
166
+ npm run version:minor # 1.0.0 -> 1.1.0
167
+ npm run version:major # 1.0.0 -> 2.0.0
168
+ npm run version:beta # 1.0.0 -> 1.0.1-beta.0
169
+ ```
170
+
171
+ ## 发布流程
172
+
173
+ ### 发布前检查
174
+
175
+ 1. **确保代码已提交**
176
+ ```bash
177
+ git status
178
+ git add .
179
+ git commit -m "chore: prepare release"
180
+ ```
181
+
182
+ 2. **运行测试**
183
+ ```bash
184
+ npm test
185
+ ```
186
+
187
+ 3. **检查打包内容**
188
+ ```bash
189
+ npm pack --dry-run
190
+ ```
191
+
192
+ ### 发布 Beta 版本
193
+
194
+ Beta 版本用于测试新功能,不会影响 `latest` 标签。
195
+
196
+ ```bash
197
+ # 方式 1:使用便捷脚本(推荐)
198
+ npm run release:beta
199
+
200
+ # 方式 2:分步执行
201
+ npm run version:beta # 更新版本号为 beta(如:1.0.0 -> 1.0.1-beta.0)
202
+ npm run publish:beta # 发布到 beta 标签
203
+ ```
204
+
205
+ **安装 Beta 版本:**
206
+ ```bash
207
+ npm install @bm-fe/react-native-multi-bundle@beta
208
+ # 或安装特定 beta 版本
209
+ npm install @bm-fe/react-native-multi-bundle@1.0.1-beta.0
210
+ ```
211
+
212
+ ### 发布正式版本
213
+
214
+ ```bash
215
+ # 方式 1:使用便捷脚本(推荐)
216
+ npm run release
217
+
218
+ # 方式 2:分步执行
219
+ npm run version # 自动判断版本类型并更新 CHANGELOG
220
+ npm publish # 发布到 latest 标签
221
+ ```
222
+
223
+ **发布流程说明:**
224
+ 1. `npm run version` 会根据提交信息自动判断版本类型(patch/minor/major)
225
+ 2. 自动更新 `CHANGELOG.md`
226
+ 3. 自动创建 git tag
227
+ 4. `npm publish` 发布到 npm(默认 `latest` 标签)
228
+
229
+ ### 版本标签说明
230
+
231
+ - **latest**:稳定版本,用户通过 `npm install @bm-fe/react-native-multi-bundle` 安装
232
+ - **beta**:测试版本,需要显式指定 `@beta` 标签安装
233
+
234
+ ### 从 Beta 升级到正式版
235
+
236
+ 当 beta 版本稳定后,发布正式版本:
237
+
238
+ ```bash
239
+ # 1. 更新版本号为正式版本(移除 beta 后缀)
240
+ npm version 1.0.1 --no-git-tag-version
241
+
242
+ # 2. 发布到 latest
243
+ npm publish
244
+ ```
245
+
246
+ ## 开发
247
+
248
+ ```bash
249
+ # 安装依赖
250
+ npm install
251
+
252
+ # 运行测试
253
+ npm test
254
+
255
+ # 运行示例应用
256
+ cd examples/demo-app
257
+ npm install
258
+ npm start
259
+ ```
260
+
261
+ ## 许可证
262
+
263
+ MIT
264
+
265
+ ## 贡献
266
+
267
+ 欢迎提交 Issue 和 Pull Request!
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@bm-fe/react-native-ui-components",
3
+ "version": "1.0.1",
4
+ "description": "React Native UI Components Library",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "react-native": "src/index.ts",
8
+ "files": [
9
+ "src/components",
10
+ "src/screens",
11
+ "src/types",
12
+ "src/index.ts",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "android": "react-native run-android",
18
+ "ios": "react-native run-ios",
19
+ "start": "react-native start",
20
+ "lint": "eslint .",
21
+ "test": "jest",
22
+ "version": "standard-version",
23
+ "version:patch": "standard-version --release-as patch",
24
+ "version:minor": "standard-version --release-as minor",
25
+ "version:major": "standard-version --release-as major",
26
+ "version:beta": "npm version prerelease --preid=beta --no-git-tag-version --no-scripts",
27
+ "publish:beta": "npm publish --tag beta",
28
+ "release:beta": "npm run version:beta && npm run publish:beta",
29
+ "release": "npm run version && npm publish"
30
+ },
31
+ "peerDependencies": {
32
+ "react": ">=18.0.0",
33
+ "react-native": ">=0.70.0"
34
+ },
35
+ "devDependencies": {
36
+ "@babel/core": "7.25.2",
37
+ "@babel/preset-env": "7.25.2",
38
+ "@babel/runtime": "7.25.0",
39
+ "@react-native-community/cli": "18.0.0",
40
+ "@react-native-community/cli-platform-android": "18.0.0",
41
+ "@react-native-community/cli-platform-ios": "18.0.0",
42
+ "@react-native/babel-preset": "0.79.0",
43
+ "@react-native/eslint-config": "0.79.0",
44
+ "@react-native/gradle-plugin": "0.79.0",
45
+ "@react-native/metro-config": "0.79.0",
46
+ "@react-native/typescript-config": "0.79.0",
47
+ "@testing-library/react-native": "^13.3.3",
48
+ "@types/jest": "29.5.13",
49
+ "@types/react": "19.0.0",
50
+ "@types/react-test-renderer": "19.0.0",
51
+ "eslint": "8.22.0",
52
+ "jest": "29.7.0",
53
+ "prettier": "3.2.5",
54
+ "react": "19.0.0",
55
+ "react-native": "0.79.5",
56
+ "react-test-renderer": "19.0.0",
57
+ "standard-version": "^9.5.0",
58
+ "typescript": "5.5.4"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "engines": {
64
+ "node": ">=18.0.0 <22.0.0"
65
+ }
66
+ }
@@ -0,0 +1,154 @@
1
+ import React, { useCallback } from 'react';
2
+ import {
3
+ Platform,
4
+ StyleProp,
5
+ StyleSheet,
6
+ Text,
7
+ TouchableOpacity,
8
+ View,
9
+ ViewStyle,
10
+ requireNativeComponent,
11
+ } from 'react-native';
12
+
13
+ /**
14
+ * BMTextButton 样式类型
15
+ * - black: Widget.BitMart4.Button.TextButton.Black
16
+ * - gray: Widget.BitMart4.Button.TextButton.Gray
17
+ * - blue: Widget.BitMart4.Button.TextButton.Blue
18
+ */
19
+ export type BMTextButtonStyleType = 'black' | 'gray' | 'blue';
20
+
21
+ interface BMTextButtonProps {
22
+ /** 按钮文本 */
23
+ text: string;
24
+ /** 样式类型 */
25
+ styleType?: BMTextButtonStyleType;
26
+ /** 是否启用 */
27
+ enabled?: boolean;
28
+ /** 点击回调 */
29
+ onPress?: () => void;
30
+ /** 样式 */
31
+ style?: StyleProp<ViewStyle>;
32
+ }
33
+
34
+ interface NativeBMTextButtonProps {
35
+ text: string;
36
+ styleType: string;
37
+ enabled: boolean;
38
+ onPress?: () => void;
39
+ style?: StyleProp<ViewStyle>;
40
+ }
41
+
42
+ // Android Native 组件
43
+ const NativeBMTextButton =
44
+ Platform.OS === 'android'
45
+ ? requireNativeComponent<NativeBMTextButtonProps>('BMTextButton')
46
+ : null;
47
+
48
+ /**
49
+ * BMTextButton - 使用 BitMart Design 组件库样式的文本按钮
50
+ *
51
+ * 在 Android 上使用 Native 组件渲染,在 iOS 上使用 RN 组件模拟
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * <BMTextButton
56
+ * text="Button Text Link"
57
+ * styleType="black"
58
+ * onPress={() => console.log('pressed')}
59
+ * />
60
+ * ```
61
+ */
62
+ export const BMTextButton: React.FC<BMTextButtonProps> = ({
63
+ text,
64
+ styleType = 'black',
65
+ enabled = true,
66
+ onPress,
67
+ style,
68
+ }) => {
69
+ const handlePress = useCallback(() => {
70
+ if (enabled && onPress) {
71
+ onPress();
72
+ }
73
+ }, [enabled, onPress]);
74
+
75
+ // DEBUG: 打印调试信息
76
+ console.log(
77
+ `🔴 BMTextButton render - Platform: ${Platform.OS}, NativeBMTextButton exists: ${!!NativeBMTextButton}, text: ${text}`,
78
+ );
79
+
80
+ // Android: 使用 Native 组件
81
+ if (Platform.OS === 'android' && NativeBMTextButton) {
82
+ console.log('🔴 Rendering Native BMTextButton with red container');
83
+ return (
84
+ <View style={[styles.container, style]}>
85
+ <NativeBMTextButton
86
+ text={text}
87
+ styleType={styleType}
88
+ enabled={enabled}
89
+ onPress={handlePress}
90
+ style={styles.nativeButton}
91
+ />
92
+ </View>
93
+ );
94
+ }
95
+
96
+ // iOS: 使用 RN 组件模拟(后续可以替换为 iOS Native 组件)
97
+ const textColor = getTextColorForStyleType(styleType, enabled);
98
+
99
+ return (
100
+ <TouchableOpacity
101
+ onPress={handlePress}
102
+ disabled={!enabled}
103
+ style={[styles.container, style]}
104
+ activeOpacity={0.7}
105
+ >
106
+ <Text style={[styles.text, { color: textColor }]}>{text}</Text>
107
+ </TouchableOpacity>
108
+ );
109
+ };
110
+
111
+ /**
112
+ * 根据样式类型获取文本颜色
113
+ */
114
+ function getTextColorForStyleType(
115
+ styleType: BMTextButtonStyleType,
116
+ enabled: boolean,
117
+ ): string {
118
+ if (!enabled) {
119
+ return '#CCCCCC';
120
+ }
121
+
122
+ switch (styleType) {
123
+ case 'black':
124
+ return '#151515';
125
+ case 'gray':
126
+ return '#666666';
127
+ case 'blue':
128
+ return '#2196F3';
129
+ default:
130
+ return '#151515';
131
+ }
132
+ }
133
+
134
+ const styles = StyleSheet.create({
135
+ container: {
136
+ alignSelf: 'flex-start',
137
+ },
138
+ // DEBUG: 红色背景 - RN 容器层
139
+ debugContainerRed: {
140
+ backgroundColor: 'red',
141
+ padding: 8,
142
+ },
143
+ nativeButton: {
144
+ minHeight: 48,
145
+ minWidth: 150,
146
+ },
147
+ text: {
148
+ fontSize: 14,
149
+ fontWeight: '500',
150
+ color: '#151515',
151
+ },
152
+ });
153
+
154
+ export default BMTextButton;
@@ -0,0 +1,233 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import {
3
+ Platform,
4
+ StyleProp,
5
+ StyleSheet,
6
+ View,
7
+ ViewStyle,
8
+ requireNativeComponent,
9
+ } from 'react-native';
10
+
11
+ /**
12
+ * 支持的按钮样式类型
13
+ */
14
+ export type ButtonType = 'Primary' | 'Secondary' | 'Green' | 'Red' | 'White';
15
+
16
+ /**
17
+ * 支持的按钮尺寸
18
+ */
19
+ export type ButtonSize = 'XLarge' | 'Large' | 'Medium' | 'Small' | 'XSmall' | 'XXSmall';
20
+
21
+ /**
22
+ * 按钮样式名称(格式:Type.Size)
23
+ */
24
+ export type ButtonStyleName = `${ButtonType}.${ButtonSize}`;
25
+
26
+ /**
27
+ * 图标位置(内部使用)
28
+ */
29
+ export type IconPosition = 'left' | 'right';
30
+
31
+ /**
32
+ * 按钮尺寸映射表(对应 Android 样式中的 android:layout_height)
33
+ * 用于 iOS 端的尺寸计算
34
+ */
35
+ const BUTTON_SIZE_MAP: Record<ButtonSize, { height: number; minWidth: number }> = {
36
+ XLarge: { height: 56, minWidth: 120 }, // Widget.BitMart4.Button.*.XLarge
37
+ Large: { height: 48, minWidth: 100 }, // Widget.BitMart4.Button.*.Large
38
+ Medium: { height: 40, minWidth: 80 }, // Widget.BitMart4.Button.*.Medium
39
+ Small: { height: 36, minWidth: 70 }, // Widget.BitMart4.Button.*.Small
40
+ XSmall: { height: 32, minWidth: 60 }, // Widget.BitMart4.Button.*.XSmall
41
+ XXSmall: { height: 26, minWidth: 50 }, // Widget.BitMart4.Button.*.XXSmall
42
+ };
43
+
44
+ /**
45
+ * 从 styleName 中提取尺寸
46
+ */
47
+ const getSizeFromStyleName = (styleName: ButtonStyleName): ButtonSize => {
48
+ const parts = styleName.split('.');
49
+ return parts[1] as ButtonSize;
50
+ };
51
+
52
+ /**
53
+ * 根据按钮样式自动推断图标颜色
54
+ */
55
+ const getIconColorForStyle = (styleName: ButtonStyleName): string => {
56
+ const type = styleName.split('.')[0] as ButtonType;
57
+
58
+ // Secondary 和 White 使用蓝色图标,其他使用白色图标
59
+ switch (type) {
60
+ case 'Secondary':
61
+ case 'White':
62
+ return '#0066FF';
63
+ case 'Primary':
64
+ case 'Green':
65
+ case 'Red':
66
+ default:
67
+ return '#ffffff';
68
+ }
69
+ };
70
+
71
+ interface ButtonProps {
72
+ /** 按钮文本 */
73
+ text: string;
74
+ /** 按钮样式,格式:Type.Size(如 "Primary.XLarge")*/
75
+ styleName?: ButtonStyleName;
76
+ /** 是否启用 */
77
+ enabled?: boolean;
78
+ /** 是否显示加载状态(原生进度条/ActivityIndicator)*/
79
+ loading?: boolean;
80
+ /** 点击回调 */
81
+ onPress?: () => void;
82
+ /** 样式 */
83
+ style?: StyleProp<ViewStyle>;
84
+ /** 左侧图标(SVG 字符串,颜色自动推断) */
85
+ iconLeft?: string;
86
+ /** 右侧图标(SVG 字符串,颜色自动推断) */
87
+ iconRight?: string;
88
+ /** 是否全宽布局(自动填充父容器宽度,常用于 Footer 按钮) */
89
+ fullWidth?: boolean;
90
+ }
91
+
92
+ interface NativeSizeChangeEvent {
93
+ nativeEvent: {
94
+ width: number;
95
+ height: number;
96
+ };
97
+ }
98
+
99
+ interface NativeButtonProps {
100
+ text: string;
101
+ styleName?: string;
102
+ enabled: boolean;
103
+ loading?: boolean;
104
+ onPress?: () => void;
105
+ onSizeChange?: (event: NativeSizeChangeEvent) => void;
106
+ style?: StyleProp<ViewStyle>;
107
+ iconSvgString?: string;
108
+ iconColor?: string;
109
+ iconPosition?: string;
110
+ }
111
+
112
+ // Native Components for different platforms
113
+ const NativeButton =
114
+ Platform.OS === 'android'
115
+ ? requireNativeComponent<NativeButtonProps>('PrimaryXLargeButton')
116
+ : requireNativeComponent<NativeButtonProps>('PrimaryButtonView');
117
+
118
+ /**
119
+ * Button - 跨平台原生按钮组件
120
+ *
121
+ * @example
122
+ * ```tsx
123
+ * // 普通按钮
124
+ * <Button text="Submit" styleName={ButtonStyles.Primary.Large} onPress={handleSubmit} />
125
+ *
126
+ * // 左侧图标(图标颜色自动推断)
127
+ * <Button text="Back" styleName={ButtonStyles.Primary.Large} iconLeft={IC_BUTTON_SVG} />
128
+ *
129
+ * // 右侧图标
130
+ * <Button text="Next" styleName={ButtonStyles.Primary.Large} iconRight={IC_BUTTON_SVG} />
131
+ *
132
+ * // 全宽按钮
133
+ * <Button text="Confirm" styleName={ButtonStyles.Primary.Large} fullWidth />
134
+ * ```
135
+ */
136
+ export const Button: React.FC<ButtonProps> = ({
137
+ text,
138
+ styleName = 'Primary.XLarge',
139
+ enabled = true,
140
+ loading = false,
141
+ onPress,
142
+ style,
143
+ iconLeft,
144
+ iconRight,
145
+ fullWidth = false,
146
+ }) => {
147
+ const [nativeSize, setNativeSize] = useState<{ width: number; height: number } | null>(null);
148
+
149
+ const handlePress = useCallback(() => {
150
+ if (enabled && onPress) {
151
+ onPress();
152
+ }
153
+ }, [enabled, onPress]);
154
+
155
+ const handleSizeChange = useCallback((event: NativeSizeChangeEvent) => {
156
+ // fullWidth 模式下忽略原生测量
157
+ if (fullWidth) return;
158
+
159
+ const { width, height } = event.nativeEvent;
160
+ setNativeSize({ width, height });
161
+ }, [fullWidth]);
162
+
163
+ const size = getSizeFromStyleName(styleName);
164
+ const dimensions = BUTTON_SIZE_MAP[size];
165
+
166
+ // 确定使用哪个图标(优先使用 iconLeft)
167
+ const iconSvg = iconLeft || iconRight;
168
+ const iconPosition = iconRight ? 'trailing' : 'leading';
169
+
170
+ // 自动推断图标颜色
171
+ const iconColor = iconSvg ? getIconColorForStyle(styleName) : undefined;
172
+
173
+ const baseStyle: ViewStyle = useMemo(() => {
174
+ // fullWidth 模式:使用 100% 宽度,由 wrapper 的 flex 布局控制实际宽度
175
+ if (fullWidth) {
176
+ return {
177
+ width: '100%' as any,
178
+ height: dimensions.height,
179
+ };
180
+ }
181
+
182
+ // 非 fullWidth 模式:检查是否由父级决定宽度
183
+ const flat = style ? StyleSheet.flatten(style) as ViewStyle : null;
184
+ const userWantsOwnWidth = flat && (
185
+ flat.width !== undefined ||
186
+ flat.flex !== undefined ||
187
+ flat.position === 'absolute'
188
+ );
189
+
190
+ if (userWantsOwnWidth) {
191
+ // 由父级决定宽度,只设高度
192
+ return {
193
+ height: nativeSize ? nativeSize.height : dimensions.height,
194
+ };
195
+ }
196
+
197
+ // 自动宽度模式:使用原生测量的尺寸或默认尺寸
198
+ if (nativeSize) {
199
+ return { width: nativeSize.width, height: nativeSize.height };
200
+ }
201
+ return { height: dimensions.height, minWidth: dimensions.minWidth };
202
+ }, [fullWidth, style, nativeSize, dimensions.height, dimensions.minWidth]);
203
+
204
+ const mergedStyle = [baseStyle, style];
205
+
206
+ const buttonElement = (
207
+ <NativeButton
208
+ text={text}
209
+ styleName={styleName}
210
+ enabled={enabled}
211
+ loading={loading}
212
+ onPress={handlePress}
213
+ onSizeChange={handleSizeChange}
214
+ iconSvgString={iconSvg}
215
+ iconColor={iconColor}
216
+ iconPosition={iconPosition}
217
+ style={mergedStyle}
218
+ />
219
+ );
220
+
221
+ // fullWidth 模式:wrapper 使用 flex 布局,按钮使用 100% 填充
222
+ if (fullWidth) {
223
+ return (
224
+ <View style={{ flex: 1, alignSelf: 'stretch' }}>
225
+ {buttonElement}
226
+ </View>
227
+ );
228
+ }
229
+
230
+ return buttonElement;
231
+ };
232
+
233
+ export default Button;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Button 样式常量定义
3
+ *
4
+ * 使用方式:
5
+ * import { ButtonStyles } from '@/components/NativeDesign/ButtonStyles';
6
+ *
7
+ * <Button styleName={ButtonStyles.Primary.Large} />
8
+ * <TextButton styleName={ButtonStyles.Text.Blue} />
9
+ */
10
+
11
+ export const ButtonStyles = {
12
+ // Button 组件样式
13
+ Primary: {
14
+ XLarge: 'Primary.XLarge' as const,
15
+ Large: 'Primary.Large' as const,
16
+ Medium: 'Primary.Medium' as const,
17
+ Small: 'Primary.Small' as const,
18
+ XSmall: 'Primary.XSmall' as const,
19
+ XXSmall: 'Primary.XXSmall' as const,
20
+ },
21
+ Secondary: {
22
+ XLarge: 'Secondary.XLarge' as const,
23
+ Large: 'Secondary.Large' as const,
24
+ Medium: 'Secondary.Medium' as const,
25
+ Small: 'Secondary.Small' as const,
26
+ XSmall: 'Secondary.XSmall' as const,
27
+ XXSmall: 'Secondary.XXSmall' as const,
28
+ },
29
+ Green: {
30
+ XLarge: 'Green.XLarge' as const,
31
+ Large: 'Green.Large' as const,
32
+ Medium: 'Green.Medium' as const,
33
+ Small: 'Green.Small' as const,
34
+ XSmall: 'Green.XSmall' as const,
35
+ XXSmall: 'Green.XXSmall' as const,
36
+ },
37
+ Red: {
38
+ XLarge: 'Red.XLarge' as const,
39
+ Large: 'Red.Large' as const,
40
+ Medium: 'Red.Medium' as const,
41
+ Small: 'Red.Small' as const,
42
+ XSmall: 'Red.XSmall' as const,
43
+ XXSmall: 'Red.XXSmall' as const,
44
+ },
45
+ White: {
46
+ XLarge: 'White.XLarge' as const,
47
+ Large: 'White.Large' as const,
48
+ Medium: 'White.Medium' as const,
49
+ Small: 'White.Small' as const,
50
+ XSmall: 'White.XSmall' as const,
51
+ XXSmall: 'White.XXSmall' as const,
52
+ },
53
+ // TextButton 组件样式
54
+ Text: {
55
+ Black: 'Black' as const,
56
+ Gray: 'Gray' as const,
57
+ Blue: 'Blue' as const,
58
+ },
59
+ } as const;
60
+
61
+ export default ButtonStyles;
@@ -0,0 +1,111 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { Platform, StyleProp, ViewStyle, requireNativeComponent } from 'react-native';
3
+
4
+ /**
5
+ * 支持的文本按钮样式类型
6
+ */
7
+ type TextButtonStyleName = 'Black' | 'Gray' | 'Blue';
8
+
9
+ /**
10
+ * 尺寸映射(文本按钮通常没有固定高度,但提供最小高度参考)
11
+ */
12
+ const TEXT_BUTTON_SIZE_MAP: Record<TextButtonStyleName, { minHeight: number }> = {
13
+ Black: { minHeight: 28 }, // Widget.BitMart4.Button.TextButton.Black
14
+ Gray: { minHeight: 28 }, // Widget.BitMart4.Button.TextButton.Gray
15
+ Blue: { minHeight: 28 }, // Widget.BitMart4.Button.TextButton.Blue
16
+ };
17
+
18
+ interface TextButtonProps {
19
+ /** 按钮文本 */
20
+ text: string;
21
+ /** 按钮样式,支持 "Black", "Gray", "Blue" */
22
+ styleName?: TextButtonStyleName;
23
+ /** 是否启用 */
24
+ enabled?: boolean;
25
+ /** 点击回调 */
26
+ onPress?: () => void;
27
+ /** 样式 */
28
+ style?: StyleProp<ViewStyle>;
29
+ }
30
+
31
+ interface NativeSizeChangeEvent {
32
+ nativeEvent: {
33
+ width: number;
34
+ height: number;
35
+ };
36
+ }
37
+
38
+ interface NativeTextButtonProps {
39
+ text: string;
40
+ styleName?: string;
41
+ enabled: boolean;
42
+ onPress?: () => void;
43
+ onSizeChange?: (event: NativeSizeChangeEvent) => void;
44
+ style?: StyleProp<ViewStyle>;
45
+ }
46
+
47
+ // Native Components for different platforms
48
+ const NativeTextButton =
49
+ Platform.OS === 'android'
50
+ ? requireNativeComponent<NativeTextButtonProps>('TextButton')
51
+ : requireNativeComponent<NativeTextButtonProps>('TextButtonView');
52
+
53
+ /**
54
+ * TextButton - 跨平台文本按钮组件
55
+ *
56
+ * Android: 基于 AppCompatButton + ContextThemeWrapper,支持 BitMart4 TextButton 样式
57
+ * iOS: 基于 UILabel + 手势识别,实现纯文本按钮效果,支持 Black/Gray/Blue 样式
58
+ *
59
+ * 按钮宽度会根据文字内容自适应,文字保持单行显示
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * <TextButton
64
+ * text="确认"
65
+ * styleName="Blue"
66
+ * onPress={() => console.log('pressed')}
67
+ * />
68
+ * ```
69
+ */
70
+ export const TextButton: React.FC<TextButtonProps> = ({
71
+ text,
72
+ styleName = 'Black', // 默认黑色文本样式
73
+ enabled = true,
74
+ onPress,
75
+ style,
76
+ }) => {
77
+ // 用于存储原生测量的尺寸
78
+ const [nativeSize, setNativeSize] = useState<{ width: number; height: number } | null>(null);
79
+
80
+ const handlePress = useCallback(() => {
81
+ if (enabled && onPress) {
82
+ onPress();
83
+ }
84
+ }, [enabled, onPress]);
85
+
86
+ // 处理原生尺寸变化事件
87
+ const handleSizeChange = useCallback((event: NativeSizeChangeEvent) => {
88
+ const { width, height } = event.nativeEvent;
89
+ setNativeSize({ width, height });
90
+ }, []);
91
+
92
+ // 获取样式对应的尺寸信息
93
+ const dimensions = TEXT_BUTTON_SIZE_MAP[styleName];
94
+
95
+ return (
96
+ <NativeTextButton
97
+ text={text}
98
+ styleName={styleName}
99
+ enabled={enabled}
100
+ onPress={handlePress}
101
+ onSizeChange={handleSizeChange}
102
+ style={[
103
+ // 使用原生测量的尺寸(如果有),否则使用默认尺寸
104
+ nativeSize
105
+ ? { width: nativeSize.width, height: nativeSize.height }
106
+ : { minHeight: dimensions.minHeight },
107
+ style,
108
+ ]}
109
+ />
110
+ );
111
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * NativeDesign - BitMart Design 组件库的 React Native 桥接组件
3
+ *
4
+ * 这些组件使用 Android 的 com.bitmart.android:design 组件库的样式,
5
+ * 通过 Native Module 桥接到 React Native 中使用。
6
+ */
7
+
8
+ export { BMTextButton } from './BMTextButton';
9
+ export type { BMTextButtonStyleType } from './BMTextButton';
10
+
11
+ export { Button } from './Button';
12
+ export { TextButton } from './TextButton';
13
+ export type { ButtonType, ButtonSize, ButtonStyleName } from './Button';
14
+ export { ButtonStyles } from './ButtonStyles';
@@ -0,0 +1 @@
1
+ export * from './NativeDesign';
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ // UI Components Library Entry Point
2
+ // Export all UI components here
3
+
4
+ export * from './components';
@@ -0,0 +1,335 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ Alert,
4
+ SafeAreaView,
5
+ ScrollView,
6
+ StyleSheet,
7
+ Switch,
8
+ Text,
9
+ TouchableOpacity,
10
+ View,
11
+ } from 'react-native';
12
+ import { Button, ButtonStyleName, ButtonStyles, TextButton } from '../components/NativeDesign';
13
+ import { IC_BUTTON_SVG } from '../assets/svgs/svgStrings';
14
+
15
+ const NativeButtonsScreen: React.FC = () => {
16
+ const [activeTab, setActiveTab] = useState(0);
17
+
18
+ const handleButtonPress = (styleName: string) => {
19
+ Alert.alert('按钮点击', `点击了样式: ${styleName}`);
20
+ };
21
+
22
+ const tabs = [
23
+ { id: 0, title: '文本按钮' },
24
+ { id: 1, title: 'Primary' },
25
+ { id: 2, title: 'Secondary' },
26
+ { id: 3, title: 'Green' },
27
+ { id: 4, title: 'Red' },
28
+ { id: 5, title: 'White' },
29
+ { id: 6, title: 'Footer' },
30
+ ];
31
+
32
+ const renderTabContent = () => {
33
+ switch (activeTab) {
34
+ case 0: return <TextButtonsTab onButtonPress={handleButtonPress} />;
35
+ case 1: return <PrimaryButtonsTab onButtonPress={handleButtonPress} />;
36
+ case 2: return <SecondaryButtonsTab onButtonPress={handleButtonPress} />;
37
+ case 3: return <GreenButtonsTab onButtonPress={handleButtonPress} />;
38
+ case 4: return <RedButtonsTab onButtonPress={handleButtonPress} />;
39
+ case 5: return <WhiteButtonsTab onButtonPress={handleButtonPress} />;
40
+ case 6: return <FooterButtonsTab onButtonPress={handleButtonPress} />;
41
+ default: return null;
42
+ }
43
+ };
44
+
45
+ return (
46
+ <SafeAreaView style={styles.safeArea}>
47
+ {/* Header */}
48
+ <View style={styles.header}>
49
+ <Text style={styles.headerTitle}>Native Buttons</Text>
50
+ </View>
51
+
52
+ {/* Tab 导航 */}
53
+ <View style={styles.tabContainer}>
54
+ <ScrollView
55
+ horizontal
56
+ showsHorizontalScrollIndicator={false}
57
+ contentContainerStyle={styles.tabScrollContent}
58
+ >
59
+ {tabs.map((tab) => (
60
+ <TouchableOpacity
61
+ key={tab.id}
62
+ style={[styles.tab, activeTab === tab.id && styles.tabActive]}
63
+ onPress={() => setActiveTab(tab.id)}
64
+ >
65
+ <Text style={[styles.tabText, activeTab === tab.id && styles.tabTextActive]}>
66
+ {tab.title}
67
+ </Text>
68
+ </TouchableOpacity>
69
+ ))}
70
+ </ScrollView>
71
+ </View>
72
+
73
+ {/* Tab 内容 */}
74
+ <ScrollView
75
+ style={styles.container}
76
+ contentContainerStyle={styles.contentContainer}
77
+ showsVerticalScrollIndicator={false}
78
+ >
79
+ {renderTabContent()}
80
+ </ScrollView>
81
+ </SafeAreaView>
82
+ );
83
+ };
84
+
85
+ // ─── Text Buttons Tab ────────────────────────────────────────────────────────
86
+
87
+ const TextButtonsTab: React.FC<{ onButtonPress: (style: string) => void }> = ({ onButtonPress }) => (
88
+ <View style={styles.tabContent}>
89
+ <SectionTitle title="常规文本按钮" />
90
+ <View style={styles.buttonGroup}>
91
+ <TextButton text="Black Text Button" styleName={ButtonStyles.Text.Black} onPress={() => onButtonPress('TextButton.Black')} />
92
+ <TextButton text="Gray Text Button" styleName={ButtonStyles.Text.Gray} onPress={() => onButtonPress('TextButton.Gray')} style={styles.buttonMargin} />
93
+ <TextButton text="Blue Text Button" styleName={ButtonStyles.Text.Blue} onPress={() => onButtonPress('TextButton.Blue')} style={styles.buttonMargin} />
94
+ </View>
95
+
96
+ <SectionTitle title="禁用状态" />
97
+ <View style={styles.buttonGroup}>
98
+ <TextButton text="Black Text Button (Disabled)" styleName={ButtonStyles.Text.Black} enabled={false} onPress={() => onButtonPress('TextButton.Black.Disabled')} />
99
+ <TextButton text="Gray Text Button (Disabled)" styleName={ButtonStyles.Text.Gray} enabled={false} onPress={() => onButtonPress('TextButton.Gray.Disabled')} style={styles.buttonMargin} />
100
+ <TextButton text="Blue Text Button (Disabled)" styleName={ButtonStyles.Text.Blue} enabled={false} onPress={() => onButtonPress('TextButton.Blue.Disabled')} style={styles.buttonMargin} />
101
+ </View>
102
+ </View>
103
+ );
104
+
105
+ // ─── Primary Buttons Tab ─────────────────────────────────────────────────────
106
+
107
+ const PrimaryButtonsTab: React.FC<{ onButtonPress: (style: string) => void }> = ({ onButtonPress }) => {
108
+ const [loading, setLoading] = useState(false);
109
+ return (
110
+ <View style={styles.tabContent}>
111
+ <SectionTitle title="常规" />
112
+ <View style={styles.buttonGroup}>
113
+ {(['XLarge', 'Large', 'Medium', 'Small', 'XSmall', 'XXSmall'] as const).map((size, i) => (
114
+ <Button key={size} text={`Primary ${size}`} styleName={ButtonStyles.Primary[size]} onPress={() => onButtonPress(`Primary.${size}`)} style={i > 0 ? styles.buttonMargin : undefined} />
115
+ ))}
116
+ </View>
117
+
118
+ <SectionTitle title="左图标(SVG原生)" />
119
+ <View style={styles.buttonGroup}>
120
+ {(['XLarge', 'Large', 'Medium', 'Small', 'XSmall', 'XXSmall'] as const).map((size, i) => (
121
+ <Button key={size} text="Button" styleName={ButtonStyles.Primary[size]} iconLeft={IC_BUTTON_SVG} onPress={() => onButtonPress(`Primary.${size}.Icon.Leading`)} style={i > 0 ? styles.buttonMargin : undefined} />
122
+ ))}
123
+ </View>
124
+
125
+ <SectionTitle title="右图标(SVG原生)" />
126
+ <View style={styles.buttonGroup}>
127
+ {(['XLarge', 'Large', 'Medium', 'Small', 'XSmall', 'XXSmall'] as const).map((size, i) => (
128
+ <Button key={size} text="Button" styleName={ButtonStyles.Primary[size]} iconRight={IC_BUTTON_SVG} onPress={() => onButtonPress(`Primary.${size}.Icon.Trailing`)} style={i > 0 ? styles.buttonMargin : undefined} />
129
+ ))}
130
+ </View>
131
+
132
+ <SectionTitle title="加载状态" rightElement={<LoadingSwitch value={loading} onChange={setLoading} />} />
133
+ <View style={styles.buttonGroup}>
134
+ <Button text="Primary XLarge" styleName={ButtonStyles.Primary.XLarge} loading={loading} onPress={() => onButtonPress('Primary.XLarge')} />
135
+ <Button text="Primary Large" styleName={ButtonStyles.Primary.Large} loading={loading} onPress={() => onButtonPress('Primary.Large')} style={styles.buttonMargin} />
136
+ <Button text="Primary Medium" styleName={ButtonStyles.Primary.Medium} loading={loading} onPress={() => onButtonPress('Primary.Medium')} style={styles.buttonMargin} />
137
+ <Button text="Button" styleName={ButtonStyles.Primary.Large} loading={loading} iconLeft={IC_BUTTON_SVG} onPress={() => onButtonPress('Primary.Large.Icon.Leading')} style={styles.buttonMargin} />
138
+ </View>
139
+
140
+ <SectionTitle title="禁用状态" />
141
+ <View style={styles.buttonGroup}>
142
+ <Button text="Primary Large (Disabled)" styleName={ButtonStyles.Primary.Large} enabled={false} onPress={() => onButtonPress('Primary.Large.Disabled')} />
143
+ <Button text="Primary Medium (Disabled)" styleName={ButtonStyles.Primary.Medium} enabled={false} onPress={() => onButtonPress('Primary.Medium.Disabled')} style={styles.buttonMargin} />
144
+ </View>
145
+ </View>
146
+ );
147
+ };
148
+
149
+ // ─── Generic Color Tabs (Secondary / Green / Red / White) ────────────────────
150
+
151
+ type ColorTabProps = { colorKey: 'Secondary' | 'Green' | 'Red' | 'White'; onButtonPress: (style: string) => void };
152
+
153
+ const ColorButtonsTab: React.FC<ColorTabProps> = ({ colorKey, onButtonPress }) => {
154
+ const [loading, setLoading] = useState(false);
155
+ const sizes = ['XLarge', 'Large', 'Medium', 'Small', 'XSmall', 'XXSmall'] as const;
156
+ const styleMap = ButtonStyles[colorKey] as Record<string, string>;
157
+
158
+ return (
159
+ <View style={styles.tabContent}>
160
+ <SectionTitle title="常规" />
161
+ <View style={styles.buttonGroup}>
162
+ {sizes.map((size, i) => (
163
+ <Button key={size} text="Button" styleName={styleMap[size] as ButtonStyleName} onPress={() => onButtonPress(`${colorKey}.${size}`)} style={i > 0 ? styles.buttonMargin : undefined} />
164
+ ))}
165
+ </View>
166
+
167
+ <SectionTitle title="左图标(SVG原生)" />
168
+ <View style={styles.buttonGroup}>
169
+ {sizes.map((size, i) => (
170
+ <Button key={size} text="Button" styleName={styleMap[size] as ButtonStyleName} iconLeft={IC_BUTTON_SVG} onPress={() => onButtonPress(`${colorKey}.${size}.Icon.Leading`)} style={i > 0 ? styles.buttonMargin : undefined} />
171
+ ))}
172
+ </View>
173
+
174
+ <SectionTitle title="右图标(SVG原生)" />
175
+ <View style={styles.buttonGroup}>
176
+ {sizes.map((size, i) => (
177
+ <Button key={size} text="Button" styleName={styleMap[size] as ButtonStyleName} iconRight={IC_BUTTON_SVG} onPress={() => onButtonPress(`${colorKey}.${size}.Icon.Trailing`)} style={i > 0 ? styles.buttonMargin : undefined} />
178
+ ))}
179
+ </View>
180
+
181
+ <SectionTitle title="加载状态" rightElement={<LoadingSwitch value={loading} onChange={setLoading} />} />
182
+ <View style={styles.buttonGroup}>
183
+ {sizes.slice(0, 4).map((size, i) => (
184
+ <Button key={size} text="Button" styleName={styleMap[size] as ButtonStyleName} loading={loading} onPress={() => onButtonPress(`${colorKey}.${size}`)} style={i > 0 ? styles.buttonMargin : undefined} />
185
+ ))}
186
+ </View>
187
+ </View>
188
+ );
189
+ };
190
+
191
+ const SecondaryButtonsTab: React.FC<{ onButtonPress: (style: string) => void }> = ({ onButtonPress }) => (
192
+ <ColorButtonsTab colorKey="Secondary" onButtonPress={onButtonPress} />
193
+ );
194
+ const GreenButtonsTab: React.FC<{ onButtonPress: (style: string) => void }> = ({ onButtonPress }) => (
195
+ <ColorButtonsTab colorKey="Green" onButtonPress={onButtonPress} />
196
+ );
197
+ const RedButtonsTab: React.FC<{ onButtonPress: (style: string) => void }> = ({ onButtonPress }) => (
198
+ <ColorButtonsTab colorKey="Red" onButtonPress={onButtonPress} />
199
+ );
200
+ const WhiteButtonsTab: React.FC<{ onButtonPress: (style: string) => void }> = ({ onButtonPress }) => (
201
+ <ColorButtonsTab colorKey="White" onButtonPress={onButtonPress} />
202
+ );
203
+
204
+ // ─── Footer Buttons Tab ──────────────────────────────────────────────────────
205
+
206
+ const FooterButtonsTab: React.FC<{ onButtonPress: (style: string) => void }> = ({ onButtonPress }) => (
207
+ <View style={styles.tabContent}>
208
+ <SectionTitle title="底部全宽按钮" />
209
+ <View style={styles.footerButtonGroup}>
210
+ <Button text="Button" styleName={ButtonStyles.Primary.Large} fullWidth onPress={() => onButtonPress('Primary.Large')} />
211
+ <Button text="Button" styleName={ButtonStyles.Secondary.Large} fullWidth onPress={() => onButtonPress('Secondary.Large')} style={styles.footerButtonSpacing} />
212
+ <View style={styles.footerTwoButtonRow}>
213
+ <Button text="Button" styleName={ButtonStyles.Secondary.Large} fullWidth onPress={() => onButtonPress('Secondary.Large')} />
214
+ <Button text="Button" styleName={ButtonStyles.Primary.Large} fullWidth onPress={() => onButtonPress('Primary.Large')} />
215
+ </View>
216
+ </View>
217
+ </View>
218
+ );
219
+
220
+ // ─── Shared helpers ──────────────────────────────────────────────────────────
221
+
222
+ const SectionTitle: React.FC<{ title: string; rightElement?: React.ReactNode }> = ({ title, rightElement }) => (
223
+ <View style={styles.titleRow}>
224
+ <Text style={styles.title}>{title}</Text>
225
+ {rightElement}
226
+ </View>
227
+ );
228
+
229
+ const LoadingSwitch: React.FC<{ value: boolean; onChange: (v: boolean) => void }> = ({ value, onChange }) => (
230
+ <View style={styles.switchRow}>
231
+ <Text style={styles.switchLabel}>进度条</Text>
232
+ <Switch value={value} onValueChange={onChange} />
233
+ </View>
234
+ );
235
+
236
+ // ─── Styles ──────────────────────────────────────────────────────────────────
237
+
238
+ const styles = StyleSheet.create({
239
+ safeArea: {
240
+ flex: 1,
241
+ backgroundColor: '#FFFFFF',
242
+ },
243
+ header: {
244
+ height: 56,
245
+ paddingHorizontal: 16,
246
+ justifyContent: 'center',
247
+ borderBottomWidth: StyleSheet.hairlineWidth,
248
+ borderBottomColor: '#E0E0E0',
249
+ backgroundColor: '#FFFFFF',
250
+ },
251
+ headerTitle: {
252
+ fontSize: 18,
253
+ fontWeight: '600',
254
+ color: '#151515',
255
+ },
256
+ tabContainer: {
257
+ backgroundColor: '#FFFFFF',
258
+ borderBottomWidth: 1,
259
+ borderBottomColor: '#E0E0E0',
260
+ },
261
+ tabScrollContent: {
262
+ paddingHorizontal: 16,
263
+ },
264
+ tab: {
265
+ paddingVertical: 12,
266
+ paddingHorizontal: 16,
267
+ marginRight: 8,
268
+ borderBottomWidth: 2,
269
+ borderBottomColor: 'transparent',
270
+ },
271
+ tabActive: {
272
+ borderBottomColor: '#2196F3',
273
+ },
274
+ tabText: {
275
+ fontSize: 14,
276
+ fontWeight: '400',
277
+ color: '#666666',
278
+ },
279
+ tabTextActive: {
280
+ fontWeight: '600',
281
+ color: '#2196F3',
282
+ },
283
+ container: {
284
+ flex: 1,
285
+ backgroundColor: '#FFFFFF',
286
+ },
287
+ contentContainer: {
288
+ paddingBottom: 24,
289
+ },
290
+ tabContent: {
291
+ padding: 24,
292
+ },
293
+ titleRow: {
294
+ flexDirection: 'row',
295
+ alignItems: 'center',
296
+ marginBottom: 16,
297
+ },
298
+ title: {
299
+ flex: 1,
300
+ fontSize: 20,
301
+ fontWeight: '600',
302
+ color: '#151515',
303
+ },
304
+ switchRow: {
305
+ flexDirection: 'row',
306
+ alignItems: 'center',
307
+ gap: 8,
308
+ },
309
+ switchLabel: {
310
+ fontSize: 14,
311
+ color: '#666666',
312
+ },
313
+ buttonGroup: {
314
+ alignItems: 'center',
315
+ marginBottom: 32,
316
+ },
317
+ buttonMargin: {
318
+ marginTop: 16,
319
+ },
320
+ footerButtonGroup: {
321
+ width: '100%',
322
+ alignSelf: 'stretch',
323
+ },
324
+ footerButtonSpacing: {
325
+ marginTop: 16,
326
+ },
327
+ footerTwoButtonRow: {
328
+ flexDirection: 'row',
329
+ width: '100%',
330
+ marginTop: 16,
331
+ gap: 16,
332
+ },
333
+ });
334
+
335
+ export default NativeButtonsScreen;
@@ -0,0 +1,14 @@
1
+ import { ModuleRegistry } from '../multi-bundle/ModuleRegistry';
2
+ export {};
3
+
4
+ declare global {
5
+ var __ModuleRegistry: typeof ModuleRegistry;
6
+
7
+ // React Native 环境中的 process.env 类型定义
8
+ namespace NodeJS {
9
+ interface ProcessEnv {
10
+ MODULE_DEBUG?: string;
11
+ NODE_ENV?: 'development' | 'production' | 'test';
12
+ }
13
+ }
14
+ }