@momo-kits/native-kits 0.167.1-duo.1-debug → 0.167.1-duo.10-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +32 -0
- package/compose/build.gradle.kts +15 -1
- package/compose/build.gradle.kts.backup +14 -0
- package/compose/compose.podspec +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +53 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +13 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +21 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +232 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +11 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +4 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +143 -31
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +100 -66
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +22 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +66 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +23 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +81 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +185 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +4 -1
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +86 -0
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +17 -0
- package/ios/Application/ComposeApi.swift +2 -1
- package/ios/Application/{Components.swift → LegacyHeader.swift} +7 -65
- package/ios/Application/Screen.swift +3 -2
- package/ios/Avatar/Avatar.swift +4 -3
- package/ios/Badge/Badge.swift +6 -4
- package/ios/Badge/BadgeDot.swift +1 -1
- package/ios/Badge/BadgeRibbon.swift +0 -1
- package/ios/Button/Button.swift +15 -12
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
- package/ios/Carousel/Carousel.swift +29 -12
- package/ios/Checkbox/Checkbox.swift +7 -3
- package/ios/Chip/Chip.swift +5 -4
- package/ios/Collapse/Collapse.swift +70 -19
- package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
- package/ios/DateTimePicker/DateTimePicker.swift +54 -20
- package/ios/DateTimePicker/WheelPicker.swift +103 -105
- package/ios/Divider/Divider.swift +37 -4
- package/ios/Extensions/Color++.swift +38 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Extensions/SkeletonUICompat.swift +11 -0
- package/ios/Icon/Icon.swift +21 -23
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/IconButton/IconButton.swift +15 -7
- package/ios/Image/Image.swift +7 -23
- package/ios/Information/Information.swift +27 -9
- package/ios/Input/ErrorView.swift +2 -2
- package/ios/Input/Input.swift +160 -147
- package/ios/Input/InputPhoneNumber.swift +12 -9
- package/ios/Input/InputSearch.swift +102 -75
- package/ios/Input/InputTextArea.swift +128 -145
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +33 -11
- package/ios/InputOTP/InputOTP.swift +22 -12
- package/ios/Layout/Card.swift +50 -0
- package/ios/Layout/GridContext.swift +123 -3
- package/ios/Layout/Item.swift +5 -4
- package/ios/Layout/ScreenLayout.swift +312 -0
- package/ios/Layout/Section.swift +4 -100
- package/ios/Loader/Loader.swift +5 -9
- package/ios/Lottie/LottieView.swift +14 -2
- package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +97 -8
- package/ios/Navigation/BottomTab/BottomTab.swift +187 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +15 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +105 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +304 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
- package/ios/Navigation/Component/Header.swift +493 -0
- package/ios/Navigation/Component/HeaderBackground.swift +134 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +386 -0
- package/ios/Navigation/Component/HeaderTitle.swift +36 -0
- package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
- package/ios/Navigation/Component/SideBar.swift +176 -0
- package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +5 -5
- package/ios/Navigation/HostBackGestureBridge.swift +57 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +5 -1
- package/ios/Navigation/Navigation.swift +190 -0
- package/ios/{Application/Navigation → Navigation}/NavigationContainer.swift +92 -81
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +185 -36
- package/ios/Navigation/StackScreen.swift +782 -0
- package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
- package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
- package/ios/Pagination/PaginationScroll.swift +2 -2
- package/ios/Popup/PopupDisplay.swift +72 -136
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +140 -0
- package/ios/Popup/PopupNotify.swift +56 -73
- package/ios/Popup/PopupPromotion.swift +31 -47
- package/ios/PopupView/FullscreenPopup.swift +13 -19
- package/ios/PopupView/PopupView.swift +1 -1
- package/ios/PopupView/Utils++.swift +0 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +2 -1
- package/ios/Rating/Rating.swift +9 -1
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
- package/ios/Skeleton/Skeleton.swift +22 -27
- package/ios/Slider/Slider.swift +16 -18
- package/ios/Stepper/Stepper.swift +6 -1
- package/ios/Steps/Steps.swift +8 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -0
- package/ios/Swipeable/SwipeCell.swift +10 -6
- package/ios/Switch/Switch.swift +22 -20
- package/ios/TabView/TabView.swift +102 -21
- package/ios/Tag/Tag.swift +15 -4
- package/ios/Template/Logo/Logo.swift +2 -1
- package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/Text.swift +61 -19
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/package.json +1 -1
- package/scripts/gen-ios-icons.mjs +36 -0
- package/.claude/settings.local.json +0 -185
- package/ios/Application/HeaderRight.swift +0 -306
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
- package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
- package/ios/Application/Navigation/HeaderTitle.swift +0 -57
- package/ios/Application/Navigation/NavigationOptions.swift +0 -109
- package/ios/Application/Navigation/NavigatorManager.swift +0 -22
- package/ios/Application/Navigation/component/Header.swift +0 -178
- package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
- package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
- package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
- package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
- package/ios/Application/StackScreen.swift +0 -492
- package/local.properties +0 -8
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -76,16 +76,15 @@ public struct InputPhoneNumber: View {
|
|
|
76
76
|
}
|
|
77
77
|
)
|
|
78
78
|
|
|
79
|
-
VStack(alignment: .leading, spacing:
|
|
79
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
80
80
|
HStack(spacing: 0) {
|
|
81
81
|
// 🇻🇳 Flag
|
|
82
82
|
ImageView("https://static.momocdn.net/app/img/icon/ic-qrcode-package/ic_vn_flag.png")
|
|
83
83
|
.frame(width: 24, height: 24)
|
|
84
84
|
.padding(.trailing, Spacing.XS)
|
|
85
|
-
|
|
86
|
-
MomoText("+84", typography: size == .small ? .headerSSemibold : .headerMBold)
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
|
|
86
|
+
MomoText("+84", typography: size == .small ? .headerSSemibold : .headerMBold, color: theme.colors.text.default)
|
|
87
|
+
|
|
89
88
|
Rectangle()
|
|
90
89
|
.fill(theme.colors.border.default)
|
|
91
90
|
.frame(width: 1, height: size == .small ? 24 : 32)
|
|
@@ -105,7 +104,9 @@ public struct InputPhoneNumber: View {
|
|
|
105
104
|
handleFocusChange(focused)
|
|
106
105
|
})
|
|
107
106
|
.keyboardType(.numberPad)
|
|
108
|
-
.font(size == .small
|
|
107
|
+
.font(size == .small
|
|
108
|
+
? fontScale.font(size: 14, weight: .semibold)
|
|
109
|
+
: fontScale.font(size: 18, weight: .bold))
|
|
109
110
|
.ignoreBoldTextSetting()
|
|
110
111
|
.foregroundColor(theme.colors.text.default)
|
|
111
112
|
.applyCursorColor(theme.colors.primary)
|
|
@@ -125,13 +126,14 @@ public struct InputPhoneNumber: View {
|
|
|
125
126
|
onChangeText?("")
|
|
126
127
|
}) {
|
|
127
128
|
Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
|
|
128
|
-
.padding(.
|
|
129
|
+
.padding(.horizontal, Spacing.S)
|
|
129
130
|
}
|
|
131
|
+
.accessibilityIdentifier("ic_clear")
|
|
130
132
|
}
|
|
131
|
-
|
|
133
|
+
|
|
132
134
|
// Loading indicator
|
|
133
135
|
if loading {
|
|
134
|
-
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
136
|
+
ActivityIndicator(isAnimating: .constant(true), style: .medium, color: rightIconColor ?? theme.colors.text.default)
|
|
135
137
|
.frame(width: 16, height: 16)
|
|
136
138
|
}
|
|
137
139
|
|
|
@@ -166,6 +168,7 @@ public struct InputPhoneNumber: View {
|
|
|
166
168
|
// MARK: - Helpers
|
|
167
169
|
|
|
168
170
|
private func handleFocusChange(_ focused: Bool) {
|
|
171
|
+
guard focused != isFocused else { return }
|
|
169
172
|
isFocused = focused
|
|
170
173
|
if focused {
|
|
171
174
|
onFocus?()
|
|
@@ -18,16 +18,17 @@ struct RenderRightIconSearch: View {
|
|
|
18
18
|
var body: some View {
|
|
19
19
|
HStack(spacing: 0) {
|
|
20
20
|
if loading {
|
|
21
|
-
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
21
|
+
ActivityIndicator(isAnimating: .constant(true), style: .medium, color: color)
|
|
22
22
|
.frame(width: 16, height: 16)
|
|
23
23
|
.padding(.horizontal, Spacing.S)
|
|
24
24
|
}
|
|
25
25
|
if !icon.isEmpty {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.
|
|
26
|
+
// Compose: a 1pt rule inset S from the top and bottom of the 36pt bar.
|
|
27
|
+
Rectangle()
|
|
28
|
+
.fill(theme.colors.primary)
|
|
29
|
+
.frame(width: 1, height: 36 - Spacing.S * 2)
|
|
29
30
|
.padding(.leading, Spacing.S)
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
Icon(source: icon, size: 24, color: color)
|
|
32
33
|
.padding(.leading, Spacing.S)
|
|
33
34
|
.onTapGesture(perform: onClick)
|
|
@@ -58,6 +59,10 @@ public struct InputSearchProps {
|
|
|
58
59
|
var searchIcon: (() -> AnyView)? = nil
|
|
59
60
|
// Mirrors Compose's InputSearchProps.placeholderCustomRender — a custom placeholder view.
|
|
60
61
|
var placeholderCustomRender: (() -> AnyView)? = nil
|
|
62
|
+
var backgroundColor: Color? = nil
|
|
63
|
+
var clearCondition: (() -> Bool)? = nil
|
|
64
|
+
var onClearPress: () -> Void = {}
|
|
65
|
+
var onSubmit: () -> Void = {}
|
|
61
66
|
|
|
62
67
|
public init(text: Binding<String>,
|
|
63
68
|
buttonText: String = "",
|
|
@@ -77,7 +82,11 @@ public struct InputSearchProps {
|
|
|
77
82
|
fontWeight: Font.Weight = .regular,
|
|
78
83
|
keyboardType: UIKeyboardType = .default,
|
|
79
84
|
searchIcon: (() -> AnyView)? = nil,
|
|
80
|
-
placeholderCustomRender: (() -> AnyView)? = nil
|
|
85
|
+
placeholderCustomRender: (() -> AnyView)? = nil,
|
|
86
|
+
backgroundColor: Color? = nil,
|
|
87
|
+
clearCondition: (() -> Bool)? = nil,
|
|
88
|
+
onClearPress: @escaping () -> Void = {},
|
|
89
|
+
onSubmit: @escaping () -> Void = {}) {
|
|
81
90
|
self._text = text
|
|
82
91
|
self.buttonText = buttonText
|
|
83
92
|
self.showButtonText = showButtonText
|
|
@@ -97,6 +106,10 @@ public struct InputSearchProps {
|
|
|
97
106
|
self.keyboardType = keyboardType
|
|
98
107
|
self.searchIcon = searchIcon
|
|
99
108
|
self.placeholderCustomRender = placeholderCustomRender
|
|
109
|
+
self.backgroundColor = backgroundColor
|
|
110
|
+
self.clearCondition = clearCondition
|
|
111
|
+
self.onClearPress = onClearPress
|
|
112
|
+
self.onSubmit = onSubmit
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
|
|
@@ -124,6 +137,10 @@ public struct InputSearch: View {
|
|
|
124
137
|
var keyboardType: UIKeyboardType = .default
|
|
125
138
|
var searchIcon: (() -> AnyView)? = nil
|
|
126
139
|
var placeholderCustomRender: (() -> AnyView)? = nil
|
|
140
|
+
var backgroundColor: Color? = nil
|
|
141
|
+
var clearCondition: (() -> Bool)? = nil
|
|
142
|
+
var onClearPress: () -> Void = {}
|
|
143
|
+
var onSubmit: () -> Void = {}
|
|
127
144
|
|
|
128
145
|
@State private var isFocused = false
|
|
129
146
|
@State private var isBlurred = false
|
|
@@ -146,7 +163,11 @@ public struct InputSearch: View {
|
|
|
146
163
|
fontWeight: Font.Weight = .regular,
|
|
147
164
|
keyboardType: UIKeyboardType = .default,
|
|
148
165
|
searchIcon: (() -> AnyView)? = nil,
|
|
149
|
-
placeholderCustomRender: (() -> AnyView)? = nil
|
|
166
|
+
placeholderCustomRender: (() -> AnyView)? = nil,
|
|
167
|
+
backgroundColor: Color? = nil,
|
|
168
|
+
clearCondition: (() -> Bool)? = nil,
|
|
169
|
+
onClearPress: @escaping () -> Void = {},
|
|
170
|
+
onSubmit: @escaping () -> Void = {}) {
|
|
150
171
|
self._text = text
|
|
151
172
|
self.buttonText = buttonText
|
|
152
173
|
self.showButtonText = showButtonText
|
|
@@ -166,6 +187,10 @@ public struct InputSearch: View {
|
|
|
166
187
|
self.keyboardType = keyboardType
|
|
167
188
|
self.searchIcon = searchIcon
|
|
168
189
|
self.placeholderCustomRender = placeholderCustomRender
|
|
190
|
+
self.backgroundColor = backgroundColor
|
|
191
|
+
self.clearCondition = clearCondition
|
|
192
|
+
self.onClearPress = onClearPress
|
|
193
|
+
self.onSubmit = onSubmit
|
|
169
194
|
}
|
|
170
195
|
|
|
171
196
|
/// Convenience initializer mirroring Compose's `InputSearch(inputSearchProps)`.
|
|
@@ -189,88 +214,90 @@ public struct InputSearch: View {
|
|
|
189
214
|
fontWeight: props.fontWeight,
|
|
190
215
|
keyboardType: props.keyboardType,
|
|
191
216
|
searchIcon: props.searchIcon,
|
|
192
|
-
placeholderCustomRender: props.placeholderCustomRender
|
|
217
|
+
placeholderCustomRender: props.placeholderCustomRender,
|
|
218
|
+
backgroundColor: props.backgroundColor,
|
|
219
|
+
clearCondition: props.clearCondition,
|
|
220
|
+
onClearPress: props.onClearPress,
|
|
221
|
+
onSubmit: props.onSubmit
|
|
193
222
|
)
|
|
194
223
|
}
|
|
195
224
|
|
|
196
225
|
|
|
197
226
|
public var body: some View {
|
|
198
227
|
let iconColor: Color = disabled ? theme.colors.text.disable : (self.iconColor ?? theme.colors.text.default)
|
|
199
|
-
|
|
228
|
+
HStack(spacing: 0) {
|
|
200
229
|
HStack(spacing: 0) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
.frame(width: 24, height: 24)
|
|
208
|
-
.padding(.trailing, Spacing.XS)
|
|
209
|
-
}
|
|
230
|
+
if let searchIcon = searchIcon {
|
|
231
|
+
searchIcon()
|
|
232
|
+
} else {
|
|
233
|
+
Icon(source: "navigation_search", size: 24, color: theme.colors.text.hint)
|
|
234
|
+
.padding(.trailing, Spacing.XS)
|
|
235
|
+
}
|
|
210
236
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
237
|
+
ZStack(alignment: .leading) {
|
|
238
|
+
if text.isEmpty {
|
|
239
|
+
if let placeholderCustomRender = placeholderCustomRender {
|
|
240
|
+
placeholderCustomRender()
|
|
241
|
+
} else {
|
|
242
|
+
MomoText(
|
|
243
|
+
placeholder,
|
|
244
|
+
typography: fontWeight == .bold ? .actionSBold : .bodyDefaultRegular,
|
|
245
|
+
color: disabled ? theme.colors.text.disable : theme.colors.text.hint,
|
|
246
|
+
maxLines: 1
|
|
247
|
+
)
|
|
221
248
|
}
|
|
222
|
-
TextField("", text: $text, onEditingChanged: { editing in
|
|
223
|
-
isFocused = editing
|
|
224
|
-
if editing {
|
|
225
|
-
onFocus()
|
|
226
|
-
} else if isBlurred {
|
|
227
|
-
onBlur()
|
|
228
|
-
}
|
|
229
|
-
if editing && !isBlurred {
|
|
230
|
-
isBlurred = true
|
|
231
|
-
}
|
|
232
|
-
}, onCommit: {})
|
|
233
|
-
.disabled(disabled)
|
|
234
|
-
.foregroundColor(disabled ? theme.colors.text.disable : theme.colors.text.default)
|
|
235
|
-
.font(.system(size: fontScale(14), weight: fontWeight))
|
|
236
|
-
.ignoreBoldTextSetting()
|
|
237
|
-
.keyboardType(keyboardType)
|
|
238
|
-
.applyCursorColor(theme.colors.primary)
|
|
239
249
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// through onChangeText so clearing isn't a silent no-op.
|
|
247
|
-
onChangeText("")
|
|
248
|
-
}) {
|
|
249
|
-
Image(systemName: "xmark.circle.fill")
|
|
250
|
-
.foregroundColor(theme.colors.text.hint)
|
|
251
|
-
.frame(width: 16, height: 16)
|
|
250
|
+
TextField("", text: $text, onEditingChanged: { editing in
|
|
251
|
+
isFocused = editing
|
|
252
|
+
if editing {
|
|
253
|
+
onFocus()
|
|
254
|
+
} else if isBlurred {
|
|
255
|
+
onBlur()
|
|
252
256
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
+
if editing && !isBlurred {
|
|
258
|
+
isBlurred = true
|
|
259
|
+
}
|
|
260
|
+
}, onCommit: onSubmit)
|
|
261
|
+
.disabled(disabled)
|
|
262
|
+
.foregroundColor(disabled ? theme.colors.text.disable : theme.colors.text.default)
|
|
263
|
+
.font(.system(size: fontScale(14), weight: fontWeight))
|
|
264
|
+
.ignoreBoldTextSetting()
|
|
265
|
+
.keyboardType(keyboardType)
|
|
266
|
+
.applyCursorColor(theme.colors.primary)
|
|
257
267
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if showButtonText {
|
|
269
|
-
SwiftButton(action: onPressButtonText) {
|
|
270
|
-
MomoText(buttonText, typography: .actionDefaultBold, color: theme.colors.text.default)
|
|
268
|
+
|
|
269
|
+
if clearCondition?() == true || (isFocused && !text.isEmpty) {
|
|
270
|
+
SwiftButton(action: {
|
|
271
|
+
text = ""
|
|
272
|
+
// Compose only fires onClearPress; onChangeText("") is kept so existing
|
|
273
|
+
// SwiftUI callers that filter on it still see the clear.
|
|
274
|
+
onChangeText("")
|
|
275
|
+
onClearPress()
|
|
276
|
+
}) {
|
|
277
|
+
Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
|
|
271
278
|
}
|
|
272
|
-
.padding(.leading, Spacing.
|
|
279
|
+
.padding(.leading, Spacing.XS)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
RenderRightIconSearch(loading: loading, icon: icon, color: iconColor, onClick: onRightIconPressed)
|
|
283
|
+
}
|
|
284
|
+
.padding(.horizontal, Spacing.M)
|
|
285
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
286
|
+
.frame(height: 36)
|
|
287
|
+
.background(
|
|
288
|
+
RoundedRectangle(cornerRadius: Radius.XL)
|
|
289
|
+
.fill(backgroundColor ?? theme.colors.background.surface)
|
|
290
|
+
)
|
|
291
|
+
.overlay(
|
|
292
|
+
RoundedRectangle(cornerRadius: Radius.XL)
|
|
293
|
+
.stroke(showBorder ? getBorderColor(isFocused: isFocused, error: error, disabled: disabled) : .clear, lineWidth: 1)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if showButtonText {
|
|
297
|
+
SwiftButton(action: onPressButtonText) {
|
|
298
|
+
MomoText(buttonText, typography: .actionDefaultBold, color: theme.colors.text.default)
|
|
273
299
|
}
|
|
300
|
+
.padding(.leading, Spacing.L)
|
|
274
301
|
}
|
|
275
302
|
}
|
|
276
303
|
}
|
|
@@ -16,22 +16,24 @@ struct UITextViewWrapper: UIViewRepresentable {
|
|
|
16
16
|
var textColor: Color
|
|
17
17
|
var cursorColor: Color
|
|
18
18
|
var fontWeight: Font.Weight
|
|
19
|
+
var keyboardType: UIKeyboardType = .default
|
|
19
20
|
var disabled: Bool
|
|
20
21
|
var onChangeText: (String) -> Void
|
|
21
|
-
var onDone: (() -> Void)?
|
|
22
22
|
var onFocus: (() -> Void)?
|
|
23
23
|
var onBlur: (() -> Void)?
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
func makeUIView(context: Context) -> UITextView {
|
|
26
26
|
let textView = UITextView()
|
|
27
27
|
textView.delegate = context.coordinator
|
|
28
28
|
textView.backgroundColor = .clear
|
|
29
29
|
textView.textContainerInset = .zero
|
|
30
30
|
textView.textContainer.lineFragmentPadding = 0
|
|
31
|
+
textView.keyboardType = keyboardType
|
|
31
32
|
return textView
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
func updateUIView(_ uiView: UITextView, context: Context) {
|
|
36
|
+
context.coordinator.parent = self
|
|
35
37
|
if uiView.text != text {
|
|
36
38
|
uiView.text = text
|
|
37
39
|
}
|
|
@@ -44,19 +46,23 @@ struct UITextViewWrapper: UIViewRepresentable {
|
|
|
44
46
|
weight: fontWeight == .bold ? .bold : .regular
|
|
45
47
|
)
|
|
46
48
|
uiView.isEditable = !disabled
|
|
49
|
+
if uiView.keyboardType != keyboardType {
|
|
50
|
+
uiView.keyboardType = keyboardType
|
|
51
|
+
uiView.reloadInputViews()
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
|
-
|
|
54
|
+
|
|
49
55
|
func makeCoordinator() -> Coordinator {
|
|
50
56
|
Coordinator(self)
|
|
51
57
|
}
|
|
52
|
-
|
|
58
|
+
|
|
53
59
|
class Coordinator: NSObject, UITextViewDelegate {
|
|
54
60
|
var parent: UITextViewWrapper
|
|
55
|
-
|
|
61
|
+
|
|
56
62
|
init(_ parent: UITextViewWrapper) {
|
|
57
63
|
self.parent = parent
|
|
58
64
|
}
|
|
59
|
-
|
|
65
|
+
|
|
60
66
|
func textViewDidChange(_ textView: UITextView) {
|
|
61
67
|
let limited = String(textView.text.prefix(parent.maxLength))
|
|
62
68
|
if textView.text != limited {
|
|
@@ -65,20 +71,17 @@ struct UITextViewWrapper: UIViewRepresentable {
|
|
|
65
71
|
parent.text = limited
|
|
66
72
|
parent.onChangeText(limited)
|
|
67
73
|
}
|
|
68
|
-
|
|
74
|
+
|
|
69
75
|
func textViewDidBeginEditing(_ textView: UITextView) {
|
|
70
76
|
parent.onFocus?()
|
|
71
77
|
}
|
|
72
|
-
|
|
78
|
+
|
|
73
79
|
func textViewDidEndEditing(_ textView: UITextView) {
|
|
74
80
|
parent.onBlur?()
|
|
75
81
|
}
|
|
76
|
-
|
|
82
|
+
|
|
83
|
+
// Like Compose's `onValueChange`, an edit that would exceed maxLength is rejected whole.
|
|
77
84
|
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
|
78
|
-
if text == "\n" {
|
|
79
|
-
parent.onDone?()
|
|
80
|
-
return false
|
|
81
|
-
}
|
|
82
85
|
let current = textView.text ?? ""
|
|
83
86
|
guard let range = Range(range, in: current) else { return false }
|
|
84
87
|
let proposed = current.replacingCharacters(in: range, with: text)
|
|
@@ -90,6 +93,7 @@ struct UITextViewWrapper: UIViewRepresentable {
|
|
|
90
93
|
// Make the struct public
|
|
91
94
|
public struct InputTextArea: View {
|
|
92
95
|
@Environment(\.appTheme) private var theme
|
|
96
|
+
@Environment(\.momoFontScale) private var fontScale
|
|
93
97
|
@Binding var text: String
|
|
94
98
|
var maxLength: Int = MAX_LENGTH
|
|
95
99
|
var height: CGFloat = DEFAULT_HEIGHT
|
|
@@ -111,11 +115,9 @@ public struct InputTextArea: View {
|
|
|
111
115
|
var required: Bool = false
|
|
112
116
|
var fontWeight: Font.Weight = .regular
|
|
113
117
|
var keyboardType: UIKeyboardType = .default
|
|
114
|
-
|
|
118
|
+
|
|
115
119
|
@State private var isFocused = false
|
|
116
|
-
|
|
117
|
-
@State private var floatingLabelHeight: CGFloat = 16
|
|
118
|
-
|
|
120
|
+
|
|
119
121
|
// Make the initializer public
|
|
120
122
|
public init(
|
|
121
123
|
text: Binding<String>,
|
|
@@ -162,147 +164,128 @@ public struct InputTextArea: View {
|
|
|
162
164
|
self.fontWeight = fontWeight
|
|
163
165
|
self.keyboardType = keyboardType
|
|
164
166
|
}
|
|
165
|
-
|
|
167
|
+
|
|
166
168
|
public var body: some View {
|
|
167
169
|
VStack(alignment: .leading, spacing: 0) {
|
|
170
|
+
// Compose: `height` is the whole box (text, counter and paddings), scaled.
|
|
171
|
+
VStack(spacing: 0) {
|
|
172
|
+
HStack(alignment: .top, spacing: 0) {
|
|
168
173
|
ZStack(alignment: .topLeading) {
|
|
169
|
-
if
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
color: disabled ? theme.colors.text.disable : (floatingValueColor ?? theme.colors.text.hint)
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
if required {
|
|
178
|
-
MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if !floatingIcon.isEmpty {
|
|
182
|
-
Icon(
|
|
183
|
-
source: floatingIcon,
|
|
184
|
-
size: 16,
|
|
185
|
-
color: disabled ? theme.colors.text.disable : (floatingIconColor ?? theme.colors.text.default)
|
|
186
|
-
)
|
|
187
|
-
.padding(.leading, Spacing.XS)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
.padding(.horizontal, Spacing.S)
|
|
191
|
-
.background(
|
|
192
|
-
GeometryReader { geo in
|
|
193
|
-
Color.clear.onAppear { floatingLabelHeight = geo.size.height }
|
|
194
|
-
}
|
|
174
|
+
if text.isEmpty {
|
|
175
|
+
MomoText(
|
|
176
|
+
placeholder,
|
|
177
|
+
typography: fontWeight == .bold ? .actionSBold : .bodyDefaultRegular,
|
|
178
|
+
color: disabled ? theme.colors.text.disable : theme.colors.text.hint
|
|
195
179
|
)
|
|
196
|
-
.
|
|
197
|
-
// Kotlin centers the label within the box-height-tall decorationBox
|
|
198
|
-
// by default, then applies `offsetY = -scaleHeight / 2` — net effect:
|
|
199
|
-
// the label ends up centered exactly on the box's top border. Our
|
|
200
|
-
// ZStack anchors top-leading instead, so matching that requires
|
|
201
|
-
// offsetting by half the label's OWN measured height, not half the
|
|
202
|
-
// (104pt-tall) text-area box height.
|
|
203
|
-
.offset(x: Spacing.S, y: -floatingLabelHeight / 2)
|
|
204
|
-
.zIndex(10)
|
|
180
|
+
.fontSize(16, lineHeight: 24)
|
|
205
181
|
}
|
|
206
182
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
fontWeight: fontWeight,
|
|
224
|
-
disabled: disabled,
|
|
225
|
-
onChangeText: onChangeText,
|
|
226
|
-
onDone: {
|
|
227
|
-
// Handle done action if needed
|
|
228
|
-
},
|
|
229
|
-
onFocus: {
|
|
230
|
-
isFocused = true
|
|
231
|
-
onFocus()
|
|
232
|
-
}, onBlur: {
|
|
233
|
-
isFocused = false
|
|
234
|
-
isBlurred = true
|
|
235
|
-
onBlur()
|
|
236
|
-
})
|
|
237
|
-
.frame(height: height)
|
|
238
|
-
.padding(.horizontal, 12)
|
|
239
|
-
.padding(.vertical, 8)
|
|
240
|
-
.disabled(disabled)
|
|
241
|
-
}
|
|
242
|
-
.frame(maxWidth: .infinity)
|
|
243
|
-
|
|
244
|
-
// Clear button (mirrors Compose: focused + non-empty).
|
|
245
|
-
if isFocused && !text.isEmpty {
|
|
246
|
-
SwiftUI.Button(action: {
|
|
247
|
-
text = ""
|
|
248
|
-
onChangeText("")
|
|
249
|
-
}) {
|
|
250
|
-
Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
|
|
251
|
-
}
|
|
252
|
-
.padding(.leading, Spacing.XS)
|
|
253
|
-
.padding(.top, 12)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Loading indicator and right icon (independent, mirrors
|
|
257
|
-
// Compose's RenderRightIcon: both can render at once).
|
|
258
|
-
if loading {
|
|
259
|
-
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
260
|
-
.frame(width: 16, height: 16)
|
|
261
|
-
.padding(.leading, Spacing.XS)
|
|
262
|
-
.padding(.top, 12)
|
|
263
|
-
}
|
|
264
|
-
if !icon.isEmpty {
|
|
265
|
-
SwiftUI.Button(action: onRightIconPressed) {
|
|
266
|
-
Icon(source: icon, size: 24, color: disabled ? theme.colors.text.disable : (iconColor ?? theme.colors.text.default))
|
|
267
|
-
}
|
|
268
|
-
.padding(.leading, Spacing.XS)
|
|
269
|
-
.padding(.top, 12)
|
|
270
|
-
}
|
|
183
|
+
UITextViewWrapper(
|
|
184
|
+
text: $text,
|
|
185
|
+
maxLength: maxLength,
|
|
186
|
+
textColor: disabled ? theme.colors.text.disable : theme.colors.text.default,
|
|
187
|
+
cursorColor: theme.colors.primary,
|
|
188
|
+
fontWeight: fontWeight,
|
|
189
|
+
keyboardType: keyboardType,
|
|
190
|
+
disabled: disabled,
|
|
191
|
+
onChangeText: onChangeText,
|
|
192
|
+
onFocus: {
|
|
193
|
+
isFocused = true
|
|
194
|
+
onFocus()
|
|
195
|
+
},
|
|
196
|
+
onBlur: {
|
|
197
|
+
isFocused = false
|
|
198
|
+
onBlur()
|
|
271
199
|
}
|
|
200
|
+
)
|
|
201
|
+
.disabled(disabled)
|
|
202
|
+
}
|
|
203
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
272
204
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
205
|
+
// Clear button (mirrors Compose: focused + non-empty).
|
|
206
|
+
if isFocused && !text.isEmpty {
|
|
207
|
+
SwiftUI.Button(action: {
|
|
208
|
+
text = ""
|
|
209
|
+
onChangeText("")
|
|
210
|
+
}) {
|
|
211
|
+
Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
|
|
279
212
|
}
|
|
280
|
-
.
|
|
281
|
-
.cornerRadius(Radius.S)
|
|
282
|
-
.overlay(
|
|
283
|
-
RoundedRectangle(cornerRadius: Radius.S)
|
|
284
|
-
.stroke(borderColor, lineWidth: 1)
|
|
285
|
-
)
|
|
213
|
+
.padding(.leading, Spacing.XS)
|
|
286
214
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
215
|
+
|
|
216
|
+
// Loading indicator and right icon (independent, mirrors
|
|
217
|
+
// Compose's RenderRightIcon: both can render at once).
|
|
218
|
+
if loading {
|
|
219
|
+
ActivityIndicator(isAnimating: .constant(true), style: .medium, color: iconTintColor)
|
|
220
|
+
.frame(width: 16, height: 16)
|
|
221
|
+
.padding(.leading, Spacing.XS)
|
|
294
222
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
onBlur()
|
|
223
|
+
if !icon.isEmpty {
|
|
224
|
+
SwiftUI.Button(action: onRightIconPressed) {
|
|
225
|
+
Icon(source: icon, size: 24, color: iconTintColor)
|
|
226
|
+
}
|
|
227
|
+
.padding(.leading, Spacing.XS)
|
|
301
228
|
}
|
|
302
229
|
}
|
|
303
|
-
.
|
|
230
|
+
.padding([.horizontal, .top], Spacing.M)
|
|
231
|
+
.frame(maxHeight: .infinity, alignment: .top)
|
|
232
|
+
|
|
233
|
+
MomoText("\(text.count)/\(maxLength)", typography: .descriptionXsRegular, color: theme.colors.text.hint)
|
|
234
|
+
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
235
|
+
.padding(.trailing, Spacing.M)
|
|
236
|
+
.padding(.bottom, Spacing.M)
|
|
237
|
+
}
|
|
238
|
+
.frame(height: fontScale(height))
|
|
239
|
+
.background(
|
|
240
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
241
|
+
.fill(theme.colors.background.surface)
|
|
242
|
+
)
|
|
243
|
+
.overlay(
|
|
244
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
245
|
+
.stroke(borderColor, lineWidth: 1)
|
|
246
|
+
)
|
|
247
|
+
.overlay(alignment: .topLeading) { floatingLabel }
|
|
248
|
+
|
|
249
|
+
ErrorView(errorMessage: error, errorSpacing: errorSpacing, hintText: "")
|
|
250
|
+
}
|
|
251
|
+
.ignoreBoldTextSetting()
|
|
304
252
|
}
|
|
305
|
-
|
|
253
|
+
|
|
254
|
+
/// Centered on the box's top border, like Compose's `FloatingLabel` (offset by -height / 2).
|
|
255
|
+
@ViewBuilder
|
|
256
|
+
private var floatingLabel: some View {
|
|
257
|
+
if !floatingValue.isEmpty || !floatingIcon.isEmpty {
|
|
258
|
+
HStack(spacing: 0) {
|
|
259
|
+
MomoText(
|
|
260
|
+
floatingValue,
|
|
261
|
+
typography: .labelSMedium,
|
|
262
|
+
color: disabled ? theme.colors.text.disable : (floatingValueColor ?? theme.colors.text.hint)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if required {
|
|
266
|
+
MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if !floatingIcon.isEmpty {
|
|
270
|
+
Icon(
|
|
271
|
+
source: floatingIcon,
|
|
272
|
+
size: 16,
|
|
273
|
+
color: disabled ? theme.colors.text.disable : (floatingIconColor ?? theme.colors.text.default)
|
|
274
|
+
)
|
|
275
|
+
.padding(.leading, Spacing.XS)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
.padding(.horizontal, Spacing.S)
|
|
279
|
+
.background(theme.colors.background.surface)
|
|
280
|
+
.alignmentGuide(.top) { $0[VerticalAlignment.center] }
|
|
281
|
+
.offset(x: Spacing.S)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private var iconTintColor: Color {
|
|
286
|
+
disabled ? theme.colors.text.disable : (iconColor ?? theme.colors.text.default)
|
|
287
|
+
}
|
|
288
|
+
|
|
306
289
|
private var borderColor: Color {
|
|
307
290
|
if disabled {
|
|
308
291
|
return theme.colors.border.disable
|