@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,237 +0,0 @@
1
- import SwiftUI
2
-
3
- // ── Design constants ─────────────────────────────────────────────────────────
4
-
5
- private let THUMB_SIZE: CGFloat = 20
6
- private let THUMB_BORDER: CGFloat = 3
7
- private let TRACK_HEIGHT: CGFloat = 4
8
- private let LABEL_GAP: CGFloat = 4 // space between label and thumb top (Spacing.XS)
9
-
10
- // ── Pure helpers (mirrors Compose / RN helpers) ──────────────────────────────
11
-
12
- private func clampF(_ value: CGFloat, _ minV: CGFloat, _ maxV: CGFloat) -> CGFloat {
13
- Swift.min(Swift.max(value, minV), maxV)
14
- }
15
-
16
- /// Converts a raw touch position into a snapped slider value.
17
- /// Direct port of Compose getValueForPosition().
18
- private func getValueForPosition(
19
- positionInView: CGFloat,
20
- containerWidth: CGFloat,
21
- thumbWidth: CGFloat,
22
- minV: CGFloat,
23
- maxV: CGFloat,
24
- step: CGFloat
25
- ) -> CGFloat {
26
- let availableSpace = containerWidth - thumbWidth
27
- if availableSpace <= 0 { return minV }
28
- let relStepUnit = step / (maxV - minV)
29
- var relPosition = (positionInView - thumbWidth / 2) / availableSpace
30
- let relOffset = relPosition.truncatingRemainder(dividingBy: relStepUnit)
31
- relPosition -= relOffset
32
- if relStepUnit != 0, relOffset / relStepUnit >= 0.5 { relPosition += relStepUnit }
33
- let steps = (relPosition / relStepUnit).rounded()
34
- return clampF(minV + steps * step, minV, maxV)
35
- }
36
-
37
- /// Direct port of Compose isLowCloser().
38
- private func isLowCloser(_ downX: CGFloat, _ lowPosition: CGFloat, _ highPosition: CGFloat) -> Bool {
39
- if lowPosition == highPosition { return downX < lowPosition }
40
- return abs(downX - lowPosition) < abs(downX - highPosition)
41
- }
42
-
43
- private func valueToFraction(_ value: CGFloat, _ minV: CGFloat, _ maxV: CGFloat) -> CGFloat {
44
- if maxV == minV { return 0 }
45
- return clampF((value - minV) / (maxV - minV), 0, 1)
46
- }
47
-
48
- private func formatLabel(_ value: CGFloat) -> String {
49
- if value == value.rounded() {
50
- return String(Int(value))
51
- }
52
- return String(format: "%.1f", value)
53
- }
54
-
55
- // ── Component ─────────────────────────────────────────────────────────────────
56
-
57
- /// Range slider (or single-thumb when `disableRange` = true), ported from Compose.
58
- ///
59
- /// SwiftUI ships a built-in `Slider`; this custom type is named `MomoSlider`
60
- /// (convention, like `MomoText`) to avoid module-wide shadowing.
61
- public struct MomoSlider: View {
62
- @Environment(\.appTheme) private var theme
63
-
64
- private let minV: CGFloat
65
- private let maxV: CGFloat
66
- private let step: CGFloat
67
- private let initLow: CGFloat
68
- private let initHigh: CGFloat
69
- private let minRange: CGFloat
70
- private let disableRange: Bool
71
- private let floatingLabel: Bool
72
- private let disabled: Bool
73
- private let onValueChanged: (CGFloat, CGFloat, Bool) -> Void
74
- private let onSliderTouchStart: (CGFloat, CGFloat) -> Void
75
- private let onSliderTouchEnd: (CGFloat, CGFloat) -> Void
76
-
77
- @State private var lowValue: CGFloat = 0
78
- @State private var highValue: CGFloat = 0
79
- @State private var draggingLow: Bool = true
80
- @State private var isDragging: Bool = false
81
- @State private var didInit: Bool = false
82
-
83
- public init(
84
- min: CGFloat,
85
- max: CGFloat,
86
- step: CGFloat,
87
- low: CGFloat? = nil,
88
- high: CGFloat? = nil,
89
- minRange: CGFloat = 0,
90
- disableRange: Bool = false,
91
- floatingLabel: Bool = false,
92
- disabled: Bool = false,
93
- onValueChanged: @escaping (CGFloat, CGFloat, Bool) -> Void = { _, _, _ in },
94
- onSliderTouchStart: @escaping (CGFloat, CGFloat) -> Void = { _, _ in },
95
- onSliderTouchEnd: @escaping (CGFloat, CGFloat) -> Void = { _, _ in }
96
- ) {
97
- self.minV = min
98
- self.maxV = max
99
- self.step = step
100
- self.minRange = minRange
101
- self.disableRange = disableRange
102
- self.floatingLabel = floatingLabel
103
- self.disabled = disabled
104
- self.onValueChanged = onValueChanged
105
- self.onSliderTouchStart = onSliderTouchStart
106
- self.onSliderTouchEnd = onSliderTouchEnd
107
-
108
- self.initLow = clampF(low ?? min, min, max)
109
- self.initHigh = disableRange ? max : clampF(high ?? max, min, max)
110
- }
111
-
112
- private var thumbColor: Color { disabled ? theme.colors.text.disable : theme.colors.primary }
113
- private var trackActiveColor: Color { disabled ? theme.colors.text.disable : theme.colors.primary }
114
- private var trackInactiveColor: Color { theme.colors.background.default }
115
-
116
- public var body: some View {
117
- GeometryReader { geo in
118
- let trackWidth = geo.size.width
119
- let availableWidth = max(trackWidth - THUMB_SIZE, 0)
120
- let topPad: CGFloat = floatingLabel ? (THUMB_SIZE + LABEL_GAP) : 0
121
-
122
- let lowFraction = valueToFraction(lowValue, minV, maxV)
123
- let highFraction = valueToFraction(highValue, minV, maxV)
124
-
125
- let activeStartFraction = disableRange ? 0 : lowFraction
126
- let activeEndFraction = disableRange ? lowFraction : highFraction
127
- let activeStartX = THUMB_SIZE / 2 + availableWidth * activeStartFraction
128
- let activeWidth = availableWidth * (activeEndFraction - activeStartFraction)
129
-
130
- ZStack(alignment: .topLeading) {
131
- // ── Floating label ────────────────────────────────────────────
132
- if floatingLabel && isDragging {
133
- let activeFraction = draggingLow ? lowFraction : highFraction
134
- let activeValue = draggingLow ? lowValue : highValue
135
- MomoText(formatLabel(activeValue), typography: .descriptionXsRegular, color: theme.colors.text.default)
136
- .padding(.horizontal, Spacing.XS)
137
- .padding(.vertical, Spacing.XXS)
138
- .background(Colors.black01)
139
- .clipShape(RoundedRectangle(cornerRadius: Radius.XS))
140
- .offset(x: availableWidth * activeFraction, y: 0)
141
- }
142
-
143
- // ── Inactive track (full width) ───────────────────────────────
144
- Capsule()
145
- .fill(trackInactiveColor)
146
- .frame(width: max(trackWidth - THUMB_SIZE, 0), height: TRACK_HEIGHT)
147
- .offset(x: THUMB_SIZE / 2, y: topPad + (THUMB_SIZE - TRACK_HEIGHT) / 2)
148
-
149
- // ── Active track (selected range / single value) ──────────────
150
- if activeWidth > 0 {
151
- Capsule()
152
- .fill(trackActiveColor)
153
- .frame(width: activeWidth, height: TRACK_HEIGHT)
154
- .offset(x: activeStartX, y: topPad + (THUMB_SIZE - TRACK_HEIGHT) / 2)
155
- }
156
-
157
- // ── Low thumb ─────────────────────────────────────────────────
158
- thumb
159
- .offset(x: availableWidth * lowFraction, y: topPad)
160
-
161
- // ── High thumb (range mode only) ──────────────────────────────
162
- if !disableRange {
163
- thumb
164
- .offset(x: availableWidth * highFraction, y: topPad)
165
- }
166
- }
167
- .frame(width: trackWidth, height: topPad + THUMB_SIZE, alignment: .topLeading)
168
- .contentShape(Rectangle())
169
- .gesture(dragGesture(trackWidth: trackWidth))
170
- }
171
- .frame(height: (floatingLabel ? (THUMB_SIZE + LABEL_GAP) : 0) + THUMB_SIZE)
172
- .onAppear {
173
- if !didInit {
174
- lowValue = initLow
175
- highValue = initHigh
176
- didInit = true
177
- }
178
- }
179
- }
180
-
181
- private var thumb: some View {
182
- Circle()
183
- .fill(thumbColor)
184
- .frame(width: THUMB_SIZE, height: THUMB_SIZE)
185
- .overlay(
186
- Circle().strokeBorder(theme.colors.background.surface, lineWidth: THUMB_BORDER)
187
- )
188
- }
189
-
190
- private func dragGesture(trackWidth: CGFloat) -> some Gesture {
191
- DragGesture(minimumDistance: 0)
192
- .onChanged { gesture in
193
- if disabled { return }
194
- let touchX = gesture.location.x
195
-
196
- if !isDragging {
197
- // Finger down: pick the closer thumb
198
- let lowCenterX = THUMB_SIZE / 2 +
199
- valueToFraction(lowValue, minV, maxV) * (trackWidth - THUMB_SIZE)
200
- let highCenterX = THUMB_SIZE / 2 +
201
- valueToFraction(highValue, minV, maxV) * (trackWidth - THUMB_SIZE)
202
- draggingLow = disableRange || isLowCloser(touchX, lowCenterX, highCenterX)
203
- isDragging = true
204
- onSliderTouchStart(lowValue, highValue)
205
- }
206
-
207
- applyDrag(positionX: touchX, trackWidthPx: trackWidth)
208
- }
209
- .onEnded { _ in
210
- if disabled { return }
211
- isDragging = false
212
- onSliderTouchEnd(lowValue, highValue)
213
- }
214
- }
215
-
216
- private func applyDrag(positionX: CGFloat, trackWidthPx: CGFloat) {
217
- let snapped = getValueForPosition(
218
- positionInView: positionX,
219
- containerWidth: trackWidthPx,
220
- thumbWidth: THUMB_SIZE,
221
- minV: minV, maxV: maxV, step: step
222
- )
223
- if draggingLow {
224
- let newLow = clampF(snapped, minV, highValue - minRange)
225
- if newLow != lowValue {
226
- lowValue = newLow
227
- onValueChanged(newLow, highValue, true)
228
- }
229
- } else {
230
- let newHigh = clampF(snapped, lowValue + minRange, maxV)
231
- if newHigh != highValue {
232
- highValue = newHigh
233
- onValueChanged(lowValue, newHigh, true)
234
- }
235
- }
236
- }
237
- }
@@ -1,220 +0,0 @@
1
- import SwiftUI
2
-
3
- // ---------------------------------------------------------------------------
4
- // Size variant
5
- // ---------------------------------------------------------------------------
6
-
7
- public enum StepperSize {
8
- case large
9
- case small
10
- }
11
-
12
- private struct StepperSizeSpec {
13
- let buttonSize: CGFloat
14
- let iconSize: CGFloat
15
- let numberMinWidth: CGFloat
16
- let numberHeight: CGFloat
17
- }
18
-
19
- private func stepperSizeSpec(_ size: StepperSize) -> StepperSizeSpec {
20
- switch size {
21
- case .large:
22
- return StepperSizeSpec(buttonSize: 36, iconSize: 22, numberMinWidth: 32, numberHeight: 28)
23
- case .small:
24
- return StepperSizeSpec(buttonSize: 28, iconSize: 18, numberMinWidth: 28, numberHeight: 24)
25
- }
26
- }
27
-
28
- // ---------------------------------------------------------------------------
29
- // Disabled spec — mirrors Compose `disabled: Any` (Bool or per-part array)
30
- // ---------------------------------------------------------------------------
31
-
32
- public struct StepperDisabled {
33
- let minus: Bool
34
- let plus: Bool
35
-
36
- fileprivate var number: Bool { minus && plus }
37
-
38
- public init(minus: Bool, plus: Bool) {
39
- self.minus = minus
40
- self.plus = plus
41
- }
42
-
43
- /// All parts share the same disabled state.
44
- public init(_ all: Bool) {
45
- self.minus = all
46
- self.plus = all
47
- }
48
- }
49
-
50
- extension StepperDisabled: ExpressibleByBooleanLiteral {
51
- public init(booleanLiteral value: Bool) {
52
- self.init(value)
53
- }
54
- }
55
-
56
- // ---------------------------------------------------------------------------
57
- // Private sub-views
58
- // ---------------------------------------------------------------------------
59
-
60
- private struct StepperButton: View {
61
- @Environment(\.appTheme) private var theme
62
-
63
- var sign: String
64
- var size: StepperSize
65
- var disabled: Bool
66
- var onPress: () -> Void
67
-
68
- var body: some View {
69
- let specs = stepperSizeSpec(size)
70
- let iconColor: Color = disabled ? theme.colors.text.disable : theme.colors.primary
71
- let iconName = sign == "-" ? "24_navigation_minus_circle" : "24_navigation_plus_circle"
72
-
73
- SwiftUI.Button(action: { if !disabled { onPress() } }) {
74
- ZStack {
75
- Circle().fill(theme.colors.background.disable)
76
- Icon(source: iconName, size: specs.iconSize, color: iconColor)
77
- }
78
- .frame(width: specs.buttonSize, height: specs.buttonSize)
79
- }
80
- .buttonStyle(.plain)
81
- .disabled(disabled)
82
- }
83
- }
84
-
85
- private struct StepperNumberView: View {
86
- @Environment(\.appTheme) private var theme
87
-
88
- @Binding var text: String
89
- var size: StepperSize
90
- var disabled: Bool
91
- var editable: Bool
92
-
93
- var body: some View {
94
- let specs = stepperSizeSpec(size)
95
- let textColor: Color = disabled ? theme.colors.text.disable : theme.colors.text.default
96
- let borderColor: Color = disabled ? theme.colors.border.disable : theme.colors.border.default
97
-
98
- Group {
99
- if editable {
100
- TextField("", text: $text)
101
- .keyboardType(.numberPad)
102
- .multilineTextAlignment(.center)
103
- .disabled(disabled)
104
- } else {
105
- MomoText(text, typography: .descriptionDefaultRegular, color: textColor)
106
- }
107
- }
108
- .font(.system(size: scaleSize(12)))
109
- .ignoreBoldTextSetting()
110
- .foregroundColor(textColor)
111
- .frame(minWidth: specs.numberMinWidth)
112
- .frame(height: specs.numberHeight)
113
- .padding(.horizontal, Spacing.XS)
114
- .overlay(
115
- RoundedRectangle(cornerRadius: Radius.S)
116
- .stroke(borderColor, lineWidth: 1)
117
- )
118
- }
119
- }
120
-
121
- // ---------------------------------------------------------------------------
122
- // Public API — MomoStepper
123
- // ---------------------------------------------------------------------------
124
-
125
- /// MomoStepper — quantity +/- control. Layout: [- button] [value] [+ button].
126
- ///
127
- /// Named `MomoStepper` to avoid shadowing SwiftUI's built-in `Stepper`.
128
- ///
129
- /// - Parameters:
130
- /// - value: Binding to the current value. Clamped to `min...max`.
131
- /// - min: Minimum allowed value (default 0). Minus auto-disables at this bound.
132
- /// - max: Maximum allowed value (default 100). Plus auto-disables at this bound.
133
- /// - step: Amount added/subtracted per press (default 1).
134
- /// - size: Visual size variant (default `.large`).
135
- /// - disabled: Disable state — `true` for everything, or `StepperDisabled(minus:plus:)`.
136
- /// - editable: When `true` the number display becomes a keyboard input (default false).
137
- /// - onValueChange: Callback invoked with the new value on every change.
138
- public struct MomoStepper: View {
139
- @Binding private var value: Int
140
- private let min: Int
141
- private let max: Int
142
- private let step: Int
143
- private let size: StepperSize
144
- private let disabled: StepperDisabled
145
- private let editable: Bool
146
- private let onValueChange: (Int) -> Void
147
-
148
- @State private var editingText: String = ""
149
-
150
- public init(
151
- value: Binding<Int>,
152
- min: Int = 0,
153
- max: Int = 100,
154
- step: Int = 1,
155
- size: StepperSize = .large,
156
- disabled: StepperDisabled = false,
157
- editable: Bool = false,
158
- onValueChange: @escaping (Int) -> Void = { _ in }
159
- ) {
160
- self._value = value
161
- self.min = min
162
- self.max = max
163
- self.step = step
164
- self.size = size
165
- self.disabled = disabled
166
- self.editable = editable
167
- self.onValueChange = onValueChange
168
- }
169
-
170
- private func clamp(_ raw: Int) -> Int {
171
- Swift.max(min, Swift.min(max, raw))
172
- }
173
-
174
- private func applyValue(_ raw: Int) {
175
- let parsed = clamp(raw)
176
- value = parsed
177
- editingText = String(parsed)
178
- onValueChange(parsed)
179
- }
180
-
181
- public var body: some View {
182
- let effectiveMinusDisabled = disabled.minus || value <= min
183
- let effectivePlusDisabled = disabled.plus || value >= max
184
-
185
- HStack(spacing: Spacing.S) {
186
- StepperButton(
187
- sign: "-",
188
- size: size,
189
- disabled: effectiveMinusDisabled,
190
- onPress: { applyValue(value - step) }
191
- )
192
-
193
- StepperNumberView(
194
- text: Binding(
195
- get: { editable ? editingText : String(value) },
196
- set: { newText in
197
- editingText = newText
198
- let parsed = Int(newText.filter { $0.isNumber }) ?? 0
199
- applyValue(parsed)
200
- }
201
- ),
202
- size: size,
203
- disabled: disabled.number,
204
- editable: editable
205
- )
206
-
207
- StepperButton(
208
- sign: "+",
209
- size: size,
210
- disabled: effectivePlusDisabled,
211
- onPress: { applyValue(value + step) }
212
- )
213
- }
214
- .onAppear {
215
- let clamped = clamp(value)
216
- if clamped != value { value = clamped }
217
- editingText = String(clamped)
218
- }
219
- }
220
- }