@momo-kits/native-kits 0.163.1-sp.8-debug → 0.163.2-beta.1-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/.codegraph/.gitignore +5 -0
- package/CLAUDE.md +0 -10
- package/compose/build.gradle.kts +1 -1
- package/compose/compose.podspec +1 -1
- package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Example.xcscheme +32 -0
- package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/WorkspaceSettings.xcsettings +16 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +6 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +5 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Pods-Example.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImage.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SkeletonUI.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios-LottiePrivacyInfo.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios.xcscheme +58 -0
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +60 -0
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +4 -6
- package/ios/Application/HeaderRight.swift +17 -1
- package/ios/Application/Localize.swift +4 -73
- package/ios/Application/Navigation/NavigationContainer.swift +18 -25
- package/ios/Application/Navigation/Navigator.swift +29 -91
- package/ios/Application/StackScreen.swift +21 -18
- package/ios/Badge/BadgeRibbon.swift +1 -4
- package/ios/Button/Button.swift +61 -41
- package/ios/Icon/Icon.swift +4 -13
- package/ios/native-kits.podspec +1 -2
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +8 -8
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +2 -3
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +12 -5
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LocalizeDemo.swift +4 -51
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +2 -14
- package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +1 -3
- package/local.properties +2 -2
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -158
- package/ios/Application/LanguageSource.swift +0 -93
- package/ios/Application/LocalizeModifier.swift +0 -30
- package/ios/Application/Navigation/NavigatorManager.swift +0 -49
- package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
- package/ios/Layout/GridContext.swift +0 -30
- package/ios/Layout/Item.swift +0 -42
- package/ios/Layout/Section.swift +0 -134
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconDemo.swift +0 -104
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SectionDemo.swift +0 -100
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// NavigatorManager.swift
|
|
3
|
-
// MoMoUIKits
|
|
4
|
-
//
|
|
5
|
-
// Host bridge for the native iOS edge-swipe back — the SwiftUI mirror of Compose's
|
|
6
|
-
// `ComposeNavigatorManager` (momo-app `vn.momo.compose.uikits.ComposeNavigatorManager`).
|
|
7
|
-
//
|
|
8
|
-
// The kit does NOT own the swipe gesture. In the host app a kit screen lives inside a
|
|
9
|
-
// `UIHostingController` hosted by the mini-app container VC, and that VC owns the
|
|
10
|
-
// `UIScreenEdgePanGestureRecognizer`. When the swipe fires the host asks this manager
|
|
11
|
-
// whether a kit stack is mounted (`checkCanPop`) and, if so, routes the back into it
|
|
12
|
-
// (`pop` → `Navigator.onBackSafe`) instead of dismissing the whole mini-app. Standalone
|
|
13
|
-
// (no such host, e.g. the demo app) there is no edge swipe — same as Compose on iOS,
|
|
14
|
-
// whose `BackHandler` is a no-op and which relies entirely on the host.
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
import Foundation
|
|
18
|
-
|
|
19
|
-
@available(iOS 16.0, *)
|
|
20
|
-
public final class NavigatorManager {
|
|
21
|
-
public static let shared = NavigatorManager()
|
|
22
|
-
private init() {}
|
|
23
|
-
|
|
24
|
-
/// Stack of mounted navigators; the last one is the visible top (nested mini-apps
|
|
25
|
-
/// push more than one). Held strongly, mirroring `ComposeNavigatorManager` — entries
|
|
26
|
-
/// are released by `NavigationContainer` on disappear, not by ARC.
|
|
27
|
-
private var navigators: [Navigator] = []
|
|
28
|
-
|
|
29
|
-
/// Idempotent: SwiftUI can re-run a view's `onAppear`, and a double push would need a
|
|
30
|
-
/// matching double remove to unwind.
|
|
31
|
-
public func push(_ navigator: Navigator) {
|
|
32
|
-
guard !navigators.contains(where: { $0 === navigator }) else { return }
|
|
33
|
-
navigators.append(navigator)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/// Removes by identity rather than `removeLast` so a spurious disappear of a covered
|
|
37
|
-
/// (non-top) container can't drop the wrong navigator.
|
|
38
|
-
public func remove(_ navigator: Navigator) {
|
|
39
|
-
navigators.removeAll { $0 === navigator }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/// True while any kit stack is mounted — tells the host to route the back into the kit
|
|
43
|
-
/// instead of dismissing the mini-app. Matches `ComposeNavigatorManager.checkCanPop`:
|
|
44
|
-
/// it does not inspect stack depth; `onBackSafe` handles the at-root case itself by
|
|
45
|
-
/// asking the host to dismiss (via `composeApi`).
|
|
46
|
-
public func checkCanPop() -> Bool { !navigators.isEmpty }
|
|
47
|
-
|
|
48
|
-
public func pop() { navigators.last?.onBackSafe() }
|
|
49
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// NavigationFooter.swift
|
|
3
|
-
// MoMoUIKits
|
|
4
|
-
//
|
|
5
|
-
// SwiftUI mirror of Compose's `Footer` (navigation/StackScreen.kt): the band
|
|
6
|
-
// pinned under the screen content — surface background, S/M padding, the
|
|
7
|
-
// navigation-bar reserve, and the 6pt gradient shadow cast up onto the content.
|
|
8
|
-
//
|
|
9
|
-
// Named `NavigationFooter` to coexist with the legacy `Footer` in `Screen.swift`
|
|
10
|
-
// (kept as-is for the old `Screen` path). Used by `StackScreen`.
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
import SwiftUI
|
|
14
|
-
|
|
15
|
-
public struct NavigationFooter: View {
|
|
16
|
-
/// Compose's `footerComponent`: nothing renders when nil.
|
|
17
|
-
var footerComponent: (() -> AnyView)?
|
|
18
|
-
/// Current keyboard height, measured from the bottom of the screen.
|
|
19
|
-
var keyboardSize: CGFloat
|
|
20
|
-
|
|
21
|
-
public init(
|
|
22
|
-
footerComponent: (() -> AnyView)?,
|
|
23
|
-
keyboardSize: CGFloat = 0
|
|
24
|
-
) {
|
|
25
|
-
self.footerComponent = footerComponent
|
|
26
|
-
self.keyboardSize = keyboardSize
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@Environment(\.appTheme) private var theme
|
|
30
|
-
|
|
31
|
-
/// Read from the window, not from an enclosing `GeometryProxy`: the proxy's bottom
|
|
32
|
-
/// inset grows to the keyboard height while the keyboard is up.
|
|
33
|
-
static var safeAreaBottom: CGFloat {
|
|
34
|
-
UIApplication.shared.connectedScenes
|
|
35
|
-
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
|
|
36
|
-
.first ?? 0
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/// Compose's `AppNavigationBar.current` (`getNavigationBarHeight` clamps iOS to 21pt).
|
|
40
|
-
static var navigationBarInset: CGFloat { min(safeAreaBottom, 21) }
|
|
41
|
-
|
|
42
|
-
/// Compose's `bottomPadding + Spacing.S` with the keyboard down. The footer is laid
|
|
43
|
-
/// out inside the safe area, which already covers part of that gap.
|
|
44
|
-
static var bottomPadding: CGFloat {
|
|
45
|
-
max(navigationBarInset + Spacing.S - safeAreaBottom, 0)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/// Deliberately an offset and not padding: growing the footer feeds back into SwiftUI's
|
|
49
|
-
/// keyboard avoidance, so a padding-based lift never settles at the right place.
|
|
50
|
-
/// Negative lifts the band over the keyboard; positive shaves the few points by which
|
|
51
|
-
/// the safe area over-reserves compared to Compose. `FloatingContent` rides the same
|
|
52
|
-
/// amount so the FAB keeps its distance above the band.
|
|
53
|
-
///
|
|
54
|
-
/// Natural position is `safeAreaBottom + bottomPadding` above the bottom of the screen;
|
|
55
|
-
/// Compose puts it at `(AppNavigationBar - keyboard).coerceAtLeast(0) + Spacing.S` on
|
|
56
|
-
/// top of the `imePadding()` lift — i.e. `max(navigationBar, keyboard) + Spacing.S`.
|
|
57
|
-
static func verticalOffset(keyboardSize: CGFloat) -> CGFloat {
|
|
58
|
-
(safeAreaBottom + bottomPadding) - (max(navigationBarInset, keyboardSize) + Spacing.S)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public var body: some View {
|
|
62
|
-
if let footerComponent = footerComponent {
|
|
63
|
-
VStack(alignment: .leading, spacing: 0) {
|
|
64
|
-
footerComponent()
|
|
65
|
-
}
|
|
66
|
-
.frame(maxWidth: .infinity, alignment: .leading)
|
|
67
|
-
.padding(.top, Spacing.S)
|
|
68
|
-
.padding(.horizontal, Spacing.M)
|
|
69
|
-
.padding(.bottom, Self.bottomPadding)
|
|
70
|
-
// Compose paints the surface all the way to the bottom of the screen; the
|
|
71
|
-
// footer's own frame stops at the safe area, so only the fill bleeds down.
|
|
72
|
-
.background(theme.colors.background.surface.ignoresSafeArea(.container, edges: .bottom))
|
|
73
|
-
.overlay(alignment: .top) { shadow }
|
|
74
|
-
.offset(y: Self.verticalOffset(keyboardSize: keyboardSize))
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/// Compose offsets the shadow box by -6dp, so it lands on the content above the
|
|
79
|
-
/// footer rather than on the footer surface itself.
|
|
80
|
-
private var shadow: some View {
|
|
81
|
-
LinearGradient(
|
|
82
|
-
colors: [Color.clear, Color.black.opacity(0.05)],
|
|
83
|
-
startPoint: .top,
|
|
84
|
-
endPoint: .bottom
|
|
85
|
-
)
|
|
86
|
-
.frame(height: 6)
|
|
87
|
-
.offset(y: -6)
|
|
88
|
-
.allowsHitTesting(false)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
|
-
|
|
3
|
-
/// Grid metrics published by a layout container (`SectionView`) so span-based children
|
|
4
|
-
/// can size themselves. Mirrors `vn.momo.kits.layout.GridContext`.
|
|
5
|
-
public struct GridContext: Equatable {
|
|
6
|
-
public let numberOfColumns: Int
|
|
7
|
-
public let gutter: CGFloat
|
|
8
|
-
public let sizePerSpan: CGFloat
|
|
9
|
-
|
|
10
|
-
public init(numberOfColumns: Int = 12, gutter: CGFloat = Spacing.M, sizePerSpan: CGFloat = 0) {
|
|
11
|
-
self.numberOfColumns = numberOfColumns
|
|
12
|
-
self.gutter = gutter
|
|
13
|
-
self.sizePerSpan = sizePerSpan
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
public func sizeForSpan(_ span: Int) -> CGFloat {
|
|
17
|
-
sizePerSpan * CGFloat(span) + gutter * CGFloat(span - 1)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
private struct GridContextKey: EnvironmentKey {
|
|
22
|
-
static let defaultValue: GridContext? = nil
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public extension EnvironmentValues {
|
|
26
|
-
var gridContext: GridContext? {
|
|
27
|
-
get { self[GridContextKey.self] }
|
|
28
|
-
set { self[GridContextKey.self] = newValue }
|
|
29
|
-
}
|
|
30
|
-
}
|
package/ios/Layout/Item.swift
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
|
-
|
|
3
|
-
/// Span-sized cell inside a `SectionView`. Reads the grid published by its container;
|
|
4
|
-
/// without one it just wraps its content. Mirrors `vn.momo.kits.layout.Item`.
|
|
5
|
-
public struct ItemView<Content: View>: View {
|
|
6
|
-
// MARK: Lifecycle
|
|
7
|
-
|
|
8
|
-
public init(
|
|
9
|
-
widthSpan: Int = 12,
|
|
10
|
-
heightSpan: Int = 0,
|
|
11
|
-
@ViewBuilder content: @escaping () -> Content
|
|
12
|
-
) {
|
|
13
|
-
self.widthSpan = widthSpan
|
|
14
|
-
self.heightSpan = heightSpan
|
|
15
|
-
self.content = content
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// MARK: Public
|
|
19
|
-
|
|
20
|
-
public var body: some View {
|
|
21
|
-
// A VStack (not a ZStack) so multiple children flow VERTICALLY instead of stacking
|
|
22
|
-
// on top of each other — matches the React Item's flex-column content.
|
|
23
|
-
VStack(alignment: .leading, spacing: 0) { content() }
|
|
24
|
-
.frame(width: width, height: height, alignment: .topLeading)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// MARK: Private
|
|
28
|
-
|
|
29
|
-
private let widthSpan: Int
|
|
30
|
-
private let heightSpan: Int
|
|
31
|
-
private let content: () -> Content
|
|
32
|
-
|
|
33
|
-
@Environment(\.gridContext) private var grid
|
|
34
|
-
|
|
35
|
-
private var width: CGFloat? {
|
|
36
|
-
grid.map { $0.sizeForSpan(widthSpan) }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private var height: CGFloat? {
|
|
40
|
-
heightSpan > 0 ? grid.map { $0.sizeForSpan(heightSpan) } : nil
|
|
41
|
-
}
|
|
42
|
-
}
|
package/ios/Layout/Section.swift
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
|
-
|
|
3
|
-
/// 12-column section container: horizontal margin, optional background image, and a
|
|
4
|
-
/// wrapping row of children that read their span size from `\.gridContext`.
|
|
5
|
-
/// Mirrors `vn.momo.kits.layout.Section`.
|
|
6
|
-
public struct SectionView<Content: View>: View {
|
|
7
|
-
// MARK: Lifecycle
|
|
8
|
-
|
|
9
|
-
public init(
|
|
10
|
-
useMargin: Bool = true,
|
|
11
|
-
backgroundImage: String? = nil,
|
|
12
|
-
@ViewBuilder content: @escaping () -> Content
|
|
13
|
-
) {
|
|
14
|
-
self.useMargin = useMargin
|
|
15
|
-
self.backgroundImage = backgroundImage
|
|
16
|
-
self.content = content
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// MARK: Public
|
|
20
|
-
|
|
21
|
-
public var body: some View {
|
|
22
|
-
flow
|
|
23
|
-
.environment(\.gridContext, grid)
|
|
24
|
-
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
25
|
-
.background(widthReader)
|
|
26
|
-
.background(background)
|
|
27
|
-
.padding(.horizontal, useMargin ? Spacing.M : 0)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// MARK: Private
|
|
31
|
-
|
|
32
|
-
private static var columns: Int { 12 }
|
|
33
|
-
private static var gutter: CGFloat { Spacing.M }
|
|
34
|
-
|
|
35
|
-
private let useMargin: Bool
|
|
36
|
-
private let backgroundImage: String?
|
|
37
|
-
private let content: () -> Content
|
|
38
|
-
|
|
39
|
-
/// Width available *inside* the horizontal margin — the Compose `BoxWithConstraints` equivalent.
|
|
40
|
-
@State private var availableWidth: CGFloat = 0
|
|
41
|
-
|
|
42
|
-
private var grid: GridContext {
|
|
43
|
-
let columns = CGFloat(Self.columns)
|
|
44
|
-
// The trailing `- 1 / columns` mirrors Compose: it absorbs rounding so 12 spans
|
|
45
|
-
// plus their gutters never overflow the row by a sub-point.
|
|
46
|
-
let sizePerSpan = (availableWidth - Self.gutter * (columns - 1)) / columns - 1 / columns
|
|
47
|
-
return GridContext(numberOfColumns: Self.columns, gutter: Self.gutter, sizePerSpan: max(0, sizePerSpan))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
@ViewBuilder private var flow: some View {
|
|
51
|
-
if #available(iOS 16.0, *) {
|
|
52
|
-
FlowRowLayout(gutter: Self.gutter) { content() }
|
|
53
|
-
} else {
|
|
54
|
-
// No `Layout` protocol before iOS 16: children stack instead of wrapping.
|
|
55
|
-
// Full-width (span 12) items — the common case — look identical.
|
|
56
|
-
VStack(alignment: .leading, spacing: Self.gutter) { content() }
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@ViewBuilder private var background: some View {
|
|
61
|
-
if let backgroundImage {
|
|
62
|
-
ImageView(backgroundImage, contentMode: .fill).clipped()
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private var widthReader: some View {
|
|
67
|
-
GeometryReader { proxy in
|
|
68
|
-
Color.clear.preference(key: SectionWidthKey.self, value: proxy.size.width)
|
|
69
|
-
}
|
|
70
|
-
.onPreferenceChange(SectionWidthKey.self) { availableWidth = $0 }
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private struct SectionWidthKey: PreferenceKey {
|
|
75
|
-
static var defaultValue: CGFloat = 0
|
|
76
|
-
|
|
77
|
-
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
|
78
|
-
value = max(value, nextValue())
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/// Wrapping row with equal horizontal/vertical spacing — the Compose `FlowRow` equivalent.
|
|
83
|
-
@available(iOS 16.0, *)
|
|
84
|
-
private struct FlowRowLayout: Layout {
|
|
85
|
-
struct Row {
|
|
86
|
-
var items: [(index: Int, size: CGSize)] = []
|
|
87
|
-
var width: CGFloat = 0
|
|
88
|
-
var height: CGFloat = 0
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
let gutter: CGFloat
|
|
92
|
-
|
|
93
|
-
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize {
|
|
94
|
-
let maxWidth = proposal.width ?? .infinity
|
|
95
|
-
let rows = rows(for: subviews, maxWidth: maxWidth)
|
|
96
|
-
let height = rows.reduce(0) { $0 + $1.height } + gutter * CGFloat(max(0, rows.count - 1))
|
|
97
|
-
return CGSize(width: proposal.width ?? rows.map(\.width).max() ?? 0, height: height)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) {
|
|
101
|
-
var y = bounds.minY
|
|
102
|
-
for row in rows(for: subviews, maxWidth: bounds.width) {
|
|
103
|
-
var x = bounds.minX
|
|
104
|
-
for item in row.items {
|
|
105
|
-
subviews[item.index].place(
|
|
106
|
-
at: CGPoint(x: x, y: y),
|
|
107
|
-
anchor: .topLeading,
|
|
108
|
-
proposal: ProposedViewSize(item.size)
|
|
109
|
-
)
|
|
110
|
-
x += item.size.width + gutter
|
|
111
|
-
}
|
|
112
|
-
y += row.height + gutter
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private func rows(for subviews: Subviews, maxWidth: CGFloat) -> [Row] {
|
|
117
|
-
var rows: [Row] = []
|
|
118
|
-
var current = Row()
|
|
119
|
-
for index in subviews.indices {
|
|
120
|
-
let size = subviews[index].sizeThatFits(ProposedViewSize(width: maxWidth, height: nil))
|
|
121
|
-
let widthIfAppended = current.items.isEmpty ? size.width : current.width + gutter + size.width
|
|
122
|
-
// Sub-point tolerance: spans are computed to land exactly on `maxWidth`.
|
|
123
|
-
if !current.items.isEmpty, widthIfAppended > maxWidth + 0.5 {
|
|
124
|
-
rows.append(current)
|
|
125
|
-
current = Row()
|
|
126
|
-
}
|
|
127
|
-
current.width = current.items.isEmpty ? size.width : current.width + gutter + size.width
|
|
128
|
-
current.height = max(current.height, size.height)
|
|
129
|
-
current.items.append((index, size))
|
|
130
|
-
}
|
|
131
|
-
if !current.items.isEmpty { rows.append(current) }
|
|
132
|
-
return rows
|
|
133
|
-
}
|
|
134
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import MoMoUIKits
|
|
2
|
-
import SwiftUI
|
|
3
|
-
|
|
4
|
-
// MARK: - IconDemo
|
|
5
|
-
|
|
6
|
-
struct IconDemo: View {
|
|
7
|
-
var body: some View {
|
|
8
|
-
ScrollView {
|
|
9
|
-
VStack(alignment: .leading, spacing: 24) {
|
|
10
|
-
Group {
|
|
11
|
-
Text("Source")
|
|
12
|
-
.font(.headline)
|
|
13
|
-
HStack(spacing: 12) {
|
|
14
|
-
Icon(source: "basic_home", size: 24)
|
|
15
|
-
Icon(source: "basic_flag", size: 24)
|
|
16
|
-
Icon(source: "basic_filter", size: 24)
|
|
17
|
-
Icon(source: "basic_delete", size: 24)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
Group {
|
|
22
|
-
Text("Size")
|
|
23
|
-
.font(.headline)
|
|
24
|
-
HStack(alignment: .center, spacing: 12) {
|
|
25
|
-
Icon(source: "basic_home", size: 16)
|
|
26
|
-
Icon(source: "basic_flag", size: 20)
|
|
27
|
-
Icon(source: "basic_filter", size: 24)
|
|
28
|
-
Icon(source: "basic_delete", size: 28)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
Group {
|
|
33
|
-
Text("Color")
|
|
34
|
-
.font(.headline)
|
|
35
|
-
HStack(spacing: 12) {
|
|
36
|
-
Icon(source: "basic_home", size: 24, color: Colors.red03)
|
|
37
|
-
Icon(source: "basic_flag", size: 24, color: Colors.green04)
|
|
38
|
-
Icon(source: "basic_filter", size: 24, color: Colors.pink06)
|
|
39
|
-
Icon(source: "basic_delete", size: 24, color: Colors.yellow02)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
Group {
|
|
44
|
-
// Untinted / explicit colour / theme default (no colour argument)
|
|
45
|
-
Text("URL source")
|
|
46
|
-
.font(.headline)
|
|
47
|
-
HStack(spacing: 12) {
|
|
48
|
-
Icon(source: remoteIcon, size: 24, color: nil)
|
|
49
|
-
Icon(source: remoteIcon, size: 24, color: Colors.blue04)
|
|
50
|
-
Icon(source: remoteIcon, size: 24)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
Group {
|
|
55
|
-
Text("No theme icon")
|
|
56
|
-
.font(.headline)
|
|
57
|
-
// `ic_round_momo_tiny` is in noThemeIcons: the tint is dropped, so
|
|
58
|
-
// both render identically.
|
|
59
|
-
HStack(spacing: 12) {
|
|
60
|
-
Icon(source: "ic_round_momo_tiny", size: 24)
|
|
61
|
-
Icon(source: "ic_round_momo_tiny", size: 24, color: Colors.red03)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
Group {
|
|
66
|
-
Text("Gallery")
|
|
67
|
-
.font(.headline)
|
|
68
|
-
LazyVGrid(
|
|
69
|
-
columns: Array(repeating: GridItem(.flexible(), spacing: 12), count: 4),
|
|
70
|
-
spacing: 16
|
|
71
|
-
) {
|
|
72
|
-
ForEach(gallery, id: \.self) { source in
|
|
73
|
-
VStack(spacing: 6) {
|
|
74
|
-
Icon(source: source, size: 24, color: Colors.black17)
|
|
75
|
-
Text(source)
|
|
76
|
-
.font(.caption2)
|
|
77
|
-
.multilineTextAlignment(.center)
|
|
78
|
-
.foregroundColor(Colors.black17)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
.padding(16)
|
|
85
|
-
}
|
|
86
|
-
.padding(.top)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// MARK: Private
|
|
90
|
-
|
|
91
|
-
private let remoteIcon = "https://img.mservice.com.vn/app/img/kits/basic_home.png"
|
|
92
|
-
|
|
93
|
-
private let gallery = [
|
|
94
|
-
"basic_home",
|
|
95
|
-
"basic_flag",
|
|
96
|
-
"basic_filter",
|
|
97
|
-
"basic_delete",
|
|
98
|
-
"arrow_chevron_right",
|
|
99
|
-
"shopping_coupon",
|
|
100
|
-
"shopping_store",
|
|
101
|
-
"travel_plane",
|
|
102
|
-
"time_alarm_check",
|
|
103
|
-
]
|
|
104
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
|
-
import MoMoUIKits
|
|
3
|
-
|
|
4
|
-
struct SectionDemo: View {
|
|
5
|
-
var body: some View {
|
|
6
|
-
ScrollView {
|
|
7
|
-
// No horizontal padding here — SectionView brings its own 12pt margin.
|
|
8
|
-
VStack(alignment: .leading, spacing: 24) {
|
|
9
|
-
block("Full width — span 12") {
|
|
10
|
-
SectionView {
|
|
11
|
-
SectionTile("12")
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
block("Even splits") {
|
|
16
|
-
SectionView {
|
|
17
|
-
ItemView(widthSpan: 6) { SectionTile("6") }
|
|
18
|
-
ItemView(widthSpan: 6) { SectionTile("6") }
|
|
19
|
-
ItemView(widthSpan: 4) { SectionTile("4") }
|
|
20
|
-
ItemView(widthSpan: 4) { SectionTile("4") }
|
|
21
|
-
ItemView(widthSpan: 4) { SectionTile("4") }
|
|
22
|
-
ItemView(widthSpan: 3) { SectionTile("3") }
|
|
23
|
-
ItemView(widthSpan: 3) { SectionTile("3") }
|
|
24
|
-
ItemView(widthSpan: 3) { SectionTile("3") }
|
|
25
|
-
ItemView(widthSpan: 3) { SectionTile("3") }
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
block("Wrapping — a row that overflows breaks to the next line") {
|
|
30
|
-
SectionView {
|
|
31
|
-
ItemView(widthSpan: 8) { SectionTile("8") }
|
|
32
|
-
ItemView(widthSpan: 5) { SectionTile("5") }
|
|
33
|
-
ItemView(widthSpan: 7) { SectionTile("7") }
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
block("heightSpan — square tiles") {
|
|
38
|
-
SectionView {
|
|
39
|
-
ItemView(widthSpan: 3, heightSpan: 3) { SectionTile("3×3") }
|
|
40
|
-
ItemView(widthSpan: 3, heightSpan: 3) { SectionTile("3×3") }
|
|
41
|
-
ItemView(widthSpan: 6, heightSpan: 3) { SectionTile("6×3") }
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
block("useMargin: false — edge to edge") {
|
|
46
|
-
SectionView(useMargin: false) {
|
|
47
|
-
ItemView(widthSpan: 6) { SectionTile("6") }
|
|
48
|
-
ItemView(widthSpan: 6) { SectionTile("6") }
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
block("backgroundImage") {
|
|
53
|
-
SectionView(backgroundImage: "https://images.unsplash.com/photo-1509395062183-67c5ad6faff9") {
|
|
54
|
-
ItemView(widthSpan: 6, heightSpan: 4) { SectionTile("6×4") }
|
|
55
|
-
ItemView(widthSpan: 6, heightSpan: 4) { SectionTile("6×4") }
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
block("Multiple children stack vertically inside one Item") {
|
|
60
|
-
SectionView {
|
|
61
|
-
ItemView(widthSpan: 6) {
|
|
62
|
-
SectionTile("top")
|
|
63
|
-
SectionTile("bottom")
|
|
64
|
-
}
|
|
65
|
-
ItemView(widthSpan: 6) { SectionTile("6") }
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
.padding(.vertical)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
@ViewBuilder
|
|
74
|
-
private func block<Content: View>(_ title: String, @ViewBuilder content: () -> Content) -> some View {
|
|
75
|
-
VStack(alignment: .leading, spacing: 8) {
|
|
76
|
-
Text(title)
|
|
77
|
-
.font(.headline)
|
|
78
|
-
.padding(.horizontal, Spacing.M)
|
|
79
|
-
content()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/// Filled placeholder so span widths are visible.
|
|
85
|
-
private struct SectionTile: View {
|
|
86
|
-
let label: String
|
|
87
|
-
|
|
88
|
-
init(_ label: String) {
|
|
89
|
-
self.label = label
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
var body: some View {
|
|
93
|
-
Text(label)
|
|
94
|
-
.font(.footnote)
|
|
95
|
-
.foregroundColor(Colors.black17)
|
|
96
|
-
.frame(maxWidth: .infinity, minHeight: 44, maxHeight: .infinity)
|
|
97
|
-
.background(Colors.blue08)
|
|
98
|
-
.clipShape(RoundedRectangle(cornerRadius: Radius.S, style: .continuous))
|
|
99
|
-
}
|
|
100
|
-
}
|