@momo-kits/native-kits 0.162.2-beta.26 → 0.162.2-beta.27

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 (180) hide show
  1. package/CLAUDE.md +1 -1
  2. package/ios/Application/ApplicationEnvironment.swift +0 -11
  3. package/ios/Application/Components.swift +44 -34
  4. package/ios/Application/FloatingButton.swift +9 -10
  5. package/ios/Application/HeaderRight.swift +45 -79
  6. package/ios/Application/Screen.swift +1 -2
  7. package/ios/Badge/Badge.swift +14 -11
  8. package/ios/Badge/BadgeRibbon.swift +21 -12
  9. package/ios/Button/Button.swift +22 -61
  10. package/ios/Checkbox/Checkbox.swift +13 -14
  11. package/ios/Chip/Chip.swift +9 -23
  12. package/ios/Colors+Radius+Spacing/Colors.swift +12 -12
  13. package/ios/Extensions/Color++.swift +0 -32
  14. package/ios/Icon/Icon.swift +1 -6
  15. package/ios/Input/Input.swift +25 -41
  16. package/ios/Input/InputPhoneNumber.swift +19 -20
  17. package/ios/Input/InputSearch.swift +47 -100
  18. package/ios/Input/InputTextArea.swift +40 -66
  19. package/ios/Popup/PopupDisplay.swift +16 -8
  20. package/ios/Popup/PopupInput.swift +8 -2
  21. package/ios/Swipeable/SwipeCell.swift +0 -185
  22. package/ios/Swipeable/SwipeCellModel.swift +0 -21
  23. package/ios/Typography/Text.swift +5 -79
  24. package/ios/Typography/Typography.swift +0 -3
  25. package/ios/native-kits.podspec +8 -61
  26. package/package.json +1 -1
  27. package/publish.sh +0 -50
  28. package/.claude/settings.local.json +0 -39
  29. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -103
  30. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +0 -215
  31. package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +0 -65
  32. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  33. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  34. package/ios/Application/Navigation/HeaderUser.swift +0 -209
  35. package/ios/Application/Navigation/NavigationContainer.swift +0 -176
  36. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  37. package/ios/Application/Navigation/Navigator.swift +0 -364
  38. package/ios/Application/Navigation/Overplay/BottomSheet.swift +0 -188
  39. package/ios/Application/Navigation/Overplay/ModalScreen.swift +0 -68
  40. package/ios/Application/Navigation/Overplay/SnackBar.swift +0 -144
  41. package/ios/Application/Navigation/component/Header.swift +0 -178
  42. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  43. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  44. package/ios/Application/StackScreen.swift +0 -453
  45. package/ios/Avatar/Avatar.swift +0 -193
  46. package/ios/BaselineView/BaselineView.swift +0 -163
  47. package/ios/Carousel/Carousel.swift +0 -181
  48. package/ios/Collapse/Collapse.swift +0 -182
  49. package/ios/Colors+Radius+Spacing/Theme.swift +0 -295
  50. package/ios/DateTimePicker/DateTimePicker.swift +0 -210
  51. package/ios/DateTimePicker/DateTimePickerTypes.swift +0 -55
  52. package/ios/DateTimePicker/DateTimePickerUtils.swift +0 -167
  53. package/ios/DateTimePicker/WheelPicker.swift +0 -161
  54. package/ios/Divider/Divider.swift +0 -16
  55. package/ios/IconButton/IconButton.swift +0 -112
  56. package/ios/InputDropDown/InputDropDown.swift +0 -198
  57. package/ios/InputMoney/InputMoney.swift +0 -335
  58. package/ios/InputOTP/InputOTP.swift +0 -210
  59. package/ios/Loader/Loader.swift +0 -113
  60. package/ios/MoMoUIKits.podspec +0 -5
  61. package/ios/Pagination/PaginationDot.swift +0 -61
  62. package/ios/Pagination/PaginationNumber.swift +0 -35
  63. package/ios/Pagination/PaginationScroll.swift +0 -101
  64. package/ios/Pagination/PaginationWhiteDot.swift +0 -37
  65. package/ios/Popup/PopupNotify.swift +0 -219
  66. package/ios/ProgressInfo/ProgressInfo.swift +0 -294
  67. package/ios/Radio/Radio.swift +0 -70
  68. package/ios/Rating/Rating.swift +0 -82
  69. package/ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json +0 -11
  70. package/ios/Resources/MoMoUIKitsColors.xcassets/Background.colorset/Contents.json +0 -38
  71. package/ios/Resources/MoMoUIKitsColors.xcassets/Border.colorset/Contents.json +0 -38
  72. package/ios/Resources/MoMoUIKitsColors.xcassets/Card.colorset/Contents.json +0 -38
  73. package/ios/Resources/MoMoUIKitsColors.xcassets/Contents.json +0 -6
  74. package/ios/Resources/MoMoUIKitsColors.xcassets/Error.colorset/Contents.json +0 -38
  75. package/ios/Resources/MoMoUIKitsColors.xcassets/Primary.colorset/Contents.json +0 -38
  76. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryDark.colorset/Contents.json +0 -38
  77. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryLight.colorset/Contents.json +0 -38
  78. package/ios/Resources/MoMoUIKitsColors.xcassets/Secondary.colorset/Contents.json +0 -38
  79. package/ios/Resources/MoMoUIKitsColors.xcassets/Success.colorset/Contents.json +0 -38
  80. package/ios/Resources/MoMoUIKitsColors.xcassets/Text.colorset/Contents.json +0 -38
  81. package/ios/Resources/MoMoUIKitsColors.xcassets/TextSecondary.colorset/Contents.json +0 -38
  82. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +0 -9
  83. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +0 -38
  84. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +0 -38
  85. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +0 -38
  86. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +0 -38
  87. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +0 -38
  88. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +0 -38
  89. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +0 -38
  90. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +0 -38
  91. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +0 -38
  92. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +0 -38
  93. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +0 -38
  94. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +0 -38
  95. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +0 -38
  96. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +0 -38
  97. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +0 -38
  98. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +0 -38
  99. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +0 -38
  100. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +0 -38
  101. package/ios/Resources/MoMoUIKitsColors.xcassets/Warning.colorset/Contents.json +0 -38
  102. package/ios/Skeleton/Skeleton.swift +0 -99
  103. package/ios/Slider/Slider.swift +0 -237
  104. package/ios/Stepper/Stepper.swift +0 -220
  105. package/ios/Steps/Steps.swift +0 -451
  106. package/ios/SuggestAction/SuggestAction.swift +0 -97
  107. package/ios/TabView/TabView.swift +0 -530
  108. package/ios/Tag/Tag.swift +0 -98
  109. package/ios/Title/Title.swift +0 -269
  110. package/ios/Tooltip/Tooltip.swift +0 -482
  111. package/ios/Typography/FontScaleStore.swift +0 -19
  112. package/ios/Uploader/Uploader.swift +0 -183
  113. package/ios-demo/MoMoUIKitsDemo.podspec +0 -20
  114. package/ios-demo/Resources/dot_loading.json +0 -558
  115. package/ios-demo/Resources/icon.json +0 -4052
  116. package/ios-demo/Resources/lottie_circle_loader.json +0 -1
  117. package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +0 -48
  118. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +0 -25
  119. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +0 -26
  120. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +0 -19
  121. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +0 -33
  122. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +0 -48
  123. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +0 -21
  124. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +0 -17
  125. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +0 -28
  126. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +0 -31
  127. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +0 -24
  128. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +0 -110
  129. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +0 -93
  130. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +0 -269
  131. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +0 -117
  132. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +0 -89
  133. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +0 -100
  134. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +0 -126
  135. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +0 -63
  136. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +0 -20
  137. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +0 -93
  138. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +0 -107
  139. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +0 -124
  140. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +0 -14
  141. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +0 -49
  142. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +0 -278
  143. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +0 -58
  144. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +0 -78
  145. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +0 -138
  146. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +0 -111
  147. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +0 -136
  148. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +0 -107
  149. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +0 -73
  150. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +0 -100
  151. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +0 -32
  152. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +0 -57
  153. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +0 -70
  154. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +0 -227
  155. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +0 -72
  156. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +0 -175
  157. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +0 -121
  158. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +0 -76
  159. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +0 -89
  160. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +0 -32
  161. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +0 -56
  162. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +0 -32
  163. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +0 -150
  164. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +0 -138
  165. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +0 -103
  166. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +0 -94
  167. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +0 -79
  168. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +0 -152
  169. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +0 -33
  170. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +0 -190
  171. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +0 -34
  172. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +0 -182
  173. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +0 -109
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +0 -187
  175. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +0 -87
  176. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +0 -131
  177. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +0 -51
  178. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +0 -15
  179. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +0 -73
  180. package/scripts/gen-ios-color.mjs +0 -136
@@ -1,193 +0,0 @@
1
- import SwiftUI
2
-
3
- let LOGO_MOMO_URL =
4
- "https://static.momocdn.net/app/img/kits/logo_momo_circle.png"
5
- let ICON_SUPPORT_DEFAULT_URL =
6
- "https://static.momocdn.net/app/img/kits/_indicator.png"
7
-
8
- public enum AvatarSize {
9
- case size24
10
- case size32
11
- case size40
12
- case size48
13
- case size56
14
- case size72
15
-
16
- var sizeDp: CGFloat {
17
- switch self {
18
- case .size24: return 24
19
- case .size32: return 32
20
- case .size40: return 40
21
- case .size48: return 48
22
- case .size56: return 56
23
- case .size72: return 72
24
- }
25
- }
26
-
27
- var iconSizeDp: CGFloat {
28
- switch self {
29
- case .size24: return 16
30
- case .size32: return 16
31
- case .size40: return 24
32
- case .size48: return 32
33
- case .size56: return 32
34
- case .size72: return 40
35
- }
36
- }
37
- }
38
-
39
- public struct Avatar: View {
40
- @Environment(\.appTheme) private var theme
41
-
42
- // MARK: Lifecycle
43
-
44
- public init(
45
- size: AvatarSize = .size32,
46
- rounded: Bool = true,
47
- name: String? = nil,
48
- image: String? = nil,
49
- showIconMomo: Bool = false,
50
- showIconSupport: Bool = false,
51
- iconSupport: String = "https://static.momocdn.net/app/img/kits/_indicator.png",
52
- accessibilityLabel: String? = nil
53
- ) {
54
- self.size = size
55
- self.rounded = rounded
56
- self.name = name
57
- self.image = image
58
- self.showIconMomo = showIconMomo
59
- self.showIconSupport = showIconSupport
60
- self.iconSupport = iconSupport
61
- self.accessibilityLabel = accessibilityLabel
62
- }
63
-
64
- // MARK: Public
65
-
66
- public var body: some View {
67
- ZStack {
68
- // Inner content clipped by shape with border
69
- ZStack {
70
- content
71
- }
72
- .frame(width: size.sizeDp, height: size.sizeDp)
73
- .background(Colors.pink09)
74
- .clipShape(RoundedRectangle(cornerRadius: cornerRadius))
75
- .overlay(
76
- RoundedRectangle(cornerRadius: cornerRadius)
77
- .stroke(theme.colors.border.default, lineWidth: 1)
78
- )
79
-
80
- // Badge icons — outside clip so they are not cut by border
81
- if shouldShowIconTop {
82
- ImageView(LOGO_MOMO_URL)
83
- .frame(width: badgeSizeDp, height: badgeSizeDp)
84
- .clipShape(RoundedRectangle(cornerRadius: badgeCornerRadius))
85
- .offset(x: 1, y: -1)
86
- .frame(
87
- width: size.sizeDp,
88
- height: size.sizeDp,
89
- alignment: .topTrailing
90
- )
91
- .zIndex(1)
92
- }
93
-
94
- if shouldShowIconBottom {
95
- ImageView(iconSupport)
96
- .frame(width: badgeSizeDp, height: badgeSizeDp)
97
- .clipShape(RoundedRectangle(cornerRadius: badgeCornerRadius))
98
- .offset(x: 1, y: 1)
99
- .frame(
100
- width: size.sizeDp,
101
- height: size.sizeDp,
102
- alignment: .bottomTrailing
103
- )
104
- .zIndex(1)
105
- }
106
- }
107
- .frame(width: size.sizeDp, height: size.sizeDp)
108
- .accessibilityElement(children: .ignore)
109
- .accessibilityLabel(automationId)
110
- }
111
-
112
- // MARK: Internal
113
-
114
- let size: AvatarSize
115
- let rounded: Bool
116
- let name: String?
117
- let image: String?
118
- let showIconMomo: Bool
119
- let showIconSupport: Bool
120
- let iconSupport: String
121
- let accessibilityLabel: String?
122
-
123
- @ViewBuilder
124
- private var content: some View {
125
- if let image {
126
- ImageView(image, contentMode: .fill)
127
- .frame(width: size.sizeDp, height: size.sizeDp)
128
- .clipShape(RoundedRectangle(cornerRadius: cornerRadius))
129
- } else if let name {
130
- MomoText(
131
- Self.avatarInitials(name),
132
- typography: .descriptionXsRegular,
133
- color: Colors.pink03
134
- )
135
- } else {
136
- Icon(
137
- source: "basic_person",
138
- size: size.iconSizeDp,
139
- color: Colors.pink03
140
- )
141
- }
142
- }
143
-
144
- private var cornerRadius: CGFloat {
145
- rounded ? size.sizeDp / 2 : Spacing.XS
146
- }
147
-
148
- private var badgeSizeDp: CGFloat {
149
- size.iconSizeDp / 2
150
- }
151
-
152
- private var badgeCornerRadius: CGFloat {
153
- size.iconSizeDp / 4
154
- }
155
-
156
- private var shouldShowIconTop: Bool {
157
- showIconMomo && rounded
158
- }
159
-
160
- private var shouldShowIconBottom: Bool {
161
- showIconSupport && rounded
162
- }
163
-
164
- private var automationId: String {
165
- accessibilityLabel ?? "Avatar\(name != nil ? "/\(name!)" : "")"
166
- }
167
-
168
- private static func avatarInitials(_ name: String) -> String {
169
- let words = name.trimmingCharacters(in: .whitespaces)
170
- .split(separator: " ")
171
- .filter { !$0.isEmpty }
172
- return words.suffix(2)
173
- .compactMap { $0.first?.uppercased() }
174
- .joined()
175
- }
176
- }
177
-
178
- // MARK: - Preview
179
- #if DEBUG
180
- struct Avatar_Previews: PreviewProvider {
181
- static var previews: some View {
182
- HStack(spacing: 12) {
183
- Avatar(size: .size40)
184
- Avatar(size: .size40, name: "Nguyen Van A")
185
- Avatar(size: .size40, rounded: false, name: "John Doe")
186
- Avatar(size: .size56, image: "https://i.pravatar.cc/150?img=1", showIconMomo: true)
187
- Avatar(size: .size56, showIconSupport: true)
188
- }
189
- .padding()
190
- .previewLayout(.sizeThatFits)
191
- }
192
- }
193
- #endif
@@ -1,163 +0,0 @@
1
- import SwiftUI
2
-
3
- public struct BaselineView: View {
4
- private let enabled: Bool
5
-
6
- public init(enabled: Bool = true) {
7
- self.enabled = enabled
8
- }
9
-
10
- public var body: some View {
11
- if enabled {
12
- GeometryReader { geometry in
13
- let topInset = geometry.safeAreaInsets.top
14
- let bottomInset = min(geometry.safeAreaInsets.bottom, 21)
15
-
16
- ZStack(alignment: .topLeading) {
17
- dangerZones(topInset: topInset, bottomInset: bottomInset)
18
- guideLines(
19
- topInset: topInset,
20
- bottomInset: bottomInset,
21
- width: geometry.size.width
22
- )
23
- }
24
- .frame(maxWidth: .infinity, maxHeight: .infinity)
25
- .ignoresSafeArea()
26
- .allowsHitTesting(false)
27
- .accessibilityHidden(true)
28
- }
29
- }
30
- }
31
-
32
- private func dangerZones(topInset: CGFloat, bottomInset: CGFloat) -> some View {
33
- let safeBackground = Color.red.opacity(0.15)
34
-
35
- return ZStack(alignment: .topLeading) {
36
- safeBackground
37
- .frame(maxWidth: .infinity)
38
- .frame(height: topInset)
39
-
40
- HStack(spacing: 0) {
41
- safeBackground
42
- .frame(width: 12)
43
- Spacer(minLength: 0)
44
- safeBackground
45
- .frame(width: 12)
46
- }
47
- .padding(.top, topInset)
48
- .padding(.bottom, bottomInset)
49
-
50
- VStack(spacing: 0) {
51
- Spacer(minLength: 0)
52
- safeBackground
53
- .frame(maxWidth: .infinity)
54
- .frame(height: bottomInset)
55
- }
56
- }
57
- }
58
-
59
- private func guideLines(topInset: CGFloat, bottomInset: CGFloat, width: CGFloat) -> some View {
60
- ZStack(alignment: .topLeading) {
61
- baselineLine(isDotted: false, color: Color(hex: 0xE400FF))
62
- .offset(y: topInset)
63
-
64
- baselineLine(color: Color(hex: 0xE400FF))
65
- .offset(y: topInset + 52)
66
-
67
- baselineLine(color: Color(hex: 0xE400FF))
68
- .offset(y: topInset + 104)
69
-
70
- bottomLine(bottomInset: bottomInset + 64, color: Color(hex: 0xE400FF))
71
-
72
- bottomLine(bottomInset: bottomInset, isDotted: false, color: Color(hex: 0xE400FF))
73
-
74
- baselineLine(orientation: .vertical, isDotted: false, color: Color(hex: 0xE400FF))
75
- .offset(x: 12)
76
-
77
- baselineLine(orientation: .vertical, isDotted: false, color: Color(hex: 0xE400FF))
78
- .offset(x: width - 12)
79
-
80
- baselineLine(color: Color(hex: 0xFF7A00))
81
- .offset(y: topInset + 26)
82
-
83
- bottomLine(bottomInset: bottomInset + 56, color: Color(hex: 0xFFCC00))
84
-
85
- bottomLine(bottomInset: bottomInset + 8, color: Color(hex: 0xFFCC00))
86
-
87
- baselineLine(orientation: .vertical, color: Color(hex: 0x00C520))
88
- .frame(height: 52)
89
- .offset(x: 40, y: topInset)
90
-
91
- baselineLine(orientation: .vertical, color: Color(hex: 0x00C520))
92
- .frame(height: 52)
93
- .offset(x: 48, y: topInset)
94
- }
95
- }
96
-
97
- private func bottomLine(
98
- bottomInset: CGFloat,
99
- isDotted: Bool = true,
100
- color: Color
101
- ) -> some View {
102
- VStack(spacing: 0) {
103
- Spacer(minLength: 0)
104
- baselineLine(isDotted: isDotted, color: color)
105
- .padding(.bottom, bottomInset)
106
- }
107
- }
108
-
109
- private func baselineLine(
110
- orientation: BaselineOrientation = .horizontal,
111
- isDotted: Bool = true,
112
- color: Color
113
- ) -> some View {
114
- BaselineLineShape(orientation: orientation)
115
- .stroke(
116
- color,
117
- style: StrokeStyle(
118
- lineWidth: 1,
119
- lineCap: .round,
120
- dash: isDotted ? [8, 8] : []
121
- )
122
- )
123
- .frame(
124
- maxWidth: orientation == .horizontal ? .infinity : 1,
125
- maxHeight: orientation == .vertical ? .infinity : 1
126
- )
127
- }
128
- }
129
-
130
- public enum BaselineOrientation: Sendable {
131
- case horizontal
132
- case vertical
133
- }
134
-
135
- private struct BaselineLineShape: Shape {
136
- let orientation: BaselineOrientation
137
-
138
- func path(in rect: CGRect) -> Path {
139
- var path = Path()
140
-
141
- switch orientation {
142
- case .horizontal:
143
- path.move(to: CGPoint(x: rect.minX, y: rect.midY))
144
- path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
145
- case .vertical:
146
- path.move(to: CGPoint(x: rect.midX, y: rect.minY))
147
- path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
148
- }
149
-
150
- return path
151
- }
152
- }
153
-
154
- #if DEBUG
155
- struct BaselineView_Previews: PreviewProvider {
156
- static var previews: some View {
157
- ZStack {
158
- Colors.black01
159
- BaselineView()
160
- }
161
- }
162
- }
163
- #endif
@@ -1,181 +0,0 @@
1
- import Combine
2
- import SwiftUI
3
-
4
- /// Native SwiftUI port of the Compose `Carousel` component.
5
- ///
6
- /// Mirrors the Compose API: a paging horizontal carousel driven by an
7
- /// `itemCount` and a `content` builder that receives the real (modulo) index.
8
- /// Supports infinite `loop`, `autoplay` with a configurable interval,
9
- /// inactive-slide `opacity`/`scale` effects, optional `contentPadding`,
10
- /// snap behavior, and scroll enable/disable. Snap / scroll-index callbacks
11
- /// report the real item index.
12
- ///
13
- /// Available iOS 14+ (uses a custom paging ScrollView so behavior is identical
14
- /// across versions and supports the loop virtualization + transform effects
15
- /// that `TabView(.page)` cannot express).
16
- public struct Carousel<Content: View>: View {
17
- // MARK: Lifecycle
18
-
19
- public init(
20
- itemCount: Int,
21
- loop: Bool = false,
22
- autoplay: Bool = false,
23
- autoplayInterval: TimeInterval = 3.0,
24
- enableSnap: Bool = true,
25
- scrollEnabled: Bool = true,
26
- inactiveSlideOpacity: Double = 1.0,
27
- inactiveSlideScale: Double = 1.0,
28
- contentPadding: CGFloat = 0,
29
- onSnapToItem: @escaping (Int) -> Void = { _ in },
30
- onScrollIndexChanged: @escaping (Int) -> Void = { _ in },
31
- @ViewBuilder content: @escaping (_ index: Int) -> Content
32
- ) {
33
- self.itemCount = itemCount
34
- self.loop = loop
35
- self.autoplay = autoplay
36
- self.autoplayInterval = autoplayInterval
37
- self.enableSnap = enableSnap
38
- self.scrollEnabled = scrollEnabled
39
- self.inactiveSlideOpacity = inactiveSlideOpacity
40
- self.inactiveSlideScale = inactiveSlideScale
41
- self.contentPadding = contentPadding
42
- self.onSnapToItem = onSnapToItem
43
- self.onScrollIndexChanged = onScrollIndexChanged
44
- self.content = content
45
-
46
- let virtual = loop ? itemCount * 1000 : itemCount
47
- let start = loop ? (virtual / 2) - ((virtual / 2) % max(itemCount, 1)) : 0
48
- self.virtualPageCount = virtual
49
- _currentPage = State(initialValue: start)
50
- }
51
-
52
- // MARK: Public
53
-
54
- public var body: some View {
55
- if itemCount <= 0 {
56
- EmptyView()
57
- } else {
58
- GeometryReader { geo in
59
- let pageWidth = max(geo.size.width - contentPadding * 2, 1)
60
- carouselContent(pageWidth: pageWidth, viewportWidth: geo.size.width)
61
- }
62
- }
63
- }
64
-
65
- // MARK: Private
66
-
67
- private let itemCount: Int
68
- private let loop: Bool
69
- private let autoplay: Bool
70
- private let autoplayInterval: TimeInterval
71
- private let enableSnap: Bool
72
- private let scrollEnabled: Bool
73
- private let inactiveSlideOpacity: Double
74
- private let inactiveSlideScale: Double
75
- private let contentPadding: CGFloat
76
- private let onSnapToItem: (Int) -> Void
77
- private let onScrollIndexChanged: (Int) -> Void
78
- private let content: (Int) -> Content
79
-
80
- private let virtualPageCount: Int
81
-
82
- @State private var currentPage: Int
83
- @State private var dragOffset: CGFloat = 0
84
- @State private var isDragging: Bool = false
85
- @State private var autoplayTick: Int = 0
86
-
87
- private func realIndex(_ page: Int) -> Int {
88
- ((page % itemCount) + itemCount) % itemCount
89
- }
90
-
91
- @ViewBuilder
92
- private func carouselContent(pageWidth: CGFloat, viewportWidth: CGFloat) -> some View {
93
- let step = pageWidth
94
- // Offset so the current page is centered within the viewport padding.
95
- let baseOffset = -CGFloat(currentPage) * step + contentPadding
96
-
97
- ZStack {
98
- HStack(spacing: 0) {
99
- ForEach(0 ..< virtualPageCount, id: \.self) { page in
100
- slide(page: page, step: step, viewportWidth: viewportWidth)
101
- .frame(width: pageWidth)
102
- }
103
- }
104
- .offset(x: baseOffset + dragOffset)
105
- .animation(isDragging ? nil : .easeInOut(duration: 0.25), value: currentPage)
106
- .frame(width: viewportWidth, alignment: .leading)
107
- .contentShape(Rectangle())
108
- .gesture(scrollEnabled ? dragGesture(step: step) : nil)
109
- }
110
- .frame(width: viewportWidth)
111
- .clipped()
112
- .onReceive(autoplayTimer) { _ in
113
- guard autoplay, itemCount > 1, !isDragging else { return }
114
- advance()
115
- }
116
- }
117
-
118
- @ViewBuilder
119
- private func slide(page: Int, step: CGFloat, viewportWidth: CGFloat) -> some View {
120
- // Fractional offset of this page from the current page (incl. drag).
121
- let dragFraction = step > 0 ? -dragOffset / step : 0
122
- let pageOffset = abs(CGFloat(currentPage - page) + dragFraction)
123
- let isActive = pageOffset < 0.5
124
- let scale = isActive ? 1.0 : inactiveSlideScale
125
- let alpha = isActive ? 1.0 : inactiveSlideOpacity
126
-
127
- content(realIndex(page))
128
- .scaleEffect(scale)
129
- .opacity(alpha)
130
- .animation(.easeInOut(duration: 0.15), value: isActive)
131
- }
132
-
133
- private func dragGesture(step: CGFloat) -> some Gesture {
134
- DragGesture(minimumDistance: 5)
135
- .onChanged { value in
136
- isDragging = true
137
- dragOffset = value.translation.width
138
- }
139
- .onEnded { value in
140
- isDragging = false
141
- let predicted = value.predictedEndTranslation.width
142
- let threshold = step / 4
143
- var target = currentPage
144
- if enableSnap {
145
- if predicted < -threshold {
146
- target = min(currentPage + 1, virtualPageCount - 1)
147
- } else if predicted > threshold {
148
- target = max(currentPage - 1, 0)
149
- }
150
- } else {
151
- // Free scroll still settles to nearest page boundary.
152
- let moved = step > 0 ? Int((-dragOffset / step).rounded()) : 0
153
- target = min(max(currentPage + moved, 0), virtualPageCount - 1)
154
- }
155
- dragOffset = 0
156
- setPage(target)
157
- }
158
- }
159
-
160
- private func advance() {
161
- let next = currentPage + 1
162
- if next < virtualPageCount {
163
- setPage(next)
164
- }
165
- }
166
-
167
- private func setPage(_ page: Int) {
168
- let clamped = min(max(page, 0), virtualPageCount - 1)
169
- let changed = clamped != currentPage
170
- currentPage = clamped
171
- if changed {
172
- let real = realIndex(clamped)
173
- onSnapToItem(real)
174
- onScrollIndexChanged(real)
175
- }
176
- }
177
-
178
- private var autoplayTimer: Publishers.Autoconnect<Timer.TimerPublisher> {
179
- Timer.publish(every: autoplayInterval, on: .main, in: .common).autoconnect()
180
- }
181
- }