@bm-fe/react-native-ui-components 1.0.1 → 1.1.3
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/android/design/build.gradle +51 -0
- package/android/design/consumer-rules.pro +1 -0
- package/android/design/proguard-rules.pro +1 -0
- package/android/design/src/main/AndroidManifest.xml +2 -0
- package/android/design/src/main/java/com/bitmart/react/design/DataUriFetcher.kt +42 -0
- package/android/design/src/main/java/com/bitmart/react/design/DesignComponentPackage.kt +35 -0
- package/android/design/src/main/java/com/bitmart/react/design/PrimaryXLargeViewManager.kt +785 -0
- package/android/design/src/main/java/com/bitmart/react/design/TextButtonViewManager.kt +294 -0
- package/ios/DemoProject/NativeDesign/PrimaryButtonViewManager.m +25 -0
- package/ios/DemoProject/NativeDesign/PrimaryButtonViewManager.swift +321 -0
- package/ios/DemoProject/NativeDesign/TextButtonViewManager.m +21 -0
- package/ios/DemoProject/NativeDesign/TextButtonViewManager.swift +184 -0
- package/package.json +8 -1
- package/react-native-ui-components.podspec +23 -0
- package/react-native.config.js +16 -0
- package/src/screens/NativeButtonsScreen.tsx +0 -335
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
//
|
|
2
|
+
// TextButtonViewManager.swift
|
|
3
|
+
// BitMartUS
|
|
4
|
+
//
|
|
5
|
+
// Created by React Native on 2026/01/24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import UIKit
|
|
9
|
+
import React
|
|
10
|
+
import BMTheme
|
|
11
|
+
import BMUIComponents
|
|
12
|
+
import SnapKit
|
|
13
|
+
|
|
14
|
+
/// iOS 原生文本按钮 ViewManager - 对应 Android 的 TextButtonViewManager
|
|
15
|
+
/// 实现纯文本按钮,无背景,支持 Black/Gray/Blue 样式
|
|
16
|
+
@objc(TextButtonViewManager)
|
|
17
|
+
class TextButtonViewManager: RCTViewManager {
|
|
18
|
+
|
|
19
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override func view() -> UIView! {
|
|
24
|
+
return TextButtonView()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// iOS 原生文本按钮视图 - 纯文本按钮实现
|
|
29
|
+
class TextButtonView: UIView {
|
|
30
|
+
|
|
31
|
+
// MARK: - Properties
|
|
32
|
+
private let textLabel: UILabel
|
|
33
|
+
private var currentStyleName: String = "Black"
|
|
34
|
+
|
|
35
|
+
/// RN 事件回调
|
|
36
|
+
@objc var onPress: RCTBubblingEventBlock?
|
|
37
|
+
@objc var onSizeChange: RCTDirectEventBlock?
|
|
38
|
+
|
|
39
|
+
// MARK: - Initialization
|
|
40
|
+
override init(frame: CGRect) {
|
|
41
|
+
textLabel = UILabel()
|
|
42
|
+
super.init(frame: frame)
|
|
43
|
+
setupButton()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
required init?(coder: NSCoder) {
|
|
47
|
+
fatalError("init(coder:) has not been implemented")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// MARK: - Setup
|
|
51
|
+
private func setupButton() {
|
|
52
|
+
// 设置为可点击
|
|
53
|
+
isUserInteractionEnabled = true
|
|
54
|
+
|
|
55
|
+
// 配置文本标签
|
|
56
|
+
textLabel.textAlignment = .center
|
|
57
|
+
textLabel.numberOfLines = 1
|
|
58
|
+
textLabel.adjustsFontSizeToFitWidth = false
|
|
59
|
+
textLabel.lineBreakMode = .byTruncatingTail
|
|
60
|
+
|
|
61
|
+
// 添加标签到容器
|
|
62
|
+
addSubview(textLabel)
|
|
63
|
+
|
|
64
|
+
// 设置约束 - 标签填充整个容器
|
|
65
|
+
textLabel.snp.makeConstraints { make in
|
|
66
|
+
make.edges.equalToSuperview()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 添加点击手势
|
|
70
|
+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(buttonTapped))
|
|
71
|
+
addGestureRecognizer(tapGesture)
|
|
72
|
+
|
|
73
|
+
// 应用默认样式
|
|
74
|
+
applyStyle(currentStyleName)
|
|
75
|
+
|
|
76
|
+
// 发送初始尺寸
|
|
77
|
+
sendSizeChangeEvent()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// MARK: - Event Handlers
|
|
81
|
+
@objc private func buttonTapped() {
|
|
82
|
+
if isUserInteractionEnabled {
|
|
83
|
+
onPress?([:])
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private func sendSizeChangeEvent() {
|
|
88
|
+
DispatchQueue.main.async { [weak self] in
|
|
89
|
+
guard let self = self else { return }
|
|
90
|
+
let size = self.intrinsicContentSize
|
|
91
|
+
|
|
92
|
+
self.onSizeChange?([
|
|
93
|
+
"width": size.width,
|
|
94
|
+
"height": size.height
|
|
95
|
+
])
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// MARK: - RN Props
|
|
100
|
+
|
|
101
|
+
/// 设置按钮文本
|
|
102
|
+
@objc var text: NSString = "" {
|
|
103
|
+
didSet {
|
|
104
|
+
textLabel.text = text as String
|
|
105
|
+
sendSizeChangeEvent()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// 设置样式名称
|
|
110
|
+
@objc var styleName: NSString = "Black" {
|
|
111
|
+
didSet {
|
|
112
|
+
let styleNameStr = styleName as String
|
|
113
|
+
if currentStyleName != styleNameStr {
|
|
114
|
+
currentStyleName = styleNameStr
|
|
115
|
+
applyStyle(styleNameStr)
|
|
116
|
+
sendSizeChangeEvent()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// 设置启用状态
|
|
122
|
+
@objc var enabled: Bool = true {
|
|
123
|
+
didSet {
|
|
124
|
+
isUserInteractionEnabled = enabled
|
|
125
|
+
alpha = enabled ? 1.0 : 0.5
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// MARK: - Style Application
|
|
130
|
+
|
|
131
|
+
/// 应用文本按钮样式
|
|
132
|
+
private func applyStyle(_ styleName: String) {
|
|
133
|
+
// 设置字体 - 使用统一的文本按钮字体
|
|
134
|
+
textLabel.font = .S4Font // 14pt Medium,与Android TextButton对应
|
|
135
|
+
|
|
136
|
+
// 根据样式名称设置文字颜色
|
|
137
|
+
switch styleName.lowercased() {
|
|
138
|
+
case "black":
|
|
139
|
+
textLabel.cexTheme.textColor = .primaryColor // 对应 bm4_color_primary
|
|
140
|
+
case "gray":
|
|
141
|
+
textLabel.cexTheme.textColor = .secondaryColor // 对应 bm4_color_secondary
|
|
142
|
+
case "blue":
|
|
143
|
+
textLabel.cexTheme.textColor = .brandColor // 对应 bm4_color_brand
|
|
144
|
+
default:
|
|
145
|
+
textLabel.cexTheme.textColor = .primaryColor
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 确保背景透明(文本按钮特征)
|
|
149
|
+
backgroundColor = .clear
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// MARK: - Layout
|
|
153
|
+
override var intrinsicContentSize: CGSize {
|
|
154
|
+
// 让标签自己计算尺寸
|
|
155
|
+
textLabel.sizeToFit()
|
|
156
|
+
let labelSize = textLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 28))
|
|
157
|
+
|
|
158
|
+
// 文本按钮最小高度28pt,宽度根据文本内容
|
|
159
|
+
return CGSize(width: max(labelSize.width, 20), height: max(labelSize.height, 28))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
override func layoutSubviews() {
|
|
163
|
+
super.layoutSubviews()
|
|
164
|
+
// UILabel 会自动处理布局
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// MARK: - Touch Feedback
|
|
168
|
+
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
169
|
+
super.touchesBegan(touches, with: event)
|
|
170
|
+
if isUserInteractionEnabled {
|
|
171
|
+
alpha = 0.7 // 点击时降低透明度
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
176
|
+
super.touchesEnded(touches, with: event)
|
|
177
|
+
alpha = enabled ? 1.0 : 0.5 // 恢复透明度
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
181
|
+
super.touchesCancelled(touches, with: event)
|
|
182
|
+
alpha = enabled ? 1.0 : 0.5 // 恢复透明度
|
|
183
|
+
}
|
|
184
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bm-fe/react-native-ui-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "React Native UI Components Library",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
"src/screens",
|
|
11
11
|
"src/types",
|
|
12
12
|
"src/index.ts",
|
|
13
|
+
"android/design/src",
|
|
14
|
+
"android/design/build.gradle",
|
|
15
|
+
"android/design/proguard-rules.pro",
|
|
16
|
+
"android/design/consumer-rules.pro",
|
|
17
|
+
"ios/DemoProject/NativeDesign",
|
|
18
|
+
"react-native-ui-components.podspec",
|
|
19
|
+
"react-native.config.js",
|
|
13
20
|
"README.md",
|
|
14
21
|
"LICENSE"
|
|
15
22
|
],
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "react-native-ui-components"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = "https://github.com/bm-fe/react-native-ui-components"
|
|
10
|
+
s.license = { :type => "MIT", :file => "LICENSE" }
|
|
11
|
+
s.authors = { "bm-fe" => "bm-fe@bitmart.com" }
|
|
12
|
+
s.platforms = { :ios => "13.0" }
|
|
13
|
+
s.source = { :git => "", :tag => "#{s.version}" }
|
|
14
|
+
|
|
15
|
+
# iOS 原生桥接代码(ViewManager)
|
|
16
|
+
s.source_files = "ios/DemoProject/NativeDesign/**/*.{h,m,swift}"
|
|
17
|
+
|
|
18
|
+
s.dependency "React-Core"
|
|
19
|
+
s.dependency "BMUIComponents"
|
|
20
|
+
s.dependency "SDWebImage"
|
|
21
|
+
s.dependency "SDWebImageSVGCoder"
|
|
22
|
+
s.dependency "SnapKit"
|
|
23
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
dependency: {
|
|
3
|
+
platforms: {
|
|
4
|
+
android: {
|
|
5
|
+
// Android 原生模块目录(包含 build.gradle 和 Kotlin 源码)
|
|
6
|
+
sourceDir: './android/design',
|
|
7
|
+
packageImportPath: 'import com.bitmart.react.design.DesignComponentPackage;',
|
|
8
|
+
packageInstance: 'new DesignComponentPackage()',
|
|
9
|
+
},
|
|
10
|
+
ios: {
|
|
11
|
+
// iOS podspec 路径(用于 CocoaPods 自动链接)
|
|
12
|
+
podspecPath: './react-native-ui-components.podspec',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -1,335 +0,0 @@
|
|
|
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;
|