@momo-kits/native-kits 0.162.30-debug → 0.162.31-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.
Files changed (93) hide show
  1. package/CLAUDE.md +19 -0
  2. package/compose/build.gradle.kts +1 -1
  3. package/compose/compose.podspec +1 -1
  4. package/gradle.properties +1 -1
  5. package/ios/Application/ApplicationEnvironment.swift +14 -6
  6. package/ios/Application/LanguageSource.swift +93 -0
  7. package/ios/Application/Localize.swift +73 -4
  8. package/ios/Application/LocalizeModifier.swift +30 -0
  9. package/ios/Application/Navigation/NavigationContainer.swift +28 -18
  10. package/ios/Application/Navigation/Navigator.swift +121 -53
  11. package/ios/Application/Navigation/NavigatorManager.swift +49 -0
  12. package/ios/Application/Navigation/component/NavigationFooter.swift +90 -0
  13. package/ios/Application/StackScreen.swift +19 -28
  14. package/ios/Badge/Badge.swift +2 -1
  15. package/ios/Badge/BadgeRibbon.swift +6 -2
  16. package/ios/Button/Button.swift +41 -61
  17. package/ios/Chip/Chip.swift +6 -5
  18. package/ios/Icon/Icon.swift +13 -4
  19. package/ios/Input/ErrorView.swift +2 -1
  20. package/ios/Input/Input.swift +9 -2
  21. package/ios/Input/InputPhoneNumber.swift +2 -1
  22. package/ios/Input/InputSearch.swift +3 -2
  23. package/ios/Input/InputTextArea.swift +6 -1
  24. package/ios/InputMoney/InputMoney.swift +5 -2
  25. package/ios/InputOTP/InputOTP.swift +2 -1
  26. package/ios/Layout/GridContext.swift +30 -0
  27. package/ios/Layout/Item.swift +42 -0
  28. package/ios/Layout/Section.swift +134 -0
  29. package/ios/Popup/PopupNotify.swift +2 -1
  30. package/ios/ProgressInfo/ProgressInfo.swift +4 -3
  31. package/ios/Rating/Rating.swift +2 -1
  32. package/ios/Stepper/Stepper.swift +2 -1
  33. package/ios/SuggestAction/SuggestAction.swift +3 -2
  34. package/ios/Tag/Tag.swift +2 -1
  35. package/ios/Template/TrustBanner/TrustBanner.swift +4 -2
  36. package/ios/Title/Title.swift +3 -2
  37. package/ios/Tooltip/Tooltip.swift +2 -1
  38. package/ios/Typography/FontScale.swift +103 -0
  39. package/ios/Typography/FontScaleStore.swift +52 -13
  40. package/ios/Typography/Text.swift +12 -29
  41. package/ios/Typography/Typography.swift +66 -86
  42. package/ios/native-kits.podspec +2 -1
  43. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  44. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +87 -73
  45. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +55 -249
  46. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +90 -82
  47. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +74 -104
  48. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +47 -50
  49. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +63 -7
  50. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +90 -81
  51. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +73 -97
  52. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DemoScreen.swift +420 -0
  53. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +46 -4
  54. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +54 -57
  55. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +80 -45
  56. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconDemo.swift +81 -0
  57. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +49 -62
  58. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +66 -116
  59. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +104 -98
  60. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +76 -118
  61. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +73 -86
  62. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +69 -54
  63. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +74 -54
  64. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +73 -20
  65. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +68 -38
  66. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +55 -52
  67. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LocalizeDemo.swift +51 -4
  68. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +14 -2
  69. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +47 -51
  70. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PlaygroundData.swift +76 -0
  71. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +67 -93
  72. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +34 -43
  73. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +83 -71
  74. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +55 -23
  75. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +58 -46
  76. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SectionDemo.swift +83 -0
  77. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +41 -12
  78. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +67 -127
  79. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +54 -110
  80. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +69 -79
  81. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +78 -58
  82. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +76 -65
  83. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +82 -127
  84. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +44 -19
  85. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +92 -168
  86. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +79 -27
  87. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +0 -1
  88. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +87 -92
  89. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +94 -163
  90. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +70 -70
  91. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +68 -114
  92. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +3 -1
  93. package/package.json +2 -2
@@ -0,0 +1,420 @@
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
+ var id: String { key }
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
+ // Identity is the title, never a fresh UUID: the array is rebuilt on every body
122
+ // pass, so a per-instance id would re-key the ForEach on each keystroke — tearing
123
+ // down the row (an editing TextField loses first responder) and wiping its @State.
124
+ var id: String { title }
125
+ let title: String
126
+ let type: PlaygroundDataType
127
+ let list: [PlaygroundOption]
128
+ let enable: Binding<Bool>
129
+ let required: Bool
130
+ let sliderMin: Double
131
+ let sliderMax: Double
132
+ let sliderStep: Double
133
+
134
+ // Only the binding matching `type` is non-nil.
135
+ let text: Binding<String>?
136
+ let selection: Binding<PlaygroundOption?>?
137
+ let flag: Binding<Bool>?
138
+ let number: Binding<Double>?
139
+
140
+ private init(
141
+ title: String,
142
+ type: PlaygroundDataType,
143
+ list: [PlaygroundOption] = [],
144
+ enable: Binding<Bool> = .constant(true),
145
+ required: Bool = false,
146
+ sliderMin: Double = 0,
147
+ sliderMax: Double = 100,
148
+ sliderStep: Double = 1,
149
+ text: Binding<String>? = nil,
150
+ selection: Binding<PlaygroundOption?>? = nil,
151
+ flag: Binding<Bool>? = nil,
152
+ number: Binding<Double>? = nil
153
+ ) {
154
+ self.title = title
155
+ self.type = type
156
+ self.list = list
157
+ self.enable = enable
158
+ self.required = required
159
+ self.sliderMin = sliderMin
160
+ self.sliderMax = sliderMax
161
+ self.sliderStep = sliderStep
162
+ self.text = text
163
+ self.selection = selection
164
+ self.flag = flag
165
+ self.number = number
166
+ }
167
+
168
+ static func string(_ title: String, _ text: Binding<String>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
169
+ PlaygroundParams(title: title, type: .string, enable: enable, required: required, text: text)
170
+ }
171
+
172
+ static func number(_ title: String, _ text: Binding<String>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
173
+ PlaygroundParams(title: title, type: .number, enable: enable, required: required, text: text)
174
+ }
175
+
176
+ static func option(_ title: String, _ list: [PlaygroundOption], _ selection: Binding<PlaygroundOption?>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
177
+ PlaygroundParams(title: title, type: .option, list: list, enable: enable, required: required, selection: selection)
178
+ }
179
+
180
+ static func color(_ title: String, _ list: [PlaygroundOption], _ selection: Binding<PlaygroundOption?>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
181
+ PlaygroundParams(title: title, type: .color, list: list, enable: enable, required: required, selection: selection)
182
+ }
183
+
184
+ static func radio(_ title: String, _ list: [PlaygroundOption], _ selection: Binding<PlaygroundOption?>, required: Bool = false, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
185
+ PlaygroundParams(title: title, type: .radio, list: list, enable: enable, required: required, selection: selection)
186
+ }
187
+
188
+ static func checkbox(_ title: String, _ flag: Binding<Bool>, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
189
+ PlaygroundParams(title: title, type: .checkbox, enable: enable, flag: flag)
190
+ }
191
+
192
+ static func slider(_ title: String, _ number: Binding<Double>, min: Double = 0, max: Double = 100, step: Double = 1, enable: Binding<Bool> = .constant(true)) -> PlaygroundParams {
193
+ PlaygroundParams(title: title, type: .slider, enable: enable, sliderMin: min, sliderMax: max, sliderStep: step, number: number)
194
+ }
195
+ }
196
+
197
+ // MARK: - PlaygroundTab (live component + scrollable props list)
198
+
199
+ @available(iOS 16.0, *)
200
+ struct PlaygroundTab<Component: View>: View {
201
+ @ViewBuilder let component: () -> Component
202
+ let params: [PlaygroundParams]
203
+ var isHideComponent: Bool = false
204
+
205
+ var body: some View {
206
+ VStack(spacing: 0) {
207
+ if !isHideComponent {
208
+ BasicColumnBox("Component", alignment: .leading) { component() }
209
+ }
210
+ ScrollView {
211
+ BasicColumnBox("Props", alignment: .leading) {
212
+ ForEach(params) { param in
213
+ MomoText(param.title, typography: .headerDefaultBold)
214
+ .opacity(param.enable.wrappedValue ? 1 : 0.4)
215
+ PlaygroundView(param: param)
216
+ Spacer().frame(height: Spacing.L)
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
223
+
224
+ // MARK: - PlaygroundView (per-type editor, mirrors PlayGroundView.kt)
225
+
226
+ @available(iOS 16.0, *)
227
+ struct PlaygroundView: View {
228
+ let param: PlaygroundParams
229
+ @EnvironmentObject private var navigator: Navigator
230
+ @State private var isUndefined = false
231
+ @State private var initialSelection: PlaygroundOption?
232
+ @State private var didCapture = false
233
+
234
+ var body: some View {
235
+ editor
236
+ .opacity(param.enable.wrappedValue ? 1 : 0.4)
237
+ .disabled(!param.enable.wrappedValue)
238
+ .allowsHitTesting(param.enable.wrappedValue)
239
+ .onAppear {
240
+ if !didCapture {
241
+ initialSelection = param.selection?.wrappedValue
242
+ didCapture = true
243
+ }
244
+ }
245
+ }
246
+
247
+ @ViewBuilder
248
+ private var editor: some View {
249
+ switch param.type {
250
+ case .string, .number: stringEditor
251
+ case .option, .color: optionEditor
252
+ case .radio: radioEditor
253
+ case .checkbox: checkboxEditor
254
+ case .slider: sliderEditor
255
+ }
256
+ }
257
+
258
+ // STRING / NUMBER
259
+ @ViewBuilder
260
+ private var stringEditor: some View {
261
+ if let text = param.text {
262
+ Input(
263
+ text: text,
264
+ keyboardType: param.type == .number ? .numberPad : .default,
265
+ onChangeText: { _ in isUndefined = false }
266
+ )
267
+ if !param.required {
268
+ undefinedCheckbox { text.wrappedValue = "" }
269
+ }
270
+ }
271
+ }
272
+
273
+ // OPTION / COLOR
274
+ @ViewBuilder
275
+ private var optionEditor: some View {
276
+ if let selection = param.selection {
277
+ HStack {
278
+ Spacer().frame(width: 30)
279
+ MomoText(selection.wrappedValue?.key ?? "", typography: .bodyDefaultRegular)
280
+ Spacer()
281
+ Icon(source: "arrow_chevron_down", size: 20)
282
+ .padding(.trailing, Spacing.XS)
283
+ }
284
+ .frame(height: 40)
285
+ .overlay(
286
+ RoundedRectangle(cornerRadius: Radius.XS)
287
+ .stroke(defaultTheme.colors.border.default, lineWidth: 1)
288
+ )
289
+ .contentShape(Rectangle())
290
+ .onTapGesture { openPicker(selection: selection) }
291
+
292
+ if !param.required {
293
+ // An OPTION/COLOR prop is genuinely nullable, so "undefined" clears it
294
+ // outright rather than restoring the seeded default.
295
+ undefinedCheckbox { selection.wrappedValue = nil }
296
+ }
297
+ }
298
+ }
299
+
300
+ // RADIO
301
+ @ViewBuilder
302
+ private var radioEditor: some View {
303
+ if let selection = param.selection {
304
+ ForEach(param.list) { option in
305
+ Radio(
306
+ value: option.key,
307
+ groupValue: Binding(
308
+ get: { AnyHashable(selection.wrappedValue?.key ?? "") },
309
+ set: { _ in }
310
+ ),
311
+ onChange: { isUndefined = false; selection.wrappedValue = option },
312
+ label: option.key
313
+ )
314
+ }
315
+ if !param.required {
316
+ undefinedCheckbox { selection.wrappedValue = initialSelection }
317
+ }
318
+ }
319
+ }
320
+
321
+ // CHECKBOX
322
+ @ViewBuilder
323
+ private var checkboxEditor: some View {
324
+ if let flag = param.flag {
325
+ Checkbox(flag, title: flag.wrappedValue.description)
326
+ }
327
+ }
328
+
329
+ // SLIDER
330
+ @ViewBuilder
331
+ private var sliderEditor: some View {
332
+ if let number = param.number {
333
+ MomoText(formatSlider(number.wrappedValue), typography: .headerDefaultBold)
334
+ MomoSlider(
335
+ min: param.sliderMin,
336
+ max: param.sliderMax,
337
+ step: param.sliderStep,
338
+ low: number.wrappedValue,
339
+ disableRange: true,
340
+ onValueChanged: { low, _, _ in number.wrappedValue = low }
341
+ )
342
+ .frame(maxWidth: .infinity)
343
+ }
344
+ }
345
+
346
+ // "is undefined" toggle for optional params. RADIO restores the value captured on
347
+ // first render (Compose's `remember { data.value }`) since its prop is never null.
348
+ @ViewBuilder
349
+ private func undefinedCheckbox(_ onUndefined: @escaping () -> Void) -> some View {
350
+ Checkbox($isUndefined, onChange: { if $0 { onUndefined() } }, title: "is undefined")
351
+ }
352
+
353
+ private func openPicker(selection: Binding<PlaygroundOption?>) {
354
+ let isColor = param.type == .color
355
+ navigator.showBottomSheet({
356
+ PlaygroundOptionPicker(list: param.list, isColor: isColor) { picked in
357
+ isUndefined = false
358
+ selection.wrappedValue = picked
359
+ navigator.hideOverplay()
360
+ }
361
+ }, header: .title("Choose option"))
362
+ }
363
+
364
+ private func formatSlider(_ v: Double) -> String {
365
+ v == v.rounded() ? String(Int(v)) : String(format: "%.1f", v)
366
+ }
367
+ }
368
+
369
+ // MARK: - Option picker (searchable bottom-sheet list)
370
+
371
+ @available(iOS 16.0, *)
372
+ private struct PlaygroundOptionPicker: View {
373
+ let list: [PlaygroundOption]
374
+ let isColor: Bool
375
+ let onSelect: (PlaygroundOption) -> Void
376
+
377
+ @State private var query = ""
378
+
379
+ private var filtered: [PlaygroundOption] {
380
+ query.isEmpty ? list : list.filter { $0.key.localizedCaseInsensitiveContains(query) }
381
+ }
382
+
383
+ var body: some View {
384
+ VStack(spacing: 0) {
385
+ InputSearch(props: InputSearchProps(
386
+ text: $query,
387
+ placeholder: "Search input",
388
+ onChangeText: { query = $0.replacingOccurrences(of: "\n", with: " ") }
389
+ ))
390
+ .padding(.horizontal, Spacing.L)
391
+
392
+ ScrollView {
393
+ LazyVStack(alignment: .leading, spacing: 0) {
394
+ ForEach(filtered) { item in
395
+ HStack(spacing: Spacing.S) {
396
+ if isColor, let color = item.value.base as? Color {
397
+ RoundedRectangle(cornerRadius: Radius.S)
398
+ .fill(color)
399
+ .frame(width: 36, height: 36)
400
+ .overlay(
401
+ RoundedRectangle(cornerRadius: Radius.S)
402
+ .stroke(defaultTheme.colors.border.default, lineWidth: 1)
403
+ )
404
+ } else if let iconName = item.value.base as? String {
405
+ Icon(source: iconName, size: 24)
406
+ }
407
+ MomoText(item.key, typography: .bodyDefaultRegular)
408
+ }
409
+ .frame(maxWidth: .infinity, alignment: .leading)
410
+ .padding(.vertical, Spacing.XS)
411
+ .contentShape(Rectangle())
412
+ .onTapGesture { onSelect(item) }
413
+ }
414
+ }
415
+ .padding(Spacing.L)
416
+ }
417
+ }
418
+ .frame(height: 600)
419
+ }
420
+ }
@@ -1,14 +1,56 @@
1
- import SwiftUI
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
- VStack(alignment: .leading, spacing: 8) {
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
- Text("Item\(index)")
27
+ MomoText("Item \(index)", typography: .bodyDefaultRegular)
9
28
  MomoDivider()
10
29
  }
11
30
  }
12
- .padding()
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
- public init(title: String, destination: AnyView, icon: String) {
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,59 +39,51 @@ public func getIcon(icon: String) -> String {
34
39
  }
35
40
 
36
41
  public let ScreenUsages: [ScreenUsage] = [
37
- // ScreenUsage(title: "ScreenUsage", destination: AnyView(ContentView()), icon: getIcon(icon: "Layout")),
38
- ScreenUsage(title: "Badge", destination: AnyView(BadgeDemo()), icon: getIcon(icon: "Badge")),
39
- ScreenUsage(title: "Button", destination: AnyView(ButtonDemo()), icon: getIcon(icon: "Button")),
40
- ScreenUsage(title: "CheckBox", destination: AnyView(CheckboxDemo()), icon: getIcon(icon: "Checkbox")),
41
- ScreenUsage(title: "Chip", destination: AnyView(ChipDemo()), icon: getIcon(icon: "Chip")),
42
- // ScreenUsage(title: "IconButton", destination: AnyView(ContentView()), icon: getIcon(icon: "Button")),
43
- // ScreenUsage(title: "Icon", destination: AnyView(ContentView()), icon: getIcon(icon: "Icon")),
44
- ScreenUsage(title: "Image", destination: AnyView(ImageDemo()), icon: getIcon(icon: "Layout")),
45
- ScreenUsage(title: "Baseline View", destination: AnyView(BaselineViewDemo()), icon: getIcon(icon: "Layout")),
46
- // ScreenUsage(title: "InputMoney", destination: AnyView(ContentView()), icon: getIcon(icon: "FormInput")),
47
- ScreenUsage(title: "Input", destination: AnyView(InputDemo()), icon: getIcon(icon: "FormInput")),
48
- ScreenUsage(title: "Input Phone Number", destination: AnyView(InputPhoneNumberDemo()), icon: getIcon(icon: "FormInput")),
49
- ScreenUsage(title: "InputTextArea", destination: AnyView(InputTextAreaDemo()), icon: getIcon(icon: "FormInput")),
50
- ScreenUsage(title: "InputSearch", destination: AnyView(InputSearchDemo()), icon: getIcon(icon: "FormInput")),
51
- // ScreenUsage(title: "Pagination", destination: AnyView(ContentView()), icon: getIcon(icon: "Badge")),
52
- // ScreenUsage(title: "InputDropdown", destination: AnyView(ContentView()), icon: getIcon(icon: "FormInput")),
53
- ScreenUsage(title: "PopupNotify", destination: AnyView(PopupNotifyDemo()), icon: getIcon(icon: "Popup")),
54
- ScreenUsage(title: "PopupPromotion", destination: AnyView(PopupPromotionDemo()), icon: getIcon(icon: "Popup")),
55
- ScreenUsage(title: "SnackBar", destination: AnyView(SnackBarDemo()), icon: getIcon(icon: "Popup")),
56
- // ScreenUsage(title: "Radio", destination: AnyView(ContentView()), icon: getIcon(icon: "Radio")),
57
- // ScreenUsage(title: "Skeleton", destination: AnyView(ContentView()), icon: getIcon(icon: "Skeleton")),
58
- ScreenUsage(title: "Switch", destination: AnyView(SwitchDemo()), icon: getIcon(icon: "Toggle")),
59
- // ScreenUsage(title: "Tag", destination: AnyView(ContentView()), icon: getIcon(icon: "Skeleton")),
60
- 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),
61
58
  // ScreenUsage(title: "TrustBanner", destination: AnyView(TrustBanner(onPress: {_ in })), icon: getIcon(icon: "Typography")),
62
- ScreenUsage(title: "Information", destination: AnyView(InformationDemo()), icon: getIcon(icon: "Typography")),
63
- ScreenUsage(title: "Avatar", destination: AnyView(AvatarDemo()), icon: getIcon(icon: "Avatar")),
64
- ScreenUsage(title: "Carousel", destination: AnyView(CarouselDemo()), icon: getIcon(icon: "Carousel")),
65
- ScreenUsage(title: "Divider", destination: AnyView(DividerDemo()), icon: getIcon(icon: "Divider")),
66
- ScreenUsage(title: "IconButton", destination: AnyView(IconButtonDemo()), icon: getIcon(icon: "Button")),
67
- ScreenUsage(title: "Loader", destination: AnyView(LoaderDemo()), icon: getIcon(icon: "Loader")),
68
- ScreenUsage(title: "Pagination", destination: AnyView(PaginationDemo()), icon: getIcon(icon: "Badge")),
69
- ScreenUsage(title: "Radio", destination: AnyView(RadioDemo()), icon: getIcon(icon: "Radio")),
70
- ScreenUsage(title: "Rating", destination: AnyView(RatingDemo()), icon: getIcon(icon: "Rating")),
71
- ScreenUsage(title: "Skeleton", destination: AnyView(SkeletonDemo()), icon: getIcon(icon: "Skeleton")),
72
- ScreenUsage(title: "SuggestAction", destination: AnyView(SuggestActionDemo()), icon: getIcon(icon: "Information")),
73
- ScreenUsage(title: "Tag", destination: AnyView(TagDemo()), icon: getIcon(icon: "Tag")),
74
- ScreenUsage(title: "Title", destination: AnyView(TitleDemo()), icon: getIcon(icon: "Typography")),
75
- ScreenUsage(title: "Collapse", destination: AnyView(CollapseDemo()), icon: getIcon(icon: "Layout")),
76
- ScreenUsage(title: "Stepper", destination: AnyView(StepperDemo()), icon: getIcon(icon: "Layout")),
77
- ScreenUsage(title: "Uploader", destination: AnyView(UploaderDemo()), icon: getIcon(icon: "FormInput")),
78
- ScreenUsage(title: "Swipe", destination: AnyView(SwipeDemo()), icon: getIcon(icon: "Layout")),
79
- ScreenUsage(title: "InputDropDown", destination: AnyView(InputDropDownDemo()), icon: getIcon(icon: "FormInput")),
80
- ScreenUsage(title: "InputMoney", destination: AnyView(InputMoneyDemo()), icon: getIcon(icon: "FormInput")),
81
- ScreenUsage(title: "InputOTP", destination: AnyView(InputOTPDemo()), icon: getIcon(icon: "FormInput")),
82
- ScreenUsage(title: "ProgressInfo", destination: AnyView(ProgressInfoDemo()), icon: getIcon(icon: "Layout")),
83
- ScreenUsage(title: "Steps", destination: AnyView(StepsDemo()), icon: getIcon(icon: "Layout")),
84
- ScreenUsage(title: "Slider", destination: AnyView(SliderDemo()), icon: getIcon(icon: "Layout")),
85
- ScreenUsage(title: "TabView", destination: AnyView(TabViewDemo()), icon: getIcon(icon: "Layout")),
86
- ScreenUsage(title: "Tooltip", destination: AnyView(TooltipDemo()), icon: getIcon(icon: "Layout")),
87
- 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),
88
85
  ScreenUsage(title: "Navigation", destination: AnyView(NavigationDemo()), icon: getIcon(icon: "Layout")),
89
- 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),
90
87
  ScreenUsage(title: "Animated Header", destination: AnyView(AnimatedHeaderDemo()), icon: getIcon(icon: "Layout")),
91
88
  ScreenUsage(title: "Theme", destination: AnyView(ThemeDemo()), icon: getIcon(icon: "Typography")),
92
89
  ScreenUsage(title: "Localize", destination: AnyView(LocalizeDemo()), icon: getIcon(icon: "Typography")),
@@ -134,11 +131,11 @@ public struct HomeView: View {
134
131
  headerRight: {
135
132
  HeaderRight(headerRight: HeaderRightData(useShortcut: true))
136
133
  },
137
- // footerComponent = { Button("Footer Button") }
134
+ // footerComponent = { Button("Open SwiftUI (iOS only)") }
138
135
  footer: {
139
136
  AnyView(
140
137
  MoMoUIKits.Button(
141
- title: "Footer Button",
138
+ title: "Open Compose",
142
139
  action: onOpenCompose,
143
140
  type: .primary,
144
141
  size: .large
@@ -240,7 +237,7 @@ public struct HomeItemView: View {
240
237
  public var body: some View {
241
238
  ForEach(screenUsages) { screenUsage in
242
239
  SwiftUI.Button {
243
- let options = screenUsage.title == "Bottom Tab"
240
+ let options = screenUsage.hostsOwnChrome
244
241
  ? NavigationOptions(
245
242
  title: screenUsage.title,
246
243
  headerType: .none,
@@ -267,7 +264,7 @@ public struct HomeItemView: View {
267
264
  Divider()
268
265
  }
269
266
  .padding(.horizontal, 16)
270
- .padding(.vertical, 8)
267
+ .padding(.vertical, 16)
271
268
  }
272
269
  }
273
270