@momo-kits/native-kits 0.163.1-sp.8-debug → 0.163.1-sp.9-debug
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/.claude/settings.local.json +3 -1
- package/compose/build.gradle.kts +1 -1
- package/compose/compose.podspec +1 -1
- package/gradle.properties +1 -1
- package/ios/Application/Navigation/Navigator.swift +30 -24
- package/ios/Application/StackScreen.swift +1 -7
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +87 -73
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +55 -249
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +89 -82
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +74 -104
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +47 -51
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +63 -7
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +90 -81
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +73 -97
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DemoScreen.swift +418 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +46 -4
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +51 -47
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +80 -45
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconDemo.swift +67 -90
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +49 -62
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +66 -116
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +104 -98
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +76 -118
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +73 -86
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +69 -54
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +74 -54
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +73 -20
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +68 -38
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +55 -52
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +47 -51
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PlaygroundData.swift +76 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +67 -93
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +34 -43
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +83 -71
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +55 -23
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +58 -46
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SectionDemo.swift +57 -74
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +41 -12
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +67 -127
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +54 -110
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +69 -79
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +78 -58
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +76 -65
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +82 -127
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +44 -19
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +92 -168
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +79 -27
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +0 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +87 -92
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +94 -163
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +70 -70
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +68 -114
- package/package.json +2 -2
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DemoScreen.swift
|
|
3
|
+
// Example
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI port of Compose's `sample/shared/.../screens/DemoScreen.kt`: the demo
|
|
6
|
+
// shell every component uses. `DemoScreen(preview:playground:)` renders a two-tab
|
|
7
|
+
// `BottomTab` — a static "Preview" showcase and an interactive, data-driven
|
|
8
|
+
// "Playground" props editor (BasicColumnBox / PlaygroundParams / PlaygroundView /
|
|
9
|
+
// PlaygroundTab). Must be pushed inside the `NavigationContainer` (BottomTab reads
|
|
10
|
+
// the Navigator) with `headerType: .none, scrollable: false`.
|
|
11
|
+
//
|
|
12
|
+
|
|
13
|
+
import MoMoUIKits
|
|
14
|
+
import SwiftUI
|
|
15
|
+
|
|
16
|
+
// MARK: - DemoScreen shell
|
|
17
|
+
|
|
18
|
+
@available(iOS 16.0, *)
|
|
19
|
+
struct DemoScreen<Preview: View, Playground: View>: View {
|
|
20
|
+
@ViewBuilder let preview: () -> Preview
|
|
21
|
+
@ViewBuilder let playground: () -> Playground
|
|
22
|
+
|
|
23
|
+
var body: some View {
|
|
24
|
+
BottomTab(items: [
|
|
25
|
+
BottomTabItem(
|
|
26
|
+
name: "Preview",
|
|
27
|
+
label: "Preview",
|
|
28
|
+
icon: "security_eye_open",
|
|
29
|
+
options: NavigationOptions(headerTitle: .default("Preview"), scrollable: true),
|
|
30
|
+
// StackScreen provides the scroll when scrollable == true.
|
|
31
|
+
screen: {
|
|
32
|
+
VStack(alignment: .leading, spacing: 0) { preview() }
|
|
33
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
34
|
+
}
|
|
35
|
+
),
|
|
36
|
+
BottomTabItem(
|
|
37
|
+
name: "Playground",
|
|
38
|
+
label: "Playground",
|
|
39
|
+
icon: "24_basic_chart",
|
|
40
|
+
// Playground owns its own scroll (Props list), so disable the outer one.
|
|
41
|
+
options: NavigationOptions(headerTitle: .default("Playground"), scrollable: false),
|
|
42
|
+
screen: { playground() }
|
|
43
|
+
),
|
|
44
|
+
])
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MARK: - BasicColumnBox (titled surface card)
|
|
49
|
+
|
|
50
|
+
struct BasicColumnBox<Content: View>: View {
|
|
51
|
+
let title: String
|
|
52
|
+
let alignment: HorizontalAlignment
|
|
53
|
+
let background: Color?
|
|
54
|
+
@ViewBuilder let content: () -> Content
|
|
55
|
+
|
|
56
|
+
init(
|
|
57
|
+
_ title: String = "",
|
|
58
|
+
alignment: HorizontalAlignment = .center,
|
|
59
|
+
background: Color? = nil,
|
|
60
|
+
@ViewBuilder content: @escaping () -> Content
|
|
61
|
+
) {
|
|
62
|
+
self.title = title
|
|
63
|
+
self.alignment = alignment
|
|
64
|
+
self.background = background
|
|
65
|
+
self.content = content
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
var body: some View {
|
|
69
|
+
VStack(alignment: .leading, spacing: Spacing.S) {
|
|
70
|
+
if !title.isEmpty {
|
|
71
|
+
MomoText(title, typography: .headerMBold)
|
|
72
|
+
.padding(.top, Spacing.S)
|
|
73
|
+
}
|
|
74
|
+
VStack(alignment: alignment, spacing: Spacing.S) {
|
|
75
|
+
content()
|
|
76
|
+
}
|
|
77
|
+
.frame(maxWidth: .infinity, alignment: alignmentFrame)
|
|
78
|
+
.padding(Spacing.M)
|
|
79
|
+
.background(
|
|
80
|
+
RoundedRectangle(cornerRadius: Radius.M)
|
|
81
|
+
.fill(background ?? defaultTheme.colors.background.surface)
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
.padding(Spacing.M)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private var alignmentFrame: Alignment {
|
|
88
|
+
switch alignment {
|
|
89
|
+
case .leading: return .leading
|
|
90
|
+
case .trailing: return .trailing
|
|
91
|
+
default: return .center
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// MARK: - Playground model (mirrors PlaygroundParams / PlaygroundDataType)
|
|
97
|
+
|
|
98
|
+
enum PlaygroundDataType {
|
|
99
|
+
case string, number, option, color, radio, checkbox, slider
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// One selectable entry for OPTION / COLOR / RADIO params. Replaces Compose's
|
|
103
|
+
/// `Map<String, Any>` + `Map.Entry`. Equality is by `key`.
|
|
104
|
+
struct PlaygroundOption: Identifiable, Hashable {
|
|
105
|
+
let id = UUID()
|
|
106
|
+
let key: String
|
|
107
|
+
let value: AnyHashable
|
|
108
|
+
|
|
109
|
+
init(_ key: String, _ value: AnyHashable) {
|
|
110
|
+
self.key = key
|
|
111
|
+
self.value = value
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static func == (lhs: PlaygroundOption, rhs: PlaygroundOption) -> Bool { lhs.key == rhs.key }
|
|
115
|
+
func hash(into hasher: inout Hasher) { hasher.combine(key) }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// A single editable prop row. Build with the per-type factory methods so the
|
|
119
|
+
/// call site stays type-safe (`.radio("Type", buttonTypeList, $type)`).
|
|
120
|
+
struct PlaygroundParams: Identifiable {
|
|
121
|
+
let id = UUID()
|
|
122
|
+
let title: String
|
|
123
|
+
let type: PlaygroundDataType
|
|
124
|
+
let list: [PlaygroundOption]
|
|
125
|
+
let enable: Binding<Bool>
|
|
126
|
+
let required: Bool
|
|
127
|
+
let sliderMin: Double
|
|
128
|
+
let sliderMax: Double
|
|
129
|
+
let sliderStep: Double
|
|
130
|
+
|
|
131
|
+
// Only the binding matching `type` is non-nil.
|
|
132
|
+
let text: Binding<String>?
|
|
133
|
+
let selection: Binding<PlaygroundOption?>?
|
|
134
|
+
let flag: Binding<Bool>?
|
|
135
|
+
let number: Binding<Double>?
|
|
136
|
+
|
|
137
|
+
private init(
|
|
138
|
+
title: String,
|
|
139
|
+
type: PlaygroundDataType,
|
|
140
|
+
list: [PlaygroundOption] = [],
|
|
141
|
+
enable: Binding<Bool> = .constant(true),
|
|
142
|
+
required: Bool = false,
|
|
143
|
+
sliderMin: Double = 0,
|
|
144
|
+
sliderMax: Double = 100,
|
|
145
|
+
sliderStep: Double = 1,
|
|
146
|
+
text: Binding<String>? = nil,
|
|
147
|
+
selection: Binding<PlaygroundOption?>? = nil,
|
|
148
|
+
flag: Binding<Bool>? = nil,
|
|
149
|
+
number: Binding<Double>? = nil
|
|
150
|
+
) {
|
|
151
|
+
self.title = title
|
|
152
|
+
self.type = type
|
|
153
|
+
self.list = list
|
|
154
|
+
self.enable = enable
|
|
155
|
+
self.required = required
|
|
156
|
+
self.sliderMin = sliderMin
|
|
157
|
+
self.sliderMax = sliderMax
|
|
158
|
+
self.sliderStep = sliderStep
|
|
159
|
+
self.text = text
|
|
160
|
+
self.selection = selection
|
|
161
|
+
self.flag = flag
|
|
162
|
+
self.number = number
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static func string(_ title: String, _ text: Binding<String>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
|
|
166
|
+
PlaygroundParams(title: title, type: .string, enable: enable, required: required, text: text)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static func number(_ title: String, _ text: Binding<String>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
|
|
170
|
+
PlaygroundParams(title: title, type: .number, enable: enable, required: required, text: text)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static func option(_ title: String, _ list: [PlaygroundOption], _ selection: Binding<PlaygroundOption?>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
|
|
174
|
+
PlaygroundParams(title: title, type: .option, list: list, enable: enable, required: required, selection: selection)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static func color(_ title: String, _ list: [PlaygroundOption], _ selection: Binding<PlaygroundOption?>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
|
|
178
|
+
PlaygroundParams(title: title, type: .color, list: list, enable: enable, required: required, selection: selection)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static func radio(_ title: String, _ list: [PlaygroundOption], _ selection: Binding<PlaygroundOption?>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
|
|
182
|
+
PlaygroundParams(title: title, type: .radio, list: list, enable: enable, required: required, selection: selection)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static func checkbox(_ title: String, _ flag: Binding<Bool>, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
|
|
186
|
+
PlaygroundParams(title: title, type: .checkbox, enable: enable, flag: flag)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static func slider(_ title: String, _ number: Binding<Double>, min: Double = 0, max: Double = 100, step: Double = 1, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
|
|
190
|
+
PlaygroundParams(title: title, type: .slider, enable: enable, sliderMin: min, sliderMax: max, sliderStep: step, number: number)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// MARK: - PlaygroundTab (live component + scrollable props list)
|
|
195
|
+
|
|
196
|
+
@available(iOS 16.0, *)
|
|
197
|
+
struct PlaygroundTab<Component: View>: View {
|
|
198
|
+
@ViewBuilder let component: () -> Component
|
|
199
|
+
let params: [PlaygroundParams]
|
|
200
|
+
var isHideComponent: Bool = false
|
|
201
|
+
|
|
202
|
+
var body: some View {
|
|
203
|
+
VStack(spacing: 0) {
|
|
204
|
+
if !isHideComponent {
|
|
205
|
+
BasicColumnBox("Component", alignment: .leading) { component() }
|
|
206
|
+
}
|
|
207
|
+
ScrollView {
|
|
208
|
+
BasicColumnBox("Props", alignment: .leading) {
|
|
209
|
+
ForEach(params) { param in
|
|
210
|
+
MomoText(param.title, typography: .headerDefaultBold)
|
|
211
|
+
.opacity(param.enable.wrappedValue ? 1 : 0.4)
|
|
212
|
+
PlaygroundView(param: param)
|
|
213
|
+
Spacer().frame(height: Spacing.L)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// MARK: - PlaygroundView (per-type editor, mirrors PlayGroundView.kt)
|
|
222
|
+
|
|
223
|
+
@available(iOS 16.0, *)
|
|
224
|
+
struct PlaygroundView: View {
|
|
225
|
+
let param: PlaygroundParams
|
|
226
|
+
@EnvironmentObject private var navigator: Navigator
|
|
227
|
+
@State private var isUndefined = false
|
|
228
|
+
@State private var initialSelection: PlaygroundOption?
|
|
229
|
+
@State private var didCapture = false
|
|
230
|
+
|
|
231
|
+
var body: some View {
|
|
232
|
+
editor
|
|
233
|
+
.opacity(param.enable.wrappedValue ? 1 : 0.4)
|
|
234
|
+
.disabled(!param.enable.wrappedValue)
|
|
235
|
+
.allowsHitTesting(param.enable.wrappedValue)
|
|
236
|
+
.onAppear {
|
|
237
|
+
if !didCapture {
|
|
238
|
+
initialSelection = param.selection?.wrappedValue
|
|
239
|
+
didCapture = true
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@ViewBuilder
|
|
245
|
+
private var editor: some View {
|
|
246
|
+
switch param.type {
|
|
247
|
+
case .string, .number: stringEditor
|
|
248
|
+
case .option, .color: optionEditor
|
|
249
|
+
case .radio: radioEditor
|
|
250
|
+
case .checkbox: checkboxEditor
|
|
251
|
+
case .slider: sliderEditor
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// STRING / NUMBER
|
|
256
|
+
@ViewBuilder
|
|
257
|
+
private var stringEditor: some View {
|
|
258
|
+
if let text = param.text {
|
|
259
|
+
Input(
|
|
260
|
+
text: text,
|
|
261
|
+
keyboardType: param.type == .number ? .numberPad : .default,
|
|
262
|
+
onChangeText: { _ in isUndefined = false }
|
|
263
|
+
)
|
|
264
|
+
if !param.required {
|
|
265
|
+
undefinedCheckbox { if isUndefined { text.wrappedValue = "" } }
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// OPTION / COLOR
|
|
271
|
+
@ViewBuilder
|
|
272
|
+
private var optionEditor: some View {
|
|
273
|
+
if let selection = param.selection {
|
|
274
|
+
HStack {
|
|
275
|
+
Spacer().frame(width: 30)
|
|
276
|
+
MomoText(selection.wrappedValue?.key ?? "", typography: .bodyDefaultRegular)
|
|
277
|
+
Spacer()
|
|
278
|
+
Icon(source: "arrow_chevron_down", size: 20)
|
|
279
|
+
.padding(.trailing, Spacing.XS)
|
|
280
|
+
}
|
|
281
|
+
.frame(height: 40)
|
|
282
|
+
.overlay(
|
|
283
|
+
RoundedRectangle(cornerRadius: Radius.XS)
|
|
284
|
+
.stroke(defaultTheme.colors.border.default, lineWidth: 1)
|
|
285
|
+
)
|
|
286
|
+
.contentShape(Rectangle())
|
|
287
|
+
.onTapGesture { openPicker(selection: selection) }
|
|
288
|
+
|
|
289
|
+
if !param.required {
|
|
290
|
+
undefinedCheckbox { if isUndefined { selection.wrappedValue = initialSelection } }
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// RADIO
|
|
296
|
+
@ViewBuilder
|
|
297
|
+
private var radioEditor: some View {
|
|
298
|
+
if let selection = param.selection {
|
|
299
|
+
ForEach(param.list) { option in
|
|
300
|
+
Radio(
|
|
301
|
+
value: option.key,
|
|
302
|
+
groupValue: Binding(
|
|
303
|
+
get: { AnyHashable(selection.wrappedValue?.key ?? "") },
|
|
304
|
+
set: { _ in }
|
|
305
|
+
),
|
|
306
|
+
onChange: { isUndefined = false; selection.wrappedValue = option },
|
|
307
|
+
label: option.key
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
if !param.required {
|
|
311
|
+
undefinedCheckbox { if isUndefined { selection.wrappedValue = initialSelection } }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// CHECKBOX
|
|
317
|
+
@ViewBuilder
|
|
318
|
+
private var checkboxEditor: some View {
|
|
319
|
+
if let flag = param.flag {
|
|
320
|
+
Checkbox(flag, title: flag.wrappedValue.description)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// SLIDER
|
|
325
|
+
@ViewBuilder
|
|
326
|
+
private var sliderEditor: some View {
|
|
327
|
+
if let number = param.number {
|
|
328
|
+
MomoText(formatSlider(number.wrappedValue), typography: .headerDefaultBold)
|
|
329
|
+
MomoSlider(
|
|
330
|
+
min: param.sliderMin,
|
|
331
|
+
max: param.sliderMax,
|
|
332
|
+
step: param.sliderStep,
|
|
333
|
+
low: number.wrappedValue,
|
|
334
|
+
disableRange: true,
|
|
335
|
+
onValueChanged: { low, _, _ in number.wrappedValue = low }
|
|
336
|
+
)
|
|
337
|
+
.frame(maxWidth: .infinity)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// "is undefined" toggle for optional params.
|
|
342
|
+
@ViewBuilder
|
|
343
|
+
private func undefinedCheckbox(_ onToggle: @escaping () -> Void) -> some View {
|
|
344
|
+
Checkbox(
|
|
345
|
+
Binding(get: { isUndefined }, set: { _ in }),
|
|
346
|
+
onChange: { _ in isUndefined.toggle(); onToggle() },
|
|
347
|
+
title: "is undefined"
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private func openPicker(selection: Binding<PlaygroundOption?>) {
|
|
352
|
+
let isColor = param.type == .color
|
|
353
|
+
navigator.showBottomSheet({
|
|
354
|
+
PlaygroundOptionPicker(list: param.list, isColor: isColor) { picked in
|
|
355
|
+
isUndefined = false
|
|
356
|
+
selection.wrappedValue = picked
|
|
357
|
+
navigator.hideOverplay()
|
|
358
|
+
}
|
|
359
|
+
}, header: .title("Choose option"))
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private func formatSlider(_ v: Double) -> String {
|
|
363
|
+
v == v.rounded() ? String(Int(v)) : String(format: "%.1f", v)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// MARK: - Option picker (searchable bottom-sheet list)
|
|
368
|
+
|
|
369
|
+
@available(iOS 16.0, *)
|
|
370
|
+
private struct PlaygroundOptionPicker: View {
|
|
371
|
+
let list: [PlaygroundOption]
|
|
372
|
+
let isColor: Bool
|
|
373
|
+
let onSelect: (PlaygroundOption) -> Void
|
|
374
|
+
|
|
375
|
+
@State private var query = ""
|
|
376
|
+
|
|
377
|
+
private var filtered: [PlaygroundOption] {
|
|
378
|
+
query.isEmpty ? list : list.filter { $0.key.localizedCaseInsensitiveContains(query) }
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
var body: some View {
|
|
382
|
+
VStack(spacing: 0) {
|
|
383
|
+
InputSearch(props: InputSearchProps(
|
|
384
|
+
text: $query,
|
|
385
|
+
placeholder: "Search input",
|
|
386
|
+
onChangeText: { query = $0.replacingOccurrences(of: "\n", with: " ") }
|
|
387
|
+
))
|
|
388
|
+
.padding(.horizontal, Spacing.L)
|
|
389
|
+
|
|
390
|
+
ScrollView {
|
|
391
|
+
LazyVStack(alignment: .leading, spacing: 0) {
|
|
392
|
+
ForEach(filtered) { item in
|
|
393
|
+
HStack(spacing: Spacing.S) {
|
|
394
|
+
if isColor, let color = item.value.base as? Color {
|
|
395
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
396
|
+
.fill(color)
|
|
397
|
+
.frame(width: 36, height: 36)
|
|
398
|
+
.overlay(
|
|
399
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
400
|
+
.stroke(defaultTheme.colors.border.default, lineWidth: 1)
|
|
401
|
+
)
|
|
402
|
+
} else if let iconName = item.value.base as? String {
|
|
403
|
+
Icon(source: iconName, size: 24)
|
|
404
|
+
}
|
|
405
|
+
MomoText(item.key, typography: .bodyDefaultRegular)
|
|
406
|
+
}
|
|
407
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
408
|
+
.padding(.vertical, Spacing.XS)
|
|
409
|
+
.contentShape(Rectangle())
|
|
410
|
+
.onTapGesture { onSelect(item) }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
.padding(Spacing.L)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
.frame(height: 600)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
@@ -1,14 +1,56 @@
|
|
|
1
|
-
|
|
1
|
+
//
|
|
2
|
+
// DividerDemo.swift
|
|
3
|
+
// Example
|
|
4
|
+
//
|
|
5
|
+
// Preview + Playground demo for MomoDivider.
|
|
6
|
+
//
|
|
7
|
+
|
|
2
8
|
import MoMoUIKits
|
|
9
|
+
import SwiftUI
|
|
3
10
|
|
|
4
11
|
struct DividerDemo: View {
|
|
5
12
|
var body: some View {
|
|
6
|
-
|
|
13
|
+
if #available(iOS 16.0, *) {
|
|
14
|
+
DemoScreen(preview: { DividerPreview() }, playground: { DividerPlayground() })
|
|
15
|
+
} else {
|
|
16
|
+
Text("Requires iOS 16+").padding()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// MARK: - Preview
|
|
22
|
+
|
|
23
|
+
private struct DividerPreview: View {
|
|
24
|
+
var body: some View {
|
|
25
|
+
BasicColumnBox("Between items", alignment: .leading) {
|
|
7
26
|
ForEach(0..<5, id: \.self) { index in
|
|
8
|
-
|
|
27
|
+
MomoText("Item \(index)", typography: .bodyDefaultRegular)
|
|
9
28
|
MomoDivider()
|
|
10
29
|
}
|
|
11
30
|
}
|
|
12
|
-
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// MARK: - Playground
|
|
35
|
+
|
|
36
|
+
// MomoDivider takes no props; the playground varies how many are rendered.
|
|
37
|
+
@available(iOS 16.0, *)
|
|
38
|
+
private struct DividerPlayground: View {
|
|
39
|
+
@State private var count: Double = 3
|
|
40
|
+
|
|
41
|
+
var body: some View {
|
|
42
|
+
PlaygroundTab(
|
|
43
|
+
component: {
|
|
44
|
+
VStack(alignment: .leading, spacing: Spacing.S) {
|
|
45
|
+
ForEach(0..<Int(count), id: \.self) { index in
|
|
46
|
+
MomoText("Item \(index)", typography: .bodyDefaultRegular)
|
|
47
|
+
MomoDivider()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
params: [
|
|
52
|
+
.slider("Count", $count, min: 1, max: 6, step: 1),
|
|
53
|
+
]
|
|
54
|
+
)
|
|
13
55
|
}
|
|
14
56
|
}
|
|
@@ -13,11 +13,16 @@ public struct ScreenUsage: Hashable, Identifiable {
|
|
|
13
13
|
public let title: String
|
|
14
14
|
public let destination: AnyView
|
|
15
15
|
public let icon: String
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/// True when the destination hosts its own chrome (e.g. a `BottomTab` — the
|
|
17
|
+
/// Preview/Playground `DemoScreen` or `BottomTabDemo`); such screens are pushed
|
|
18
|
+
/// with `headerType: .none, scrollable: false`.
|
|
19
|
+
public let hostsOwnChrome: Bool
|
|
20
|
+
|
|
21
|
+
public init(title: String, destination: AnyView, icon: String, hostsOwnChrome: Bool = false) {
|
|
18
22
|
self.title = title
|
|
19
23
|
self.destination = destination
|
|
20
24
|
self.icon = icon
|
|
25
|
+
self.hostsOwnChrome = hostsOwnChrome
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
public static func == (lhs: ScreenUsage, rhs: ScreenUsage) -> Bool {
|
|
@@ -34,52 +39,51 @@ public func getIcon(icon: String) -> String {
|
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
public let ScreenUsages: [ScreenUsage] = [
|
|
37
|
-
ScreenUsage(title: "Badge", destination: AnyView(BadgeDemo()), icon: getIcon(icon: "Badge")),
|
|
38
|
-
ScreenUsage(title: "Button", destination: AnyView(ButtonDemo()), icon: getIcon(icon: "Button")),
|
|
39
|
-
ScreenUsage(title: "CheckBox", destination: AnyView(CheckboxDemo()), icon: getIcon(icon: "Checkbox")),
|
|
40
|
-
ScreenUsage(title: "Chip", destination: AnyView(ChipDemo()), icon: getIcon(icon: "Chip")),
|
|
41
|
-
ScreenUsage(title: "Icon", destination: AnyView(IconDemo()), icon: getIcon(icon: "Icon")),
|
|
42
|
-
ScreenUsage(title: "Image", destination: AnyView(ImageDemo()), icon: getIcon(icon: "Layout")),
|
|
43
|
-
ScreenUsage(title: "
|
|
44
|
-
ScreenUsage(title: "
|
|
45
|
-
ScreenUsage(title: "Input", destination: AnyView(
|
|
46
|
-
ScreenUsage(title: "
|
|
47
|
-
ScreenUsage(title: "
|
|
48
|
-
ScreenUsage(title: "
|
|
49
|
-
ScreenUsage(title: "
|
|
50
|
-
ScreenUsage(title: "
|
|
51
|
-
ScreenUsage(title: "
|
|
52
|
-
ScreenUsage(title: "
|
|
53
|
-
ScreenUsage(title: "Text", destination: AnyView(TypographyDemo()), icon: getIcon(icon: "Typography")),
|
|
42
|
+
ScreenUsage(title: "Badge", destination: AnyView(BadgeDemo()), icon: getIcon(icon: "Badge"), hostsOwnChrome: true),
|
|
43
|
+
ScreenUsage(title: "Button", destination: AnyView(ButtonDemo()), icon: getIcon(icon: "Button"), hostsOwnChrome: true),
|
|
44
|
+
ScreenUsage(title: "CheckBox", destination: AnyView(CheckboxDemo()), icon: getIcon(icon: "Checkbox"), hostsOwnChrome: true),
|
|
45
|
+
ScreenUsage(title: "Chip", destination: AnyView(ChipDemo()), icon: getIcon(icon: "Chip"), hostsOwnChrome: true),
|
|
46
|
+
ScreenUsage(title: "Icon", destination: AnyView(IconDemo()), icon: getIcon(icon: "Icon"), hostsOwnChrome: true),
|
|
47
|
+
ScreenUsage(title: "Image", destination: AnyView(ImageDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
48
|
+
ScreenUsage(title: "Section", destination: AnyView(SectionDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
49
|
+
ScreenUsage(title: "Input", destination: AnyView(InputDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
|
|
50
|
+
ScreenUsage(title: "Input Phone Number", destination: AnyView(InputPhoneNumberDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
|
|
51
|
+
ScreenUsage(title: "InputTextArea", destination: AnyView(InputTextAreaDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
|
|
52
|
+
ScreenUsage(title: "InputSearch", destination: AnyView(InputSearchDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
|
|
53
|
+
ScreenUsage(title: "PopupNotify", destination: AnyView(PopupNotifyDemo()), icon: getIcon(icon: "Popup"), hostsOwnChrome: true),
|
|
54
|
+
ScreenUsage(title: "PopupPromotion", destination: AnyView(PopupPromotionDemo()), icon: getIcon(icon: "Popup"), hostsOwnChrome: true),
|
|
55
|
+
ScreenUsage(title: "SnackBar", destination: AnyView(SnackBarDemo()), icon: getIcon(icon: "Popup"), hostsOwnChrome: true),
|
|
56
|
+
ScreenUsage(title: "Switch", destination: AnyView(SwitchDemo()), icon: getIcon(icon: "Toggle"), hostsOwnChrome: true),
|
|
57
|
+
ScreenUsage(title: "Text", destination: AnyView(TypographyDemo()), icon: getIcon(icon: "Typography"), hostsOwnChrome: true),
|
|
54
58
|
// ScreenUsage(title: "TrustBanner", destination: AnyView(TrustBanner(onPress: {_ in })), icon: getIcon(icon: "Typography")),
|
|
55
|
-
ScreenUsage(title: "Information", destination: AnyView(InformationDemo()), icon: getIcon(icon: "Typography")),
|
|
56
|
-
ScreenUsage(title: "Avatar", destination: AnyView(AvatarDemo()), icon: getIcon(icon: "Avatar")),
|
|
57
|
-
ScreenUsage(title: "Carousel", destination: AnyView(CarouselDemo()), icon: getIcon(icon: "Carousel")),
|
|
58
|
-
ScreenUsage(title: "Divider", destination: AnyView(DividerDemo()), icon: getIcon(icon: "Divider")),
|
|
59
|
-
ScreenUsage(title: "IconButton", destination: AnyView(IconButtonDemo()), icon: getIcon(icon: "Button")),
|
|
60
|
-
ScreenUsage(title: "Loader", destination: AnyView(LoaderDemo()), icon: getIcon(icon: "Loader")),
|
|
61
|
-
ScreenUsage(title: "Pagination", destination: AnyView(PaginationDemo()), icon: getIcon(icon: "Badge")),
|
|
62
|
-
ScreenUsage(title: "Radio", destination: AnyView(RadioDemo()), icon: getIcon(icon: "Radio")),
|
|
63
|
-
ScreenUsage(title: "Rating", destination: AnyView(RatingDemo()), icon: getIcon(icon: "Rating")),
|
|
64
|
-
ScreenUsage(title: "Skeleton", destination: AnyView(SkeletonDemo()), icon: getIcon(icon: "Skeleton")),
|
|
65
|
-
ScreenUsage(title: "SuggestAction", destination: AnyView(SuggestActionDemo()), icon: getIcon(icon: "Information")),
|
|
66
|
-
ScreenUsage(title: "Tag", destination: AnyView(TagDemo()), icon: getIcon(icon: "Tag")),
|
|
67
|
-
ScreenUsage(title: "Title", destination: AnyView(TitleDemo()), icon: getIcon(icon: "Typography")),
|
|
68
|
-
ScreenUsage(title: "Collapse", destination: AnyView(CollapseDemo()), icon: getIcon(icon: "Layout")),
|
|
69
|
-
ScreenUsage(title: "Stepper", destination: AnyView(StepperDemo()), icon: getIcon(icon: "Layout")),
|
|
70
|
-
ScreenUsage(title: "Uploader", destination: AnyView(UploaderDemo()), icon: getIcon(icon: "FormInput")),
|
|
71
|
-
ScreenUsage(title: "Swipe", destination: AnyView(SwipeDemo()), icon: getIcon(icon: "Layout")),
|
|
72
|
-
ScreenUsage(title: "InputDropDown", destination: AnyView(InputDropDownDemo()), icon: getIcon(icon: "FormInput")),
|
|
73
|
-
ScreenUsage(title: "InputMoney", destination: AnyView(InputMoneyDemo()), icon: getIcon(icon: "FormInput")),
|
|
74
|
-
ScreenUsage(title: "InputOTP", destination: AnyView(InputOTPDemo()), icon: getIcon(icon: "FormInput")),
|
|
75
|
-
ScreenUsage(title: "ProgressInfo", destination: AnyView(ProgressInfoDemo()), icon: getIcon(icon: "Layout")),
|
|
76
|
-
ScreenUsage(title: "Steps", destination: AnyView(StepsDemo()), icon: getIcon(icon: "Layout")),
|
|
77
|
-
ScreenUsage(title: "Slider", destination: AnyView(SliderDemo()), icon: getIcon(icon: "Layout")),
|
|
78
|
-
ScreenUsage(title: "TabView", destination: AnyView(TabViewDemo()), icon: getIcon(icon: "Layout")),
|
|
79
|
-
ScreenUsage(title: "Tooltip", destination: AnyView(TooltipDemo()), icon: getIcon(icon: "Layout")),
|
|
80
|
-
ScreenUsage(title: "DateTimePicker", destination: AnyView(DateTimePickerDemo()), icon: getIcon(icon: "Layout")),
|
|
59
|
+
ScreenUsage(title: "Information", destination: AnyView(InformationDemo()), icon: getIcon(icon: "Typography"), hostsOwnChrome: true),
|
|
60
|
+
ScreenUsage(title: "Avatar", destination: AnyView(AvatarDemo()), icon: getIcon(icon: "Avatar"), hostsOwnChrome: true),
|
|
61
|
+
ScreenUsage(title: "Carousel", destination: AnyView(CarouselDemo()), icon: getIcon(icon: "Carousel"), hostsOwnChrome: true),
|
|
62
|
+
ScreenUsage(title: "Divider", destination: AnyView(DividerDemo()), icon: getIcon(icon: "Divider"), hostsOwnChrome: true),
|
|
63
|
+
ScreenUsage(title: "IconButton", destination: AnyView(IconButtonDemo()), icon: getIcon(icon: "Button"), hostsOwnChrome: true),
|
|
64
|
+
ScreenUsage(title: "Loader", destination: AnyView(LoaderDemo()), icon: getIcon(icon: "Loader"), hostsOwnChrome: true),
|
|
65
|
+
ScreenUsage(title: "Pagination", destination: AnyView(PaginationDemo()), icon: getIcon(icon: "Badge"), hostsOwnChrome: true),
|
|
66
|
+
ScreenUsage(title: "Radio", destination: AnyView(RadioDemo()), icon: getIcon(icon: "Radio"), hostsOwnChrome: true),
|
|
67
|
+
ScreenUsage(title: "Rating", destination: AnyView(RatingDemo()), icon: getIcon(icon: "Rating"), hostsOwnChrome: true),
|
|
68
|
+
ScreenUsage(title: "Skeleton", destination: AnyView(SkeletonDemo()), icon: getIcon(icon: "Skeleton"), hostsOwnChrome: true),
|
|
69
|
+
ScreenUsage(title: "SuggestAction", destination: AnyView(SuggestActionDemo()), icon: getIcon(icon: "Information"), hostsOwnChrome: true),
|
|
70
|
+
ScreenUsage(title: "Tag", destination: AnyView(TagDemo()), icon: getIcon(icon: "Tag"), hostsOwnChrome: true),
|
|
71
|
+
ScreenUsage(title: "Title", destination: AnyView(TitleDemo()), icon: getIcon(icon: "Typography"), hostsOwnChrome: true),
|
|
72
|
+
ScreenUsage(title: "Collapse", destination: AnyView(CollapseDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
73
|
+
ScreenUsage(title: "Stepper", destination: AnyView(StepperDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
74
|
+
ScreenUsage(title: "Uploader", destination: AnyView(UploaderDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
|
|
75
|
+
ScreenUsage(title: "Swipe", destination: AnyView(SwipeDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
76
|
+
ScreenUsage(title: "InputDropDown", destination: AnyView(InputDropDownDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
|
|
77
|
+
ScreenUsage(title: "InputMoney", destination: AnyView(InputMoneyDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
|
|
78
|
+
ScreenUsage(title: "InputOTP", destination: AnyView(InputOTPDemo()), icon: getIcon(icon: "FormInput"), hostsOwnChrome: true),
|
|
79
|
+
ScreenUsage(title: "ProgressInfo", destination: AnyView(ProgressInfoDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
80
|
+
ScreenUsage(title: "Steps", destination: AnyView(StepsDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
81
|
+
ScreenUsage(title: "Slider", destination: AnyView(SliderDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
82
|
+
ScreenUsage(title: "TabView", destination: AnyView(TabViewDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
83
|
+
ScreenUsage(title: "Tooltip", destination: AnyView(TooltipDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
84
|
+
ScreenUsage(title: "DateTimePicker", destination: AnyView(DateTimePickerDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
81
85
|
ScreenUsage(title: "Navigation", destination: AnyView(NavigationDemo()), icon: getIcon(icon: "Layout")),
|
|
82
|
-
ScreenUsage(title: "Bottom Tab", destination: AnyView(BottomTabDemo()), icon: getIcon(icon: "Layout")),
|
|
86
|
+
ScreenUsage(title: "Bottom Tab", destination: AnyView(BottomTabDemo()), icon: getIcon(icon: "Layout"), hostsOwnChrome: true),
|
|
83
87
|
ScreenUsage(title: "Animated Header", destination: AnyView(AnimatedHeaderDemo()), icon: getIcon(icon: "Layout")),
|
|
84
88
|
ScreenUsage(title: "Theme", destination: AnyView(ThemeDemo()), icon: getIcon(icon: "Typography")),
|
|
85
89
|
ScreenUsage(title: "Localize", destination: AnyView(LocalizeDemo()), icon: getIcon(icon: "Typography")),
|
|
@@ -233,7 +237,7 @@ public struct HomeItemView: View {
|
|
|
233
237
|
public var body: some View {
|
|
234
238
|
ForEach(screenUsages) { screenUsage in
|
|
235
239
|
SwiftUI.Button {
|
|
236
|
-
let options = screenUsage.
|
|
240
|
+
let options = screenUsage.hostsOwnChrome
|
|
237
241
|
? NavigationOptions(
|
|
238
242
|
title: screenUsage.title,
|
|
239
243
|
headerType: .none,
|