@hzblj/kara-components 0.6.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 (197) hide show
  1. package/CHANGELOG.md +49 -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/date-picker/date-picker.ios.js +32 -11
  6. package/lib/module/atoms/date-picker/date-picker.ios.js.map +1 -1
  7. package/lib/module/atoms/index.js +0 -1
  8. package/lib/module/atoms/index.js.map +1 -1
  9. package/lib/module/atoms/rn-flat-list/rn-flat-list.js +10 -9
  10. package/lib/module/atoms/rn-flat-list/rn-flat-list.js.map +1 -1
  11. package/lib/module/atoms/rn-section-list/rn-section-list.js +5 -8
  12. package/lib/module/atoms/rn-section-list/rn-section-list.js.map +1 -1
  13. package/lib/module/atoms/segmented-control/index.js +1 -1
  14. package/lib/module/atoms/segmented-control/index.js.map +1 -1
  15. package/lib/module/atoms/segmented-control/segmented-control.android.js +74 -0
  16. package/lib/module/atoms/segmented-control/segmented-control.android.js.map +1 -0
  17. package/lib/module/atoms/segmented-control/segmented-control.ios.js +44 -0
  18. package/lib/module/atoms/segmented-control/segmented-control.ios.js.map +1 -0
  19. package/lib/module/atoms/switch/switch.android.js +4 -1
  20. package/lib/module/atoms/switch/switch.android.js.map +1 -1
  21. package/lib/module/molecules/search-bar/search-bar.js +15 -2
  22. package/lib/module/molecules/search-bar/search-bar.js.map +1 -1
  23. package/lib/module/molecules/tooltip/tooltip.android.js +16 -5
  24. package/lib/module/molecules/tooltip/tooltip.android.js.map +1 -1
  25. package/lib/module/organisms/action-sheet/action-sheet.android.js +41 -20
  26. package/lib/module/organisms/action-sheet/action-sheet.android.js.map +1 -1
  27. package/lib/module/organisms/action-sheet/action-sheet.ios.js +24 -8
  28. package/lib/module/organisms/action-sheet/action-sheet.ios.js.map +1 -1
  29. package/lib/module/organisms/alert/alert.ios.js +15 -2
  30. package/lib/module/organisms/alert/alert.ios.js.map +1 -1
  31. package/lib/module/organisms/form/form.android.js +22 -9
  32. package/lib/module/organisms/form/form.android.js.map +1 -1
  33. package/lib/module/organisms/swipe-actions/swipe-actions.android.js +125 -11
  34. package/lib/module/organisms/swipe-actions/swipe-actions.android.js.map +1 -1
  35. package/lib/module/organisms/swipe-actions/swipe-actions.ios.js +46 -18
  36. package/lib/module/organisms/swipe-actions/swipe-actions.ios.js.map +1 -1
  37. package/lib/module/templates/screen/screen.android.js +31 -10
  38. package/lib/module/templates/screen/screen.android.js.map +1 -1
  39. package/lib/typescript/atoms/avatar/avatar.types.d.ts +5 -0
  40. package/lib/typescript/atoms/badge/badge.types.d.ts +3 -0
  41. package/lib/typescript/atoms/button/button.types.d.ts +12 -0
  42. package/lib/typescript/atoms/card/card.types.d.ts +6 -0
  43. package/lib/typescript/atoms/checkbox/checkbox.types.d.ts +5 -0
  44. package/lib/typescript/atoms/chip/chip.types.d.ts +5 -0
  45. package/lib/typescript/atoms/color-picker/color-picker.types.d.ts +4 -0
  46. package/lib/typescript/atoms/date-picker/date-picker.ios.d.ts +6 -1
  47. package/lib/typescript/atoms/date-picker/date-picker.types.d.ts +14 -0
  48. package/lib/typescript/atoms/divider/divider.types.d.ts +3 -0
  49. package/lib/typescript/atoms/fab/fab.types.d.ts +7 -0
  50. package/lib/typescript/atoms/glass-view/glass-view.types.d.ts +1 -0
  51. package/lib/typescript/atoms/h-stack/h-stack.types.d.ts +6 -0
  52. package/lib/typescript/atoms/icon/icon.types.d.ts +8 -0
  53. package/lib/typescript/atoms/icon-button/icon-button.types.d.ts +5 -0
  54. package/lib/typescript/atoms/index.d.ts +0 -1
  55. package/lib/typescript/atoms/island/island.types.d.ts +4 -0
  56. package/lib/typescript/atoms/list/list.types.d.ts +2 -0
  57. package/lib/typescript/atoms/picker/picker.types.d.ts +12 -0
  58. package/lib/typescript/atoms/pressable/pressable.types.d.ts +8 -0
  59. package/lib/typescript/atoms/progress/progress.types.d.ts +6 -0
  60. package/lib/typescript/atoms/rn-flat-list/rn-flat-list.d.ts +5 -8
  61. package/lib/typescript/atoms/rn-flat-list/rn-flat-list.types.d.ts +3 -1
  62. package/lib/typescript/atoms/rn-scroll-view/rn-scroll-view.types.d.ts +4 -0
  63. package/lib/typescript/atoms/rn-section-list/rn-section-list.d.ts +2 -8
  64. package/lib/typescript/atoms/rn-section-list/rn-section-list.types.d.ts +3 -1
  65. package/lib/typescript/atoms/rn-text/rn-text.types.d.ts +4 -0
  66. package/lib/typescript/atoms/rn-view/rn-view.types.d.ts +2 -0
  67. package/lib/typescript/atoms/scroll-view/scroll-view.types.d.ts +4 -0
  68. package/lib/typescript/atoms/segmented-control/segmented-control.d.ts +1 -6
  69. package/lib/typescript/atoms/segmented-control/segmented-control.ios.d.ts +7 -0
  70. package/lib/typescript/atoms/segmented-control/segmented-control.types.d.ts +13 -0
  71. package/lib/typescript/atoms/skeleton/skeleton.types.d.ts +5 -0
  72. package/lib/typescript/atoms/slider/slider.types.d.ts +7 -0
  73. package/lib/typescript/atoms/spacer/spacer.types.d.ts +3 -0
  74. package/lib/typescript/atoms/surface/surface.types.d.ts +7 -0
  75. package/lib/typescript/atoms/switch/switch.types.d.ts +8 -0
  76. package/lib/typescript/atoms/text/text.types.d.ts +12 -0
  77. package/lib/typescript/atoms/text-field/text-field.types.d.ts +12 -0
  78. package/lib/typescript/atoms/v-stack/v-stack.types.d.ts +9 -0
  79. package/lib/typescript/atoms/z-stack/z-stack.types.d.ts +8 -0
  80. package/lib/typescript/molecules/collapsible/collapsible.types.d.ts +8 -0
  81. package/lib/typescript/molecules/empty-state/empty-state.types.d.ts +5 -0
  82. package/lib/typescript/molecules/label/label.types.d.ts +6 -0
  83. package/lib/typescript/molecules/labeled-content/labeled-content.types.d.ts +6 -0
  84. package/lib/typescript/molecules/link/link.types.d.ts +6 -0
  85. package/lib/typescript/molecules/list-item/list-item.types.d.ts +9 -0
  86. package/lib/typescript/molecules/navigation-bar/navigation-bar.types.d.ts +8 -0
  87. package/lib/typescript/molecules/pager/pager.types.d.ts +2 -0
  88. package/lib/typescript/molecules/radio-group/radio-group.types.d.ts +10 -0
  89. package/lib/typescript/molecules/rn-list-item/rn-list-item.types.d.ts +1 -1
  90. package/lib/typescript/molecules/rn-tooltip/rn-tooltip.types.d.ts +11 -0
  91. package/lib/typescript/molecules/scroll-edge-container/scroll-edge-container.types.d.ts +3 -0
  92. package/lib/typescript/molecules/search-bar/search-bar.types.d.ts +8 -0
  93. package/lib/typescript/molecules/stepper/stepper.types.d.ts +13 -0
  94. package/lib/typescript/molecules/tooltip/tooltip.types.d.ts +12 -0
  95. package/lib/typescript/organisms/action-sheet/action-sheet.types.d.ts +8 -0
  96. package/lib/typescript/organisms/alert/alert.types.d.ts +11 -0
  97. package/lib/typescript/organisms/context-menu/context-menu.types.d.ts +4 -0
  98. package/lib/typescript/organisms/fab-menu/fab-menu.types.d.ts +2 -0
  99. package/lib/typescript/organisms/form/form.types.d.ts +6 -0
  100. package/lib/typescript/organisms/menu/menu.types.d.ts +17 -0
  101. package/lib/typescript/organisms/sheet/sheet.types.d.ts +19 -0
  102. package/lib/typescript/organisms/snackbar/snackbar.types.d.ts +8 -0
  103. package/lib/typescript/organisms/swipe-actions/swipe-actions.types.d.ts +36 -13
  104. package/lib/typescript/templates/screen/screen.types.d.ts +9 -0
  105. package/package.json +5 -7
  106. package/src/atoms/avatar/avatar.types.ts +5 -0
  107. package/src/atoms/badge/badge.types.ts +3 -0
  108. package/src/atoms/button/button.types.ts +12 -0
  109. package/src/atoms/card/card.types.ts +6 -0
  110. package/src/atoms/checkbox/checkbox.android.tsx +7 -3
  111. package/src/atoms/checkbox/checkbox.types.ts +5 -0
  112. package/src/atoms/chip/chip.types.ts +5 -0
  113. package/src/atoms/color-picker/color-picker.types.ts +4 -0
  114. package/src/atoms/date-picker/date-picker.ios.tsx +33 -13
  115. package/src/atoms/date-picker/date-picker.types.ts +14 -0
  116. package/src/atoms/divider/divider.types.ts +3 -0
  117. package/src/atoms/fab/fab.types.ts +7 -0
  118. package/src/atoms/glass-view/glass-view.types.ts +1 -0
  119. package/src/atoms/h-stack/h-stack.types.ts +6 -0
  120. package/src/atoms/icon/icon.types.ts +8 -0
  121. package/src/atoms/icon-button/icon-button.types.ts +5 -0
  122. package/src/atoms/index.ts +0 -1
  123. package/src/atoms/island/island.types.ts +4 -0
  124. package/src/atoms/list/list.types.ts +2 -0
  125. package/src/atoms/picker/picker.types.ts +12 -0
  126. package/src/atoms/pressable/pressable.types.ts +8 -0
  127. package/src/atoms/progress/progress.types.ts +6 -0
  128. package/src/atoms/rn-flat-list/rn-flat-list.tsx +8 -8
  129. package/src/atoms/rn-flat-list/rn-flat-list.types.ts +3 -1
  130. package/src/atoms/rn-scroll-view/rn-scroll-view.types.ts +4 -0
  131. package/src/atoms/rn-section-list/rn-section-list.tsx +5 -8
  132. package/src/atoms/rn-section-list/rn-section-list.types.ts +3 -1
  133. package/src/atoms/rn-text/rn-text.types.ts +4 -0
  134. package/src/atoms/rn-view/rn-view.types.ts +2 -0
  135. package/src/atoms/scroll-view/scroll-view.types.ts +4 -0
  136. package/src/atoms/segmented-control/segmented-control.android.tsx +79 -0
  137. package/src/atoms/segmented-control/segmented-control.ios.tsx +47 -0
  138. package/src/atoms/segmented-control/segmented-control.types.ts +13 -0
  139. package/src/atoms/skeleton/skeleton.types.ts +5 -0
  140. package/src/atoms/slider/slider.types.ts +7 -0
  141. package/src/atoms/spacer/spacer.types.ts +3 -0
  142. package/src/atoms/surface/surface.types.ts +7 -0
  143. package/src/atoms/switch/switch.android.tsx +4 -1
  144. package/src/atoms/switch/switch.types.ts +8 -0
  145. package/src/atoms/text/text.types.ts +12 -0
  146. package/src/atoms/text-field/text-field.types.ts +12 -0
  147. package/src/atoms/v-stack/v-stack.types.ts +9 -0
  148. package/src/atoms/z-stack/z-stack.types.ts +8 -0
  149. package/src/molecules/collapsible/collapsible.types.ts +8 -0
  150. package/src/molecules/empty-state/empty-state.types.ts +5 -0
  151. package/src/molecules/label/label.types.ts +6 -0
  152. package/src/molecules/labeled-content/labeled-content.types.ts +6 -0
  153. package/src/molecules/link/link.types.ts +6 -0
  154. package/src/molecules/list-item/list-item.types.ts +9 -0
  155. package/src/molecules/navigation-bar/navigation-bar.types.ts +8 -0
  156. package/src/molecules/pager/pager.types.ts +2 -0
  157. package/src/molecules/radio-group/radio-group.types.ts +10 -0
  158. package/src/molecules/rn-list-item/rn-list-item.types.ts +1 -1
  159. package/src/molecules/rn-tooltip/rn-tooltip.types.ts +11 -0
  160. package/src/molecules/scroll-edge-container/scroll-edge-container.types.ts +3 -0
  161. package/src/molecules/search-bar/search-bar.tsx +9 -2
  162. package/src/molecules/search-bar/search-bar.types.ts +8 -0
  163. package/src/molecules/stepper/stepper.types.ts +13 -0
  164. package/src/molecules/tooltip/tooltip.android.tsx +17 -9
  165. package/src/molecules/tooltip/tooltip.types.ts +12 -0
  166. package/src/organisms/action-sheet/action-sheet.android.tsx +37 -19
  167. package/src/organisms/action-sheet/action-sheet.ios.tsx +22 -9
  168. package/src/organisms/action-sheet/action-sheet.types.ts +8 -0
  169. package/src/organisms/alert/alert.ios.tsx +11 -1
  170. package/src/organisms/alert/alert.types.ts +11 -0
  171. package/src/organisms/context-menu/context-menu.types.ts +4 -0
  172. package/src/organisms/fab-menu/fab-menu.types.ts +2 -0
  173. package/src/organisms/form/form.android.tsx +15 -7
  174. package/src/organisms/form/form.types.ts +6 -0
  175. package/src/organisms/menu/menu.types.ts +17 -0
  176. package/src/organisms/sheet/sheet.types.ts +19 -0
  177. package/src/organisms/snackbar/snackbar.types.ts +8 -0
  178. package/src/organisms/swipe-actions/swipe-actions.android.tsx +129 -9
  179. package/src/organisms/swipe-actions/swipe-actions.ios.tsx +45 -11
  180. package/src/organisms/swipe-actions/swipe-actions.types.ts +37 -14
  181. package/src/templates/screen/screen.android.tsx +33 -12
  182. package/src/templates/screen/screen.types.ts +9 -0
  183. package/lib/module/atoms/flash-list/flash-list.js +0 -31
  184. package/lib/module/atoms/flash-list/flash-list.js.map +0 -1
  185. package/lib/module/atoms/flash-list/flash-list.types.js +0 -4
  186. package/lib/module/atoms/flash-list/flash-list.types.js.map +0 -1
  187. package/lib/module/atoms/flash-list/index.js +0 -5
  188. package/lib/module/atoms/flash-list/index.js.map +0 -1
  189. package/lib/module/atoms/segmented-control/segmented-control.js +0 -40
  190. package/lib/module/atoms/segmented-control/segmented-control.js.map +0 -1
  191. package/lib/typescript/atoms/flash-list/flash-list.d.ts +0 -8
  192. package/lib/typescript/atoms/flash-list/flash-list.types.d.ts +0 -18
  193. package/lib/typescript/atoms/flash-list/index.d.ts +0 -2
  194. package/src/atoms/flash-list/flash-list.tsx +0 -28
  195. package/src/atoms/flash-list/flash-list.types.ts +0 -20
  196. package/src/atoms/flash-list/index.ts +0 -2
  197. package/src/atoms/segmented-control/segmented-control.tsx +0 -47
@@ -1,20 +1,43 @@
1
+ import type { ColorToken } from '@hzblj/kara-theme';
1
2
  import type { ReactNode } from 'react';
2
- export type SwipeActionsEdge = 'leading' | 'trailing';
3
+ import type { IconName } from '../../atoms/icon';
4
+ /** One button a swiped row uncovers. */
5
+ export type SwipeAction = {
6
+ /** Fills the button with `fill.destructive.primary` unless `tint` says otherwise. */
7
+ destructive?: boolean;
8
+ /** One of the app's icons, on the button — a round one on iOS 26, as in Mail, a filled stadium on Android. */
9
+ icon: IconName;
10
+ /**
11
+ * The action's name, written under the button — before iOS 26 a row of the usual height shows the icon alone and
12
+ * keeps the label for VoiceOver. Actions are keyed by it, so keep labels unique within one row.
13
+ */
14
+ label: string;
15
+ /** Called when the button is pressed. The row closes by itself. */
16
+ onPress: () => void;
17
+ /**
18
+ * The button's fill — `fill.destructive.primary` for a destructive action, `fill.accent.primary` otherwise. The icon
19
+ * on it is white, so pick a fill that holds white in both appearances.
20
+ */
21
+ tint?: ColorToken;
22
+ };
3
23
  /**
4
- * Swipe-to-reveal buttons on a row. iOS only — Compose ships no equivalent, so on Android the
5
- * row renders exactly as it would without this wrapper and the action buttons are dropped.
6
- * Degrading that way rather than to `null` keeps the content on screen.
24
+ * A list row with buttons behind it, uncovered by a swipe — SwiftUI's `swipeActions` on iOS, and on Android, where
25
+ * Compose has none, kara's own: the row slides on an anchored drag and the buttons show in the strip it leaves. One
26
+ * row is open at a time on both; a tap on an open row closes it. A native organism — it belongs inside a `List`.
7
27
  */
8
28
  export type SwipeActionsProps = {
9
- children: ReactNode;
10
- testID?: string;
11
- };
12
- export type SwipeActionsGroupProps = {
13
- /** Whether swiping all the way performs the first action in the group. */
29
+ /**
30
+ * iOS: a swipe all the way across performs the edge's first action, as in Mail. Android has no full swipe. `true`
31
+ * by default, as in SwiftUI.
32
+ */
14
33
  allowsFullSwipe?: boolean;
34
+ /** The row. */
15
35
  children: ReactNode;
16
- edge?: SwipeActionsEdge;
17
- };
18
- export type SwipeActionsComponent = ((props: SwipeActionsProps) => ReactNode) & {
19
- Actions: (props: SwipeActionsGroupProps) => ReactNode;
36
+ /** The buttons at the leading edge, uncovered by a swipe toward the trailing one. The first sits outermost. */
37
+ leading?: SwipeAction[];
38
+ /** Test identifier. iOS: on the row. Android: the row is a Compose node, reach it by its content. */
39
+ testID?: string;
40
+ /** The buttons at the trailing edge, uncovered by a swipe toward the leading one. The first sits outermost. */
41
+ trailing?: SwipeAction[];
20
42
  };
43
+ export type SwipeActionsComponent = (props: SwipeActionsProps) => ReactNode;
@@ -1,9 +1,18 @@
1
1
  import type { Sx } from '@hzblj/kara-sx';
2
2
  import type { ReactNode } from 'react';
3
+ /**
4
+ * The full-bleed native host a screen starts with — a SwiftUI or Compose `Host` filling its parent, so native atoms
5
+ * render anywhere inside it. The background runs to the edges; the content keeps clear of the status bar where the
6
+ * screen covers it (no navigation bar above), on Android as SwiftUI's safe area does on iOS.
7
+ */
3
8
  export type ScreenProps = {
9
+ /** The screen's colour. `surface.base` by default. */
4
10
  bg?: Sx['bg'];
11
+ /** Laid out in a column that fills the screen. */
5
12
  children?: ReactNode;
13
+ /** Keeps the content where it is when the keyboard opens, instead of avoiding it. Android reads it once, on mount. */
6
14
  ignoreKeyboardInset?: boolean;
15
+ /** Test identifier — on the host view on iOS, on the view around it on Android. */
7
16
  testID?: string;
8
17
  };
9
18
  export type ScreenComponent = (props: ScreenProps) => ReactNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hzblj/kara-components",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "The atoms, molecules, organisms and templates of @hzblj/kara-ui — real SwiftUI and Jetpack Compose views through @expo/ui.",
5
5
  "author": "Jan Blazej",
6
6
  "license": "MIT",
@@ -12,7 +12,7 @@
12
12
  "bugs": {
13
13
  "url": "https://github.com/hzblj/kara-ui/issues"
14
14
  },
15
- "homepage": "https://github.com/hzblj/kara-ui/tree/main/packages/components#readme",
15
+ "homepage": "https://kara.janblazej.dev/docs/components",
16
16
  "keywords": [
17
17
  "react-native",
18
18
  "expo",
@@ -59,15 +59,14 @@
59
59
  ]
60
60
  },
61
61
  "dependencies": {
62
- "@hzblj/kara-native": "^0.6.0",
63
- "@hzblj/kara-sx": "^0.6.0",
64
- "@hzblj/kara-theme": "^0.6.0",
62
+ "@hzblj/kara-native": "^0.7.0",
63
+ "@hzblj/kara-sx": "^0.7.0",
64
+ "@hzblj/kara-theme": "^0.7.0",
65
65
  "sf-symbols-typescript": "^2.2.0"
66
66
  },
67
67
  "peerDependencies": {
68
68
  "@expo/ui": "~57.0.0",
69
69
  "@lodev09/react-native-true-sheet": ">=3.11.0",
70
- "@shopify/flash-list": ">=2.0.0",
71
70
  "expo": "~57.0.0",
72
71
  "expo-image": "*",
73
72
  "react": "*",
@@ -77,7 +76,6 @@
77
76
  "devDependencies": {
78
77
  "@expo/ui": "~57.0.18",
79
78
  "@lodev09/react-native-true-sheet": "3.11.18",
80
- "@shopify/flash-list": "^2.3.2",
81
79
  "@storybook/react-native": "^10.6.0",
82
80
  "@types/react": "~19.2.18",
83
81
  "expo": "~57.0.22",
@@ -3,10 +3,15 @@ import type {ImageSourcePropType} from 'react-native'
3
3
 
4
4
  export type AvatarSize = 'lg' | 'md' | 'sm'
5
5
 
6
+ /** A round avatar: the image in `source`, or `initials` without one. The React Native tier — it needs no host. */
6
7
  export type AvatarProps = {
8
+ /** Shown in place of the image when there is no `source`. */
7
9
  initials?: string
10
+ /** The diameter — `sm` is `s24`, `md` `s40`, `lg` `s64`. `md` by default. */
8
11
  size?: AvatarSize
12
+ /** The image, cropped to the circle. */
9
13
  source?: ImageSourcePropType
14
+ /** Test identifier, forwarded to the view. */
10
15
  testID?: string
11
16
  }
12
17
 
@@ -2,8 +2,11 @@ import type {ReactNode} from 'react'
2
2
 
3
3
  export type BadgeTone = 'accent' | 'destructive' | 'neutral' | 'success'
4
4
 
5
+ /** A small status label in a capsule — a SwiftUI `HStack` in the tone's fill on iOS, Material's `Badge` on Android. */
5
6
  export type BadgeProps = {
7
+ /** The text inside. */
6
8
  label: string
9
+ /** The fill and text colours. `neutral` by default. */
7
10
  tone?: BadgeTone
8
11
  }
9
12
 
@@ -22,12 +22,22 @@ export type ButtonStyleName =
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
26
  export type ButtonProps = {
27
+ /**
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
+ */
26
31
  buttonStyle?: ButtonStyleName
32
+ /** SwiftUI's control size. `large` by default. iOS only — Android ignores it. */
27
33
  controlSize?: ButtonControlSize
34
+ /** Draws the button in its disabled colours and stops it responding to presses. */
28
35
  disabled?: boolean
36
+ /** The button's text. */
29
37
  label: string
38
+ /** Called when the button is pressed. */
30
39
  onPress?: () => void
40
+ /** Test identifier, forwarded to the native view. */
31
41
  testID?: string
32
42
  /**
33
43
  * Overrides the variant's own tint. A prominent style takes it as the fill and picks a contrasting
@@ -35,7 +45,9 @@ export type ButtonProps = {
35
45
  * the stock `.glass` style — see the iOS implementation.
36
46
  */
37
47
  tint?: ColorToken
48
+ /** The look. `accent` by default. Material has no glass, so on Android `glass` is an outlined button. */
38
49
  variant?: ButtonVariant
50
+ /** Stretches the button to the full width it is offered. On by default. */
39
51
  wide?: boolean
40
52
  }
41
53
 
@@ -1,8 +1,14 @@
1
1
  import type {ResponsiveSx} from '@hzblj/kara-sx'
2
2
  import type {ReactNode} from 'react'
3
3
 
4
+ /**
5
+ * A raised container — SwiftUI `VStack` / Compose `Column` on `surface.primary`, with a large radius, `s16` padding and
6
+ * the `card` shadow. Any `sx` prop overrides its default.
7
+ */
4
8
  export type CardProps = ResponsiveSx & {
9
+ /** Native views, stacked vertically from the leading edge. */
5
10
  children?: ReactNode
11
+ /** Test identifier, forwarded to the native view. */
6
12
  testID?: string
7
13
  }
8
14
 
@@ -6,9 +6,13 @@ import {useMemo} from 'react'
6
6
  import {useHostGuard} from '../../host-context'
7
7
  import type {CheckboxComponent, CheckboxProps} from './checkbox.types'
8
8
 
9
+ /** Stands in for a missing handler: Material drops its 48dp touch target without one, and the box moves. */
10
+ const NOOP = () => undefined
11
+
9
12
  /**
10
- * Material's checkbox, tinted with the accent. With a label it is a full-width row that toggles as a whole; the gap is
11
- * explicit because a checkbox without a handler drops Material's 48dp touch target, and with it the space around it.
13
+ * Material's checkbox, tinted with the accent. With a label it is a full-width row that toggles as a whole. It always
14
+ * gets a handler, so a disabled or read-only box keeps the touch target an enabled one has, and lines up with it;
15
+ * `enabled` is what stops the input.
12
16
  */
13
17
  export const Checkbox: CheckboxComponent = ({disabled = false, label, onValueChange, value}: CheckboxProps) => {
14
18
  useHostGuard('Checkbox')
@@ -21,7 +25,7 @@ export const Checkbox: CheckboxComponent = ({disabled = false, label, onValueCha
21
25
  <ComposeCheckbox
22
26
  colors={{checkedColor: theme.color('fill.accent.primary'), checkmarkColor: theme.color('content.on.bg')}}
23
27
  enabled={!disabled}
24
- onCheckedChange={onValueChange}
28
+ onCheckedChange={onValueChange ?? NOOP}
25
29
  value={value}
26
30
  />
27
31
  )
@@ -1,9 +1,14 @@
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
4
  export type CheckboxProps = {
5
+ /** Dims the control and ignores taps. */
4
6
  disabled?: boolean
7
+ /** Text beside the control. On Android the whole labelled row toggles it. */
5
8
  label?: string
9
+ /** Called with the new value when the user toggles it. */
6
10
  onValueChange?: (value: boolean) => void
11
+ /** Whether it is checked. Controlled — update it from `onValueChange`. */
7
12
  value: boolean
8
13
  }
9
14
 
@@ -1,9 +1,14 @@
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
4
  export type ChipProps = {
5
+ /** The text in the pill. */
4
6
  label: string
7
+ /** Called when the chip is pressed, with the `selection` haptic. */
5
8
  onPress?: () => void
9
+ /** Draws the chip in the accent colours. Off by default. */
6
10
  selected?: boolean
11
+ /** Test identifier, forwarded to the view. */
7
12
  testID?: string
8
13
  }
9
14
 
@@ -5,9 +5,13 @@ import type {ReactNode} from 'react'
5
5
  * renders nothing and says so in dev. Branch on `Platform.OS` if the screen needs a fallback.
6
6
  */
7
7
  export type ColorPickerProps = {
8
+ /** Text beside the colour well. */
8
9
  label?: string
10
+ /** Called with the colour the user picks. */
9
11
  onValueChange?: (color: string) => void
12
+ /** Lets the user pick an opacity as well. Off by default. */
10
13
  supportsOpacity?: boolean
14
+ /** Test identifier, forwarded to the native view. */
11
15
  testID?: string
12
16
  /** `#RRGGBB` or `#RRGGBBAA`. */
13
17
  value: string | null
@@ -1,9 +1,22 @@
1
- import {DateTimePicker} from '@expo/ui/community/datetime-picker'
2
- import {useCallback} from 'react'
1
+ import {Host, DatePicker as SwiftUIDatePicker} from '@expo/ui/swift-ui'
2
+ import {datePickerStyle, labelsHidden, tint} from '@expo/ui/swift-ui/modifiers'
3
+ import {useAppTheme} from '@hzblj/kara-theme'
4
+ import {useMemo} from 'react'
3
5
 
4
- import type {DatePickerComponent, DatePickerProps} from './date-picker.types'
6
+ import type {DatePickerComponent, DatePickerMode, DatePickerProps} from './date-picker.types'
5
7
 
6
- /** The system's compact date picker, inline: a tap on it opens the calendar popover UIKit owns. */
8
+ const COMPONENTS = {
9
+ date: ['date'],
10
+ datetime: ['date', 'hourAndMinute'],
11
+ time: ['hourAndMinute'],
12
+ } as const satisfies Record<DatePickerMode, ('date' | 'hourAndMinute')[]>
13
+
14
+ /**
15
+ * The system's compact date picker: the value in a pill, and a tap on it opens the calendar popover UIKit owns. Its own
16
+ * host, sized to the pill on both axes — a host that took the width it was given would lay the pill out against the
17
+ * far edge of it, or past its container's — so it drops into a row the way a `Text` does. The label is the caller's,
18
+ * beside it; the calendar is in the accent.
19
+ */
7
20
  export const DatePicker: DatePickerComponent = ({
8
21
  max,
9
22
  min,
@@ -12,16 +25,23 @@ export const DatePicker: DatePickerComponent = ({
12
25
  testID,
13
26
  value,
14
27
  }: DatePickerProps) => {
15
- const onChange = useCallback((_event: unknown, date: Date) => onValueChange?.(date), [onValueChange])
28
+ const theme = useAppTheme()
29
+
30
+ const modifiers = useMemo(
31
+ () => [datePickerStyle('compact'), labelsHidden(), tint(theme.color('fill.accent.primary'))],
32
+ [theme]
33
+ )
16
34
 
17
35
  return (
18
- <DateTimePicker
19
- maximumDate={max}
20
- minimumDate={min}
21
- mode={mode}
22
- onValueChange={onChange}
23
- testID={testID}
24
- value={value}
25
- />
36
+ <Host colorScheme={theme.dark ? 'dark' : 'light'} matchContents>
37
+ <SwiftUIDatePicker
38
+ displayedComponents={[...COMPONENTS[mode]]}
39
+ modifiers={modifiers}
40
+ onDateChange={onValueChange}
41
+ range={min || max ? {end: max, start: min} : undefined}
42
+ selection={value}
43
+ testID={testID}
44
+ />
45
+ </Host>
26
46
  )
27
47
  }
@@ -2,12 +2,26 @@ import type {ReactNode} from 'react'
2
2
 
3
3
  export type DatePickerMode = 'date' | 'datetime' | 'time'
4
4
 
5
+ /**
6
+ * A date or time picker — SwiftUI's compact `DatePicker` on iOS, a field that opens Material's picker dialog on a tap
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
+ * its label like a `Text`.
9
+ */
5
10
  export type DatePickerProps = {
11
+ /** The latest date that can be picked. Android ignores it in `time` mode. */
6
12
  max?: Date
13
+ /** The earliest date that can be picked. Android ignores it in `time` mode. */
7
14
  min?: Date
15
+ /**
16
+ * What it picks. `date` by default. Android has no combined dialog, so there `datetime` picks the date and keeps
17
+ * `value`'s time of day.
18
+ */
8
19
  mode?: DatePickerMode
20
+ /** Called with the new date — on iOS as it changes, on Android when the dialog is confirmed. */
9
21
  onValueChange?: (date: Date) => void
22
+ /** Test identifier — on the native picker on iOS, on the tappable field on Android. */
10
23
  testID?: string
24
+ /** The selected date. Controlled — update it from `onValueChange`. */
11
25
  value: Date
12
26
  }
13
27
 
@@ -1,8 +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
5
  export type DividerProps = {
6
+ /** The line's colour. `border.tertiary` by default. */
5
7
  color?: ColorToken
8
+ /** Test identifier, forwarded to the native view on iOS — Android ignores it. */
6
9
  testID?: string
7
10
  }
8
11
 
@@ -2,9 +2,16 @@ import type {ReactNode} from 'react'
2
2
 
3
3
  import type {IconName} from '../icon'
4
4
 
5
+ /**
6
+ * A floating action button — Material's `FloatingActionButton` on Android, a 56pt accent circle (a plain SwiftUI
7
+ * `Button`) on iOS. It renders inline; pinning it to a corner is up to the screen.
8
+ */
5
9
  export type FabProps = {
10
+ /** The icon, one of the app's, drawn in `content.on.bg`. */
6
11
  name: IconName
12
+ /** Called when the button is pressed. */
7
13
  onPress?: () => void
14
+ /** Test identifier, forwarded to the native view on iOS — Android ignores it. */
8
15
  testID?: string
9
16
  }
10
17
 
@@ -12,6 +12,7 @@ export type GlassViewProps = ResponsiveViewSx & {
12
12
  children?: ReactNode
13
13
  /** Swells toward the finger on touch, carrying the children with it. Wrap the view in a `Pressable` for the press. */
14
14
  interactive?: boolean
15
+ /** Test identifier, forwarded to the view. */
15
16
  testID?: string
16
17
  /** Tints the material. Unset leaves it clear. */
17
18
  tint?: ColorToken
@@ -4,11 +4,17 @@ 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`. */
7
8
  export type HStackProps = ResponsiveSx & {
9
+ /** How the children line up vertically — `start` is the top, `end` the bottom. `start` by default. */
8
10
  align?: HStackAlign
11
+ /** Native views, laid out from the leading edge. */
9
12
  children?: ReactNode
13
+ /** Makes the stack pressable — a tap gesture on iOS, a `clickable` with Material's ripple on Android. */
10
14
  onPress?: () => void
15
+ /** The gap between children. Unset, SwiftUI puts its default spacing between them and Compose none. */
11
16
  spacing?: SpaceSize
17
+ /** Test identifier, forwarded to the native view. */
12
18
  testID?: string
13
19
  }
14
20
 
@@ -18,12 +18,20 @@ type DeclaredIconName = keyof KaraIcons & string
18
18
  */
19
19
  export type IconName = [DeclaredIconName] extends [never] ? string : DeclaredIconName
20
20
 
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
23
+ * drawable on Android. `RnIcon` draws the same asset outside one.
24
+ */
21
25
  export type IconProps = {
22
26
  /** What a screen reader says. Unset, the icon is decorative and hidden from it. */
23
27
  accessibilityLabel?: string
28
+ /** The icon's tint. `content.primary` by default. */
24
29
  color?: ColorToken
30
+ /** Which of the app's icons — a key of `KaraIcons`. */
25
31
  name: IconName
32
+ /** Its width and height, a spacing token. `s24` by default. */
26
33
  size?: SpaceSize
34
+ /** Test identifier, forwarded to the native view on iOS — Android ignores it. */
27
35
  testID?: string
28
36
  }
29
37
 
@@ -2,10 +2,15 @@ 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
  export type IconButtonProps = {
7
+ /** Draws the icon in `btn.content.disabled` and stops the button responding to presses. */
6
8
  disabled?: boolean
9
+ /** The icon, one of the app's. */
7
10
  name: IconName
11
+ /** Called when the button is pressed. */
8
12
  onPress?: () => void
13
+ /** Test identifier, forwarded to the native view on iOS — Android ignores it. */
9
14
  testID?: string
10
15
  }
11
16
 
@@ -10,7 +10,6 @@ export * from './color-picker'
10
10
  export * from './date-picker'
11
11
  export * from './divider'
12
12
  export * from './fab'
13
- export * from './flash-list'
14
13
  export * from './glass-view'
15
14
  export * from './h-stack'
16
15
  export * from './icon'
@@ -1,7 +1,9 @@
1
1
  import type {ReactNode} from 'react'
2
2
  import type {StyleProp, ViewStyle} from 'react-native'
3
3
 
4
+ /** A host for native atoms in a React Native layout — the SwiftUI / Compose `Host` an `@expo/ui` tree mounts into. */
4
5
  export type IslandProps = {
6
+ /** The native views it hosts. */
5
7
  children?: ReactNode
6
8
  /**
7
9
  * Sizes the island's height to its native content while the width still comes from the parent, which
@@ -15,7 +17,9 @@ export type IslandProps = {
15
17
  * hangs the main thread as soon as the views lay out on screen.
16
18
  */
17
19
  matchContents?: boolean
20
+ /** A React Native style for the host view — its size and placement. It needs a height when `matchContents` is off. */
18
21
  style?: StyleProp<ViewStyle>
22
+ /** Test identifier, forwarded to the host view on iOS — Android ignores it. */
19
23
  testID?: string
20
24
  }
21
25
 
@@ -10,12 +10,14 @@ import type {ReactNode} from 'react'
10
10
  * the children is one too many: on iOS it becomes an empty row of its own.
11
11
  */
12
12
  export type ListProps = {
13
+ /** The rows — native views, one row each. */
13
14
  children: ReactNode
14
15
  /**
15
16
  * Enables pull-to-refresh. Async on purpose — see `ScrollViewProps.onRefresh`: the spinner runs
16
17
  * until the promise settles, so neither platform needs a `refreshing` flag kept in sync by hand.
17
18
  */
18
19
  onRefresh?: () => Promise<void>
20
+ /** Test identifier, forwarded to the native view on iOS — Android ignores it. */
19
21
  testID?: string
20
22
  }
21
23
 
@@ -1,15 +1,27 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
+ /** One choice of a `Picker`. */
3
4
  export type PickerOption = {
5
+ /** What the user reads. */
4
6
  label: string
7
+ /** What `onValueChange` reports. It also keys the option, so it must be unique. */
5
8
  value: string
6
9
  }
7
10
 
11
+ /**
12
+ * A single choice from a list — a SwiftUI wheel picker on iOS, Material's exposed dropdown on Android. It brings its
13
+ * own host, so it renders outside `Screen` and `Island` as well.
14
+ */
8
15
  export type PickerProps = {
16
+ /** Stops the user changing the selection, and dims the picker. */
9
17
  disabled?: boolean
18
+ /** Called with the chosen option's `value`. */
10
19
  onValueChange?: (value: string) => void
20
+ /** The choices, in order. */
11
21
  options: PickerOption[]
22
+ /** Android: the dropdown field's test tag. iOS: `@expo/ui`'s wheel picker drops it. */
12
23
  testID?: string
24
+ /** The selected option's `value`. Controlled: pass back what `onValueChange` reports. */
13
25
  value: string
14
26
  }
15
27
 
@@ -3,12 +3,20 @@ import type {ReactNode} from 'react'
3
3
 
4
4
  import type {Haptic} from '../../haptics'
5
5
 
6
+ /**
7
+ * React Native's `Pressable`, styled through `sx` like a `Box`: it springs down to 96% while pressed and takes
8
+ * touches 8 points past its edges. The React Native tier — it needs no host.
9
+ */
6
10
  export type PressableProps = ResponsiveViewSx & {
11
+ /** The pressable content. */
7
12
  children?: ReactNode
13
+ /** Ignores presses — no `onPress`, no haptic, no spring. */
8
14
  disabled?: boolean
9
15
  /** Played on press. `true` is a `light` impact; a name picks the haptic, e.g. `selection` for a choice. */
10
16
  haptic?: boolean | Haptic
17
+ /** Called when the press is released, after the `haptic` plays. */
11
18
  onPress?: () => void
19
+ /** Test identifier, forwarded to the view. */
12
20
  testID?: string
13
21
  }
14
22
 
@@ -1,7 +1,13 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
+ /**
4
+ * A progress bar or ring, tinted `fill.accent.primary` — SwiftUI `ProgressView` / Compose's
5
+ * `LinearProgressIndicator` and `CircularProgressIndicator`.
6
+ */
3
7
  export type ProgressProps = {
8
+ /** A circular indicator instead of a bar. Off by default. */
4
9
  circular?: boolean
10
+ /** Between `0` and `1`. Leave it unset for an indeterminate indicator. */
5
11
  value?: number
6
12
  }
7
13
 
@@ -7,14 +7,11 @@ import type {RnFlatListProps} from './rn-flat-list.types'
7
7
  const NO_SX = {}
8
8
 
9
9
  /**
10
- * Flat list built on React Native's own `VirtualizedList` rather than FlashList.
11
- *
12
- * FlashList is the default for lists in this codebase and should stay that way for data rows — reach
13
- * for this only for the case it cannot serve: content that hosts native views. FlashList positions its
14
- * rows absolutely and recycles their cells, and an `@expo/ui` host survives neither — the hosted view
15
- * ends up drawn where the layout no longer is. `VirtualizedList` renders its cells in normal flow
16
- * inside a scroll view, using blank spacers for the range it has not rendered, so a hosted view stays
17
- * exactly where the layout put it. This is `SectionList`'s reason to exist too, without the sections.
10
+ * kara's list for React Native content: React Native's own `VirtualizedList`, styled through `sx`. It renders its
11
+ * cells in normal flow inside a scroll view, with blank spacers for the range it has not rendered, so a row that hosts
12
+ * native views keeps them where the layout put it. A recycling list (absolutely positioned, reused cells) cannot
13
+ * promise that — an `@expo/ui` host survives neither, and draws where the layout no longer is — which is why kara
14
+ * ships none.
18
15
  */
19
16
  export const RnFlatList = <TItem,>({content, data, renderItem, ...props}: RnFlatListProps<TItem>) => {
20
17
  const [style, rest] = useSplitSx(props as Record<string, unknown>)
@@ -23,6 +20,9 @@ export const RnFlatList = <TItem,>({content, data, renderItem, ...props}: RnFlat
23
20
  return (
24
21
  <RNFlatList<TItem>
25
22
  data={data}
23
+ // Android clips by default, and a host is measured after its first layout — at zero height it counts as
24
+ // off-screen, is detached, and its native view never comes back.
25
+ removeClippedSubviews={false}
26
26
  renderItem={renderItem}
27
27
  {...rest}
28
28
  contentContainerStyle={content === undefined ? undefined : contentStyle}
@@ -3,9 +3,11 @@ import type {FlatListProps as RNFlatListProps} from 'react-native'
3
3
 
4
4
  /**
5
5
  * Top-level `sx` styles the viewport; `content` styles the scrolling content container, so padding and
6
- * gap belong there — the same split as `RnScrollView`.
6
+ * gap belong there — the same split as `RnScrollView`. `removeClippedSubviews` is off unless you turn it on:
7
+ * Android's default would detach a row's host before it has measured itself, and the row would stay empty.
7
8
  */
8
9
  export type RnFlatListProps<TItem> = ResponsiveViewSx &
9
10
  Omit<RNFlatListProps<TItem>, 'contentContainerStyle' | 'style'> & {
11
+ /** Styles the scrolling content container — padding and gap belong here. */
10
12
  content?: ResponsiveViewSx
11
13
  }
@@ -13,9 +13,13 @@ import type {ScrollViewAxis} from '../scroll-view'
13
13
  */
14
14
  export type RnScrollViewProps = ResponsiveViewSx &
15
15
  Omit<RNScrollViewProps, 'contentContainerStyle' | 'horizontal' | 'style'> & {
16
+ /** The scroll direction. `vertical` by default. */
16
17
  axis?: ScrollViewAxis
18
+ /** The scrolling content. */
17
19
  children?: ReactNode
20
+ /** Styles the scrolling content container — padding and gap belong here. */
18
21
  content?: ResponsiveViewSx
22
+ /** Shows the scroll indicators. On by default. */
19
23
  showsIndicators?: boolean
20
24
  }
21
25