@hzblj/kara-components 0.9.0 → 0.11.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 (281) hide show
  1. package/CHANGELOG.md +72 -28
  2. package/README.md +8 -8
  3. package/lib/module/atoms/activity-indicator/activity-indicator.android.js +6 -1
  4. package/lib/module/atoms/activity-indicator/activity-indicator.android.js.map +1 -1
  5. package/lib/module/atoms/activity-indicator/activity-indicator.ios.js +2 -0
  6. package/lib/module/atoms/activity-indicator/activity-indicator.ios.js.map +1 -1
  7. package/lib/module/atoms/avatar/avatar.js +5 -0
  8. package/lib/module/atoms/avatar/avatar.js.map +1 -1
  9. package/lib/module/atoms/button/button-tokens.js +2 -2
  10. package/lib/module/atoms/button/button-tokens.js.map +1 -1
  11. package/lib/module/atoms/button/button.android.js +6 -2
  12. package/lib/module/atoms/button/button.android.js.map +1 -1
  13. package/lib/module/atoms/button/button.ios.js +7 -2
  14. package/lib/module/atoms/button/button.ios.js.map +1 -1
  15. package/lib/module/atoms/card/card.android.js +5 -3
  16. package/lib/module/atoms/card/card.android.js.map +1 -1
  17. package/lib/module/atoms/card/card.ios.js +7 -3
  18. package/lib/module/atoms/card/card.ios.js.map +1 -1
  19. package/lib/module/atoms/checkbox/checkbox.android.js +1 -1
  20. package/lib/module/atoms/checkbox/checkbox.android.js.map +1 -1
  21. package/lib/module/atoms/color-picker/color-picker.android.js +32 -5
  22. package/lib/module/atoms/color-picker/color-picker.android.js.map +1 -1
  23. package/lib/module/atoms/fab/fab.android.js +9 -1
  24. package/lib/module/atoms/fab/fab.android.js.map +1 -1
  25. package/lib/module/atoms/fab/fab.ios.js +9 -3
  26. package/lib/module/atoms/fab/fab.ios.js.map +1 -1
  27. package/lib/module/atoms/glass-view/glass-view.android.js +1 -1
  28. package/lib/module/atoms/glass-view/glass-view.android.js.map +1 -1
  29. package/lib/module/atoms/h-stack/h-stack.ios.js +5 -3
  30. package/lib/module/atoms/h-stack/h-stack.ios.js.map +1 -1
  31. package/lib/module/atoms/icon-button/icon-button.android.js +9 -0
  32. package/lib/module/atoms/icon-button/icon-button.android.js.map +1 -1
  33. package/lib/module/atoms/icon-button/icon-button.ios.js +26 -6
  34. package/lib/module/atoms/icon-button/icon-button.ios.js.map +1 -1
  35. package/lib/module/atoms/image/image.js +4 -4
  36. package/lib/module/atoms/image/image.js.map +1 -1
  37. package/lib/module/atoms/pressable/pressable.js +52 -28
  38. package/lib/module/atoms/pressable/pressable.js.map +1 -1
  39. package/lib/module/atoms/progress/progress.android.js +7 -0
  40. package/lib/module/atoms/progress/progress.android.js.map +1 -1
  41. package/lib/module/atoms/progress/progress.ios.js +6 -1
  42. package/lib/module/atoms/progress/progress.ios.js.map +1 -1
  43. package/lib/module/atoms/segmented-control/segmented-control.android.js +1 -1
  44. package/lib/module/atoms/segmented-control/segmented-control.android.js.map +1 -1
  45. package/lib/module/atoms/segmented-control/segmented-control.ios.js +1 -1
  46. package/lib/module/atoms/segmented-control/segmented-control.ios.js.map +1 -1
  47. package/lib/module/atoms/skeleton/skeleton.js +20 -6
  48. package/lib/module/atoms/skeleton/skeleton.js.map +1 -1
  49. package/lib/module/atoms/surface/surface.android.js +5 -3
  50. package/lib/module/atoms/surface/surface.android.js.map +1 -1
  51. package/lib/module/atoms/surface/surface.ios.js +7 -3
  52. package/lib/module/atoms/surface/surface.ios.js.map +1 -1
  53. package/lib/module/atoms/switch/switch.android.js +1 -1
  54. package/lib/module/atoms/switch/switch.android.js.map +1 -1
  55. package/lib/module/atoms/text/text.android.js +5 -3
  56. package/lib/module/atoms/text/text.android.js.map +1 -1
  57. package/lib/module/atoms/text/text.ios.js +6 -4
  58. package/lib/module/atoms/text/text.ios.js.map +1 -1
  59. package/lib/module/atoms/v-stack/v-stack.ios.js +5 -3
  60. package/lib/module/atoms/v-stack/v-stack.ios.js.map +1 -1
  61. package/lib/module/atoms/z-stack/z-stack.ios.js +5 -3
  62. package/lib/module/atoms/z-stack/z-stack.ios.js.map +1 -1
  63. package/lib/module/glyph/glyph.android.js +2 -0
  64. package/lib/module/glyph/glyph.android.js.map +1 -1
  65. package/lib/module/glyph/glyph.ios.js +5 -2
  66. package/lib/module/glyph/glyph.ios.js.map +1 -1
  67. package/lib/module/molecules/collapsible/collapsible.android.js +18 -8
  68. package/lib/module/molecules/collapsible/collapsible.android.js.map +1 -1
  69. package/lib/module/molecules/labeled-content/labeled-content.js +4 -1
  70. package/lib/module/molecules/labeled-content/labeled-content.js.map +1 -1
  71. package/lib/module/molecules/link/link.js +4 -1
  72. package/lib/module/molecules/link/link.js.map +1 -1
  73. package/lib/module/molecules/list-item/list-item.js +4 -1
  74. package/lib/module/molecules/list-item/list-item.js.map +1 -1
  75. package/lib/module/molecules/navigation-bar/navigation-bar.android.js +1 -1
  76. package/lib/module/molecules/navigation-bar/navigation-bar.android.js.map +1 -1
  77. package/lib/module/molecules/navigation-bar/navigation-bar.ios.js +1 -1
  78. package/lib/module/molecules/navigation-bar/navigation-bar.ios.js.map +1 -1
  79. package/lib/module/molecules/rn-list-item/rn-list-item.js +10 -2
  80. package/lib/module/molecules/rn-list-item/rn-list-item.js.map +1 -1
  81. package/lib/module/molecules/scroll-edge-container/scroll-edge-container.android.js +3 -4
  82. package/lib/module/molecules/scroll-edge-container/scroll-edge-container.android.js.map +1 -1
  83. package/lib/module/molecules/stepper/stepper.ios.js +6 -2
  84. package/lib/module/molecules/stepper/stepper.ios.js.map +1 -1
  85. package/lib/module/motion.js +19 -0
  86. package/lib/module/motion.js.map +1 -0
  87. package/lib/module/organisms/fab-menu/fab-menu.android.js +8 -5
  88. package/lib/module/organisms/fab-menu/fab-menu.android.js.map +1 -1
  89. package/lib/module/organisms/fab-menu/fab-menu.ios.js +5 -2
  90. package/lib/module/organisms/fab-menu/fab-menu.ios.js.map +1 -1
  91. package/lib/module/organisms/snackbar/snackbar.js +142 -17
  92. package/lib/module/organisms/snackbar/snackbar.js.map +1 -1
  93. package/lib/module/organisms/swipe-actions/swipe-actions.android.js +1 -1
  94. package/lib/module/organisms/swipe-actions/swipe-actions.android.js.map +1 -1
  95. package/lib/module/use-stack-modifiers.js +10 -8
  96. package/lib/module/use-stack-modifiers.js.map +1 -1
  97. package/lib/typescript/atoms/activity-indicator/activity-indicator.types.d.ts +3 -1
  98. package/lib/typescript/atoms/avatar/avatar.types.d.ts +7 -2
  99. package/lib/typescript/atoms/badge/badge.types.d.ts +1 -1
  100. package/lib/typescript/atoms/blur-view/blur-view.types.d.ts +6 -6
  101. package/lib/typescript/atoms/button/button-tokens.d.ts +1 -1
  102. package/lib/typescript/atoms/button/button.types.d.ts +9 -7
  103. package/lib/typescript/atoms/card/card.types.d.ts +3 -3
  104. package/lib/typescript/atoms/checkbox/checkbox.types.d.ts +2 -2
  105. package/lib/typescript/atoms/chip/chip.types.d.ts +1 -1
  106. package/lib/typescript/atoms/color-picker/color-picker.types.d.ts +4 -3
  107. package/lib/typescript/atoms/date-picker/date-picker.types.d.ts +4 -4
  108. package/lib/typescript/atoms/divider/divider.types.d.ts +2 -2
  109. package/lib/typescript/atoms/fab/fab.types.d.ts +10 -3
  110. package/lib/typescript/atoms/glass-view/glass-view.types.d.ts +5 -5
  111. package/lib/typescript/atoms/h-stack/h-stack.types.d.ts +8 -5
  112. package/lib/typescript/atoms/icon/icon.types.d.ts +4 -4
  113. package/lib/typescript/atoms/icon-button/icon-button.types.d.ts +14 -3
  114. package/lib/typescript/atoms/image/image.d.ts +2 -2
  115. package/lib/typescript/atoms/island/island.types.d.ts +4 -4
  116. package/lib/typescript/atoms/list/list.types.d.ts +5 -5
  117. package/lib/typescript/atoms/picker/picker.types.d.ts +1 -1
  118. package/lib/typescript/atoms/pressable/pressable.types.d.ts +23 -7
  119. package/lib/typescript/atoms/progress/progress.types.d.ts +3 -1
  120. package/lib/typescript/atoms/rn-flat-list/rn-flat-list.types.d.ts +2 -2
  121. package/lib/typescript/atoms/rn-icon/rn-icon.types.d.ts +1 -1
  122. package/lib/typescript/atoms/rn-scroll-view/rn-scroll-view.types.d.ts +2 -2
  123. package/lib/typescript/atoms/rn-section-list/rn-section-list.types.d.ts +2 -2
  124. package/lib/typescript/atoms/rn-text/rn-text.types.d.ts +2 -2
  125. package/lib/typescript/atoms/rn-view/rn-view.types.d.ts +3 -3
  126. package/lib/typescript/atoms/scroll-view/scroll-view.types.d.ts +3 -3
  127. package/lib/typescript/atoms/segmented-control/segmented-control.types.d.ts +3 -3
  128. package/lib/typescript/atoms/skeleton/skeleton.types.d.ts +2 -1
  129. package/lib/typescript/atoms/spacer/spacer.types.d.ts +1 -1
  130. package/lib/typescript/atoms/surface/surface.types.d.ts +3 -3
  131. package/lib/typescript/atoms/switch/switch.types.d.ts +1 -1
  132. package/lib/typescript/atoms/text/text.types.d.ts +3 -3
  133. package/lib/typescript/atoms/text-field/text-field.types.d.ts +4 -4
  134. package/lib/typescript/atoms/v-stack/v-stack.types.d.ts +4 -4
  135. package/lib/typescript/atoms/z-stack/z-stack.types.d.ts +4 -4
  136. package/lib/typescript/glyph/glyph-button.types.d.ts +1 -1
  137. package/lib/typescript/glyph/glyph.types.d.ts +3 -1
  138. package/lib/typescript/molecules/collapsible/collapsible.types.d.ts +1 -1
  139. package/lib/typescript/molecules/empty-state/empty-state.types.d.ts +1 -1
  140. package/lib/typescript/molecules/label/label.types.d.ts +1 -1
  141. package/lib/typescript/molecules/labeled-content/labeled-content.types.d.ts +6 -4
  142. package/lib/typescript/molecules/link/link.types.d.ts +2 -1
  143. package/lib/typescript/molecules/list-item/list-item.types.d.ts +6 -4
  144. package/lib/typescript/molecules/navigation-bar/navigation-bar.types.d.ts +8 -8
  145. package/lib/typescript/molecules/pager/pager.types.d.ts +2 -2
  146. package/lib/typescript/molecules/radio-group/radio-group.types.d.ts +2 -2
  147. package/lib/typescript/molecules/rn-list-item/rn-list-item.types.d.ts +1 -1
  148. package/lib/typescript/molecules/rn-tooltip/rn-tooltip.types.d.ts +7 -7
  149. package/lib/typescript/molecules/scroll-edge-container/scroll-edge-container.types.d.ts +3 -3
  150. package/lib/typescript/molecules/search-bar/search-bar.types.d.ts +2 -2
  151. package/lib/typescript/molecules/stepper/stepper.ios.d.ts +1 -1
  152. package/lib/typescript/molecules/stepper/stepper.types.d.ts +2 -2
  153. package/lib/typescript/molecules/tooltip/tooltip.types.d.ts +6 -6
  154. package/lib/typescript/motion.d.ts +8 -0
  155. package/lib/typescript/organisms/action-sheet/action-sheet.types.d.ts +3 -3
  156. package/lib/typescript/organisms/alert/alert.types.d.ts +3 -3
  157. package/lib/typescript/organisms/context-menu/context-menu.types.d.ts +9 -9
  158. package/lib/typescript/organisms/fab-menu/fab-menu.types.d.ts +10 -5
  159. package/lib/typescript/organisms/form/form.types.d.ts +1 -1
  160. package/lib/typescript/organisms/menu/menu.types.d.ts +2 -2
  161. package/lib/typescript/organisms/sheet/sheet.types.d.ts +12 -12
  162. package/lib/typescript/organisms/snackbar/snackbar.types.d.ts +39 -6
  163. package/lib/typescript/organisms/swipe-actions/swipe-actions.types.d.ts +5 -5
  164. package/lib/typescript/templates/screen/screen.types.d.ts +2 -2
  165. package/lib/typescript/use-stack-modifiers.d.ts +6 -5
  166. package/package.json +4 -4
  167. package/src/atoms/activity-indicator/activity-indicator.android.tsx +7 -1
  168. package/src/atoms/activity-indicator/activity-indicator.ios.tsx +9 -1
  169. package/src/atoms/activity-indicator/activity-indicator.types.ts +3 -1
  170. package/src/atoms/avatar/avatar.tsx +5 -1
  171. package/src/atoms/avatar/avatar.types.ts +7 -2
  172. package/src/atoms/badge/badge.types.ts +1 -1
  173. package/src/atoms/blur-view/blur-view.types.ts +6 -6
  174. package/src/atoms/button/button-tokens.ts +2 -2
  175. package/src/atoms/button/button.android.tsx +5 -3
  176. package/src/atoms/button/button.ios.tsx +10 -2
  177. package/src/atoms/button/button.types.ts +9 -7
  178. package/src/atoms/card/card.android.tsx +6 -4
  179. package/src/atoms/card/card.ios.tsx +7 -3
  180. package/src/atoms/card/card.types.ts +9 -8
  181. package/src/atoms/checkbox/checkbox.android.tsx +1 -1
  182. package/src/atoms/checkbox/checkbox.types.ts +2 -2
  183. package/src/atoms/chip/chip.types.ts +1 -1
  184. package/src/atoms/color-picker/color-picker.android.tsx +26 -6
  185. package/src/atoms/color-picker/color-picker.types.ts +4 -3
  186. package/src/atoms/date-picker/date-picker.types.ts +4 -4
  187. package/src/atoms/divider/divider.types.ts +2 -2
  188. package/src/atoms/fab/fab.android.tsx +5 -3
  189. package/src/atoms/fab/fab.ios.tsx +8 -4
  190. package/src/atoms/fab/fab.types.ts +10 -3
  191. package/src/atoms/glass-view/glass-view.android.tsx +1 -1
  192. package/src/atoms/glass-view/glass-view.types.ts +5 -5
  193. package/src/atoms/h-stack/h-stack.ios.tsx +6 -4
  194. package/src/atoms/h-stack/h-stack.types.ts +18 -14
  195. package/src/atoms/icon/icon.types.ts +4 -4
  196. package/src/atoms/icon-button/icon-button.android.tsx +16 -3
  197. package/src/atoms/icon-button/icon-button.ios.tsx +24 -5
  198. package/src/atoms/icon-button/icon-button.types.ts +14 -3
  199. package/src/atoms/image/image.tsx +3 -3
  200. package/src/atoms/island/island.types.ts +4 -4
  201. package/src/atoms/list/list.types.ts +5 -5
  202. package/src/atoms/picker/picker.types.ts +1 -1
  203. package/src/atoms/pressable/pressable.tsx +50 -21
  204. package/src/atoms/pressable/pressable.types.ts +35 -17
  205. package/src/atoms/progress/progress.android.tsx +5 -3
  206. package/src/atoms/progress/progress.ios.tsx +5 -2
  207. package/src/atoms/progress/progress.types.ts +3 -1
  208. package/src/atoms/rn-flat-list/rn-flat-list.types.ts +2 -2
  209. package/src/atoms/rn-icon/rn-icon.types.ts +1 -1
  210. package/src/atoms/rn-scroll-view/rn-scroll-view.types.ts +2 -2
  211. package/src/atoms/rn-section-list/rn-section-list.types.ts +2 -2
  212. package/src/atoms/rn-text/rn-text.types.ts +2 -2
  213. package/src/atoms/rn-view/rn-view.types.ts +3 -3
  214. package/src/atoms/scroll-view/scroll-view.types.ts +3 -3
  215. package/src/atoms/segmented-control/segmented-control.android.tsx +1 -1
  216. package/src/atoms/segmented-control/segmented-control.ios.tsx +1 -1
  217. package/src/atoms/segmented-control/segmented-control.types.ts +3 -3
  218. package/src/atoms/skeleton/skeleton.tsx +18 -4
  219. package/src/atoms/skeleton/skeleton.types.ts +2 -1
  220. package/src/atoms/spacer/spacer.types.ts +1 -1
  221. package/src/atoms/surface/surface.android.tsx +6 -4
  222. package/src/atoms/surface/surface.ios.tsx +7 -3
  223. package/src/atoms/surface/surface.types.ts +11 -10
  224. package/src/atoms/switch/switch.android.tsx +1 -1
  225. package/src/atoms/switch/switch.types.ts +1 -1
  226. package/src/atoms/text/text.android.tsx +6 -4
  227. package/src/atoms/text/text.ios.tsx +6 -4
  228. package/src/atoms/text/text.types.ts +24 -23
  229. package/src/atoms/text-field/text-field.types.ts +4 -4
  230. package/src/atoms/v-stack/v-stack.ios.tsx +6 -4
  231. package/src/atoms/v-stack/v-stack.types.ts +18 -17
  232. package/src/atoms/z-stack/z-stack.ios.tsx +6 -4
  233. package/src/atoms/z-stack/z-stack.types.ts +16 -15
  234. package/src/glyph/glyph-button.types.ts +1 -1
  235. package/src/glyph/glyph.android.tsx +14 -2
  236. package/src/glyph/glyph.ios.tsx +13 -3
  237. package/src/glyph/glyph.types.ts +3 -1
  238. package/src/molecules/collapsible/collapsible.android.tsx +27 -7
  239. package/src/molecules/collapsible/collapsible.types.ts +1 -1
  240. package/src/molecules/empty-state/empty-state.types.ts +1 -1
  241. package/src/molecules/label/label.types.ts +1 -1
  242. package/src/molecules/labeled-content/labeled-content.tsx +12 -1
  243. package/src/molecules/labeled-content/labeled-content.types.ts +6 -4
  244. package/src/molecules/link/link.tsx +2 -1
  245. package/src/molecules/link/link.types.ts +2 -1
  246. package/src/molecules/list-item/list-item.tsx +20 -2
  247. package/src/molecules/list-item/list-item.types.ts +6 -4
  248. package/src/molecules/navigation-bar/navigation-bar.android.tsx +1 -1
  249. package/src/molecules/navigation-bar/navigation-bar.ios.tsx +1 -1
  250. package/src/molecules/navigation-bar/navigation-bar.types.ts +8 -8
  251. package/src/molecules/pager/pager.types.ts +2 -2
  252. package/src/molecules/radio-group/radio-group.types.ts +2 -2
  253. package/src/molecules/rn-list-item/rn-list-item.tsx +18 -3
  254. package/src/molecules/rn-list-item/rn-list-item.types.ts +1 -1
  255. package/src/molecules/rn-tooltip/rn-tooltip.types.ts +7 -7
  256. package/src/molecules/scroll-edge-container/scroll-edge-container.android.tsx +3 -2
  257. package/src/molecules/scroll-edge-container/scroll-edge-container.types.ts +3 -3
  258. package/src/molecules/search-bar/search-bar.types.ts +2 -2
  259. package/src/molecules/stepper/stepper.ios.tsx +8 -2
  260. package/src/molecules/stepper/stepper.types.ts +2 -2
  261. package/src/molecules/tooltip/tooltip.types.ts +6 -6
  262. package/src/motion.ts +13 -0
  263. package/src/organisms/action-sheet/action-sheet.types.ts +3 -3
  264. package/src/organisms/alert/alert.types.ts +3 -3
  265. package/src/organisms/context-menu/context-menu.types.ts +9 -9
  266. package/src/organisms/fab-menu/fab-menu.android.tsx +9 -5
  267. package/src/organisms/fab-menu/fab-menu.ios.tsx +6 -2
  268. package/src/organisms/fab-menu/fab-menu.types.ts +10 -5
  269. package/src/organisms/form/form.types.ts +1 -1
  270. package/src/organisms/menu/menu.types.ts +2 -2
  271. package/src/organisms/sheet/sheet.types.ts +12 -12
  272. package/src/organisms/snackbar/snackbar.tsx +143 -19
  273. package/src/organisms/snackbar/snackbar.types.ts +42 -6
  274. package/src/organisms/swipe-actions/swipe-actions.android.tsx +1 -1
  275. package/src/organisms/swipe-actions/swipe-actions.types.ts +5 -5
  276. package/src/templates/screen/screen.types.ts +2 -2
  277. package/src/use-stack-modifiers.ts +20 -7
  278. package/lib/module/platform-support.js +0 -19
  279. package/lib/module/platform-support.js.map +0 -1
  280. package/lib/typescript/platform-support.d.ts +0 -8
  281. package/src/platform-support.ts +0 -20
@@ -4,7 +4,7 @@ import type {ReactNode} from 'react'
4
4
  export type ButtonVariant = 'accent' | 'destructive' | 'glass' | 'secondary'
5
5
 
6
6
  /**
7
- * SwiftUI `ButtonStyle`. iOS only — Compose has no equivalent, so Android ignores it and keeps
7
+ * SwiftUI `ButtonStyle`. iOS only: Compose has no equivalent, so Android ignores it and keeps
8
8
  * rendering the variant's tokens.
9
9
  */
10
10
  export type ButtonStyleName =
@@ -17,19 +17,21 @@ export type ButtonStyleName =
17
17
  | 'plain'
18
18
 
19
19
  /**
20
- * SwiftUI `ControlSize`. iOS only, and it has no `automatic` member — leave it unset to inherit
20
+ * SwiftUI `ControlSize`. iOS only, and it has no `automatic` member: leave it unset to inherit
21
21
  * whatever the surrounding SwiftUI environment provides.
22
22
  */
23
23
  export type ButtonControlSize = 'extraLarge' | 'large' | 'mini' | 'regular' | 'small'
24
24
 
25
- /** A text button — SwiftUI `Button` on iOS, Material's filled button (outlined for `glass`) on Android. */
25
+ /** A text button: SwiftUI `Button` on iOS, Material's filled button (outlined for `glass`) on Android. */
26
26
  export type ButtonProps = {
27
+ /** What pressing it does, when the label alone does not say. See `AccessibilityProps`. */
28
+ accessibilityHint?: string
27
29
  /**
28
- * Replaces the style the variant picks — `borderedProminent` for `accent` and `destructive`, `bordered` for
29
- * `secondary`, `glass` for `glass`. iOS only — Android ignores it.
30
+ * Replaces the style the variant picks: `borderedProminent` for `accent` and `destructive`, `bordered` for
31
+ * `secondary`, `glass` for `glass`. iOS only. Android ignores it.
30
32
  */
31
33
  buttonStyle?: ButtonStyleName
32
- /** SwiftUI's control size. `large` by default. iOS only — Android ignores it. */
34
+ /** SwiftUI's control size. `large` by default. iOS only. Android ignores it. */
33
35
  controlSize?: ButtonControlSize
34
36
  /** Draws the button in its disabled colours and stops it responding to presses. */
35
37
  disabled?: boolean
@@ -42,7 +44,7 @@ export type ButtonProps = {
42
44
  /**
43
45
  * Overrides the variant's own tint. A prominent style takes it as the fill and picks a contrasting
44
46
  * label. `glass` has to reach the material through `glassEffect` instead, so tinting one costs it
45
- * the stock `.glass` style — see the iOS implementation.
47
+ * the stock `.glass` style (see the iOS implementation).
46
48
  */
47
49
  tint?: ColorToken
48
50
  /** The look. `accent` by default. Material has no glass, so on Android `glass` is an outlined button. */
@@ -1,16 +1,18 @@
1
1
  import {Column} from '@expo/ui/jetpack-compose'
2
2
  import {testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
3
- import {useSxModifiers} from '@hzblj/kara-sx'
3
+ import {splitAccessibility, useAccessibilityModifiers, useSxModifiers} from '@hzblj/kara-sx'
4
4
  import {useMemo} from 'react'
5
5
  import {useHostGuard} from '../../host-context'
6
6
  import type {CardComponent, CardProps} from './card.types'
7
7
 
8
- export const Card: CardComponent = ({children, testID, ...sx}: CardProps) => {
8
+ export const Card: CardComponent = ({children, testID, ...props}: CardProps) => {
9
9
  useHostGuard('Card')
10
+ const [accessibility, sx] = splitAccessibility(props)
10
11
  const sxModifiers = useSxModifiers({bg: 'surface.primary', borderRadius: 'large', p: 's16', shadow: 'card', ...sx})
12
+ const accessibilityModifiers = useAccessibilityModifiers(accessibility)
11
13
  const modifiers = useMemo(
12
- () => (testID ? [...sxModifiers, testIdModifier(testID)] : sxModifiers),
13
- [sxModifiers, testID]
14
+ () => [...sxModifiers, ...(testID ? [testIdModifier(testID)] : []), ...accessibilityModifiers],
15
+ [accessibilityModifiers, sxModifiers, testID]
14
16
  )
15
17
 
16
18
  return <Column modifiers={modifiers}>{children}</Column>
@@ -1,11 +1,15 @@
1
1
  import {VStack} from '@expo/ui/swift-ui'
2
- import {useSxModifiers} from '@hzblj/kara-sx'
2
+ import {splitAccessibility, useAccessibilityModifiers, useSxModifiers} from '@hzblj/kara-sx'
3
+ import {useMemo} from 'react'
3
4
  import {useHostGuard} from '../../host-context'
4
5
  import type {CardComponent, CardProps} from './card.types'
5
6
 
6
- export const Card: CardComponent = ({children, testID, ...sx}: CardProps) => {
7
+ export const Card: CardComponent = ({children, testID, ...props}: CardProps) => {
7
8
  useHostGuard('Card')
8
- const modifiers = useSxModifiers({bg: 'surface.primary', borderRadius: 'large', p: 's16', shadow: 'card', ...sx})
9
+ const [accessibility, sx] = splitAccessibility(props)
10
+ const sxModifiers = useSxModifiers({bg: 'surface.primary', borderRadius: 'large', p: 's16', shadow: 'card', ...sx})
11
+ const accessibilityModifiers = useAccessibilityModifiers(accessibility)
12
+ const modifiers = useMemo(() => [...sxModifiers, ...accessibilityModifiers], [accessibilityModifiers, sxModifiers])
9
13
 
10
14
  return (
11
15
  <VStack alignment="leading" modifiers={modifiers} testID={testID}>
@@ -1,15 +1,16 @@
1
- import type {ResponsiveSx} from '@hzblj/kara-sx'
1
+ import type {AccessibilityProps, ResponsiveSx} from '@hzblj/kara-sx'
2
2
  import type {ReactNode} from 'react'
3
3
 
4
4
  /**
5
- * A raised container — SwiftUI `VStack` / Compose `Column` on `surface.primary`, with a large radius, `s16` padding and
5
+ * A raised container: SwiftUI `VStack` / Compose `Column` on `surface.primary`, with a large radius, `s16` padding and
6
6
  * the `card` shadow. Any `sx` prop overrides its default.
7
7
  */
8
- export type CardProps = ResponsiveSx & {
9
- /** Native views, stacked vertically from the leading edge. */
10
- children?: ReactNode
11
- /** Test identifier, forwarded to the native view. */
12
- testID?: string
13
- }
8
+ export type CardProps = ResponsiveSx &
9
+ AccessibilityProps & {
10
+ /** Native views, stacked vertically from the leading edge. */
11
+ children?: ReactNode
12
+ /** Test identifier, forwarded to the native view. */
13
+ testID?: string
14
+ }
14
15
 
15
16
  export type CardComponent = (props: CardProps) => ReactNode
@@ -23,7 +23,7 @@ export const Checkbox: CheckboxComponent = ({disabled = false, label, onValueCha
23
23
  )
24
24
  const control = (
25
25
  <ComposeCheckbox
26
- colors={{checkedColor: theme.color('fill.accent.primary'), checkmarkColor: theme.color('content.on.bg')}}
26
+ colors={{checkedColor: theme.color('fill.accent.primary'), checkmarkColor: theme.color('content.on.accent')}}
27
27
  enabled={!disabled}
28
28
  onCheckedChange={onValueChange ?? NOOP}
29
29
  value={value}
@@ -1,6 +1,6 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
- /** Material's `Checkbox` on Android. iOS has none, so there it is a SwiftUI `Toggle` — the same switch as `Switch`. */
3
+ /** Material's `Checkbox` on Android. iOS has none, so there it is a SwiftUI `Toggle`, the same switch as `Switch`. */
4
4
  export type CheckboxProps = {
5
5
  /** Dims the control and ignores taps. */
6
6
  disabled?: boolean
@@ -8,7 +8,7 @@ export type CheckboxProps = {
8
8
  label?: string
9
9
  /** Called with the new value when the user toggles it. */
10
10
  onValueChange?: (value: boolean) => void
11
- /** Whether it is checked. Controlled — update it from `onValueChange`. */
11
+ /** Whether it is checked. Controlled: update it from `onValueChange`. */
12
12
  value: boolean
13
13
  }
14
14
 
@@ -1,6 +1,6 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
- /** A filter pill on a `Pressable`, not a native control — the React Native tier, so it needs no host. */
3
+ /** A filter pill on a `Pressable`, not a native control. It belongs to the React Native tier, so it needs no host. */
4
4
  export type ChipProps = {
5
5
  /** The text in the pill. */
6
6
  label: string
@@ -1,9 +1,29 @@
1
- import {warnUnsupported} from '../../platform-support'
2
- import type {ColorPickerComponent} from './color-picker.types'
1
+ import {ColorWell} from '@hzblj/kara-native/jetpack-compose'
3
2
 
4
- /** Compose has no colour picker, and there is nothing sane to degrade a colour wheel into. */
5
- export const ColorPicker: ColorPickerComponent = () => {
6
- warnUnsupported('ColorPicker', 'Android')
3
+ import {useHostGuard} from '../../host-context'
4
+ import {HStack} from '../h-stack'
5
+ import {Spacer} from '../spacer'
6
+ import {Text} from '../text'
7
+ import type {ColorPickerComponent, ColorPickerProps} from './color-picker.types'
7
8
 
8
- return null
9
+ /**
10
+ * The row SwiftUI's `ColorPicker` lays out on iOS, the label leading and the well trailing, around kara's own Compose
11
+ * well: Compose ships no colour picker, so the sheet behind it draws the iOS picker's grid, spectrum and sliders.
12
+ */
13
+ export const ColorPicker: ColorPickerComponent = ({
14
+ label,
15
+ onValueChange,
16
+ supportsOpacity = false,
17
+ testID,
18
+ value,
19
+ }: ColorPickerProps) => {
20
+ useHostGuard('ColorPicker')
21
+
22
+ return (
23
+ <HStack align="center" spacing="s12" testID={testID} wide>
24
+ {label ? <Text>{label}</Text> : null}
25
+ <Spacer />
26
+ <ColorWell onValueChange={onValueChange} supportsOpacity={supportsOpacity} title={label} value={value} />
27
+ </HStack>
28
+ )
9
29
  }
@@ -1,11 +1,12 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
3
  /**
4
- * The system colour picker. iOS only — Compose ships no equivalent, so on Android this
5
- * renders nothing and says so in dev. Branch on `Platform.OS` if the screen needs a fallback.
4
+ * A row with a label and a colour well that opens a picker: SwiftUI's `ColorPicker` on iOS. Compose ships none, so on
5
+ * Android it is kara's own, in the same shape: a well with a rainbow ring, and a sheet with a grid of swatches, a
6
+ * spectrum and RGB sliders.
6
7
  */
7
8
  export type ColorPickerProps = {
8
- /** Text beside the colour well. */
9
+ /** Text beside the colour well, and the Android sheet's title. */
9
10
  label?: string
10
11
  /** Called with the colour the user picks. */
11
12
  onValueChange?: (color: string) => void
@@ -3,7 +3,7 @@ import type {ReactNode} from 'react'
3
3
  export type DatePickerMode = 'date' | 'datetime' | 'time'
4
4
 
5
5
  /**
6
- * A date or time picker — SwiftUI's compact `DatePicker` on iOS, a field that opens Material's picker dialog on a tap
6
+ * A date or time picker: SwiftUI's compact `DatePicker` on iOS, a field that opens Material's picker dialog on a tap
7
7
  * on Android. The React Native tier: it brings its own host and sizes itself to the value, so it sits in a row beside
8
8
  * its label like a `Text`.
9
9
  */
@@ -17,11 +17,11 @@ export type DatePickerProps = {
17
17
  * `value`'s time of day.
18
18
  */
19
19
  mode?: DatePickerMode
20
- /** Called with the new date — on iOS as it changes, on Android when the dialog is confirmed. */
20
+ /** Called with the new date: on iOS as it changes, on Android when the dialog is confirmed. */
21
21
  onValueChange?: (date: Date) => void
22
- /** Test identifier — on the native picker on iOS, on the tappable field on Android. */
22
+ /** Test identifier, on the native picker on iOS and on the tappable field on Android. */
23
23
  testID?: string
24
- /** The selected date. Controlled — update it from `onValueChange`. */
24
+ /** The selected date. Controlled: update it from `onValueChange`. */
25
25
  value: Date
26
26
  }
27
27
 
@@ -1,11 +1,11 @@
1
1
  import type {ColorToken} from '@hzblj/kara-theme'
2
2
  import type {ReactNode} from 'react'
3
3
 
4
- /** A horizontal hairline — SwiftUI `Divider` / Compose `HorizontalDivider`. */
4
+ /** A horizontal hairline: SwiftUI `Divider` / Compose `HorizontalDivider`. */
5
5
  export type DividerProps = {
6
6
  /** The line's colour. `border.tertiary` by default. */
7
7
  color?: ColorToken
8
- /** Test identifier, forwarded to the native view on iOS — Android ignores it. */
8
+ /** Test identifier, forwarded to the native view on iOS. Android ignores it. */
9
9
  testID?: string
10
10
  }
11
11
 
@@ -1,20 +1,22 @@
1
1
  import {FloatingActionButton} from '@expo/ui/jetpack-compose'
2
+ import {useAccessibilityModifiers} from '@hzblj/kara-sx'
2
3
  import {useAppTheme} from '@hzblj/kara-theme'
3
4
 
4
5
  import {useHostGuard} from '../../host-context'
5
6
  import {Icon} from '../icon'
6
7
  import type {FabComponent, FabProps} from './fab.types'
7
8
 
8
- export const Fab: FabComponent = ({name, onPress}: FabProps) => {
9
+ export const Fab: FabComponent = ({accessibilityHint, accessibilityLabel, name, onPress}: FabProps) => {
9
10
  useHostGuard('Fab')
10
11
 
11
12
  const theme = useAppTheme()
13
+ const modifiers = useAccessibilityModifiers({accessibilityHint}, accessibilityHint !== undefined)
12
14
 
13
15
  return (
14
- <FloatingActionButton containerColor={theme.color('fill.accent.primary')} onClick={onPress}>
16
+ <FloatingActionButton containerColor={theme.color('fill.accent.primary')} modifiers={modifiers} onClick={onPress}>
15
17
  {/* `@expo/ui`'s FAB is slot-based: content outside `.Icon` is not composed at all. */}
16
18
  <FloatingActionButton.Icon>
17
- <Icon color="content.on.bg" name={name} />
19
+ <Icon accessibilityLabel={accessibilityLabel} color="content.on.accent" name={name} />
18
20
  </FloatingActionButton.Icon>
19
21
  </FloatingActionButton>
20
22
  )
@@ -1,13 +1,14 @@
1
1
  import {Button} from '@expo/ui/swift-ui'
2
2
  import {buttonStyle} from '@expo/ui/swift-ui/modifiers'
3
- import {useSxModifiers} from '@hzblj/kara-sx'
3
+ import {useAccessibilityModifiers, useSxModifiers} from '@hzblj/kara-sx'
4
4
  import {useMemo} from 'react'
5
5
  import {useHostGuard} from '../../host-context'
6
6
  import {Icon} from '../icon'
7
7
  import type {FabComponent, FabProps} from './fab.types'
8
8
 
9
- export const Fab: FabComponent = ({name, onPress, testID}: FabProps) => {
9
+ export const Fab: FabComponent = ({accessibilityHint, accessibilityLabel, name, onPress, testID}: FabProps) => {
10
10
  useHostGuard('Fab')
11
+ const accessibilityModifiers = useAccessibilityModifiers({accessibilityHint})
11
12
 
12
13
  const sxModifiers = useSxModifiers({
13
14
  bg: 'fill.accent.primary',
@@ -17,11 +18,14 @@ export const Fab: FabComponent = ({name, onPress, testID}: FabProps) => {
17
18
  shadow: 'btn.accent',
18
19
  w: 56,
19
20
  })
20
- const modifiers = useMemo(() => [buttonStyle('plain'), ...sxModifiers], [sxModifiers])
21
+ const modifiers = useMemo(
22
+ () => [buttonStyle('plain'), ...sxModifiers, ...accessibilityModifiers],
23
+ [accessibilityModifiers, sxModifiers]
24
+ )
21
25
 
22
26
  return (
23
27
  <Button modifiers={modifiers} onPress={onPress} testID={testID}>
24
- <Icon color="content.on.bg" name={name} />
28
+ <Icon accessibilityLabel={accessibilityLabel} color="content.on.accent" name={name} />
25
29
  </Button>
26
30
  )
27
31
  }
@@ -3,15 +3,22 @@ import type {ReactNode} from 'react'
3
3
  import type {IconName} from '../icon'
4
4
 
5
5
  /**
6
- * A floating action button — Material's `FloatingActionButton` on Android, a 56pt accent circle (a plain SwiftUI
6
+ * A floating action button: Material's `FloatingActionButton` on Android, a 56pt accent circle (a plain SwiftUI
7
7
  * `Button`) on iOS. It renders inline; pinning it to a corner is up to the screen.
8
8
  */
9
9
  export type FabProps = {
10
- /** The icon, one of the app's, drawn in `content.on.bg`. */
10
+ /**
11
+ * What VoiceOver and TalkBack say for the button, since an icon has no words of its own: "Share", "Add to
12
+ * favourites". Required for that reason.
13
+ */
14
+ accessibilityLabel: string
15
+ /** What pressing it does, when the label alone does not say. See `AccessibilityProps`. */
16
+ accessibilityHint?: string
17
+ /** The icon, one of the app's, drawn in `content.on.accent`. */
11
18
  name: IconName
12
19
  /** Called when the button is pressed. */
13
20
  onPress?: () => void
14
- /** Test identifier, forwarded to the native view on iOS — Android ignores it. */
21
+ /** Test identifier, forwarded to the native view on iOS. Android ignores it. */
15
22
  testID?: string
16
23
  }
17
24
 
@@ -31,7 +31,7 @@ export const GlassView: GlassViewComponent = ({
31
31
  cornerRadius={radii.full}
32
32
  fallbackTint={withOpacity(base, 'o95')}
33
33
  glassTint={withOpacity(base, tint ? 'o85' : 'o50')}
34
- hairlineColor={withOpacity(String(theme.color('content.on.bg')), theme.dark ? 'o20' : 'o60')}
34
+ hairlineColor={withOpacity(String(theme.color('content.on.accent')), theme.dark ? 'o20' : 'o60')}
35
35
  hairlineWidth={1}
36
36
  saturation={1.6}
37
37
  style={style}
@@ -3,13 +3,13 @@ import type {ColorToken} from '@hzblj/kara-theme'
3
3
  import type {ReactNode} from 'react'
4
4
 
5
5
  /**
6
- * Glass around a React Native subtree, shaped as a capsule — the React Native tier, so it needs no host. iOS 26:
7
- * Liquid Glass. Android: frosted glass — what lies behind, blurred on Android 12+, under a translucent tonal surface
8
- * (or `tint`) and a hairline. iOS before 26 has no Liquid Glass and draws a capsule of `tint` instead, or of the glass
9
- * buttons' translucent fill (`btn.bg.glass.base`) when there is none.
6
+ * Glass around a React Native subtree, shaped as a capsule. It belongs to the React Native tier, so it needs no host.
7
+ * iOS 26: Liquid Glass. Android: frosted glass, made of what lies behind, blurred on Android 12+, under a translucent
8
+ * tonal surface (or `tint`) and a hairline. iOS before 26 has no Liquid Glass and draws a capsule of `tint` instead, or
9
+ * of the glass buttons' translucent fill (`btn.bg.glass.base`) when there is none.
10
10
  *
11
11
  * On Android each glass re-records what lies behind it before every frame: a few buttons cost nothing, but keep it out
12
- * of the cells of a long list — a plain `tint` reads as well there.
12
+ * of the cells of a long list. A plain `tint` reads as well there.
13
13
  */
14
14
  export type GlassViewProps = ResponsiveViewSx & {
15
15
  /** React Native views. Give them `pointerEvents="none"` when `interactive`, so the press reaches the `Pressable`. */
@@ -1,6 +1,6 @@
1
1
  import {HStack as SwiftUIHStack} from '@expo/ui/swift-ui'
2
2
  import {onTapGesture} from '@expo/ui/swift-ui/modifiers'
3
- import {type FrameAlignment, useSxModifiers} from '@hzblj/kara-sx'
3
+ import {type FrameAlignment, splitAccessibility, useAccessibilityModifiers, useSxModifiers} from '@hzblj/kara-sx'
4
4
  import {spacing} from '@hzblj/kara-theme'
5
5
  import {useMemo} from 'react'
6
6
  import {useHostGuard} from '../../host-context'
@@ -24,13 +24,15 @@ export const HStack: HStackComponent = ({
24
24
  onPress,
25
25
  spacing: gap,
26
26
  testID,
27
- ...sx
27
+ ...props
28
28
  }: HStackProps) => {
29
29
  useHostGuard('HStack')
30
+ const [accessibility, sx] = splitAccessibility(props)
30
31
  const sxModifiers = useSxModifiers(sx, FRAME_ALIGNMENT[align])
32
+ const accessibilityModifiers = useAccessibilityModifiers(accessibility, onPress !== undefined)
31
33
  const modifiers = useMemo(
32
- () => (onPress ? [...sxModifiers, onTapGesture(onPress)] : sxModifiers),
33
- [onPress, sxModifiers]
34
+ () => [...sxModifiers, ...(onPress ? [onTapGesture(onPress)] : []), ...accessibilityModifiers],
35
+ [accessibilityModifiers, onPress, sxModifiers]
34
36
  )
35
37
 
36
38
  return (
@@ -1,21 +1,25 @@
1
- import type {ResponsiveSx} from '@hzblj/kara-sx'
1
+ import type {AccessibilityProps, ResponsiveSx} from '@hzblj/kara-sx'
2
2
  import type {SpaceSize} from '@hzblj/kara-theme'
3
3
  import type {ReactNode} from 'react'
4
4
 
5
5
  export type HStackAlign = 'center' | 'end' | 'start'
6
6
 
7
- /** A horizontal stack — SwiftUI `HStack` / Compose `Row`. */
8
- export type HStackProps = ResponsiveSx & {
9
- /** How the children line up vertically — `start` is the top, `end` the bottom. `start` by default. */
10
- align?: HStackAlign
11
- /** Native views, laid out from the leading edge. */
12
- children?: ReactNode
13
- /** Makes the stack pressable — a tap gesture on iOS, a `clickable` with Material's ripple on Android. */
14
- onPress?: () => void
15
- /** The gap between children. Unset, SwiftUI puts its default spacing between them and Compose none. */
16
- spacing?: SpaceSize
17
- /** Test identifier, forwarded to the native view. */
18
- testID?: string
19
- }
7
+ /** A horizontal stack: SwiftUI `HStack` / Compose `Row`. */
8
+ export type HStackProps = ResponsiveSx &
9
+ AccessibilityProps & {
10
+ /** How the children line up vertically: `start` is the top, `end` the bottom. `start` by default. */
11
+ align?: HStackAlign
12
+ /** Native views, laid out from the leading edge. */
13
+ children?: ReactNode
14
+ /**
15
+ * Makes the stack pressable: a tap gesture on iOS, a `clickable` with Material's ripple on Android. Screen readers
16
+ * then read the stack as one button.
17
+ */
18
+ onPress?: () => void
19
+ /** The gap between children. Unset, SwiftUI puts its default spacing between them and Compose none. */
20
+ spacing?: SpaceSize
21
+ /** Test identifier, forwarded to the native view. */
22
+ testID?: string
23
+ }
20
24
 
21
25
  export type HStackComponent = (props: HStackProps) => ReactNode
@@ -13,13 +13,13 @@ declare global {
13
13
  type DeclaredIconName = keyof KaraIcons & string
14
14
 
15
15
  /**
16
- * One of the app's icons. Once the plugin has written `kara-icons.d.ts` only those names type-check — an icon removed
16
+ * One of the app's icons. Once the plugin has written `kara-icons.d.ts` only those names type-check: an icon removed
17
17
  * from the folder fails at every call site. Before that, any string, so a project can adopt icons gradually.
18
18
  */
19
19
  export type IconName = [DeclaredIconName] extends [never] ? string : DeclaredIconName
20
20
 
21
21
  /**
22
- * One of the app's icons, in a host — SwiftUI `Image` from the asset catalog on iOS, Material's `Icon` with the vector
22
+ * One of the app's icons, in a host: SwiftUI `Image` from the asset catalog on iOS, Material's `Icon` with the vector
23
23
  * drawable on Android. `RnIcon` draws the same asset outside one.
24
24
  */
25
25
  export type IconProps = {
@@ -27,11 +27,11 @@ export type IconProps = {
27
27
  accessibilityLabel?: string
28
28
  /** The icon's tint. `content.primary` by default. */
29
29
  color?: ColorToken
30
- /** Which of the app's icons — a key of `KaraIcons`. */
30
+ /** Which of the app's icons, a key of `KaraIcons`. */
31
31
  name: IconName
32
32
  /** Its width and height, a spacing token. `s24` by default. */
33
33
  size?: SpaceSize
34
- /** Test identifier, forwarded to the native view on iOS — Android ignores it. */
34
+ /** Test identifier, forwarded to the native view on iOS. Android ignores it. */
35
35
  testID?: string
36
36
  }
37
37
 
@@ -1,15 +1,28 @@
1
1
  import {IconButton as ComposeIconButton} from '@expo/ui/jetpack-compose'
2
+ import {useAccessibilityModifiers} from '@hzblj/kara-sx'
2
3
 
3
4
  import {useHostGuard} from '../../host-context'
4
5
  import {Icon} from '../icon'
5
6
  import type {IconButtonComponent, IconButtonProps} from './icon-button.types'
6
7
 
7
- export const IconButton: IconButtonComponent = ({disabled = false, name, onPress}: IconButtonProps) => {
8
+ export const IconButton: IconButtonComponent = ({
9
+ accessibilityHint,
10
+ accessibilityLabel,
11
+ disabled = false,
12
+ name,
13
+ onPress,
14
+ }: IconButtonProps) => {
8
15
  useHostGuard('IconButton')
16
+ // The label rides on the icon, as Material's own icon buttons carry it; the hint labels the click.
17
+ const modifiers = useAccessibilityModifiers({accessibilityHint}, accessibilityHint !== undefined)
9
18
 
10
19
  return (
11
- <ComposeIconButton enabled={!disabled} onClick={onPress}>
12
- <Icon color={disabled ? 'btn.content.disabled' : 'content.primary'} name={name} />
20
+ <ComposeIconButton enabled={!disabled} modifiers={modifiers} onClick={onPress}>
21
+ <Icon
22
+ accessibilityLabel={accessibilityLabel}
23
+ color={disabled ? 'btn.content.disabled' : 'content.primary'}
24
+ name={name}
25
+ />
13
26
  </ComposeIconButton>
14
27
  )
15
28
  }
@@ -1,27 +1,46 @@
1
1
  import {Button} from '@expo/ui/swift-ui'
2
- import {buttonStyle, disabled as disabledModifier} from '@expo/ui/swift-ui/modifiers'
2
+ import {buttonStyle, contentShape, disabled as disabledModifier, shapes} from '@expo/ui/swift-ui/modifiers'
3
+ import {useAccessibilityModifiers} from '@hzblj/kara-sx'
3
4
  import {useMemo} from 'react'
4
5
 
5
6
  import {useHostGuard} from '../../host-context'
6
7
  import {Icon} from '../icon'
8
+ import {ZStack} from '../z-stack'
7
9
  import type {IconButtonComponent, IconButtonProps} from './icon-button.types'
8
10
 
9
- export const IconButton: IconButtonComponent = ({disabled = false, name, onPress, testID}: IconButtonProps) => {
11
+ /** Apple's least tap target. Android's `IconButton` is Material's 48dp; this is the same idea in points. */
12
+ const TARGET = 44
13
+
14
+ /** The whole square takes the tap, not only the glyph's own pixels. */
15
+ const WHOLE_TARGET = {modifiers: [contentShape(shapes.circle())]}
16
+
17
+ export const IconButton: IconButtonComponent = ({
18
+ accessibilityHint,
19
+ accessibilityLabel,
20
+ disabled = false,
21
+ name,
22
+ onPress,
23
+ testID,
24
+ }: IconButtonProps) => {
10
25
  useHostGuard('IconButton')
26
+ const accessibilityModifiers = useAccessibilityModifiers({accessibilityHint})
11
27
 
12
28
  const modifiers = useMemo(() => {
13
- const list = [buttonStyle('plain')]
29
+ const list = [buttonStyle('plain'), ...accessibilityModifiers]
14
30
 
15
31
  if (disabled) {
16
32
  list.push(disabledModifier(true))
17
33
  }
18
34
 
19
35
  return list
20
- }, [disabled])
36
+ }, [accessibilityModifiers, disabled])
21
37
 
22
38
  return (
23
39
  <Button modifiers={modifiers} onPress={onPress} testID={testID}>
24
- <Icon color={disabled ? 'btn.content.disabled' : 'content.primary'} name={name} />
40
+ <ZStack h={TARGET} ios={WHOLE_TARGET} w={TARGET}>
41
+ {/* SwiftUI dims a disabled button's label itself; a disabled colour on top of that dimmed it twice. */}
42
+ <Icon accessibilityLabel={accessibilityLabel} color="content.primary" name={name} />
43
+ </ZStack>
25
44
  </Button>
26
45
  )
27
46
  }
@@ -2,15 +2,26 @@ import type {ReactNode} from 'react'
2
2
 
3
3
  import type {IconName} from '../icon'
4
4
 
5
- /** A button that is just an icon — a plain SwiftUI `Button` on iOS, Material's `IconButton` on Android. */
5
+ /**
6
+ * A button that is just an icon: a plain SwiftUI `Button` on iOS, Material's `IconButton` on Android. The icon sits
7
+ * centred in a tap target the size each platform asks for, 44pt on iOS and 48dp on Android, so a row of them spaces
8
+ * itself the same way on both.
9
+ */
6
10
  export type IconButtonProps = {
7
- /** Draws the icon in `btn.content.disabled` and stops the button responding to presses. */
11
+ /**
12
+ * What VoiceOver and TalkBack say for the button, since an icon has no words of its own: "Share", "Add to
13
+ * favourites". Required for that reason.
14
+ */
15
+ accessibilityLabel: string
16
+ /** What pressing it does, when the label alone does not say. See `AccessibilityProps`. */
17
+ accessibilityHint?: string
18
+ /** Dims the icon (iOS's own disabled look, `btn.content.disabled` on Android) and stops the button responding. */
8
19
  disabled?: boolean
9
20
  /** The icon, one of the app's. */
10
21
  name: IconName
11
22
  /** Called when the button is pressed. */
12
23
  onPress?: () => void
13
- /** Test identifier, forwarded to the native view on iOS — Android ignores it. */
24
+ /** Test identifier, forwarded to the native view on iOS. Android ignores it. */
14
25
  testID?: string
15
26
  }
16
27
 
@@ -3,12 +3,12 @@ import {duration} from '@hzblj/kara-theme'
3
3
  import {Image as ExpoImage, type ImageProps as ExpoImageProps} from 'expo-image'
4
4
  import type {Ref} from 'react'
5
5
 
6
- const FADE = {duration: duration.d100, timing: 'linear'} as const
6
+ const FADE = {duration: duration.quick, timing: 'ease-out'} as const
7
7
 
8
8
  const FadingImage = (props: ExpoImageProps & {ref?: Ref<ExpoImage>}) => <ExpoImage transition={FADE} {...props} />
9
9
 
10
10
  /**
11
- * `expo-image` with the `sx` props, the way `Box` is a `View` with them, and the design system's 100 ms linear fade
12
- * already applied, so a loaded image never pops in. An explicit `transition` wins, and so does an explicit `style`.
11
+ * `expo-image` with the `sx` props, the way `Box` is a `View` with them, and the design system's fade (`quick`,
12
+ * eased out) already applied, so a loaded image never pops in. An explicit `transition` wins, and so does an explicit `style`.
13
13
  */
14
14
  export const Image = sx(FadingImage)
@@ -3,7 +3,7 @@ import type {ReactNode} from 'react'
3
3
  import type {StyleProp, ViewStyle} from 'react-native'
4
4
 
5
5
  /**
6
- * A host for native atoms in a React Native layout — the SwiftUI / Compose `Host` an `@expo/ui` tree mounts into. It
6
+ * A host for native atoms in a React Native layout: the SwiftUI / Compose `Host` an `@expo/ui` tree mounts into. It
7
7
  * sits in that layout like a `Box`, and takes `Box`'s `sx` props for it: `h`, `flex`, margins.
8
8
  */
9
9
  export type IslandProps = ResponsiveViewSx & {
@@ -15,15 +15,15 @@ export type IslandProps = ResponsiveViewSx & {
15
15
  * width maps to `fixedSize(horizontal: true)`, so the native side ignores the proposed width and
16
16
  * resolves a `wide` stack's ideal width as unbounded.
17
17
  *
18
- * Turn it off — and give the island an explicit height, `h` — when the subtree contains an
18
+ * Turn it off (and give the island an explicit height, `h`) when the subtree contains an
19
19
  * `RnView`: that host drives its SwiftUI frame from the child's Yoga-laid-out bounds while this one
20
20
  * writes its measured size back into the Yoga style, and the two together spin a layout loop that
21
21
  * hangs the main thread as soon as the views lay out on screen.
22
22
  */
23
23
  matchContents?: boolean
24
- /** A React Native style for the host view, merged over the `sx` props — for what they cannot say. */
24
+ /** A React Native style for the host view, merged over the `sx` props, for what they cannot say. */
25
25
  style?: StyleProp<ViewStyle>
26
- /** Test identifier, forwarded to the host view on iOS — Android ignores it. */
26
+ /** Test identifier, forwarded to the host view on iOS. Android ignores it. */
27
27
  testID?: string
28
28
  }
29
29