@momo-kits/native-kits 0.157.2 → 0.157.3-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.
Files changed (117) hide show
  1. package/build.gradle.kts +11 -0
  2. package/compose/build.gradle.kts +180 -0
  3. package/compose/build.gradle.kts.backup +180 -0
  4. package/compose/compose.podspec +54 -0
  5. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +110 -0
  6. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  7. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  8. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  9. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  10. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  11. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  12. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +107 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +305 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +720 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +121 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +405 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +85 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +340 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +198 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +94 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +136 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +148 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +188 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +116 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +448 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +172 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +255 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +231 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +233 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +254 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +241 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +92 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +352 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +103 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +70 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +17 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +96 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +96 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +92 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +130 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +214 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +590 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +177 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +205 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +239 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +13 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +185 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +285 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +59 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +14 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +50 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +239 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +119 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +98 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +161 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +331 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +497 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +162 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +243 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +187 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +279 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +32 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +370 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +132 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +42 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
  109. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +149 -0
  110. package/gradle/libs.versions.toml +57 -0
  111. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  112. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  113. package/gradle.properties +26 -0
  114. package/gradlew +252 -0
  115. package/gradlew.bat +94 -0
  116. package/package.json +1 -1
  117. package/settings.gradle.kts +52 -0
@@ -0,0 +1,590 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.Canvas
4
+ import androidx.compose.foundation.background
5
+ import androidx.compose.foundation.border
6
+ import androidx.compose.foundation.clickable
7
+ import androidx.compose.foundation.interaction.MutableInteractionSource
8
+ import androidx.compose.foundation.layout.Arrangement
9
+ import androidx.compose.foundation.layout.Box
10
+ import androidx.compose.foundation.layout.Column
11
+ import androidx.compose.foundation.layout.IntrinsicSize
12
+ import androidx.compose.foundation.layout.Row
13
+ import androidx.compose.foundation.layout.Spacer
14
+ import androidx.compose.foundation.layout.fillMaxWidth
15
+ import androidx.compose.foundation.layout.height
16
+ import androidx.compose.foundation.layout.offset
17
+ import androidx.compose.foundation.layout.padding
18
+ import androidx.compose.foundation.layout.size
19
+ import androidx.compose.foundation.layout.width
20
+ import androidx.compose.foundation.layout.widthIn
21
+ import androidx.compose.foundation.layout.wrapContentSize
22
+ import androidx.compose.foundation.shape.RoundedCornerShape
23
+ import androidx.compose.runtime.Composable
24
+ import androidx.compose.runtime.LaunchedEffect
25
+ import androidx.compose.runtime.Stable
26
+ import androidx.compose.runtime.getValue
27
+ import androidx.compose.runtime.mutableStateOf
28
+ import androidx.compose.runtime.remember
29
+ import androidx.compose.runtime.setValue
30
+ import androidx.compose.ui.Alignment
31
+ import androidx.compose.ui.Modifier
32
+ import androidx.compose.ui.draw.clip
33
+ import androidx.compose.ui.graphics.Paint
34
+ import androidx.compose.ui.graphics.Path
35
+ import androidx.compose.ui.graphics.PathEffect
36
+ import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
37
+ import androidx.compose.ui.platform.LocalDensity
38
+ import androidx.compose.ui.text.style.TextOverflow
39
+ import androidx.compose.ui.unit.IntOffset
40
+ import androidx.compose.ui.unit.IntRect
41
+ import androidx.compose.ui.unit.IntSize
42
+ import androidx.compose.ui.unit.LayoutDirection
43
+ import androidx.compose.ui.unit.dp
44
+ import androidx.compose.ui.window.Popup
45
+ import androidx.compose.ui.window.PopupPositionProvider
46
+ import androidx.compose.ui.window.PopupProperties
47
+ import vn.momo.kits.application.IsShowBaseLineDebug
48
+ import vn.momo.kits.const.Colors
49
+ import vn.momo.kits.const.Radius
50
+ import vn.momo.kits.const.Spacing
51
+ import vn.momo.kits.const.Typography
52
+ import vn.momo.kits.const.scaleSize
53
+ import vn.momo.kits.modifier.activeOpacityClickable
54
+ import vn.momo.kits.modifier.conditional
55
+
56
+
57
+ // region Types
58
+
59
+ /**
60
+ * Tooltip placement relative to the anchor element.
61
+ */
62
+ enum class TooltipPlacement {
63
+ TOP,
64
+ BOTTOM,
65
+ LEFT,
66
+ RIGHT,
67
+ }
68
+
69
+ /**
70
+ * Cross-axis alignment of the tooltip relative to the anchor.
71
+ */
72
+ enum class TooltipAlign {
73
+ START,
74
+ CENTER,
75
+ END,
76
+ }
77
+
78
+ /**
79
+ * Describes a single action button displayed in the tooltip.
80
+ *
81
+ * @param title Text label for the button (used for text buttons).
82
+ * @param icon Icon source for the button (used for icon buttons).
83
+ * @param onPress Callback invoked when the button is pressed.
84
+ */
85
+ data class TooltipButton(
86
+ val title: String? = null,
87
+ val icon: String? = null,
88
+ val onPress: (() -> Unit)? = null,
89
+ )
90
+
91
+
92
+ /**
93
+ * State holder for controlling tooltip visibility imperatively.
94
+ * Equivalent to the React Native `useImperativeHandle` ref pattern.
95
+ */
96
+ @Stable
97
+ class TooltipState {
98
+ var isVisible by mutableStateOf(false)
99
+ private set
100
+
101
+ /** Shows the tooltip. */
102
+ fun show() {
103
+ isVisible = true
104
+ }
105
+
106
+ /** Hides the tooltip. */
107
+ fun hide() {
108
+ isVisible = false
109
+ }
110
+
111
+ /** Toggles the tooltip visibility. */
112
+ fun toggle() {
113
+ isVisible = !isVisible
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Creates and remembers a [TooltipState].
119
+ */
120
+ @Composable
121
+ fun rememberTooltipState(): TooltipState {
122
+ return remember { TooltipState() }
123
+ }
124
+ private val TOOLTIP_OFFSET = Spacing.S
125
+ private val ARROW_SIZE = 6.dp
126
+ /**
127
+ * Custom [PopupPositionProvider] that positions the tooltip relative to the anchor.
128
+ *
129
+ * Mirrors the React Native `placementStyle` calculation:
130
+ * - TOP: tooltip bottom edge sits at `anchorTop - TOOLTIP_OFFSET`
131
+ * - BOTTOM: tooltip top edge sits at `anchorBottom + TOOLTIP_OFFSET`
132
+ * - LEFT: tooltip right edge sits at `anchorLeft - TOOLTIP_OFFSET`
133
+ * - RIGHT: tooltip left edge sits at `anchorRight + TOOLTIP_OFFSET`
134
+ *
135
+ * Cross-axis alignment:
136
+ * - start: tooltip aligns to anchor start edge
137
+ * - center: tooltip centers on anchor
138
+ * - end: tooltip aligns to anchor end edge
139
+ */
140
+ private class TooltipPositionProvider(
141
+ private val placement: TooltipPlacement,
142
+ private val align: TooltipAlign,
143
+ private val offsetPx: Int,
144
+ ) : PopupPositionProvider {
145
+
146
+ override fun calculatePosition(
147
+ anchorBounds: IntRect,
148
+ windowSize: IntSize,
149
+ layoutDirection: LayoutDirection,
150
+ popupContentSize: IntSize,
151
+ ): IntOffset {
152
+ val anchorX = anchorBounds.left
153
+ val anchorY = anchorBounds.top
154
+ val anchorW = anchorBounds.width
155
+ val anchorH = anchorBounds.height
156
+ val popW = popupContentSize.width
157
+ val popH = popupContentSize.height
158
+
159
+ var x: Int
160
+ var y: Int
161
+
162
+ when (placement) {
163
+ TooltipPlacement.TOP -> {
164
+ y = anchorY - popH - offsetPx
165
+ x = alignHorizontal(anchorX, anchorW, popW)
166
+ }
167
+
168
+ TooltipPlacement.BOTTOM -> {
169
+ y = anchorY + anchorH + offsetPx
170
+ x = alignHorizontal(anchorX, anchorW, popW)
171
+ }
172
+
173
+ TooltipPlacement.LEFT -> {
174
+ x = anchorX - popW - offsetPx
175
+ y = alignVertical(anchorY, anchorH, popH)
176
+ }
177
+
178
+ TooltipPlacement.RIGHT -> {
179
+ x = anchorX + anchorW + offsetPx
180
+ y = alignVertical(anchorY, anchorH, popH)
181
+ }
182
+ }
183
+
184
+ return IntOffset(x, y)
185
+ }
186
+ private fun alignHorizontal(anchorX: Int, anchorW: Int, popW: Int): Int {
187
+ return when (align) {
188
+ TooltipAlign.START -> anchorX
189
+ TooltipAlign.END -> anchorX + anchorW - popW
190
+ TooltipAlign.CENTER -> anchorX + (anchorW - popW) / 2
191
+ }
192
+ }
193
+ private fun alignVertical(anchorY: Int, anchorH: Int, popH: Int): Int {
194
+ return when (align) {
195
+ TooltipAlign.START -> anchorY
196
+ TooltipAlign.END -> anchorY + anchorH - popH
197
+ TooltipAlign.CENTER -> anchorY + (anchorH - popH) / 2
198
+ }
199
+ }
200
+ }
201
+
202
+ // endregion
203
+
204
+ // region Tooltip Composable
205
+
206
+ /**
207
+ * A tooltip component that wraps [content] (the anchor) and shows a positioned
208
+ * tooltip popup with title, description, close button, and action buttons.
209
+ *
210
+ * Port of the React Native `Tooltip` component.
211
+ *
212
+ * @param state Controls visibility (use [rememberTooltipState]).
213
+ * @param title Title text displayed in the tooltip header.
214
+ * @param description Description text shown under the title.
215
+ * @param buttons Action buttons rendered at the bottom.
216
+ * @param placement Tooltip position relative to the anchor (default TOP).
217
+ * @param align Cross-axis alignment (default CENTER).
218
+ * @param onVisibleChange Callback when visibility changes.
219
+ * @param onPressClose Callback when the close button (X) is pressed.
220
+ * @param modifier Modifier for the anchor wrapper.
221
+ * @param content The anchor element that the tooltip is attached to.
222
+ */
223
+ @Composable
224
+ fun Tooltip(
225
+ state: TooltipState,
226
+ title: String? = null,
227
+ description: String,
228
+ buttons: List<TooltipButton> = emptyList(),
229
+ placement: TooltipPlacement = TooltipPlacement.TOP,
230
+ align: TooltipAlign = TooltipAlign.CENTER,
231
+ onVisibleChange: ((Boolean) -> Unit)? = null,
232
+ onPressClose: (() -> Unit)? = null,
233
+ modifier: Modifier = Modifier,
234
+ content: @Composable () -> Unit,
235
+ ) {
236
+ val density = LocalDensity.current
237
+ val offsetPx = with(density) { (TOOLTIP_OFFSET + ARROW_SIZE).roundToPx() }
238
+
239
+ LaunchedEffect(state.isVisible) {
240
+ onVisibleChange?.invoke(state.isVisible)
241
+ }
242
+
243
+ Box(modifier = modifier) {
244
+ content()
245
+
246
+ if (state.isVisible) {
247
+ val positionProvider = remember(placement, align, offsetPx) {
248
+ TooltipPositionProvider(
249
+ placement = placement,
250
+ align = align,
251
+ offsetPx = offsetPx,
252
+ )
253
+ }
254
+
255
+ Popup(
256
+ popupPositionProvider = positionProvider,
257
+ onDismissRequest = { state.hide() },
258
+ properties = PopupProperties(clippingEnabled = false),
259
+ ) {
260
+ TooltipPopupContent(
261
+ title = title,
262
+ description = description,
263
+ buttons = buttons,
264
+ placement = placement,
265
+ align = align,
266
+ onPressClose = onPressClose,
267
+ )
268
+ }
269
+ }
270
+ }
271
+ }
272
+ @Composable
273
+ private fun TooltipPopupContent(
274
+ title: String?,
275
+ description: String?,
276
+ buttons: List<TooltipButton>,
277
+ placement: TooltipPlacement,
278
+ align: TooltipAlign,
279
+ onPressClose: (() -> Unit)? = null,
280
+ ) {
281
+ val tooltipMaxWidth = 300.dp
282
+ val tooltipShape = remember { RoundedCornerShape(Radius.S) }
283
+
284
+ Box(modifier = Modifier.wrapContentSize()) {
285
+ Column(
286
+ modifier = Modifier
287
+ .widthIn(max = tooltipMaxWidth)
288
+ .width(IntrinsicSize.Max)
289
+ .background(Colors.black_17, tooltipShape)
290
+ .clip(tooltipShape)
291
+ .conditional(IsShowBaseLineDebug) {
292
+ border(1.dp, Colors.blue_03)
293
+ }
294
+ .padding(Spacing.M)
295
+ ) {
296
+ Row(Modifier.fillMaxWidth()) {
297
+ Column(modifier = Modifier.weight(1f)) {
298
+ if (!title.isNullOrEmpty()) {
299
+ Text(
300
+ text = title,
301
+ style = Typography.headerSSemibold,
302
+ color = Colors.black_01,
303
+ maxLines = 1,
304
+ modifier = Modifier.padding(bottom = Spacing.XS),
305
+ overflow = TextOverflow.Ellipsis,
306
+ )
307
+ }
308
+ if (!description.isNullOrEmpty()) {
309
+ Text(
310
+ text = description,
311
+ style = Typography.descriptionDefaultRegular,
312
+ color = Colors.black_01,
313
+ maxLines = 2,
314
+ overflow = TextOverflow.Ellipsis,
315
+ )
316
+ }
317
+ }
318
+ if (onPressClose != null) {
319
+ Spacer(Modifier.width(Spacing.M))
320
+ Box(
321
+ modifier = Modifier
322
+ .size(20.dp)
323
+ .activeOpacityClickable {
324
+ onPressClose.invoke()
325
+ }
326
+ ) {
327
+ Icon(
328
+ source = "navigation_close",
329
+ size = 20.dp,
330
+ color = Colors.black_01,
331
+ )
332
+ }
333
+ }
334
+ }
335
+ if (buttons.isNotEmpty()) {
336
+ Spacer(Modifier.height(Spacing.M))
337
+ TooltipButtons(
338
+ buttons = buttons,
339
+ modifier = Modifier.align(Alignment.End),
340
+ )
341
+ }
342
+ }
343
+
344
+ TooltipArrow(
345
+ placement = placement,
346
+ align = align,
347
+ modifier = Modifier.matchParentSize(),
348
+ )
349
+ }
350
+ }
351
+ @Composable
352
+ private fun TooltipArrow(
353
+ placement: TooltipPlacement,
354
+ align: TooltipAlign,
355
+ modifier: Modifier = Modifier,
356
+ ) {
357
+ val arrowWidth = ARROW_SIZE * 2
358
+ val arrowHeight = ARROW_SIZE
359
+ val arrowEdgeMargin = Spacing.M
360
+
361
+ val boxAlignment = when (placement) {
362
+ TooltipPlacement.TOP -> when (align) {
363
+ TooltipAlign.START -> Alignment.BottomStart
364
+ TooltipAlign.CENTER -> Alignment.BottomCenter
365
+ TooltipAlign.END -> Alignment.BottomEnd
366
+ }
367
+
368
+ TooltipPlacement.BOTTOM -> when (align) {
369
+ TooltipAlign.START -> Alignment.TopStart
370
+ TooltipAlign.CENTER -> Alignment.TopCenter
371
+ TooltipAlign.END -> Alignment.TopEnd
372
+ }
373
+
374
+ TooltipPlacement.LEFT -> when (align) {
375
+ TooltipAlign.START -> Alignment.TopEnd
376
+ TooltipAlign.CENTER -> Alignment.CenterEnd
377
+ TooltipAlign.END -> Alignment.BottomEnd
378
+ }
379
+
380
+ TooltipPlacement.RIGHT -> when (align) {
381
+ TooltipAlign.START -> Alignment.TopStart
382
+ TooltipAlign.CENTER -> Alignment.CenterStart
383
+ TooltipAlign.END -> Alignment.BottomStart
384
+ }
385
+ }
386
+
387
+ val canvasWidth = when (placement) {
388
+ TooltipPlacement.TOP, TooltipPlacement.BOTTOM -> arrowWidth
389
+ TooltipPlacement.LEFT, TooltipPlacement.RIGHT -> arrowHeight
390
+ }
391
+ val canvasHeight = when (placement) {
392
+ TooltipPlacement.TOP, TooltipPlacement.BOTTOM -> arrowHeight
393
+ TooltipPlacement.LEFT, TooltipPlacement.RIGHT -> arrowWidth
394
+ }
395
+
396
+ val arrowOverlap = 1.dp
397
+ val offsetX = when (placement) {
398
+ TooltipPlacement.LEFT -> arrowHeight - arrowOverlap
399
+ TooltipPlacement.RIGHT -> -arrowHeight + arrowOverlap
400
+ else -> when (align) {
401
+ TooltipAlign.START -> arrowEdgeMargin
402
+ TooltipAlign.END -> -arrowEdgeMargin
403
+ TooltipAlign.CENTER -> 0.dp
404
+ }
405
+ }
406
+ val offsetY = when (placement) {
407
+ TooltipPlacement.TOP -> arrowHeight - arrowOverlap
408
+ TooltipPlacement.BOTTOM -> -arrowHeight + arrowOverlap
409
+ else -> when (align) {
410
+ TooltipAlign.START -> arrowEdgeMargin
411
+ TooltipAlign.END -> -arrowEdgeMargin
412
+ TooltipAlign.CENTER -> 0.dp
413
+ }
414
+ }
415
+
416
+ val arrowColor = Colors.black_17
417
+
418
+ Box(modifier = modifier) {
419
+ Canvas(
420
+ modifier = Modifier
421
+ .align(boxAlignment)
422
+ .offset(x = offsetX, y = offsetY)
423
+ .size(width = canvasWidth, height = canvasHeight)
424
+ ) {
425
+ val w = size.width
426
+ val h = size.height
427
+ val path = Path().apply {
428
+ when (placement) {
429
+ TooltipPlacement.TOP -> {
430
+ moveTo(0f, 0f)
431
+ lineTo(w, 0f)
432
+ lineTo(w / 2f, h)
433
+ close()
434
+ }
435
+ TooltipPlacement.BOTTOM -> {
436
+ moveTo(0f, h)
437
+ lineTo(w, h)
438
+ lineTo(w / 2f, 0f)
439
+ close()
440
+ }
441
+ TooltipPlacement.LEFT -> {
442
+ moveTo(0f, 0f)
443
+ lineTo(0f, h)
444
+ lineTo(w, h / 2f)
445
+ close()
446
+ }
447
+ TooltipPlacement.RIGHT -> {
448
+ moveTo(w, 0f)
449
+ lineTo(w, h)
450
+ lineTo(0f, h / 2f)
451
+ close()
452
+ }
453
+ }
454
+ }
455
+ drawIntoCanvas { canvas ->
456
+ val paint = Paint().apply {
457
+ color = arrowColor
458
+ pathEffect = PathEffect.cornerPathEffect(2.dp.toPx())
459
+ }
460
+ canvas.drawPath(path, paint)
461
+ }
462
+ }
463
+ }
464
+ }
465
+ @Composable
466
+ private fun TooltipButtons(buttons: List<TooltipButton>, modifier: Modifier = Modifier) {
467
+ Row(
468
+ modifier = modifier,
469
+ horizontalArrangement = Arrangement.End,
470
+ verticalAlignment = Alignment.CenterVertically,
471
+ ) {
472
+
473
+ if (buttons.size == 1) {
474
+ val btn = buttons[0]
475
+ TooltipSingleButton(btn)
476
+ } else if (buttons.size == 2) {
477
+ val primary = buttons[0]
478
+ val secondary = buttons[1]
479
+ val bothIcons = primary.icon != null && secondary.icon != null
480
+
481
+ if (bothIcons) {
482
+ TooltipIconButton(
483
+ icon = secondary.icon,
484
+ onPress = secondary.onPress ?: {},
485
+ )
486
+ Spacer(modifier = Modifier.width(Spacing.S))
487
+ TooltipIconButton(
488
+ icon = primary.icon,
489
+ onPress = primary.onPress ?: {},
490
+ )
491
+ } else {
492
+ TooltipSecondaryButton(
493
+ title = secondary.title ?: "",
494
+ onPress = secondary.onPress ?: {},
495
+ modifier = Modifier.weight(1f, fill = false),
496
+ )
497
+ Spacer(modifier = Modifier.width(Spacing.S))
498
+ TooltipPrimaryButton(
499
+ title = primary.title ?: "",
500
+ onPress = primary.onPress ?: {},
501
+ )
502
+ }
503
+ } else {
504
+ buttons.forEachIndexed { index, btn ->
505
+ if (index > 0) {
506
+ Spacer(modifier = Modifier.width(Spacing.XXS))
507
+ }
508
+ TooltipSingleButton(btn)
509
+ }
510
+ }
511
+ }
512
+ }
513
+
514
+ @Composable
515
+ private fun TooltipSingleButton(btn: TooltipButton) {
516
+ if (btn.icon != null) {
517
+ TooltipIconButton(
518
+ icon = btn.icon,
519
+ onPress = btn.onPress ?: {},
520
+ )
521
+ } else {
522
+ TooltipPrimaryButton(
523
+ title = btn.title ?: "",
524
+ onPress = btn.onPress ?: {},
525
+ )
526
+ }
527
+ }
528
+
529
+ private const val MAX_BUTTON_LENGTH = 16
530
+
531
+ private fun String.limitWithEllipsis(max: Int = 16): String {
532
+ return if (length > max) take(max) + "…" else this
533
+ }
534
+ @Composable
535
+ private fun TooltipPrimaryButton(
536
+ title: String,
537
+ onPress: () -> Unit,
538
+ ) {
539
+ Button(
540
+ onClick = onPress,
541
+ title = title.limitWithEllipsis(),
542
+ type = ButtonType.SECONDARY,
543
+ size = Size.MEDIUM,
544
+ isFull = false,
545
+ )
546
+ }
547
+ @Composable
548
+ private fun TooltipSecondaryButton(
549
+ title: String,
550
+ onPress: () -> Unit,
551
+ modifier: Modifier = Modifier,
552
+ ) {
553
+ Box(
554
+ modifier = modifier
555
+ .activeOpacityClickable {
556
+ onPress()
557
+ }
558
+ .padding(horizontal = Spacing.M, vertical = Spacing.S),
559
+ ) {
560
+ Text(
561
+ text = title.limitWithEllipsis(),
562
+ color = Colors.black_01,
563
+ style = Typography.actionSBold,
564
+ maxLines = 1,
565
+ overflow = TextOverflow.Ellipsis,
566
+ )
567
+ }
568
+ }
569
+ @Composable
570
+ private fun TooltipIconButton(
571
+ icon: String,
572
+ onPress: () -> Unit,
573
+ ) {
574
+ val iconButtonSize = scaleSize(36f).dp
575
+
576
+ Box(
577
+ modifier = Modifier
578
+ .size(iconButtonSize)
579
+ .background(Colors.black_01, RoundedCornerShape(Radius.XL))
580
+ .clip(RoundedCornerShape(Radius.XL))
581
+ .clickable(
582
+ interactionSource = remember { MutableInteractionSource() },
583
+ indication = null,
584
+ onClick = onPress,
585
+ ),
586
+ contentAlignment = Alignment.Center,
587
+ ) {
588
+ Icon(source = icon)
589
+ }
590
+ }