@hzblj/kara-components 0.5.0 → 0.7.0

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 (225) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +31 -3
  3. package/lib/module/atoms/checkbox/checkbox.android.js +7 -3
  4. package/lib/module/atoms/checkbox/checkbox.android.js.map +1 -1
  5. package/lib/module/atoms/chip/chip.js +1 -1
  6. package/lib/module/atoms/chip/chip.js.map +1 -1
  7. package/lib/module/atoms/date-picker/date-picker.ios.js +32 -11
  8. package/lib/module/atoms/date-picker/date-picker.ios.js.map +1 -1
  9. package/lib/module/atoms/index.js +0 -1
  10. package/lib/module/atoms/index.js.map +1 -1
  11. package/lib/module/atoms/pressable/pressable.js +11 -5
  12. package/lib/module/atoms/pressable/pressable.js.map +1 -1
  13. package/lib/module/atoms/rn-flat-list/rn-flat-list.js +10 -9
  14. package/lib/module/atoms/rn-flat-list/rn-flat-list.js.map +1 -1
  15. package/lib/module/atoms/rn-section-list/rn-section-list.js +5 -8
  16. package/lib/module/atoms/rn-section-list/rn-section-list.js.map +1 -1
  17. package/lib/module/atoms/segmented-control/index.js +1 -1
  18. package/lib/module/atoms/segmented-control/index.js.map +1 -1
  19. package/lib/module/atoms/segmented-control/segmented-control.android.js +74 -0
  20. package/lib/module/atoms/segmented-control/segmented-control.android.js.map +1 -0
  21. package/lib/module/atoms/segmented-control/segmented-control.ios.js +44 -0
  22. package/lib/module/atoms/segmented-control/segmented-control.ios.js.map +1 -0
  23. package/lib/module/atoms/slider/slider.android.js +26 -2
  24. package/lib/module/atoms/slider/slider.android.js.map +1 -1
  25. package/lib/module/atoms/switch/switch.android.js +14 -4
  26. package/lib/module/atoms/switch/switch.android.js.map +1 -1
  27. package/lib/module/haptics/haptics.js +56 -0
  28. package/lib/module/haptics/haptics.js.map +1 -0
  29. package/lib/module/haptics/index.js +5 -0
  30. package/lib/module/haptics/index.js.map +1 -0
  31. package/lib/module/haptics/use-haptic-feedback.js +33 -0
  32. package/lib/module/haptics/use-haptic-feedback.js.map +1 -0
  33. package/lib/module/index.js +1 -0
  34. package/lib/module/index.js.map +1 -1
  35. package/lib/module/molecules/search-bar/search-bar.js +15 -2
  36. package/lib/module/molecules/search-bar/search-bar.js.map +1 -1
  37. package/lib/module/molecules/tooltip/tooltip.android.js +16 -5
  38. package/lib/module/molecules/tooltip/tooltip.android.js.map +1 -1
  39. package/lib/module/organisms/action-sheet/action-sheet.android.js +41 -20
  40. package/lib/module/organisms/action-sheet/action-sheet.android.js.map +1 -1
  41. package/lib/module/organisms/action-sheet/action-sheet.ios.js +24 -8
  42. package/lib/module/organisms/action-sheet/action-sheet.ios.js.map +1 -1
  43. package/lib/module/organisms/alert/alert.ios.js +15 -2
  44. package/lib/module/organisms/alert/alert.ios.js.map +1 -1
  45. package/lib/module/organisms/context-menu/context-menu.android.js +2 -2
  46. package/lib/module/organisms/context-menu/context-menu.android.js.map +1 -1
  47. package/lib/module/organisms/form/form.android.js +22 -9
  48. package/lib/module/organisms/form/form.android.js.map +1 -1
  49. package/lib/module/organisms/swipe-actions/swipe-actions.android.js +125 -11
  50. package/lib/module/organisms/swipe-actions/swipe-actions.android.js.map +1 -1
  51. package/lib/module/organisms/swipe-actions/swipe-actions.ios.js +46 -18
  52. package/lib/module/organisms/swipe-actions/swipe-actions.ios.js.map +1 -1
  53. package/lib/module/templates/screen/screen.android.js +31 -10
  54. package/lib/module/templates/screen/screen.android.js.map +1 -1
  55. package/lib/typescript/atoms/avatar/avatar.types.d.ts +5 -0
  56. package/lib/typescript/atoms/badge/badge.types.d.ts +3 -0
  57. package/lib/typescript/atoms/button/button.types.d.ts +12 -0
  58. package/lib/typescript/atoms/card/card.types.d.ts +6 -0
  59. package/lib/typescript/atoms/checkbox/checkbox.types.d.ts +5 -0
  60. package/lib/typescript/atoms/chip/chip.types.d.ts +5 -0
  61. package/lib/typescript/atoms/color-picker/color-picker.types.d.ts +4 -0
  62. package/lib/typescript/atoms/date-picker/date-picker.ios.d.ts +6 -1
  63. package/lib/typescript/atoms/date-picker/date-picker.types.d.ts +14 -0
  64. package/lib/typescript/atoms/divider/divider.types.d.ts +3 -0
  65. package/lib/typescript/atoms/fab/fab.types.d.ts +7 -0
  66. package/lib/typescript/atoms/glass-view/glass-view.types.d.ts +1 -0
  67. package/lib/typescript/atoms/h-stack/h-stack.types.d.ts +6 -0
  68. package/lib/typescript/atoms/icon/icon.types.d.ts +8 -0
  69. package/lib/typescript/atoms/icon-button/icon-button.types.d.ts +5 -0
  70. package/lib/typescript/atoms/index.d.ts +0 -1
  71. package/lib/typescript/atoms/island/island.types.d.ts +4 -0
  72. package/lib/typescript/atoms/list/list.types.d.ts +2 -0
  73. package/lib/typescript/atoms/picker/picker.types.d.ts +12 -0
  74. package/lib/typescript/atoms/pressable/pressable.types.d.ts +11 -1
  75. package/lib/typescript/atoms/progress/progress.types.d.ts +6 -0
  76. package/lib/typescript/atoms/rn-flat-list/rn-flat-list.d.ts +5 -8
  77. package/lib/typescript/atoms/rn-flat-list/rn-flat-list.types.d.ts +3 -1
  78. package/lib/typescript/atoms/rn-scroll-view/rn-scroll-view.types.d.ts +4 -0
  79. package/lib/typescript/atoms/rn-section-list/rn-section-list.d.ts +2 -8
  80. package/lib/typescript/atoms/rn-section-list/rn-section-list.types.d.ts +3 -1
  81. package/lib/typescript/atoms/rn-text/rn-text.types.d.ts +4 -0
  82. package/lib/typescript/atoms/rn-view/rn-view.types.d.ts +2 -0
  83. package/lib/typescript/atoms/scroll-view/scroll-view.types.d.ts +4 -0
  84. package/lib/typescript/atoms/segmented-control/segmented-control.d.ts +1 -2
  85. package/lib/typescript/atoms/segmented-control/segmented-control.ios.d.ts +7 -0
  86. package/lib/typescript/atoms/segmented-control/segmented-control.types.d.ts +13 -0
  87. package/lib/typescript/atoms/skeleton/skeleton.types.d.ts +5 -0
  88. package/lib/typescript/atoms/slider/slider.types.d.ts +7 -0
  89. package/lib/typescript/atoms/spacer/spacer.types.d.ts +3 -0
  90. package/lib/typescript/atoms/surface/surface.types.d.ts +7 -0
  91. package/lib/typescript/atoms/switch/switch.types.d.ts +8 -0
  92. package/lib/typescript/atoms/text/text.types.d.ts +12 -0
  93. package/lib/typescript/atoms/text-field/text-field.types.d.ts +12 -0
  94. package/lib/typescript/atoms/v-stack/v-stack.types.d.ts +9 -0
  95. package/lib/typescript/atoms/z-stack/z-stack.types.d.ts +8 -0
  96. package/lib/typescript/haptics/haptics.d.ts +61 -0
  97. package/lib/typescript/haptics/index.d.ts +2 -0
  98. package/lib/typescript/haptics/use-haptic-feedback.d.ts +15 -0
  99. package/lib/typescript/index.d.ts +1 -0
  100. package/lib/typescript/molecules/collapsible/collapsible.types.d.ts +8 -0
  101. package/lib/typescript/molecules/empty-state/empty-state.types.d.ts +5 -0
  102. package/lib/typescript/molecules/label/label.types.d.ts +6 -0
  103. package/lib/typescript/molecules/labeled-content/labeled-content.types.d.ts +6 -0
  104. package/lib/typescript/molecules/link/link.types.d.ts +6 -0
  105. package/lib/typescript/molecules/list-item/list-item.types.d.ts +9 -0
  106. package/lib/typescript/molecules/navigation-bar/navigation-bar.types.d.ts +8 -0
  107. package/lib/typescript/molecules/pager/pager.types.d.ts +2 -0
  108. package/lib/typescript/molecules/radio-group/radio-group.types.d.ts +10 -0
  109. package/lib/typescript/molecules/rn-list-item/rn-list-item.types.d.ts +1 -1
  110. package/lib/typescript/molecules/rn-tooltip/rn-tooltip.types.d.ts +11 -0
  111. package/lib/typescript/molecules/scroll-edge-container/scroll-edge-container.types.d.ts +3 -0
  112. package/lib/typescript/molecules/search-bar/search-bar.types.d.ts +8 -0
  113. package/lib/typescript/molecules/stepper/stepper.types.d.ts +13 -0
  114. package/lib/typescript/molecules/tooltip/tooltip.types.d.ts +12 -0
  115. package/lib/typescript/organisms/action-sheet/action-sheet.types.d.ts +8 -0
  116. package/lib/typescript/organisms/alert/alert.types.d.ts +11 -0
  117. package/lib/typescript/organisms/context-menu/context-menu.types.d.ts +4 -0
  118. package/lib/typescript/organisms/fab-menu/fab-menu.types.d.ts +2 -0
  119. package/lib/typescript/organisms/form/form.types.d.ts +6 -0
  120. package/lib/typescript/organisms/menu/menu.types.d.ts +17 -0
  121. package/lib/typescript/organisms/sheet/sheet.types.d.ts +19 -0
  122. package/lib/typescript/organisms/snackbar/snackbar.types.d.ts +8 -0
  123. package/lib/typescript/organisms/swipe-actions/swipe-actions.types.d.ts +36 -13
  124. package/lib/typescript/templates/screen/screen.types.d.ts +9 -0
  125. package/package.json +5 -9
  126. package/src/atoms/avatar/avatar.types.ts +5 -0
  127. package/src/atoms/badge/badge.types.ts +3 -0
  128. package/src/atoms/button/button.types.ts +12 -0
  129. package/src/atoms/card/card.types.ts +6 -0
  130. package/src/atoms/checkbox/checkbox.android.tsx +7 -3
  131. package/src/atoms/checkbox/checkbox.types.ts +5 -0
  132. package/src/atoms/chip/chip.tsx +1 -1
  133. package/src/atoms/chip/chip.types.ts +5 -0
  134. package/src/atoms/color-picker/color-picker.types.ts +4 -0
  135. package/src/atoms/date-picker/date-picker.ios.tsx +33 -13
  136. package/src/atoms/date-picker/date-picker.types.ts +14 -0
  137. package/src/atoms/divider/divider.types.ts +3 -0
  138. package/src/atoms/fab/fab.types.ts +7 -0
  139. package/src/atoms/glass-view/glass-view.types.ts +1 -0
  140. package/src/atoms/h-stack/h-stack.types.ts +6 -0
  141. package/src/atoms/icon/icon.types.ts +8 -0
  142. package/src/atoms/icon-button/icon-button.types.ts +5 -0
  143. package/src/atoms/index.ts +0 -1
  144. package/src/atoms/island/island.types.ts +4 -0
  145. package/src/atoms/list/list.types.ts +2 -0
  146. package/src/atoms/picker/picker.types.ts +12 -0
  147. package/src/atoms/pressable/pressable.tsx +12 -5
  148. package/src/atoms/pressable/pressable.types.ts +12 -1
  149. package/src/atoms/progress/progress.types.ts +6 -0
  150. package/src/atoms/rn-flat-list/rn-flat-list.tsx +8 -8
  151. package/src/atoms/rn-flat-list/rn-flat-list.types.ts +3 -1
  152. package/src/atoms/rn-scroll-view/rn-scroll-view.types.ts +4 -0
  153. package/src/atoms/rn-section-list/rn-section-list.tsx +5 -8
  154. package/src/atoms/rn-section-list/rn-section-list.types.ts +3 -1
  155. package/src/atoms/rn-text/rn-text.types.ts +4 -0
  156. package/src/atoms/rn-view/rn-view.types.ts +2 -0
  157. package/src/atoms/scroll-view/scroll-view.types.ts +4 -0
  158. package/src/atoms/segmented-control/segmented-control.android.tsx +79 -0
  159. package/src/atoms/segmented-control/segmented-control.ios.tsx +47 -0
  160. package/src/atoms/segmented-control/segmented-control.types.ts +13 -0
  161. package/src/atoms/skeleton/skeleton.types.ts +5 -0
  162. package/src/atoms/slider/slider.android.tsx +33 -2
  163. package/src/atoms/slider/slider.types.ts +7 -0
  164. package/src/atoms/spacer/spacer.types.ts +3 -0
  165. package/src/atoms/surface/surface.types.ts +7 -0
  166. package/src/atoms/switch/switch.android.tsx +18 -5
  167. package/src/atoms/switch/switch.types.ts +8 -0
  168. package/src/atoms/text/text.types.ts +12 -0
  169. package/src/atoms/text-field/text-field.types.ts +12 -0
  170. package/src/atoms/v-stack/v-stack.types.ts +9 -0
  171. package/src/atoms/z-stack/z-stack.types.ts +8 -0
  172. package/src/haptics/haptics.ts +68 -0
  173. package/src/haptics/index.ts +2 -0
  174. package/src/haptics/use-haptic-feedback.ts +36 -0
  175. package/src/index.ts +1 -0
  176. package/src/molecules/collapsible/collapsible.types.ts +8 -0
  177. package/src/molecules/empty-state/empty-state.types.ts +5 -0
  178. package/src/molecules/label/label.types.ts +6 -0
  179. package/src/molecules/labeled-content/labeled-content.types.ts +6 -0
  180. package/src/molecules/link/link.types.ts +6 -0
  181. package/src/molecules/list-item/list-item.types.ts +9 -0
  182. package/src/molecules/navigation-bar/navigation-bar.types.ts +8 -0
  183. package/src/molecules/pager/pager.types.ts +2 -0
  184. package/src/molecules/radio-group/radio-group.types.ts +10 -0
  185. package/src/molecules/rn-list-item/rn-list-item.types.ts +1 -1
  186. package/src/molecules/rn-tooltip/rn-tooltip.types.ts +11 -0
  187. package/src/molecules/scroll-edge-container/scroll-edge-container.types.ts +3 -0
  188. package/src/molecules/search-bar/search-bar.tsx +9 -2
  189. package/src/molecules/search-bar/search-bar.types.ts +8 -0
  190. package/src/molecules/stepper/stepper.types.ts +13 -0
  191. package/src/molecules/tooltip/tooltip.android.tsx +17 -9
  192. package/src/molecules/tooltip/tooltip.types.ts +12 -0
  193. package/src/organisms/action-sheet/action-sheet.android.tsx +37 -19
  194. package/src/organisms/action-sheet/action-sheet.ios.tsx +22 -9
  195. package/src/organisms/action-sheet/action-sheet.types.ts +8 -0
  196. package/src/organisms/alert/alert.ios.tsx +11 -1
  197. package/src/organisms/alert/alert.types.ts +11 -0
  198. package/src/organisms/context-menu/context-menu.android.tsx +2 -2
  199. package/src/organisms/context-menu/context-menu.types.ts +4 -0
  200. package/src/organisms/fab-menu/fab-menu.types.ts +2 -0
  201. package/src/organisms/form/form.android.tsx +15 -7
  202. package/src/organisms/form/form.types.ts +6 -0
  203. package/src/organisms/menu/menu.types.ts +17 -0
  204. package/src/organisms/sheet/sheet.types.ts +19 -0
  205. package/src/organisms/snackbar/snackbar.types.ts +8 -0
  206. package/src/organisms/swipe-actions/swipe-actions.android.tsx +129 -9
  207. package/src/organisms/swipe-actions/swipe-actions.ios.tsx +45 -11
  208. package/src/organisms/swipe-actions/swipe-actions.types.ts +37 -14
  209. package/src/templates/screen/screen.android.tsx +33 -12
  210. package/src/templates/screen/screen.types.ts +9 -0
  211. package/lib/module/atoms/flash-list/flash-list.js +0 -31
  212. package/lib/module/atoms/flash-list/flash-list.js.map +0 -1
  213. package/lib/module/atoms/flash-list/flash-list.types.js +0 -4
  214. package/lib/module/atoms/flash-list/flash-list.types.js.map +0 -1
  215. package/lib/module/atoms/flash-list/index.js +0 -5
  216. package/lib/module/atoms/flash-list/index.js.map +0 -1
  217. package/lib/module/atoms/segmented-control/segmented-control.js +0 -28
  218. package/lib/module/atoms/segmented-control/segmented-control.js.map +0 -1
  219. package/lib/typescript/atoms/flash-list/flash-list.d.ts +0 -8
  220. package/lib/typescript/atoms/flash-list/flash-list.types.d.ts +0 -18
  221. package/lib/typescript/atoms/flash-list/index.d.ts +0 -2
  222. package/src/atoms/flash-list/flash-list.tsx +0 -28
  223. package/src/atoms/flash-list/flash-list.types.ts +0 -20
  224. package/src/atoms/flash-list/index.ts +0 -2
  225. package/src/atoms/segmented-control/segmented-control.tsx +0 -34
@@ -3,16 +3,25 @@ import type {ReactNode} from 'react'
3
3
 
4
4
  import type {IconName} from '../../atoms/icon'
5
5
 
6
+ /**
7
+ * A row with an optional icon, a title and subtitle, and a chevron when it can be pressed. A native atom — it renders
8
+ * inside `<Screen>` or `<Island>`; `RnListItem` is the same row without a host.
9
+ */
6
10
  export type ListItemProps = {
11
+ /** One of the app's icons, before the title, in the secondary colour. */
7
12
  icon?: IconName
13
+ /** Makes the row pressable and adds a chevron on the trailing edge. */
8
14
  onPress?: () => void
9
15
  /**
10
16
  * Horizontal padding inside the row — inside its press area too, so Android's ripple reaches the card's edges. For
11
17
  * rows in a card of your own; a `List` insets its rows by itself.
12
18
  */
13
19
  px?: SpaceSize
20
+ /** A second line under the title, smaller and in the secondary colour. */
14
21
  subtitle?: string
22
+ /** Test identifier, forwarded to the row's view. */
15
23
  testID?: string
24
+ /** The row's main text. */
16
25
  title: string
17
26
  }
18
27
 
@@ -3,6 +3,7 @@ import type {ReactNode, Ref} from 'react'
3
3
  import type {IconName} from '../../atoms/icon'
4
4
 
5
5
  type NavigationBarItemBase = {
6
+ /** Called when the item is pressed. */
6
7
  onPress: () => void
7
8
  /**
8
9
  * iOS 26: neighbouring items on one side share a glass capsule; this gives the item its own. A close next to
@@ -29,10 +30,15 @@ export type NavigationBarSearchRef = {
29
30
 
30
31
  /** A search field in place of the title — iOS only for now; Android keeps the title. */
31
32
  export type NavigationBarSearch = {
33
+ /** Every edit of the field's text. */
32
34
  onChangeText: (text: string) => void
35
+ /** The field gained or lost focus. */
33
36
  onFocusChange?: (focused: boolean) => void
37
+ /** Shown while the field is empty. */
34
38
  placeholder: string
39
+ /** Imperative `clear`. */
35
40
  ref?: Ref<NavigationBarSearchRef>
41
+ /** Test identifier, forwarded to the native search field. */
36
42
  testID?: string
37
43
  }
38
44
 
@@ -52,7 +58,9 @@ export type NavigationBarProps = {
52
58
  leadingItems?: NavigationBarItem[]
53
59
  /** Replaces the title with a search field (iOS). */
54
60
  search?: NavigationBarSearch
61
+ /** Test identifier, forwarded to the native bar. iOS only — Android ignores it. */
55
62
  testID?: string
63
+ /** The bar's title. */
56
64
  title?: string
57
65
  /** `center` by default — iOS's own; `leading` is Material's top app bar. */
58
66
  titleAlignment?: NavigationBarTitleAlignment
@@ -11,6 +11,7 @@ import type {ReactNode} from 'react'
11
11
  * ceremony would buy nothing.
12
12
  */
13
13
  export type PagerProps = {
14
+ /** The pages, one per child, in order. */
14
15
  children?: ReactNode
15
16
  /**
16
17
  * Page dots. iOS draws SwiftUI's own, over the bottom edge; Compose has no built-in indicator, so
@@ -28,6 +29,7 @@ export type PagerProps = {
28
29
  * content down with the swipe, so iOS always stays swipeable.
29
30
  */
30
31
  scrollEnabled?: boolean
32
+ /** Test identifier, forwarded to the native view. */
31
33
  testID?: string
32
34
  }
33
35
 
@@ -1,13 +1,23 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
+ /** One choice of a `RadioGroup`. */
3
4
  export type RadioOption = {
5
+ /** The option's text. */
4
6
  label: string
7
+ /** Identifies the option to `value` and `onValueChange`, and keys its row — keep it unique. */
5
8
  value: string
6
9
  }
7
10
 
11
+ /**
12
+ * A single choice from a list: rows with a checkmark on the selected one on iOS, Material radio buttons on Android. A
13
+ * native atom — it renders inside `<Screen>` or `<Island>`.
14
+ */
8
15
  export type RadioGroupProps = {
16
+ /** The user pressed an option; it passes that option's `value`. */
9
17
  onValueChange?: (value: string) => void
18
+ /** The choices, top to bottom. */
10
19
  options: RadioOption[]
20
+ /** The selected option's `value`. Update it from `onValueChange`. */
11
21
  value: string
12
22
  }
13
23
 
@@ -4,7 +4,7 @@ import type {ListItemProps} from '../list-item'
4
4
 
5
5
  /**
6
6
  * `ListItem`'s row in the React Native tier — plain views, no host. For lists long enough that a native host per row
7
- * would cost too much to mount (a navigation list, a FlashList of rows), with the platform's own press feedback: a
7
+ * would cost too much to mount (a navigation list, an `RnFlatList` of rows), with the platform's own press feedback: a
8
8
  * ripple across the whole row on Android, a highlight on iOS. In a rounded card, give the card `overflow="hidden"`:
9
9
  * Android draws the ripple as the row's foreground, which only a parent's clip keeps inside rounded corners.
10
10
  */
@@ -2,8 +2,11 @@ import type {ReactElement, ReactNode} from 'react'
2
2
 
3
3
  export type RnTooltipActionVariant = 'link' | 'primary' | 'secondary'
4
4
 
5
+ /** A button in an `RnTooltip`. */
5
6
  export type RnTooltipAction = {
7
+ /** The button's text. */
6
8
  label: string
9
+ /** Called on press. The tooltip stays up — turn `visible` off here if the action should close it. */
7
10
  onPress: () => void
8
11
  /** `link` by default — an underlined text. `primary`/`secondary` are small filled/outlined buttons. */
9
12
  variant?: RnTooltipActionVariant
@@ -18,6 +21,7 @@ export type RnTooltipDismissReason = 'anchorHidden' | 'outside'
18
21
  * Native tier, unlike `Tooltip`, whose anchor is a native view inside a host.
19
22
  */
20
23
  export type RnTooltipProps = {
24
+ /** A row of buttons under the text, in order. */
21
25
  actions?: RnTooltipAction[]
22
26
  /** Which end of the bubble the actions sit at. `end` by default. */
23
27
  actionsAlignment?: 'end' | 'start'
@@ -28,11 +32,18 @@ export type RnTooltipProps = {
28
32
  boundaryInsets?: {bottom?: number; top?: number}
29
33
  /** The anchor — a single React Native view. */
30
34
  children: ReactElement
35
+ /** The body text, under the title. */
31
36
  message?: string
37
+ /**
38
+ * Asks for the tooltip to close, with the reason — turn `visible` off. On Android any touch outside asks, the start
39
+ * of a scroll included; iOS keeps the tooltip through a scroll.
40
+ */
32
41
  onDismiss: (reason: RnTooltipDismissReason) => void
33
42
  /** The appear animation finished. */
34
43
  onShown?: () => void
44
+ /** The heading line, above the message. */
35
45
  title?: string
46
+ /** Shows the tooltip while true. It never hides itself. */
36
47
  visible: boolean
37
48
  }
38
49
 
@@ -17,12 +17,15 @@ export type ScrollEdgeContainerProps = ResponsiveViewSx & {
17
17
  * content begins below the top bar and scrolls under it. On by default.
18
18
  */
19
19
  automaticInsets?: boolean
20
+ /** Laid over the scroll view's bottom edge on iOS; stacked below it on Android, where it wants a background. */
20
21
  bottomBar?: ReactNode
21
22
  /** Exactly one React Native scroll view — `RnScrollView`, `RnFlatList`, `RnSectionList`. */
22
23
  children: ReactElement<ScrollEdgeScrollableProps>
23
24
  /** iOS 26: how hard the edge cuts. `soft` by default. */
24
25
  effect?: ScrollEdgeEffect
26
+ /** Test identifier, forwarded to the container view. */
25
27
  testID?: string
28
+ /** Laid over the scroll view's top edge on iOS; stacked above it on Android, where it wants a background. */
26
29
  topBar?: ReactNode
27
30
  }
28
31
 
@@ -1,5 +1,5 @@
1
1
  import {useAppTheme} from '@hzblj/kara-theme'
2
- import {TextInput} from 'react-native'
2
+ import {Platform, TextInput} from 'react-native'
3
3
 
4
4
  import {Box} from '../../atoms/box'
5
5
  import {Pressable} from '../../atoms/pressable'
@@ -13,6 +13,13 @@ export const SearchBar: SearchBarComponent = ({
13
13
  value,
14
14
  }: SearchBarProps) => {
15
15
  const theme = useAppTheme()
16
+ const {lineHeight, ...face} = theme.typography.body
17
+ /**
18
+ * A single-line `UITextField` sets its text against the bottom of a line taller than the font, so the variant's
19
+ * `lineHeight` pushed the text below the magnifier beside it. iOS takes the face without it, in a field as tall as
20
+ * the line would have been; Android centres the line and keeps it.
21
+ */
22
+ const text = Platform.OS === 'ios' ? {...face, height: lineHeight} : theme.typography.body
16
23
 
17
24
  return (
18
25
  <Box
@@ -31,7 +38,7 @@ export const SearchBar: SearchBarComponent = ({
31
38
  onChangeText={onChangeText}
32
39
  placeholder={placeholder}
33
40
  placeholderTextColor={theme.color('content.quaternary')}
34
- style={[theme.typography.body, {color: theme.color('content.primary'), flex: 1, padding: 0}]}
41
+ style={[text, {color: theme.color('content.primary'), flex: 1, padding: 0}]}
35
42
  value={value}
36
43
  />
37
44
  {value.length > 0 ? (
@@ -1,9 +1,17 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
+ /**
4
+ * A search field drawn the same on both platforms — a rounded box with a magnifier, a `TextInput` and a clear button
5
+ * while there is text. The React Native tier: it needs no host.
6
+ */
3
7
  export type SearchBarProps = {
8
+ /** Every edit, and `''` when the clear button is pressed. */
4
9
  onChangeText?: (text: string) => void
10
+ /** Shown while the field is empty. `Search` by default. */
5
11
  placeholder?: string
12
+ /** Test identifier, put on the field's container view rather than on the `TextInput`. */
6
13
  testID?: string
14
+ /** The field's text. Controlled — pass back what `onChangeText` reports. */
7
15
  value: string
8
16
  }
9
17
 
@@ -1,11 +1,24 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
+ /**
4
+ * A number with minus and plus buttons beside a label: SwiftUI's `Stepper` on iOS; on Android the label, the value and
5
+ * two icon buttons in a row. A native atom — it renders inside `<Screen>` or `<Island>`.
6
+ */
3
7
  export type StepperProps = {
8
+ /**
9
+ * The leading text. iOS shows only the label, not the value — put the value in it if it should be seen. Android
10
+ * shows the value between its buttons.
11
+ */
4
12
  label: string
13
+ /** The highest value. When unset, Android has no upper limit and iOS stops at `100`. */
5
14
  max?: number
15
+ /** The lowest value. When unset, Android has no lower limit and iOS stops at `0`. */
6
16
  min?: number
17
+ /** A button was pressed; it passes the new value, kept within `min` and `max`. */
7
18
  onValueChange: (value: number) => void
19
+ /** How much one press adds or takes away. `1` by default. */
8
20
  step?: number
21
+ /** The current value. Update it from `onValueChange`. iOS's native stepper counts in whole numbers. */
9
22
  value: number
10
23
  }
11
24
 
@@ -1,24 +1,32 @@
1
- import {Text as ComposeText, TooltipBox} from '@expo/ui/jetpack-compose'
1
+ import {Text as ComposeText} from '@expo/ui/jetpack-compose'
2
+ import {TooltipBox} from '@hzblj/kara-native/jetpack-compose'
2
3
  import {useAppTheme} from '@hzblj/kara-theme'
4
+ import {useCallback} from 'react'
3
5
 
4
6
  import {useHostGuard} from '../../host-context'
5
7
  import type {TooltipComponent, TooltipProps} from './tooltip.types'
6
8
 
7
- export const Tooltip: TooltipComponent = ({children, text}: TooltipProps) => {
9
+ /**
10
+ * Material's plain tooltip in kara's controlled `TooltipBox`: shown while `visible`, centred above the anchor and kept
11
+ * inside the window. Material's own would open on a long press and hide itself without a word to JS.
12
+ */
13
+ export const Tooltip: TooltipComponent = ({children, onDismiss, text, visible}: TooltipProps) => {
8
14
  useHostGuard('Tooltip')
9
-
10
15
  const theme = useAppTheme()
16
+ const onDismissRequest = useCallback(() => onDismiss(), [onDismiss])
11
17
 
12
18
  return (
13
- <TooltipBox>
14
- <TooltipBox.PlainTooltip
15
- containerColor={theme.colorString('surface.high.contrast')}
16
- contentColor={theme.colorString('content.on.high.contrast')}
17
- >
19
+ <TooltipBox
20
+ containerColor={theme.color('surface.high.contrast')}
21
+ isPresented={visible}
22
+ onDismissRequest={onDismissRequest}
23
+ plain
24
+ >
25
+ <TooltipBox.Text>
18
26
  <ComposeText color={theme.colorString('content.on.high.contrast')} style={theme.typography.footnote}>
19
27
  {text}
20
28
  </ComposeText>
21
- </TooltipBox.PlainTooltip>
29
+ </TooltipBox.Text>
22
30
  {children}
23
31
  </TooltipBox>
24
32
  )
@@ -1,9 +1,21 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
+ /**
4
+ * A short text tip on a native view, shown while `visible` — a SwiftUI popover from `children` on iOS, Material's plain
5
+ * tooltip centred above them on Android. A native atom — it renders inside `<Screen>` or `<Island>`; `RnTooltip`
6
+ * anchors to a React Native view.
7
+ */
3
8
  export type TooltipProps = {
9
+ /** The anchor — a native view. */
4
10
  children: ReactNode
11
+ /** The tip. */
5
12
  text: string
13
+ /** Shows the tip while true. Controlled: nothing opens it but this — wire a long press to it if you want one. */
6
14
  visible: boolean
15
+ /**
16
+ * The user dismissed the tip — turn `visible` off. iOS: a tap outside the popover. Android: any touch outside it, the
17
+ * start of a scroll included, or the anchor scrolling out of the window.
18
+ */
7
19
  onDismiss: () => void
8
20
  }
9
21
 
@@ -1,35 +1,53 @@
1
- import {Divider} from '../../atoms/divider'
2
1
  import {HStack} from '../../atoms/h-stack'
2
+ import {Icon} from '../../atoms/icon'
3
+ import {Island} from '../../atoms/island'
3
4
  import {Text} from '../../atoms/text'
4
5
  import {VStack} from '../../atoms/v-stack'
5
6
  import {useHostGuard} from '../../host-context'
6
7
  import {Sheet} from '../sheet'
7
8
  import type {ActionSheetComponent, ActionSheetProps} from './action-sheet.types'
8
9
 
10
+ /**
11
+ * Material's bottom-sheet menu: the title, then a full-width row per action, its icon leading. The sheet is the React
12
+ * Native tier, so the rows — Compose views — sit in an `Island` of their own; rendered straight into the sheet they had
13
+ * no host and threw.
14
+ */
9
15
  export const ActionSheet: ActionSheetComponent = ({actions, onDismiss, title, visible}: ActionSheetProps) => {
10
16
  useHostGuard('ActionSheet')
11
17
 
12
18
  return (
13
19
  <Sheet onDismiss={onDismiss} visible={visible}>
14
- <VStack p="s16" spacing="s4" wide>
15
- <Text color="content.secondary" variant="labelMedium">
16
- {title}
17
- </Text>
18
- <Divider />
19
- {actions.map(action => (
20
- <HStack
21
- key={action.label}
22
- onPress={() => {
23
- onDismiss()
24
- action.onPress()
25
- }}
26
- py="s12"
27
- wide
28
- >
29
- <Text color={action.destructive ? 'content.destructive' : 'content.primary'}>{action.label}</Text>
20
+ <Island>
21
+ <VStack pb="s16" wide>
22
+ <HStack px="s24" py="s12" wide>
23
+ <Text color="content.secondary" variant="labelMedium">
24
+ {title}
25
+ </Text>
30
26
  </HStack>
31
- ))}
32
- </VStack>
27
+ {actions.map(action => {
28
+ const color = action.destructive ? 'content.destructive' : 'content.primary'
29
+
30
+ return (
31
+ <HStack
32
+ key={action.label}
33
+ onPress={() => {
34
+ onDismiss()
35
+ action.onPress()
36
+ }}
37
+ px="s24"
38
+ py="s16"
39
+ spacing="s16"
40
+ wide
41
+ >
42
+ {action.icon ? <Icon color={color} name={action.icon} /> : null}
43
+ <Text color={color} variant="body">
44
+ {action.label}
45
+ </Text>
46
+ </HStack>
47
+ )
48
+ })}
49
+ </VStack>
50
+ </Island>
33
51
  </Sheet>
34
52
  )
35
53
  }
@@ -1,8 +1,16 @@
1
- import {Button, ConfirmationDialog} from '@expo/ui/swift-ui'
1
+ import {Button, ConfirmationDialog, Spacer} from '@expo/ui/swift-ui'
2
+ import {frame} from '@expo/ui/swift-ui/modifiers'
2
3
 
3
4
  import {useHostGuard} from '../../host-context'
4
5
  import type {ActionSheetComponent, ActionSheetProps} from './action-sheet.types'
5
6
 
7
+ /**
8
+ * `@expo/ui` hangs the dialog on its `Trigger` and reads the buttons from its `Actions` slot. Without a trigger it
9
+ * attaches to an `EmptyView`, which SwiftUI never presents from — a spacer framed to nothing is a real view that takes
10
+ * no room.
11
+ */
12
+ const ANCHOR = [frame({height: 0, width: 0})]
13
+
6
14
  export const ActionSheet: ActionSheetComponent = ({actions, onDismiss, title, visible}: ActionSheetProps) => {
7
15
  useHostGuard('ActionSheet')
8
16
 
@@ -16,14 +24,19 @@ export const ActionSheet: ActionSheetComponent = ({actions, onDismiss, title, vi
16
24
  }}
17
25
  title={title}
18
26
  >
19
- {actions.map(action => (
20
- <Button
21
- key={action.label}
22
- label={action.label}
23
- onPress={action.onPress}
24
- role={action.destructive ? 'destructive' : 'default'}
25
- />
26
- ))}
27
+ <ConfirmationDialog.Trigger>
28
+ <Spacer modifiers={ANCHOR} />
29
+ </ConfirmationDialog.Trigger>
30
+ <ConfirmationDialog.Actions>
31
+ {actions.map(action => (
32
+ <Button
33
+ key={action.label}
34
+ label={action.label}
35
+ onPress={action.onPress}
36
+ role={action.destructive ? 'destructive' : 'default'}
37
+ />
38
+ ))}
39
+ </ConfirmationDialog.Actions>
27
40
  </ConfirmationDialog>
28
41
  )
29
42
  }
@@ -2,10 +2,18 @@ import type {ReactNode} from 'react'
2
2
 
3
3
  import type {MenuAction} from '../menu'
4
4
 
5
+ /**
6
+ * A list of actions to pick one from: SwiftUI's confirmation dialog on iOS, a `Sheet` of Material rows — icon and label
7
+ * — under the title on Android. A native atom — it renders inside `<Screen>` or `<Island>`.
8
+ */
5
9
  export type ActionSheetProps = {
10
+ /** The choices, top to bottom. Picking one closes the sheet. */
6
11
  actions: MenuAction[]
12
+ /** The sheet closed — after a pick too — so turn `visible` off. */
7
13
  onDismiss: () => void
14
+ /** The heading above the actions. Android always shows it; on iOS the system decides, and may hide it. */
8
15
  title: string
16
+ /** Presents the sheet while true. */
9
17
  visible: boolean
10
18
  }
11
19
 
@@ -1,8 +1,15 @@
1
- import {Button, Alert as SwiftUIAlert, Text as SwiftUIText} from '@expo/ui/swift-ui'
1
+ import {Button, Spacer, Alert as SwiftUIAlert, Text as SwiftUIText} from '@expo/ui/swift-ui'
2
+ import {frame} from '@expo/ui/swift-ui/modifiers'
2
3
 
3
4
  import {useHostGuard} from '../../host-context'
4
5
  import type {AlertComponent, AlertProps} from './alert.types'
5
6
 
7
+ /**
8
+ * `@expo/ui` hangs the alert on its `Alert.Trigger`, and on an `EmptyView` when there is none — which SwiftUI never
9
+ * presents from, so the alert would not appear at all. A spacer framed to nothing is a real view that takes no room.
10
+ */
11
+ const ANCHOR = [frame({height: 0, width: 0})]
12
+
6
13
  export const Alert: AlertComponent = ({
7
14
  confirmLabel = 'OK',
8
15
  destructive = false,
@@ -24,6 +31,9 @@ export const Alert: AlertComponent = ({
24
31
  }}
25
32
  title={title}
26
33
  >
34
+ <SwiftUIAlert.Trigger>
35
+ <Spacer modifiers={ANCHOR} />
36
+ </SwiftUIAlert.Trigger>
27
37
  <SwiftUIAlert.Actions>
28
38
  <Button label={confirmLabel} onPress={onConfirm ?? onDismiss} role={destructive ? 'destructive' : 'default'} />
29
39
  <Button label="Cancel" onPress={onDismiss} role="cancel" />
@@ -1,12 +1,23 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
+ /**
4
+ * A modal alert with a confirm button and Cancel: SwiftUI's alert on iOS, Material's `AlertDialog` on Android. A native
5
+ * atom — it renders inside `<Screen>` or `<Island>`.
6
+ */
3
7
  export type AlertProps = {
8
+ /** The confirm button's label. `OK` by default. */
4
9
  confirmLabel?: string
10
+ /** Marks the confirm button destructive — the system's destructive role on iOS, the destructive colour on Android. */
5
11
  destructive?: boolean
12
+ /** The body text, under the title. */
6
13
  message?: string
14
+ /** The confirm button was pressed. `onDismiss` follows, as it does for every way the alert closes. */
7
15
  onConfirm?: () => void
16
+ /** The alert closed — either button, or on Android a tap outside or back — so turn `visible` off. */
8
17
  onDismiss: () => void
18
+ /** The alert's heading. */
9
19
  title: string
20
+ /** Shows the alert while true. */
10
21
  visible: boolean
11
22
  }
12
23
 
@@ -1,10 +1,10 @@
1
1
  import {Text as ComposeText, DropdownMenu, DropdownMenuItem, Host} from '@expo/ui/jetpack-compose'
2
2
  import {useSx} from '@hzblj/kara-sx'
3
3
  import {useAppTheme} from '@hzblj/kara-theme'
4
- import * as Haptics from 'expo-haptics'
5
4
  import {useCallback, useMemo, useRef, useState} from 'react'
6
5
  import {type GestureResponderEvent, Pressable, StyleSheet, View} from 'react-native'
7
6
  import {Icon} from '../../atoms/icon'
7
+ import {haptics} from '../../haptics'
8
8
  import {HostBoundary} from '../../host-context'
9
9
  import {splitForRipple} from '../../ripple-clip'
10
10
  import type {MenuAction} from '../menu'
@@ -70,7 +70,7 @@ const ContextMenuRoot = ({actions, children, testID, ...props}: ContextMenuProps
70
70
  const open = useCallback((event: GestureResponderEvent) => {
71
71
  const {pageX, pageY} = event.nativeEvent
72
72
 
73
- Haptics.performAndroidHapticsAsync(Haptics.AndroidHaptics.Long_Press).catch(() => undefined)
73
+ haptics.longPress()
74
74
  // The touch in the trigger's own coordinates: `locationX/Y` are relative to whichever child was touched.
75
75
  trigger.current?.measure((_x, _y, _width, _height, left, top) => {
76
76
  setAnchor({x: pageX - left, y: pageY - top})
@@ -10,6 +10,7 @@ import type {MenuAction} from '../menu'
10
10
  * needs no `Screen` or `Island`.
11
11
  */
12
12
  export type ContextMenuProps = ResponsiveViewSx & {
13
+ /** The menu's actions, top to bottom. */
13
14
  actions: MenuAction[]
14
15
  /** The trigger, laid out as usual. */
15
16
  children: ReactNode
@@ -29,10 +30,13 @@ export type ContextMenuProps = ResponsiveViewSx & {
29
30
  * `ZoomTransition.Source`, `pop` otherwise.
30
31
  */
31
32
  previewTransition?: 'dismiss' | 'pop' | 'zoom'
33
+ /** Test identifier — on the native view around the trigger on iOS, on the trigger's `Pressable` on Android. */
32
34
  testID?: string
33
35
  }
34
36
 
37
+ /** A region of a `ContextMenu`'s trigger that keeps its own long press — see `ContextMenu.Exclusion`. */
35
38
  export type ContextMenuExclusionProps = {
39
+ /** The region — a button inside the card, say. */
36
40
  children: ReactNode
37
41
  }
38
42
 
@@ -9,9 +9,11 @@ import type {MenuAction} from '../menu'
9
9
  * of a SwiftUI `Menu`. A native atom — it renders inside `<Screen>` or `<Island>`.
10
10
  */
11
11
  export type FabMenuProps = {
12
+ /** The actions the button unfolds into. */
12
13
  actions: MenuAction[]
13
14
  /** The closed button's icon — one of the app's. A plus by default. */
14
15
  icon?: IconName
16
+ /** Test identifier, forwarded to the SwiftUI `Menu`. iOS only — Android ignores it. */
15
17
  testID?: string
16
18
  }
17
19
 
@@ -4,11 +4,15 @@ import {VStack} from '../../atoms/v-stack'
4
4
  import {useHostGuard} from '../../host-context'
5
5
  import type {FormComponent, FormProps, FormSectionProps} from './form.types'
6
6
 
7
+ /**
8
+ * Inset from its container on every side, as SwiftUI's grouped form is — in a `Screen` the cards would otherwise run
9
+ * into the screen's edges — and the section titles and footers indented to the cards' content.
10
+ */
7
11
  const FormRoot = ({children, testID}: FormProps) => {
8
12
  useHostGuard('Form')
9
13
 
10
14
  return (
11
- <VStack spacing="s16" testID={testID} wide>
15
+ <VStack p="s16" spacing="s16" testID={testID} wide>
12
16
  {children}
13
17
  </VStack>
14
18
  )
@@ -17,15 +21,19 @@ const FormRoot = ({children, testID}: FormProps) => {
17
21
  const FormSection = ({children, footer, title}: FormSectionProps) => (
18
22
  <VStack spacing="s4" wide>
19
23
  {title ? (
20
- <Text color="content.secondary" transform="uppercase" variant="labelMedium">
21
- {title}
22
- </Text>
24
+ <VStack px="s16" wide>
25
+ <Text color="content.secondary" transform="uppercase" variant="labelMedium">
26
+ {title}
27
+ </Text>
28
+ </VStack>
23
29
  ) : null}
24
30
  <Card wide>{children}</Card>
25
31
  {footer ? (
26
- <Text color="content.tertiary" variant="footnote">
27
- {footer}
28
- </Text>
32
+ <VStack px="s16" wide>
33
+ <Text color="content.tertiary" variant="footnote">
34
+ {footer}
35
+ </Text>
36
+ </VStack>
29
37
  ) : null}
30
38
  </VStack>
31
39
  )
@@ -6,13 +6,19 @@ import type {ReactNode} from 'react'
6
6
  * an island that sizes itself to its content it has no height and shows nothing.
7
7
  */
8
8
  export type FormProps = {
9
+ /** The form's `Form.Section`s. */
9
10
  children: ReactNode
11
+ /** Test identifier, forwarded to the native view. */
10
12
  testID?: string
11
13
  }
12
14
 
15
+ /** A group of rows in a `Form`: a SwiftUI `Section` on iOS, a `Card` under a heading on Android. */
13
16
  export type FormSectionProps = {
17
+ /** The section's rows. */
14
18
  children: ReactNode
19
+ /** Small print under the section. */
15
20
  footer?: string
21
+ /** The section's heading. */
16
22
  title?: string
17
23
  }
18
24
 
@@ -2,15 +2,32 @@ import type {ReactNode} from 'react'
2
2
 
3
3
  import type {IconName} from '../../atoms/icon'
4
4
 
5
+ /** One action of a `Menu`, an `ActionSheet`, a `ContextMenu` or a `FabMenu`. */
5
6
  export type MenuAction = {
7
+ /**
8
+ * Draws the action in the destructive colour — the system's destructive role on iOS. `FabMenu` ignores it on
9
+ * Android.
10
+ */
6
11
  destructive?: boolean
12
+ /**
13
+ * One of the app's icons, before the label. `ContextMenu` and `FabMenu` show it on both platforms, `Menu` and
14
+ * `ActionSheet` on Android only.
15
+ */
7
16
  icon?: IconName
17
+ /** The action's text. Rows are keyed by it, so keep labels unique within one list. */
8
18
  label: string
19
+ /** Called when the action is chosen; the menu closes by itself. */
9
20
  onPress: () => void
10
21
  }
11
22
 
23
+ /**
24
+ * A text button that opens a list of actions: a SwiftUI `Menu` on iOS, a Material dropdown anchored to it on Android.
25
+ * A native atom — it renders inside `<Screen>` or `<Island>`.
26
+ */
12
27
  export type MenuProps = {
28
+ /** The menu's actions, top to bottom. */
13
29
  actions: MenuAction[]
30
+ /** The trigger's text. Android draws it in the accent colour, with a chevron. */
14
31
  label: string
15
32
  }
16
33