@momo-kits/native-kits 0.0.1-beta.4 → 0.2.0-kt22
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/README.md +176 -5
- package/ios/Application/ApplicationEnvironment.swift +50 -0
- package/ios/Application/Components.swift +263 -0
- package/ios/Application/ComposeApi.swift +22 -0
- package/ios/Application/FloatingButton.swift +172 -0
- package/ios/Application/HeaderRight.swift +271 -0
- package/ios/Application/Screen.swift +249 -0
- package/ios/Badge/Badge.swift +85 -0
- package/ios/Badge/BadgeDot.swift +31 -0
- package/ios/Badge/BadgeRibbon.swift +242 -0
- package/ios/Button/Button.swift +169 -89
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
- package/ios/Checkbox/Checkbox.swift +81 -0
- package/ios/Chip/Chip.swift +96 -0
- package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Colors.swift +21 -19
- package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
- package/ios/Icon/Icon.swift +51 -0
- package/ios/Image/Image.swift +70 -0
- package/ios/Input/ErrorView.swift +55 -0
- package/ios/Input/Input.swift +159 -50
- package/ios/Input/InputPhoneNumber.swift +158 -0
- package/ios/Input/InputSearch.swift +238 -0
- package/ios/Input/InputTextArea.swift +242 -0
- package/ios/Lottie/LottieView.swift +86 -0
- package/ios/OTPKeyboard/KeyboardButton.swift +12 -4
- package/ios/OTPKeyboard/OTPKeyboard.swift +1 -1
- package/ios/Popup/PopupDisplay.swift +287 -0
- package/ios/Popup/PopupInput.swift +96 -0
- package/ios/Popup/PopupPromotion.swift +73 -0
- package/ios/PopupView/FullscreenPopup.swift +251 -0
- package/ios/{Popup → PopupView}/Modifiers.swift +6 -20
- package/ios/{Popup → PopupView}/PopupView.swift +38 -146
- package/ios/PopupView/Utils++.swift +281 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
- package/ios/Swipeable/SwipeCell.swift +39 -31
- package/ios/Switch/Switch.swift +44 -0
- package/ios/Template/Logo/Logo.swift +75 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +120 -0
- package/ios/Typography/Text.swift +140 -0
- package/ios/Typography/Typography.swift +70 -5
- package/ios/native-kits.podspec +20 -15
- package/package.json +6 -6
- package/CODE_OF_CONDUCT.md +0 -133
- package/CONTRIBUTING.md +0 -114
- package/LICENSE +0 -20
- package/android/build.gradle +0 -82
- package/android/gradle.properties +0 -5
- package/android/src/main/AndroidManifest.xml +0 -4
- package/android/src/main/java/com/momoplatform/nativekits/NativeKitsPackage.kt +0 -17
- package/android/src/main/java/com/momoplatform/nativekits/NativeKitsViewManager.kt +0 -20
- package/example/ios/Example/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -63
- package/example/ios/Example/Assets.xcassets/Background.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Border.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Card.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Contents.json +0 -6
- package/example/ios/Example/Assets.xcassets/Error.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Primary.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/PrimaryDark.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/PrimaryLight.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Secondary.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Success.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Text.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/TextSecondary.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Warning.colorset/Contents.json +0 -38
- package/example/ios/Example/ComponentDetail.swift +0 -35
- package/example/ios/Example/ComponentItem.swift +0 -24
- package/example/ios/Example/ComponentModel.swift +0 -33
- package/example/ios/Example/ContentView.swift +0 -53
- package/example/ios/Example/Example.entitlements +0 -10
- package/example/ios/Example/Foundation/ButtonDemo.swift +0 -80
- package/example/ios/Example/Foundation/Popup/ActionSheets.swift +0 -136
- package/example/ios/Example/Foundation/Popup/Floats.swift +0 -127
- package/example/ios/Example/Foundation/Popup/Popups.swift +0 -101
- package/example/ios/Example/Foundation/PopupDemo.swift +0 -366
- package/example/ios/Example/Foundation/TextInputDemo.swift +0 -31
- package/example/ios/Example/Foundation/TypographyDemo.swift +0 -53
- package/example/ios/Example/Foundation.swift +0 -26
- package/example/ios/Example/Info.plist +0 -18
- package/example/ios/Example/Library/SwipeableDemo.swift +0 -105
- package/example/ios/Example/Library.swift +0 -26
- package/example/ios/Example/Main.swift +0 -11
- package/example/ios/Example/Overview.swift +0 -20
- package/example/ios/Example/Preview Content/Preview Assets.xcassets/Contents.json +0 -6
- package/example/ios/Example/Resource/SFProText-Black.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Bold.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Heavy.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Light.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Medium.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Regular.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Semibold.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Thin.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Ultralight.ttf +0 -0
- package/example/ios/Example/Resource/foundation.json +0 -33
- package/example/ios/Example/Resource/library.json +0 -8
- package/example/ios/Example/Utils.swift +0 -30
- package/example/ios/Example/ViewModifier/DarkViewModifier.swift +0 -22
- package/example/ios/Example.xcodeproj/project.pbxproj +0 -570
- package/example/ios/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/example/ios/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/example/ios/Example.xcodeproj/project.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/example/ios/Example.xcworkspace/contents.xcworkspacedata +0 -10
- package/example/ios/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
- package/example/ios/Podfile +0 -6
- package/example/ios/Podfile.lock +0 -16
- package/example/ios/Pods/Local Podspecs/MoMoUIKits.podspec.json +0 -29
- package/example/ios/Pods/Manifest.lock +0 -16
- package/example/ios/Pods/Pods.xcodeproj/project.pbxproj +0 -661
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -25
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-dummy.m +0 -5
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-prefix.pch +0 -12
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-umbrella.h +0 -16
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.debug.xcconfig +0 -12
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.modulemap +0 -6
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.release.xcconfig +0 -12
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown +0 -3
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist +0 -29
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m +0 -5
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-umbrella.h +0 -16
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig +0 -14
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.modulemap +0 -6
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig +0 -14
- package/ios/Colors+Spacing/Radius.swift +0 -11
- package/ios/Popup/Utils++.swift +0 -163
- package/ios/Theme.md +0 -18
- package/publish.sh +0 -28
- /package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Spacing.swift +0 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
public struct HeaderRightData {
|
|
5
|
+
var useShortcut: Bool
|
|
6
|
+
var useMore: Bool
|
|
7
|
+
var tools: [ToolGroup]
|
|
8
|
+
var toolCallback: ((String) -> Void)?
|
|
9
|
+
var useSystemTools: Bool
|
|
10
|
+
|
|
11
|
+
public init(
|
|
12
|
+
useShortcut: Bool = false,
|
|
13
|
+
useMore: Bool = false,
|
|
14
|
+
tools: [ToolGroup] = [],
|
|
15
|
+
toolCallback: ((String) -> Void)? = nil,
|
|
16
|
+
useSystemTools: Bool = true
|
|
17
|
+
) {
|
|
18
|
+
self.useShortcut = useShortcut
|
|
19
|
+
self.useMore = useMore
|
|
20
|
+
self.tools = tools
|
|
21
|
+
self.toolCallback = toolCallback
|
|
22
|
+
self.useSystemTools = useSystemTools
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public struct Tool {
|
|
27
|
+
var key: String
|
|
28
|
+
var icon: String
|
|
29
|
+
var showBadge: Bool
|
|
30
|
+
var name: [String: String]
|
|
31
|
+
var showRightIcon: Bool
|
|
32
|
+
|
|
33
|
+
public init(
|
|
34
|
+
key: String,
|
|
35
|
+
icon: String,
|
|
36
|
+
name: [String: String] = [:],
|
|
37
|
+
showBadge: Bool = false,
|
|
38
|
+
showRightIcon: Bool = true
|
|
39
|
+
) {
|
|
40
|
+
self.key = key
|
|
41
|
+
self.icon = icon
|
|
42
|
+
self.showBadge = showBadge
|
|
43
|
+
self.name = name
|
|
44
|
+
self.showRightIcon = showRightIcon
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func toMap() -> [String: Any] {
|
|
48
|
+
return [
|
|
49
|
+
"key": key,
|
|
50
|
+
"icon": icon,
|
|
51
|
+
"showBadge": showBadge,
|
|
52
|
+
"name": name,
|
|
53
|
+
"showRightIcon": showRightIcon
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public struct ToolGroup {
|
|
59
|
+
var title: [String: String]
|
|
60
|
+
var items: [Tool]
|
|
61
|
+
|
|
62
|
+
public init(
|
|
63
|
+
title: [String: String] = [:],
|
|
64
|
+
items: [Tool]
|
|
65
|
+
) {
|
|
66
|
+
self.title = title
|
|
67
|
+
self.items = items
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func toMap() -> [String: Any] {
|
|
71
|
+
return [
|
|
72
|
+
"title": title,
|
|
73
|
+
"items": items.map { $0.toMap() }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
struct NavigationButtonConfig {
|
|
79
|
+
let icon: String
|
|
80
|
+
let onPress: () -> Void
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// MARK: - Components
|
|
84
|
+
public struct HeaderRight: View {
|
|
85
|
+
public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
|
|
86
|
+
self.headerRight = headerRight
|
|
87
|
+
self.tintColor = tintColor
|
|
88
|
+
}
|
|
89
|
+
var headerRight: HeaderRightData?
|
|
90
|
+
var tintColor: Color? = nil
|
|
91
|
+
|
|
92
|
+
public var body: some View {
|
|
93
|
+
HStack(alignment: .center) {
|
|
94
|
+
ToolkitHeaderRight(headerRight: headerRight, tintColor: tintColor)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
struct ToolkitHeaderRight: View {
|
|
100
|
+
var headerRight: HeaderRightData?
|
|
101
|
+
var tintColor: Color?
|
|
102
|
+
@State private var isFavorite: Bool = false
|
|
103
|
+
@State private var isLoading: Bool = false
|
|
104
|
+
@EnvironmentObject private var environment: ApplicationEnvironment
|
|
105
|
+
|
|
106
|
+
// MARK: - Actions
|
|
107
|
+
private func onPressShortcut() {
|
|
108
|
+
environment.composeApi?.request(
|
|
109
|
+
funcName: "onToolAction",
|
|
110
|
+
params: ["item": ["key": "onFavorite"]]
|
|
111
|
+
) { _ in
|
|
112
|
+
isFavorite.toggle()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private func onPressHelpCenter() {
|
|
117
|
+
let context = environment.applicationContext
|
|
118
|
+
let paramMap: [String: Any] = [
|
|
119
|
+
"appId": context?.appId,
|
|
120
|
+
"code": context?.appCode,
|
|
121
|
+
"name": context?.appName,
|
|
122
|
+
"icon": context?.appIcon,
|
|
123
|
+
"description": context?.description
|
|
124
|
+
]
|
|
125
|
+
environment.composeApi?.request(
|
|
126
|
+
funcName: "showHelpCenter",
|
|
127
|
+
params: paramMap
|
|
128
|
+
) { _ in }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private func onPressClose() {
|
|
132
|
+
environment.composeApi?.request(
|
|
133
|
+
funcName: "dismissAll",
|
|
134
|
+
params: ""
|
|
135
|
+
) { _ in }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private func onPressMore() {
|
|
139
|
+
let context = environment.applicationContext
|
|
140
|
+
let params: [String: Any] = [
|
|
141
|
+
"useSystemTools": headerRight?.useSystemTools,
|
|
142
|
+
"tools": headerRight?.tools.map { $0.toMap() } ?? [],
|
|
143
|
+
"context": [
|
|
144
|
+
"appId": context?.appId,
|
|
145
|
+
"code": context?.appCode,
|
|
146
|
+
"name": context?.appName,
|
|
147
|
+
"icon": context?.appIcon,
|
|
148
|
+
"description": context?.description,
|
|
149
|
+
"support": context?.support,
|
|
150
|
+
"toolkitConfig": context?.toolkitConfig,
|
|
151
|
+
"providerId": context?.providerId,
|
|
152
|
+
"permissions": context?.permissions
|
|
153
|
+
]
|
|
154
|
+
]
|
|
155
|
+
environment.composeApi?.request(
|
|
156
|
+
funcName: "showTools",
|
|
157
|
+
params: params
|
|
158
|
+
) { response in
|
|
159
|
+
do {
|
|
160
|
+
if let jsonData = response.data(using: .utf8),
|
|
161
|
+
let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
|
|
162
|
+
let toolResponse = json["response"] as? String {
|
|
163
|
+
headerRight?.toolCallback?(toolResponse)
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
print("Error parsing response:", error)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private func getNavigationButtonConfig() -> NavigationButtonConfig {
|
|
172
|
+
let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
|
|
173
|
+
var icon = isFavorite ? "pin_star_checked" : "pin_star"
|
|
174
|
+
var onClickHandler: () -> Void = onPressShortcut
|
|
175
|
+
|
|
176
|
+
if totalTools > 1 || headerRight?.useMore == true {
|
|
177
|
+
icon = "navigation_more_icon"
|
|
178
|
+
onClickHandler = onPressMore
|
|
179
|
+
} else if totalTools == 1, let singleTool = headerRight?.tools.first?.items.first {
|
|
180
|
+
icon = singleTool.icon
|
|
181
|
+
onClickHandler = {
|
|
182
|
+
headerRight?.toolCallback?(singleTool.key)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
var body: some View {
|
|
190
|
+
let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
|
|
191
|
+
let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
|
|
192
|
+
let navButtonConfig = getNavigationButtonConfig()
|
|
193
|
+
let showBadge = headerRight?.tools.contains { group in
|
|
194
|
+
group.items.contains { $0.showBadge }
|
|
195
|
+
} ?? false
|
|
196
|
+
|
|
197
|
+
HStack(alignment: .center, spacing: 0) {
|
|
198
|
+
if headerRight?.useShortcut == true {
|
|
199
|
+
NavigationButton(
|
|
200
|
+
disabled: isLoading,
|
|
201
|
+
icon: navButtonConfig.icon,
|
|
202
|
+
showBadge: showBadge,
|
|
203
|
+
onClick: navButtonConfig.onPress,
|
|
204
|
+
tintColor: tintColor
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
HStack(alignment: .center, spacing: 0) {
|
|
209
|
+
Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
|
|
210
|
+
.padding(4)
|
|
211
|
+
.onTapGesture {
|
|
212
|
+
onPressHelpCenter()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
Rectangle()
|
|
216
|
+
.fill(tintColor ?? Colors.black20)
|
|
217
|
+
.frame(width: 0.5, height: 12)
|
|
218
|
+
|
|
219
|
+
Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
|
|
220
|
+
.padding(4)
|
|
221
|
+
.onTapGesture {
|
|
222
|
+
onPressClose()
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
.frame(width: 65, height: 28)
|
|
226
|
+
.background(backgroundButtonColor)
|
|
227
|
+
.cornerRadius(14)
|
|
228
|
+
.overlay(
|
|
229
|
+
RoundedRectangle(cornerRadius: 14)
|
|
230
|
+
.stroke(borderColor, lineWidth: 0.2)
|
|
231
|
+
)
|
|
232
|
+
.padding(.leading, Spacing.S)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
struct NavigationButton: View {
|
|
238
|
+
var disabled: Bool
|
|
239
|
+
var icon: String
|
|
240
|
+
var showBadge: Bool
|
|
241
|
+
var onClick: () -> Void
|
|
242
|
+
var tintColor: Color?
|
|
243
|
+
|
|
244
|
+
var body: some View {
|
|
245
|
+
let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
|
|
246
|
+
let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
|
|
247
|
+
|
|
248
|
+
ZStack {
|
|
249
|
+
Circle()
|
|
250
|
+
.fill(backgroundButtonColor)
|
|
251
|
+
.overlay(
|
|
252
|
+
Circle()
|
|
253
|
+
.stroke(borderColor, lineWidth: 0.2)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
Icon(source: icon, size: 16, color: tintColor)
|
|
257
|
+
|
|
258
|
+
if showBadge {
|
|
259
|
+
BadgeDot(size: .small)
|
|
260
|
+
.offset(x: -2, y: -2)
|
|
261
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
.frame(width: 28, height: 28)
|
|
265
|
+
.onTapGesture {
|
|
266
|
+
if !disabled {
|
|
267
|
+
onClick()
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Screen.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by sophia on 5/2/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import SwiftUI
|
|
9
|
+
|
|
10
|
+
public struct Screen<Content: View>: View {
|
|
11
|
+
// MARK: - Properties
|
|
12
|
+
|
|
13
|
+
var backgroundColor: Color?
|
|
14
|
+
var headerTransparent: Bool = false
|
|
15
|
+
var fullScreenContent: Bool = false
|
|
16
|
+
var tintColor: Color? = Colors.black17
|
|
17
|
+
var isBack: Bool
|
|
18
|
+
var headerType: HeaderType
|
|
19
|
+
var verticalAlignment: VerticalAlignment
|
|
20
|
+
var horizontalAlignment: HorizontalAlignment
|
|
21
|
+
var title: String
|
|
22
|
+
var titlePosition: TitlePosition
|
|
23
|
+
var goBack: (() -> Void)?
|
|
24
|
+
var scrollable: Bool
|
|
25
|
+
var onContentLayout: ((CGRect) -> Void)?
|
|
26
|
+
var useAvoidKeyboard: Bool
|
|
27
|
+
var footer: (() -> AnyView)?
|
|
28
|
+
var headerRight: (()->any View)? = {HeaderRight()}
|
|
29
|
+
var animatedHeader: AnimatedHeader? = nil
|
|
30
|
+
var layoutOffset: CGFloat
|
|
31
|
+
var inputSearchProps: InputSearchProps?
|
|
32
|
+
var fabProps: FabProps?
|
|
33
|
+
var content: () -> Content
|
|
34
|
+
|
|
35
|
+
public init(
|
|
36
|
+
backgroundColor: Color? = nil,
|
|
37
|
+
headerTransparent: Bool = false,
|
|
38
|
+
fullScreenContent: Bool = false,
|
|
39
|
+
tintColor: Color = Colors.black17,
|
|
40
|
+
isBack: Bool = true,
|
|
41
|
+
headerType: HeaderType = .default,
|
|
42
|
+
verticalAlignment: VerticalAlignment = .top,
|
|
43
|
+
horizontalAlignment: HorizontalAlignment = .leading,
|
|
44
|
+
title: String = "Stack",
|
|
45
|
+
titlePosition: TitlePosition = .left,
|
|
46
|
+
goBack: (() -> Void)? = nil,
|
|
47
|
+
scrollable: Bool = true,
|
|
48
|
+
onContentLayout: ((CGRect) -> Void)? = nil,
|
|
49
|
+
useAvoidKeyboard: Bool = true,
|
|
50
|
+
footer: (() -> AnyView)? = nil,
|
|
51
|
+
headerRight: (()->any View)? = {HeaderRight()},
|
|
52
|
+
animatedHeader: AnimatedHeader? = nil,
|
|
53
|
+
layoutOffset: CGFloat = 56,
|
|
54
|
+
inputSearchProps: InputSearchProps? = nil,
|
|
55
|
+
fabProps: FabProps? = nil,
|
|
56
|
+
@ViewBuilder content: @escaping () -> Content
|
|
57
|
+
) {
|
|
58
|
+
self.backgroundColor = backgroundColor
|
|
59
|
+
self.headerTransparent = headerTransparent
|
|
60
|
+
self.fullScreenContent = fullScreenContent
|
|
61
|
+
self.tintColor = tintColor
|
|
62
|
+
self.isBack = isBack
|
|
63
|
+
self.headerType = headerType
|
|
64
|
+
self.verticalAlignment = verticalAlignment
|
|
65
|
+
self.horizontalAlignment = horizontalAlignment
|
|
66
|
+
self.title = title
|
|
67
|
+
self.titlePosition = titlePosition
|
|
68
|
+
self.goBack = goBack
|
|
69
|
+
self.scrollable = scrollable
|
|
70
|
+
self.onContentLayout = onContentLayout
|
|
71
|
+
self.useAvoidKeyboard = useAvoidKeyboard
|
|
72
|
+
self.footer = footer
|
|
73
|
+
self.headerRight = headerRight
|
|
74
|
+
self.animatedHeader = animatedHeader
|
|
75
|
+
self.layoutOffset = layoutOffset
|
|
76
|
+
self.inputSearchProps = inputSearchProps
|
|
77
|
+
self.fabProps = fabProps
|
|
78
|
+
self.content = content
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@State private var scrollOffset: CGFloat = 0
|
|
82
|
+
@State private var keyboardHeight: CGFloat = 0
|
|
83
|
+
|
|
84
|
+
// MARK: - Body
|
|
85
|
+
|
|
86
|
+
public var body: some View {
|
|
87
|
+
GeometryReader { geometry in
|
|
88
|
+
let topInset = geometry.safeAreaInsets.top
|
|
89
|
+
let keyboardSize: CGFloat = useAvoidKeyboard ? max(keyboardHeight, 0) : 0
|
|
90
|
+
let height = geometry.size.height
|
|
91
|
+
let bottomInset = geometry.safeAreaInsets.bottom
|
|
92
|
+
let screenHeight = height + topInset + bottomInset
|
|
93
|
+
|
|
94
|
+
ZStack(alignment: .top) {
|
|
95
|
+
// Background color
|
|
96
|
+
(backgroundColor ?? Color.white)
|
|
97
|
+
.edgesIgnoringSafeArea(.all)
|
|
98
|
+
|
|
99
|
+
VStack(spacing: 0) {
|
|
100
|
+
ZStack(alignment: fabProps?.position == .center ? .bottom : .bottomTrailing) {
|
|
101
|
+
VStack(alignment: horizontalAlignment, spacing: 0) {
|
|
102
|
+
if animatedHeader != nil || (headerTransparent && fullScreenContent) {
|
|
103
|
+
ZStack(alignment: .top) {
|
|
104
|
+
// MARK: - Header
|
|
105
|
+
Header(
|
|
106
|
+
headerType: headerType,
|
|
107
|
+
titlePosition: titlePosition,
|
|
108
|
+
title: title,
|
|
109
|
+
headerRight: headerRight,
|
|
110
|
+
goBack: goBack,
|
|
111
|
+
opacity: min(scrollOffset / 114, 1),
|
|
112
|
+
animatedHeader: animatedHeader,
|
|
113
|
+
scrollState: scrollOffset,
|
|
114
|
+
inputSearchProps: inputSearchProps,
|
|
115
|
+
tintColor: tintColor
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
.background(
|
|
119
|
+
HeaderBackground(
|
|
120
|
+
headerType: headerType,
|
|
121
|
+
scrollState: scrollOffset,
|
|
122
|
+
headerTransparent: headerTransparent,
|
|
123
|
+
fullScreenContent: fullScreenContent
|
|
124
|
+
|
|
125
|
+
)
|
|
126
|
+
.overlay(animatedHeader?.composable(scrollOffset)
|
|
127
|
+
.frame(maxWidth: .infinity)
|
|
128
|
+
.aspectRatio(animatedHeader?.aspectRatio.value, contentMode: .fill)
|
|
129
|
+
)
|
|
130
|
+
).zIndex(100)
|
|
131
|
+
|
|
132
|
+
VStack {
|
|
133
|
+
if scrollable {
|
|
134
|
+
ScrollView(.vertical, showsIndicators: false) {
|
|
135
|
+
VStack {
|
|
136
|
+
content()
|
|
137
|
+
}
|
|
138
|
+
.background(GeometryReader { geo -> Color in
|
|
139
|
+
DispatchQueue.main.async {
|
|
140
|
+
self.scrollOffset = -geo.frame(in: .global).origin.y
|
|
141
|
+
}
|
|
142
|
+
return Color.clear
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
content()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
.edgesIgnoringSafeArea(.all)
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
// MARK: - Header
|
|
153
|
+
Header(
|
|
154
|
+
headerType: headerType,
|
|
155
|
+
titlePosition: titlePosition,
|
|
156
|
+
title: title,
|
|
157
|
+
headerRight: headerRight,
|
|
158
|
+
goBack: goBack,
|
|
159
|
+
opacity: min(scrollOffset / 114, 1),
|
|
160
|
+
animatedHeader: animatedHeader,
|
|
161
|
+
scrollState: scrollOffset,
|
|
162
|
+
inputSearchProps: inputSearchProps,
|
|
163
|
+
tintColor: tintColor
|
|
164
|
+
)
|
|
165
|
+
.background(
|
|
166
|
+
HeaderBackground(
|
|
167
|
+
headerType: headerType,
|
|
168
|
+
scrollState: scrollOffset,
|
|
169
|
+
headerTransparent: headerTransparent,
|
|
170
|
+
fullScreenContent: fullScreenContent
|
|
171
|
+
)
|
|
172
|
+
.edgesIgnoringSafeArea(.top)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if scrollable {
|
|
176
|
+
ScrollView(.vertical, showsIndicators: false) {
|
|
177
|
+
VStack {
|
|
178
|
+
content()
|
|
179
|
+
}
|
|
180
|
+
.background(GeometryReader { geo -> Color in
|
|
181
|
+
DispatchQueue.main.async {
|
|
182
|
+
self.scrollOffset = -geo.frame(in: .global).origin.y
|
|
183
|
+
}
|
|
184
|
+
return Color.clear
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
content()
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if let fabProps = fabProps {
|
|
194
|
+
FloatingButton(
|
|
195
|
+
props: fabProps,
|
|
196
|
+
keyboardOffset: useAvoidKeyboard ? keyboardSize : 0,
|
|
197
|
+
scrollOffset: scrollOffset
|
|
198
|
+
)
|
|
199
|
+
.padding(.horizontal, Spacing.M)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
// MARK: - Footer
|
|
205
|
+
if let footerView = footer?() {
|
|
206
|
+
Footer(footerView: footerView, keyboardSize: keyboardSize)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// MARK: - Keyboard Handling
|
|
214
|
+
|
|
215
|
+
private func observeKeyboard() {
|
|
216
|
+
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main) { notification in
|
|
217
|
+
if useAvoidKeyboard,
|
|
218
|
+
let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
|
|
219
|
+
withAnimation(.easeOut(duration: 0.25)) {
|
|
220
|
+
keyboardHeight = UIScreen.main.bounds.height - endFrame.origin.y
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// MARK: - Footer
|
|
228
|
+
|
|
229
|
+
struct Footer: View {
|
|
230
|
+
var footerView: AnyView
|
|
231
|
+
var keyboardSize: CGFloat
|
|
232
|
+
|
|
233
|
+
var body: some View {
|
|
234
|
+
VStack {
|
|
235
|
+
footerView
|
|
236
|
+
}
|
|
237
|
+
.padding(.vertical, Spacing.S)
|
|
238
|
+
.padding(.horizontal, Spacing.M)
|
|
239
|
+
.padding(.bottom, keyboardSize)
|
|
240
|
+
.background(Colors.black01)
|
|
241
|
+
.overlay(
|
|
242
|
+
Rectangle()
|
|
243
|
+
.fill(Colors.black20.opacity(0.1))
|
|
244
|
+
.frame(height: 4)
|
|
245
|
+
.shadow(color: Colors.black20.opacity(0.2), radius: 10, y: -2),
|
|
246
|
+
alignment: .top
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
public struct Badge: View {
|
|
4
|
+
let label: String
|
|
5
|
+
let backgroundColor: Color?
|
|
6
|
+
|
|
7
|
+
private var primaryColors: [Color] {
|
|
8
|
+
[
|
|
9
|
+
Colors.black03,
|
|
10
|
+
Colors.pink03,
|
|
11
|
+
Colors.violet03,
|
|
12
|
+
Colors.indigo03,
|
|
13
|
+
Colors.blue03,
|
|
14
|
+
Colors.mint03,
|
|
15
|
+
Colors.green03,
|
|
16
|
+
Colors.lime03,
|
|
17
|
+
Colors.yellow03,
|
|
18
|
+
Colors.gold03,
|
|
19
|
+
Colors.orange03,
|
|
20
|
+
Colors.red03
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public init(label: String = "Label", backgroundColor: Color? = nil) {
|
|
25
|
+
self.label = label
|
|
26
|
+
self.backgroundColor = backgroundColor
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private func isNumber(_ text: String) -> Bool {
|
|
30
|
+
return text.range(of: "^\\d+$", options: .regularExpression) != nil
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private func formatTitle(_ text: String) -> String {
|
|
34
|
+
if isNumber(text), let number = Int(text), number > 99 {
|
|
35
|
+
return "99+"
|
|
36
|
+
}
|
|
37
|
+
return text
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private var badgeColor: Color {
|
|
41
|
+
// If a custom background color is provided, use it
|
|
42
|
+
if let bgColor = backgroundColor {
|
|
43
|
+
return bgColor
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Otherwise, use default colors based on label type
|
|
47
|
+
if isNumber(label) {
|
|
48
|
+
return Colors.red03 // error.primary
|
|
49
|
+
} else {
|
|
50
|
+
return Colors.orange03 // warning.primary
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public var body: some View {
|
|
55
|
+
Text(formatTitle(label))
|
|
56
|
+
.font(.system(size: scaleSize(10), weight: .bold))
|
|
57
|
+
.foregroundColor(Colors.black01)
|
|
58
|
+
.padding(.horizontal, Spacing.XS)
|
|
59
|
+
.frame(minWidth: scaleSize(16), minHeight: scaleSize(16))
|
|
60
|
+
.background(badgeColor)
|
|
61
|
+
.cornerRadius(Radius.M)
|
|
62
|
+
.overlay(
|
|
63
|
+
RoundedRectangle(cornerRadius: Radius.M)
|
|
64
|
+
.stroke(Colors.black01, lineWidth: 1)
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// MARK: - Preview
|
|
70
|
+
#if DEBUG
|
|
71
|
+
struct Badge_Previews: PreviewProvider {
|
|
72
|
+
static var previews: some View {
|
|
73
|
+
VStack(spacing: 20) {
|
|
74
|
+
Badge(label: "5")
|
|
75
|
+
Badge(label: "99")
|
|
76
|
+
Badge(label: "100")
|
|
77
|
+
Badge(label: "New")
|
|
78
|
+
Badge(label: "Hot", backgroundColor: Colors.pink03)
|
|
79
|
+
}
|
|
80
|
+
.padding()
|
|
81
|
+
.previewLayout(.sizeThatFits)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
#endif
|
|
85
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
public enum DotSize {
|
|
4
|
+
case small
|
|
5
|
+
case large
|
|
6
|
+
|
|
7
|
+
var size: CGFloat {
|
|
8
|
+
switch self {
|
|
9
|
+
case .small: return 10
|
|
10
|
+
case .large: return 16
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public struct BadgeDot: View {
|
|
16
|
+
var size: DotSize
|
|
17
|
+
|
|
18
|
+
public init(size: DotSize = .large) {
|
|
19
|
+
self.size = size
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public var body: some View {
|
|
23
|
+
Circle()
|
|
24
|
+
.fill(Colors.red03)
|
|
25
|
+
.frame(width: size.size, height: size.size)
|
|
26
|
+
.overlay(
|
|
27
|
+
Circle()
|
|
28
|
+
.stroke(Colors.black01, lineWidth: 1)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|