@momo-kits/native-kits 0.89.33 → 0.110.1-beta.1

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.
@@ -50,7 +50,6 @@ import vn.momo.kits.const.AppTheme
50
50
  import vn.momo.kits.const.Colors
51
51
  import vn.momo.kits.const.Spacing
52
52
  import vn.momo.kits.const.Typography
53
- import vn.momo.kits.modifier.shadow
54
53
  import vn.momo.kits.platform.getStatusBarHeight
55
54
  import vn.momo.kits.utils.bottomBorder
56
55
 
@@ -67,7 +66,6 @@ data class AnimatedHeader(
67
66
  @Composable
68
67
  fun HeaderBackground(
69
68
  headerType: HeaderType = HeaderType.DEFAULT,
70
- useShadow: Boolean? = true,
71
69
  scrollState: Int
72
70
  ) {
73
71
  val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
@@ -140,21 +138,12 @@ fun HeaderBackground(
140
138
  }
141
139
  }
142
140
  }
141
+
143
142
  else -> {
144
143
  Box(modifier = Modifier.fillMaxWidth())
145
144
  }
146
145
  }
147
- Box(
148
- modifier = Modifier.fillMaxWidth().height(statusBarHeight + 52.dp)
149
- .alpha(1 - opacityAni)
150
- .background(Colors.black_01)
151
- .then(
152
- if (useShadow == true) Modifier.shadow() else Modifier.bottomBorder(
153
- 1.dp,
154
- Colors.black_04
155
- )
156
- )
157
- )
146
+
158
147
  }
159
148
 
160
149
  @Composable
@@ -163,10 +152,9 @@ fun Header(
163
152
  titlePosition: TitlePosition,
164
153
  title: String,
165
154
  headerRight: @Composable (() -> Unit)? = null,
166
- goBack: (() -> Unit)?,
155
+ goBack: (() -> Unit)? = null,
167
156
  opacity: Float? = null,
168
157
  animatedHeader: AnimatedHeader? = null,
169
- useSearchInput: Boolean? = false,
170
158
  scrollState: Int = 0,
171
159
  inputSearchProps: InputSearchProps?
172
160
  ) {
@@ -206,7 +194,7 @@ fun Header(
206
194
  horizontalArrangement = Arrangement.SpaceBetween
207
195
  ) {
208
196
  Box {
209
- if (goBack !== null) {
197
+ if (goBack != null) {
210
198
  Box(
211
199
  modifier = Modifier
212
200
  .size(28.dp)
@@ -238,12 +226,11 @@ fun Header(
238
226
  }
239
227
  }
240
228
  }
241
- if (useSearchInput == false) {
242
- Box(Modifier.graphicsLayer {
243
- alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
244
- }) {
245
- headerRight?.invoke()
246
- }
229
+
230
+ Box(Modifier.graphicsLayer {
231
+ alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
232
+ }) {
233
+ headerRight?.invoke()
247
234
  }
248
235
  }
249
236
 
@@ -254,13 +241,12 @@ fun Header(
254
241
  .padding(horizontal = Spacing.M),
255
242
  verticalAlignment = Alignment.CenterVertically,
256
243
  ) {
257
- if (goBack !== null) {
244
+ if (goBack != null && titlePosition == TitlePosition.LEFT) {
258
245
  Spacer(Modifier.width(40.dp))
259
246
  }
260
- if (useSearchInput == true && inputSearchProps != null) {
247
+ if (inputSearchProps != null) {
261
248
  InputSearch(inputSearchProps = inputSearchProps.copy(backgroundColor = backgroundSearch))
262
- }
263
- else{
249
+ } else{
264
250
  Box(
265
251
  Modifier.weight(1f).graphicsLayer {
266
252
  alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
@@ -292,7 +278,7 @@ fun HeaderTitle(
292
278
  modifier = Modifier.then(modifier).zIndex(1f),
293
279
  text = title,
294
280
  textAlign = textAlign,
295
- style = Typography.headerDefault,
281
+ style = Typography.actionS,
296
282
  color = color,
297
283
  maxLines = 1
298
284
  )
@@ -56,7 +56,7 @@ fun Screen(
56
56
  horizontalAlignment: Alignment.Horizontal = Alignment.Start,
57
57
  title: String = "Stack",
58
58
  titlePosition: TitlePosition = TitlePosition.LEFT,
59
- goBack: (() -> Unit) = { },
59
+ goBack: (() -> Unit)? = null,
60
60
  scrollable: Boolean = true,
61
61
  scrollState: ScrollState = rememberScrollState(),
62
62
  onContentLayout: ((LayoutCoordinates) -> Unit)? = null,
@@ -66,8 +66,6 @@ fun Screen(
66
66
  fabProps: FabProps? = null,
67
67
  animatedHeader: AnimatedHeader? = null,
68
68
  layoutOffset: Dp = 56.dp,
69
- useShadow: Boolean? = true,
70
- useSearchInput: Boolean? = false,
71
69
  inputSearchProps: InputSearchProps? = null,
72
70
  content: @Composable () -> Unit,
73
71
  ) {
@@ -100,13 +98,12 @@ fun Screen(
100
98
  HeaderBackground(headerType = headerType, scrollState = scrollState.value)
101
99
  } else {
102
100
  Box(
103
- Modifier.zIndex(5f)
101
+ Modifier.zIndex(2f)
104
102
  .background(Color.White.copy(alpha = opacity))
105
103
  .graphicsLayer { alpha = opacity }
106
104
  ) {
107
105
  HeaderBackground(
108
106
  headerType = headerType,
109
- useShadow = useShadow,
110
107
  scrollState = scrollState.value
111
108
  )
112
109
  }
@@ -120,7 +117,6 @@ fun Screen(
120
117
  goBack = goBack,
121
118
  opacity = opacity,
122
119
  animatedHeader = animatedHeader,
123
- useSearchInput = useSearchInput,
124
120
  inputSearchProps = inputSearchProps,
125
121
  scrollState = scrollState.value
126
122
  )
@@ -1,13 +1,17 @@
1
1
  package vn.momo.kits.components
2
2
 
3
+ import androidx.compose.foundation.layout.Box
3
4
  import androidx.compose.foundation.layout.size
4
5
  import androidx.compose.runtime.Composable
5
6
  import androidx.compose.ui.Modifier
6
7
  import androidx.compose.ui.graphics.Color
7
8
  import androidx.compose.ui.graphics.ColorFilter
8
9
  import androidx.compose.ui.layout.ContentScale
10
+ import androidx.compose.ui.semantics.contentDescription
11
+ import androidx.compose.ui.semantics.semantics
9
12
  import androidx.compose.ui.unit.Dp
10
13
  import androidx.compose.ui.unit.dp
14
+ import coil3.compose.AsyncImage
11
15
  import vn.momo.kits.const.AppTheme
12
16
  import vn.momo.kits.utils.Icons
13
17
 
@@ -23,14 +27,14 @@ fun Icon(
23
27
  } else {
24
28
  Icons[source] ?: ""
25
29
  }
26
- Image(
27
- modifier = modifier.size(size),
28
- source = icon,
29
- loading = false,
30
- options = Options(
30
+ Box(modifier = modifier.size(size).semantics {contentDescription = "img|$icon"}) {
31
+ AsyncImage(
32
+ modifier = Modifier.matchParentSize(),
33
+ model = icon,
34
+ contentDescription = null,
31
35
  contentScale = ContentScale.Fit,
32
- colorFilter = color?.let { ColorFilter.tint(it) }
33
- ),
34
- )
36
+ colorFilter = color?.let { ColorFilter.tint(it) },
37
+ )
38
+ }
35
39
  }
36
40
 
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.size
15
15
  import androidx.compose.foundation.layout.width
16
16
  import androidx.compose.foundation.shape.RoundedCornerShape
17
17
  import androidx.compose.foundation.text.BasicTextField
18
+ import androidx.compose.foundation.text.KeyboardActions
18
19
  import androidx.compose.foundation.text.KeyboardOptions
19
20
  import androidx.compose.material3.CircularProgressIndicator
20
21
  import androidx.compose.runtime.Composable
@@ -29,6 +30,7 @@ import androidx.compose.ui.focus.onFocusChanged
29
30
  import androidx.compose.ui.graphics.Color
30
31
  import androidx.compose.ui.text.TextStyle
31
32
  import androidx.compose.ui.text.input.KeyboardType
33
+ import androidx.compose.ui.text.style.TextOverflow
32
34
  import androidx.compose.ui.unit.dp
33
35
  import androidx.compose.ui.unit.sp
34
36
  import vn.momo.kits.const.AppTheme
@@ -44,6 +46,7 @@ fun RenderRightIconSearch(
44
46
  icon: String,
45
47
  color: Color,
46
48
  onClick: () -> Unit,
49
+ iconModifier: Modifier = Modifier
47
50
  ) {
48
51
  if (loading) {
49
52
  Box(Modifier.padding(horizontal = Spacing.S)) {
@@ -64,7 +67,7 @@ fun RenderRightIconSearch(
64
67
  source = icon,
65
68
  color = color,
66
69
  size = 24.dp,
67
- modifier = Modifier.padding(start = Spacing.S, end = 0.dp).clickable(
70
+ modifier = iconModifier.padding(start = Spacing.S, end = 0.dp).clickable(
68
71
  onClick = onClick,
69
72
  interactionSource = remember { MutableInteractionSource() },
70
73
  indication = null
@@ -90,6 +93,11 @@ data class InputSearchProps(
90
93
  val fontWeight: InputFontWeight = InputFontWeight.REGULAR,
91
94
  val keyboardType: KeyboardType = KeyboardType.Text,
92
95
  val backgroundColor: Color = Colors.black_01,
96
+ val clearCondition: (() -> Boolean)? = null,
97
+ val keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
98
+ val keyboardActions: KeyboardActions = KeyboardActions.Default,
99
+ val modifier: Modifier = Modifier,
100
+ val iconModifier: Modifier = Modifier,
93
101
  )
94
102
 
95
103
  @Composable
@@ -98,7 +106,6 @@ fun InputSearch(
98
106
  text = remember { mutableStateOf("") },
99
107
  iconColor = AppTheme.current.colors.text.default
100
108
  ),
101
-
102
109
  ) {
103
110
  var isFocused by remember { mutableStateOf(false) }
104
111
  var isBlurred = false
@@ -124,8 +131,9 @@ fun InputSearch(
124
131
  lineHeight = 20.sp,
125
132
  fontWeight = inputSearchProps.fontWeight.value
126
133
  ),
127
- keyboardOptions = KeyboardOptions.Default.copy(keyboardType = inputSearchProps.keyboardType),
128
- modifier = Modifier.height(36.dp).onFocusChanged {
134
+ keyboardOptions = inputSearchProps.keyboardOptions.copy(keyboardType = inputSearchProps.keyboardType),
135
+ keyboardActions = inputSearchProps.keyboardActions,
136
+ modifier = inputSearchProps.modifier.height(36.dp).onFocusChanged {
129
137
  isFocused = it.isFocused
130
138
  if (it.isFocused) {
131
139
  inputSearchProps.onFocus()
@@ -137,7 +145,12 @@ fun InputSearch(
137
145
  decorationBox = { innerTextField ->
138
146
  Row(verticalAlignment = Alignment.CenterVertically) {
139
147
  Row(
140
- modifier = Modifier.weight(inputSearchProps.showButtonText.ifTrue(8f, 1f))
148
+ modifier = Modifier.weight(
149
+ inputSearchProps.showButtonText.ifTrue(
150
+ 8f,
151
+ 1f
152
+ )
153
+ )
141
154
  .background(
142
155
  inputSearchProps.backgroundColor,
143
156
  RoundedCornerShape(Radius.XL)
@@ -152,7 +165,6 @@ fun InputSearch(
152
165
  ),
153
166
  verticalAlignment = Alignment.CenterVertically
154
167
  ) {
155
- //leading icon
156
168
  Icon(
157
169
  source = "navigation_search",
158
170
  modifier = Modifier.padding(end = Spacing.XS),
@@ -168,12 +180,14 @@ fun InputSearch(
168
180
  lineHeight = 24.sp,
169
181
  fontWeight = inputSearchProps.fontWeight.value
170
182
  ),
171
- color = placeholderColor
183
+ maxLines = 1,
184
+ color = placeholderColor,
185
+ overflow = TextOverflow.Ellipsis
172
186
  )
173
187
  }
174
188
  innerTextField()
175
189
  }
176
- if (isFocused && inputSearchProps.text.value.isNotEmpty()) {
190
+ if (inputSearchProps.clearCondition?.invoke() == true || (isFocused && inputSearchProps.text.value.isNotEmpty())) {
177
191
  Row {
178
192
  Spacer(Modifier.width(Spacing.XS))
179
193
  Icon(
@@ -192,7 +206,8 @@ fun InputSearch(
192
206
  inputSearchProps.loading,
193
207
  inputSearchProps.icon,
194
208
  iconTintColor,
195
- inputSearchProps.onRightIconPressed
209
+ inputSearchProps.onRightIconPressed,
210
+ iconModifier = inputSearchProps.iconModifier
196
211
  )
197
212
  }
198
213
  }
@@ -4,35 +4,98 @@ import SwiftUI
4
4
  public struct Checkbox: View{
5
5
  @Binding var checked: Bool
6
6
  var disabled: Bool
7
- var onChange: ((Bool)->Void)?
7
+ // var onChange: ((Bool)->Void)?
8
+ var onCheckedChange: ((Bool) -> Void)?
9
+ var indeterminate: Bool
10
+ var title: String
8
11
 
9
- public init(_ checked: Binding<Bool>,disabled: Bool = false, onChange: ((Bool)->Void)? = nil){
12
+
13
+ public init(_ checked: Binding<Bool>,disabled: Bool = false, onCheckedChange: ( (Bool) -> Void)?,
14
+ indeterminate: Bool = false,
15
+ title: String = ""){
10
16
  self._checked = checked
11
- self.onChange = onChange
12
17
  self.disabled = disabled
18
+ self.onCheckedChange = onCheckedChange
19
+ self.indeterminate = indeterminate
20
+ self.title = title
13
21
  }
14
22
 
15
23
  func onCheck(){
16
24
  checked.toggle()
17
- onChange?(checked)
25
+ onCheckedChange?(checked)
18
26
  }
19
27
 
20
- public var body: some View{
21
- return SwiftUI.Button(action: onCheck) {
22
- HStack {
23
- if (checked) {
24
- Image(systemName: "checkmark.square.fill")
28
+ public var body: some View {
29
+ HStack(spacing: Spacing.S) {
30
+ // Checkbox
31
+ ZStack {
32
+ RoundedRectangle(cornerRadius: Radius.XS)
33
+ .fill(backgroundColor)
34
+ .frame(width: 20, height: 20)
35
+ .overlay(
36
+ RoundedRectangle(cornerRadius: Radius.XS)
37
+ .stroke(borderColor, lineWidth: Spacing.XXS)
38
+ )
39
+
40
+ if checked {
41
+ Image(systemName: indeterminate ? "minus" : "checkmark")
25
42
  .resizable()
26
- .foregroundColor(disabled ? Colors.black07 : Colors.primary)
27
- .frame(width: 24, height: 24)
43
+ .foregroundColor(Colors.black20)
44
+ .frame(width: 12, height: 12)
28
45
  }
29
- }.frame(width: 24, height: 24)
30
- .overlay(RoundedRectangle(cornerRadius: 4).stroke(Colors.black20,lineWidth: checked ? 0 : 1))
31
- .background(Colors.black01)
32
-
46
+ }
47
+ .onTapGesture {
48
+ if !disabled {
49
+ onCheckedChange?(checked)
50
+ }
51
+ }
52
+
53
+ // Title
54
+ if !title.isEmpty {
55
+ Text(title)
56
+ .font(.description1)
57
+ }
58
+ }
59
+ }
60
+
61
+ private var borderColor: Color {
62
+ if disabled {
63
+ return Colors.black03
64
+ } else if checked {
65
+ return Colors.primary
66
+ } else {
67
+ return Colors.text
33
68
  }
34
- .opacity(disabled ? 0.4 : 1)
35
- .disabled(disabled)
36
69
  }
37
70
 
71
+ private var backgroundColor: Color {
72
+ if disabled && checked {
73
+ return Colors.pink08
74
+ } else if checked {
75
+ return Colors.primary
76
+ } else {
77
+ return Colors.black01
78
+ }
79
+ }
80
+
81
+
82
+
83
+ // public var body: some View{
84
+ // return SwiftUI.Button(action: onCheck) {
85
+ // HStack {
86
+ // if (checked) {
87
+ // Image(systemName: "checkmark.square.fill")
88
+ // .resizable()
89
+ // .foregroundColor(disabled ? Colors.black07 : Colors.primary)
90
+ // .frame(width: 24, height: 24)
91
+ // }
92
+ // }.frame(width: 24, height: 24)
93
+ // .overlay(RoundedRectangle(cornerRadius: 4).stroke(Colors.black20,lineWidth: checked ? 0 : 1))
94
+ // .background(Colors.black01)
95
+ //
96
+ // }
97
+ // .opacity(disabled ? 0.4 : 1)
98
+ // .disabled(disabled)
99
+ // }
100
+
38
101
  }
@@ -71,12 +71,22 @@ public struct Input: View {
71
71
  var rightIcon: AnyView?
72
72
  var onChange: ((String) -> Void)?
73
73
  var onBlur: (() -> Void)?
74
+ var floatingIconURL: String?
75
+ var floatingIconColor: Color
76
+ var size: InputSize
77
+ var fontWeight: InputFontWeight
78
+ var disabled: Bool
79
+ var leadingIcon: String?
80
+ var leadingIconColor: Color
81
+ var loading: Bool
82
+ var required: Bool
83
+ var hintText: String?
74
84
 
75
85
  func onCancel(){
76
86
  self.value.removeAll()
77
87
  }
78
88
 
79
- public init(_ value: Binding<String>, placeholder: String = "", floatingValue: String = "", multilines: Bool = false, errorMessage: String = "", isSecure: Bool = false, keyboardType: UIKeyboardType = .default, floatingIcon: AnyView? = nil, rightIcon: AnyView? = nil, onChange: ((String)->Void)? = nil, onBlur: (()->Void)? = nil){
89
+ public init(_ value: Binding<String>, placeholder: String = "", floatingValue: String = "", multilines: Bool = false, errorMessage: String = "", isSecure: Bool = false, keyboardType: UIKeyboardType = .default, floatingIcon: AnyView? = nil, rightIcon: AnyView? = nil, onChange: ((String)->Void)? = nil, onBlur: (()->Void)? = nil, floatingIconURL: String? = nil, floatingIconColor: Color = Colors.pink03, size: InputSize = .small, fontWeight: InputFontWeight = .regular, disabled: Bool = false, leadingIcon: String? = nil, leadingIconColor: Color = Colors.pink03, loading: Bool = false, required: Bool = false, hintText: String? = nil){
80
90
  self.placeholder = placeholder
81
91
  self.floatingValue = floatingValue
82
92
  self.errorMessage = errorMessage
@@ -86,6 +96,16 @@ public struct Input: View {
86
96
  self.rightIcon = rightIcon
87
97
  self.floatingIcon = floatingIcon
88
98
  self.onChange = onChange
99
+ self.floatingIconURL = floatingIconURL
100
+ self.floatingIconColor = floatingIconColor
101
+ self.size = size
102
+ self.fontWeight = fontWeight
103
+ self.disabled = disabled
104
+ self.leadingIcon = leadingIcon
105
+ self.leadingIconColor = leadingIconColor
106
+ self.loading = loading
107
+ self.required = required
108
+ self.hintText = hintText
89
109
  }
90
110
 
91
111
  func onChangeText(value: String){
@@ -93,57 +113,137 @@ public struct Input: View {
93
113
  }
94
114
 
95
115
  public var body: some View {
96
- return ZStack (alignment: .topLeading){
97
- HStack(alignment: .center){
98
- if (!isSecure){
99
- TextField("",text: $value, onEditingChanged:{ isChange in
100
- self.focused = isChange
101
- })
102
- .font(.paragraph)
103
- .padding(.horizontal, 12)
104
- .accentColor(Colors.pink03)
105
- .keyboardType(keyboardType)
106
- .valueChanged(value: value, onChange: onChangeText)
107
-
108
- HStack{
109
- SwiftButton(action: onCancel){
110
- Image(systemName: "x.circle.fill")
111
- .foregroundColor(Colors.text)
112
- .frame(width: 16, height: 16)
113
- .padding(.leading, -4)
114
- .padding(.trailing, 8)
116
+ VStack(alignment: .leading, spacing: 4) {
117
+ ZStack(alignment: .topLeading) {
118
+ HStack(alignment: .center) {
119
+ if let leadingIcon = leadingIcon {
120
+ ImageLoader(url: URL(string: leadingIcon)!)
121
+ .frame(width: 20, height: 20)
122
+ .foregroundColor(leadingIconColor)
115
123
  }
116
124
 
117
- rightIcon
125
+ if !isSecure {
126
+ TextField("", text: $value, onEditingChanged: { isChange in
127
+ self.focused = isChange
128
+ })
129
+ .disabled(disabled)
130
+ .font(fontWeight == .bold ? .headline : .body)
131
+ } else {
132
+ DotTextField(value: $value)
133
+ .disabled(disabled)
134
+ }
118
135
 
136
+ if loading {
137
+ ActivityIndicator(isAnimating: .constant(true), style: .medium)
138
+ .frame(width: 20, height: 20)
139
+ } else if !value.isEmpty {
140
+ SwiftButton(action: onCancel){
141
+ Image(systemName: "x.circle.fill")
142
+ .foregroundColor(Colors.text)
143
+ .frame(width: 16, height: 16)
144
+ .padding(.leading, -4)
145
+ .padding(.trailing, 8)
146
+ }
147
+ }
148
+
149
+ rightIcon
119
150
  }
120
- }else{
121
- DotTextField(value: $value)
122
- .padding(.horizontal, 12)
123
- .accentColor(Colors.pink03)
124
- SwiftButton(action: onCancel){
125
- Image(systemName: "x.circle.fill")
126
- .foregroundColor(Colors.text)
127
- .frame(width: 16, height: 16)
128
- .padding(.leading, -4)
129
- .padding(.trailing, 8)
151
+ .padding(.horizontal, 12)
152
+ .frame(height: size == .small ? 40 : 48)
153
+ .background(disabled ? Colors.black01 : Color.white)
154
+ .overlay(RoundedRectangle(cornerRadius: 8).stroke(focused ? Colors.pink03 : Colors.border, lineWidth: 1))
155
+
156
+ if value.isEmpty {
157
+ Text(placeholder + (required ? " *" : ""))
158
+ .foregroundColor(Colors.black09)
159
+ .padding(.horizontal, 12)
160
+ .padding(.top, 12)
161
+ .allowsHitTesting(false)
130
162
  }
163
+
164
+ if !floatingValue.isEmpty {
165
+ FloatingComponent(floatingValue, floatingIcon: floatingIconURL.map { url in
166
+ AnyView(ImageLoader(url: URL(string: url)!)
167
+ .frame(width: 20, height: 20)
168
+ .background(floatingIconColor))
169
+ })
170
+ }
171
+ }
172
+
173
+ if !errorMessage.isEmpty {
174
+ Text(errorMessage)
175
+ .foregroundColor(.red)
176
+ .font(.caption)
177
+ } else if let hint = hintText {
178
+ Text(hint)
179
+ .foregroundColor(Colors.black09)
180
+ .font(.caption)
131
181
  }
132
182
  }
133
- .frame(maxWidth: .infinity, minHeight: 48)
134
- .overlay(RoundedRectangle(cornerRadius: 8).stroke(focused == true ? Colors.pink03 : Colors.border, lineWidth: 1))
135
- .padding(.horizontal, 1)
136
- if (value.isEmpty){
137
- Text(placeholder)
138
- .font(.paragraph)
139
- .font(.system(size: 16))
140
- .foregroundColor(Colors.black09)
141
- .padding(.leading, 12)
142
- .padding(.top, 16)
143
- .allowsHitTesting(false)
183
+ }
184
+ }
185
+
186
+ struct ImageLoader: View {
187
+ @ObservedObject private var loader: ImageLoaderObject
188
+
189
+ init(url: URL) {
190
+ loader = ImageLoaderObject(url: url)
191
+ }
192
+
193
+ var body: some View {
194
+ Group {
195
+ if let image = loader.image {
196
+ Image(uiImage: image)
197
+ .resizable()
198
+ .aspectRatio(contentMode: .fit)
199
+ } else {
200
+ // Use UIActivityIndicatorView for earlier iOS versions
201
+ ActivityIndicator(isAnimating: .constant(true), style: .medium)
144
202
  }
145
- if (!floatingValue.isEmpty) {FloatingComponent(floatingValue, floatingIcon: floatingIcon)}
146
- }.padding(.bottom, 8)
203
+ }
147
204
  }
205
+ }
206
+
207
+
208
+ struct ActivityIndicator: UIViewRepresentable {
209
+ @Binding var isAnimating: Bool
210
+ let style: UIActivityIndicatorView.Style
211
+
212
+ func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
213
+ return UIActivityIndicatorView(style: style)
214
+ }
215
+
216
+ func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
217
+ isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
218
+ }
219
+ }
220
+
221
+ class ImageLoaderObject: ObservableObject {
222
+ @Published var image: UIImage?
223
+ private let url: URL
148
224
 
225
+ init(url: URL) {
226
+ self.url = url
227
+ loadImage()
228
+ }
229
+
230
+ private func loadImage() {
231
+ URLSession.shared.dataTask(with: url) { data, _, _ in
232
+ if let data = data, let loadedImage = UIImage(data: data) {
233
+ DispatchQueue.main.async {
234
+ self.image = loadedImage
235
+ }
236
+ }
237
+ }.resume()
238
+ }
239
+ }
240
+
241
+ public enum InputSize {
242
+ case small
243
+ case large
244
+ }
245
+
246
+ public enum InputFontWeight {
247
+ case regular
248
+ case bold
149
249
  }
@@ -0,0 +1,182 @@
1
+ //
2
+ // InputSearch.swift
3
+ // Pods
4
+ //
5
+ // Created by dung.pham2 on 3/12/24.
6
+ //
7
+ import SwiftUI
8
+
9
+ // MARK: - RenderRightIconSearch
10
+
11
+ struct RenderRightIconSearch: View {
12
+ let loading: Bool
13
+ let icon: String
14
+ let color: Color
15
+ let onClick: () -> Void
16
+
17
+ var body: some View {
18
+ HStack(spacing: 0) {
19
+ if loading {
20
+ ActivityIndicator(isAnimating: .constant(true), style: .medium)
21
+ .frame(width: 16, height: 16)
22
+ .padding(.horizontal, Spacing.S)
23
+ }
24
+ if !icon.isEmpty {
25
+ Divider()
26
+ .frame(width: 1)
27
+ .background(Colors.primary)
28
+ .padding(.leading, Spacing.S)
29
+
30
+ Image(systemName: icon)
31
+ .foregroundColor(color)
32
+ .frame(width: 24, height: 24)
33
+ .padding(.leading, Spacing.S)
34
+ .onTapGesture(perform: onClick)
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ // MARK: - InputSearch
41
+
42
+ public struct InputSearch: View {
43
+ @Binding var text: String
44
+ var buttonText: String = ""
45
+ var showButtonText: Bool = false
46
+ var showBorder: Bool = true
47
+ var placeholder: String = ""
48
+ var onChangeText: (String) -> Void = { _ in }
49
+ var onPressButtonText: () -> Void = {}
50
+ var error: String = ""
51
+ var disabled: Bool = false
52
+ var icon: String = ""
53
+ var iconColor: Color = Colors.text
54
+ var onRightIconPressed: () -> Void = {}
55
+ var onFocus: () -> Void = {}
56
+ var onBlur: () -> Void = {}
57
+ var loading: Bool = false
58
+ var fontWeight: Font.Weight = .regular
59
+ var keyboardType: UIKeyboardType = .default
60
+
61
+ @State private var isFocused = false
62
+ @State private var isBlurred = false
63
+
64
+ public init(text: Binding<String>,
65
+ buttonText: String = "",
66
+ showButtonText: Bool = false,
67
+ showBorder: Bool = true,
68
+ placeholder: String = "",
69
+ onChangeText: @escaping (String) -> Void = { _ in },
70
+ onPressButtonText: @escaping () -> Void = {},
71
+ error: String = "",
72
+ disabled: Bool = false,
73
+ icon: String = "",
74
+ iconColor: Color = Colors.text,
75
+ onRightIconPressed: @escaping () -> Void = {},
76
+ onFocus: @escaping () -> Void = {},
77
+ onBlur: @escaping () -> Void = {},
78
+ loading: Bool = false,
79
+ fontWeight: Font.Weight = .regular,
80
+ keyboardType: UIKeyboardType = .default) {
81
+ self._text = text
82
+ self.buttonText = buttonText
83
+ self.showButtonText = showButtonText
84
+ self.showBorder = showBorder
85
+ self.placeholder = placeholder
86
+ self.onChangeText = onChangeText
87
+ self.onPressButtonText = onPressButtonText
88
+ self.error = error
89
+ self.disabled = disabled
90
+ self.icon = icon
91
+ self.iconColor = iconColor
92
+ self.onRightIconPressed = onRightIconPressed
93
+ self.onFocus = onFocus
94
+ self.onBlur = onBlur
95
+ self.loading = loading
96
+ self.fontWeight = fontWeight
97
+ self.keyboardType = keyboardType
98
+ }
99
+
100
+
101
+ public var body: some View {
102
+ VStack(alignment: .leading, spacing: 0) {
103
+ HStack(spacing: 0) {
104
+ HStack {
105
+ Image(systemName: "magnifyingglass")
106
+ .foregroundColor(Colors.black12)
107
+ .frame(width: 24, height: 24)
108
+ .padding(.trailing, Spacing.XS)
109
+
110
+ ZStack(alignment: .leading) {
111
+ if text.isEmpty {
112
+ Text(placeholder)
113
+ .foregroundColor(disabled ? Colors.black08 : Colors.black12)
114
+ .font(.system(size: 16, weight: fontWeight))
115
+ }
116
+ TextField("", text: $text, onEditingChanged: { editing in
117
+ isFocused = editing
118
+ if editing {
119
+ onFocus()
120
+ } else if isBlurred {
121
+ onBlur()
122
+ }
123
+ if editing && !isBlurred {
124
+ isBlurred = true
125
+ }
126
+ }, onCommit: {})
127
+ .disabled(disabled)
128
+ .foregroundColor(disabled ? Colors.black08 : Colors.black17)
129
+ .font(.system(size: 15, weight: fontWeight))
130
+ .keyboardType(keyboardType)
131
+ }
132
+
133
+ if isFocused && !text.isEmpty {
134
+ SwiftButton(action: { text = "" }) {
135
+ Image(systemName: "xmark.circle.fill")
136
+ .foregroundColor(Colors.black12)
137
+ .frame(width: 16, height: 16)
138
+ }
139
+ .padding(.leading, Spacing.S)
140
+ }
141
+
142
+ RenderRightIconSearch(loading: loading, icon: icon, color: disabled ? Colors.black08 : iconColor, onClick: onRightIconPressed)
143
+ }
144
+ .padding(.horizontal, Spacing.M)
145
+ .padding(.vertical, Spacing.S)
146
+ .background(Colors.black01)
147
+ .cornerRadius(Radius.XL)
148
+ .overlay(
149
+ RoundedRectangle(cornerRadius: Radius.XL)
150
+ .stroke(getBorderColor(isFocused: isFocused, error: error, disabled: disabled), lineWidth: showBorder ? 1 : 0)
151
+ )
152
+ .frame(maxWidth: showButtonText ? .infinity : nil, alignment: .leading)
153
+
154
+ if showButtonText {
155
+ SwiftButton(action: onPressButtonText) {
156
+ Text(buttonText)
157
+ .foregroundColor(Colors.black17)
158
+ .font(.system(size: 14, weight: .medium))
159
+ }
160
+ .padding(.leading, Spacing.L)
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ // MARK: - Helper Functions
168
+
169
+ func getBorderColor(isFocused: Bool, error: String, disabled: Bool) -> Color {
170
+ if disabled {
171
+ return Colors.black08
172
+ } else if !error.isEmpty {
173
+ return Colors.red03
174
+ } else if isFocused {
175
+ return Colors.primary
176
+ } else {
177
+ return Colors.black04
178
+ }
179
+ }
180
+
181
+
182
+
@@ -0,0 +1,242 @@
1
+ //
2
+ // InputTextArea.swift
3
+ // Pods
4
+ //
5
+ // Created by dung.pham2 on 2/12/24.
6
+ //
7
+ import SwiftUI
8
+
9
+ // Make these constants public
10
+ public let MAX_LENGTH = 300
11
+ public let DEFAULT_HEIGHT: CGFloat = 104
12
+
13
+ struct UITextViewWrapper: UIViewRepresentable {
14
+ @Binding var text: String
15
+ var onDone: (() -> Void)?
16
+ var onFocus: (() -> Void)?
17
+ var onBlur: (() -> Void)?
18
+
19
+ func makeUIView(context: Context) -> UITextView {
20
+ let textView = UITextView()
21
+ textView.delegate = context.coordinator
22
+ return textView
23
+ }
24
+
25
+ func updateUIView(_ uiView: UITextView, context: Context) {
26
+ uiView.text = text
27
+ }
28
+
29
+ func makeCoordinator() -> Coordinator {
30
+ Coordinator(self)
31
+ }
32
+
33
+ class Coordinator: NSObject, UITextViewDelegate {
34
+ var parent: UITextViewWrapper
35
+
36
+ init(_ parent: UITextViewWrapper) {
37
+ self.parent = parent
38
+ }
39
+
40
+ func textViewDidChange(_ textView: UITextView) {
41
+ parent.text = textView.text
42
+ }
43
+
44
+ func textViewDidBeginEditing(_ textView: UITextView) {
45
+ parent.onFocus?()
46
+ }
47
+
48
+ func textViewDidEndEditing(_ textView: UITextView) {
49
+ parent.onBlur?()
50
+ }
51
+
52
+ func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
53
+ if text == "\n" {
54
+ parent.onDone?()
55
+ return false
56
+ }
57
+ return true
58
+ }
59
+ }
60
+ }
61
+
62
+ // Make the struct public
63
+ public struct InputTextArea: View {
64
+ @Binding var text: String
65
+ var maxLength: Int = MAX_LENGTH
66
+ var height: CGFloat = DEFAULT_HEIGHT
67
+ var floatingValue: String = ""
68
+ var floatingValueColor: Color = .gray
69
+ var floatingIcon: String = ""
70
+ var floatingIconColor: Color = .black
71
+ var placeholder: String = ""
72
+ var onChangeText: (String) -> Void = { _ in }
73
+ var error: String = ""
74
+ var errorSpacing: Bool = false
75
+ var disabled: Bool = false
76
+ var icon: String = ""
77
+ var iconColor: Color = .black
78
+ var onRightIconPressed: () -> Void = {}
79
+ var onFocus: () -> Void = {}
80
+ var onBlur: () -> Void = {}
81
+ var loading: Bool = false
82
+ var required: Bool = false
83
+ var fontWeight: Font.Weight = .regular
84
+ var keyboardType: UIKeyboardType = .default
85
+
86
+ @State private var isFocused = false
87
+ @State private var isBlurred = false
88
+
89
+ // Make the initializer public
90
+ public init(
91
+ text: Binding<String>,
92
+ maxLength: Int = MAX_LENGTH,
93
+ height: CGFloat = DEFAULT_HEIGHT,
94
+ floatingValue: String = "",
95
+ floatingValueColor: Color = .gray,
96
+ floatingIcon: String = "",
97
+ floatingIconColor: Color = .black,
98
+ placeholder: String = "",
99
+ onChangeText: @escaping (String) -> Void = { _ in },
100
+ error: String = "",
101
+ errorSpacing: Bool = false,
102
+ disabled: Bool = false,
103
+ icon: String = "",
104
+ iconColor: Color = Colors.pink03,
105
+ onRightIconPressed: @escaping () -> Void = {},
106
+ onFocus: @escaping () -> Void = {},
107
+ onBlur: @escaping () -> Void = {},
108
+ loading: Bool = false,
109
+ required: Bool = false,
110
+ fontWeight: Font.Weight = .regular,
111
+ keyboardType: UIKeyboardType = .default
112
+ ) {
113
+ self._text = text
114
+ self.maxLength = maxLength
115
+ self.height = height
116
+ self.floatingValue = floatingValue
117
+ self.floatingValueColor = floatingValueColor
118
+ self.floatingIcon = floatingIcon
119
+ self.floatingIconColor = floatingIconColor
120
+ self.placeholder = placeholder
121
+ self.onChangeText = onChangeText
122
+ self.error = error
123
+ self.errorSpacing = errorSpacing
124
+ self.disabled = disabled
125
+ self.icon = icon
126
+ self.iconColor = iconColor
127
+ self.onRightIconPressed = onRightIconPressed
128
+ self.onFocus = onFocus
129
+ self.onBlur = onBlur
130
+ self.loading = loading
131
+ self.required = required
132
+ self.fontWeight = fontWeight
133
+ self.keyboardType = keyboardType
134
+ }
135
+
136
+ public var body: some View {
137
+ VStack(alignment: .leading, spacing: 0) {
138
+ ZStack(alignment: .topLeading) {
139
+ if !floatingValue.isEmpty || !floatingIcon.isEmpty {
140
+ HStack(spacing: 4) {
141
+ Text(floatingValue)
142
+ .font(.caption)
143
+ .foregroundColor(disabled ? .gray : floatingValueColor)
144
+
145
+ if required {
146
+ Text("*")
147
+ .font(.caption)
148
+ .foregroundColor(.red)
149
+ }
150
+
151
+ if !floatingIcon.isEmpty {
152
+ Image(systemName: floatingIcon)
153
+ .font(.caption)
154
+ .foregroundColor(disabled ? .gray : floatingIconColor)
155
+ }
156
+ }
157
+ .padding(.horizontal, 8)
158
+ .background(Colors.black01)
159
+ .offset(x: Spacing.S, y: -height / 10)
160
+ .zIndex(10)
161
+ }
162
+
163
+ VStack {
164
+ ZStack(alignment: .topLeading) {
165
+ if text.isEmpty {
166
+ Text(placeholder)
167
+ .foregroundColor(disabled ? .gray : .gray)
168
+ .font(.system(size: 16, weight: fontWeight))
169
+ .padding(.horizontal, 16)
170
+ .padding(.vertical, 12)
171
+ }
172
+
173
+ UITextViewWrapper(text: $text, onDone: {
174
+ // Handle done action if needed
175
+ },
176
+ onFocus: {
177
+ isFocused = true
178
+ onFocus()
179
+ }, onBlur: {
180
+ isFocused = false
181
+ isBlurred = true
182
+ onBlur()
183
+ })
184
+ .frame(height: height)
185
+ .padding(.horizontal, 12)
186
+ .padding(.vertical, 8)
187
+ .disabled(disabled) }
188
+
189
+ HStack {
190
+ Spacer()
191
+ Text("\(text.count)/\(maxLength)")
192
+ .font(.caption)
193
+ .foregroundColor(.gray)
194
+ }
195
+ .padding(.horizontal, 16)
196
+ .padding(.bottom, 12)
197
+ }
198
+ .background(Color.white)
199
+ .cornerRadius(8)
200
+ .overlay(
201
+ RoundedRectangle(cornerRadius: 8)
202
+ .stroke(borderColor, lineWidth: 1)
203
+ )
204
+ }
205
+
206
+ if !error.isEmpty {
207
+ Text(error)
208
+ .font(.caption)
209
+ .foregroundColor(.red)
210
+ .padding(.top, errorSpacing ? 8 : 4)
211
+ }
212
+ }
213
+ .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in
214
+ if !isBlurred {
215
+ isFocused = true
216
+ onFocus()
217
+ }
218
+ }
219
+ .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
220
+ if isFocused {
221
+ isFocused = false
222
+ isBlurred = true
223
+ onBlur()
224
+ }
225
+ }
226
+ }
227
+
228
+ private var borderColor: Color {
229
+ if disabled {
230
+ return Colors.black03
231
+ } else if !error.isEmpty {
232
+ return .red
233
+ } else if isFocused {
234
+ return Colors.pink03
235
+ } else {
236
+ return Colors.border
237
+ }
238
+
239
+ }
240
+ }
241
+
242
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.89.33",
3
+ "version": "0.110.1-beta.1",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},