@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
@@ -27,6 +27,7 @@ export type SheetHeaderProps = {
27
27
  leadingItems?: NavigationBarItem[]
28
28
  /** A search field in place of the title (iOS). */
29
29
  search?: NavigationBarSearch
30
+ /** The title — in the bar, or above the content with a `large` `titleStyle`. */
30
31
  title?: string
31
32
  /** `center` by default. */
32
33
  titleStyle?: SheetTitleStyle
@@ -34,7 +35,9 @@ export type SheetHeaderProps = {
34
35
  trailingItems?: NavigationBarItem[]
35
36
  }
36
37
 
38
+ /** Imperative control of a `Sheet`, through its `ref`. */
37
39
  export type SheetRef = {
40
+ /** Dismisses it without calling `onDismiss`, which reports the user's dismissals only. */
38
41
  dismiss: () => Promise<void>
39
42
  /** Presents at a detent — the first by default. */
40
43
  present: (detentIndex?: number) => Promise<void>
@@ -50,6 +53,7 @@ export type SheetRef = {
50
53
  export type SheetProps = {
51
54
  /** The sheet's colour. `surface.primary` by default. */
52
55
  bg?: ColorToken
56
+ /** The content. A `<Sheet.Header>` among the direct children becomes the native header instead. */
53
57
  children: ReactNode
54
58
  /** Where the sheet can rest, smallest first. `['auto']` — fit the content — by default. */
55
59
  detents?: SheetDetent[]
@@ -65,15 +69,20 @@ export type SheetProps = {
65
69
  grabber?: boolean
66
70
  /** The user dismissed it — `visible` going false does not call this. */
67
71
  onDismiss: () => void
72
+ /** The sheet moved to another detent — its index in `detents`. */
68
73
  onDetentChange?: (detentIndex: number) => void
74
+ /** The sheet finished presenting. */
69
75
  onPresent?: () => void
76
+ /** `present`, `dismiss` and `resize`, for driving the sheet imperatively. */
70
77
  ref?: Ref<SheetRef>
71
78
  /**
72
79
  * The content is a scroll view (`<Sheet.ScrollView>`) the sheet drives: dragging past its top resizes the sheet,
73
80
  * and a header scrolls with the iOS 26 edge effect. Off by default — set it with a scrolling body.
74
81
  */
75
82
  scrollable?: boolean
83
+ /** Test identifier, forwarded to the native sheet. */
76
84
  testID?: string
85
+ /** Presents the sheet while true; going false dismisses it. */
77
86
  visible: boolean
78
87
  }
79
88
 
@@ -81,16 +90,26 @@ export type SheetScrollHandler = (event: NativeSyntheticEvent<NativeScrollEvent>
81
90
 
82
91
  /** What a scroll view inside a sheet needs: the room the header takes, the large title, the scroll listener. */
83
92
  export type SheetScrollContextValue = {
93
+ /** Render it first in the content. `null` unless the header's `titleStyle` is `large`. */
84
94
  largeTitle: ReactNode
95
+ /** Forward the scroll view's scroll events to it — it tracks the large title. Unset when there is none. */
85
96
  onScroll?: SheetScrollHandler
97
+ /** The room an overlaid header takes, to add to the content's top padding. `0` where the header sits above it. */
86
98
  topInset: number
87
99
  }
88
100
 
101
+ /**
102
+ * The body of a `scrollable` sheet: its content starts below an overlaid header, it carries a large title, and it
103
+ * reports its scroll to the sheet.
104
+ */
89
105
  export type SheetScrollViewProps = Pick<ScrollViewProps, 'keyboardShouldPersistTaps'> & {
106
+ /** The scrolling content, after the large title if there is one. */
90
107
  children: ReactNode
91
108
  /** Styles the scrolling content — padding and gap belong here, as with `RnScrollView`. */
92
109
  content?: ResponsiveViewSx
110
+ /** Called on every scroll event, after the sheet's own listener. */
93
111
  onScroll?: SheetScrollHandler
112
+ /** Test identifier, forwarded to the scroll view. */
94
113
  testID?: string
95
114
  }
96
115
 
@@ -1,9 +1,17 @@
1
1
  import type {ReactNode} from 'react'
2
2
 
3
+ /**
4
+ * A short message laid over the bottom of its parent, with an optional action. The React Native tier, drawn the same on
5
+ * both platforms: it needs no host, and it never hides itself.
6
+ */
3
7
  export type SnackbarProps = {
8
+ /** The action button's text, on the trailing side. No button without it. */
4
9
  actionLabel?: string
10
+ /** The message. */
5
11
  message: string
12
+ /** The action was pressed. The snackbar stays up — turn `visible` off to hide it. */
6
13
  onAction?: () => void
14
+ /** Shows the snackbar while true, fading it in and out. It sets no timer of its own. */
7
15
  visible: boolean
8
16
  }
9
17
 
@@ -1,14 +1,134 @@
1
- import {warnUnsupported} from '../../platform-support'
2
- import type {SwipeActionsComponent, SwipeActionsProps} from './swipe-actions.types'
1
+ import {FilledIconButton} from '@expo/ui/jetpack-compose'
2
+ import {size} from '@expo/ui/jetpack-compose/modifiers'
3
+ import {SwipeActionsBox, type SwipeActionsBoxEdge} from '@hzblj/kara-native/jetpack-compose'
4
+ import {spacing, useAppTheme} from '@hzblj/kara-theme'
5
+ import {useCallback, useEffect, useId, useState, useSyncExternalStore} from 'react'
3
6
 
4
- /** Passes the row straight through, so losing the gesture never costs you the content. */
5
- const SwipeActionsRoot = ({children}: SwipeActionsProps) => {
6
- warnUnsupported('SwipeActions', 'Android')
7
+ import {HStack} from '../../atoms/h-stack'
8
+ import {Icon} from '../../atoms/icon'
9
+ import {Text} from '../../atoms/text'
10
+ import {VStack} from '../../atoms/v-stack'
11
+ import {useHostGuard} from '../../host-context'
12
+ import type {SwipeAction, SwipeActionsComponent, SwipeActionsProps} from './swipe-actions.types'
7
13
 
8
- return children
14
+ /** A Material 3 Expressive wide icon button: wider than tall, a stadium, the label under it. */
15
+ const BUTTON = [size(52, 32)]
16
+
17
+ /**
18
+ * Which row is open, app-wide — SwiftUI's list keeps one row open, and so does this: opening one closes the last.
19
+ * React state cannot be shared between rows that know nothing of each other, so it lives in the module.
20
+ */
21
+ let openRow: string | null = null
22
+
23
+ const listeners = new Set<() => void>()
24
+
25
+ const setOpenRow = (row: string | null) => {
26
+ if (openRow === row) {
27
+ return
28
+ }
29
+
30
+ openRow = row
31
+
32
+ for (const listener of listeners) {
33
+ listener()
34
+ }
35
+ }
36
+
37
+ const subscribe = (listener: () => void) => {
38
+ listeners.add(listener)
39
+
40
+ return () => {
41
+ listeners.delete(listener)
42
+ }
43
+ }
44
+
45
+ const getOpenRow = () => openRow
46
+
47
+ type ActionButtonProps = {action: SwipeAction; onDone: () => void}
48
+
49
+ const ActionButton = ({action, onDone}: ActionButtonProps) => {
50
+ const theme = useAppTheme()
51
+ const fill = action.tint ?? (action.destructive ? 'fill.destructive.primary' : 'fill.accent.primary')
52
+ const content = !action.tint && action.destructive ? 'content.on.destructive' : 'content.on.bg'
53
+
54
+ const onClick = useCallback(() => {
55
+ onDone()
56
+ action.onPress()
57
+ }, [action, onDone])
58
+
59
+ return (
60
+ <VStack align="center" spacing="s2">
61
+ <FilledIconButton
62
+ colors={{containerColor: theme.color(fill), contentColor: theme.color(content)}}
63
+ modifiers={BUTTON}
64
+ onClick={onClick}
65
+ >
66
+ <Icon accessibilityLabel={action.label} color={content} name={action.icon} size="s20" />
67
+ </FilledIconButton>
68
+ <Text color="content.secondary" variant="labelMedium">
69
+ {action.label}
70
+ </Text>
71
+ </VStack>
72
+ )
9
73
  }
10
74
 
11
- /** The buttons belong to a gesture that does not exist here, so they are dropped. */
12
- const SwipeActionsGroup = () => null
75
+ const Actions = ({actions, onDone}: {actions: SwipeAction[]; onDone: () => void}) => (
76
+ <HStack align="center" spacing="s8">
77
+ {actions.map(action => (
78
+ <ActionButton action={action} key={action.label} onDone={onDone} />
79
+ ))}
80
+ </HStack>
81
+ )
82
+
83
+ const close = () => setOpenRow(null)
13
84
 
14
- export const SwipeActions: SwipeActionsComponent = Object.assign(SwipeActionsRoot, {Actions: SwipeActionsGroup})
85
+ /**
86
+ * kara's `SwipeActionsBox`: the row slides on an anchored drag and the buttons show in the strip it leaves, fading in.
87
+ * Controlled from here so one row is open at a time; a press closes the row before the action runs.
88
+ */
89
+ export const SwipeActions: SwipeActionsComponent = ({children, leading = [], trailing = []}: SwipeActionsProps) => {
90
+ useHostGuard('SwipeActions')
91
+ const row = useId()
92
+ const isOpenRow = useSyncExternalStore(subscribe, getOpenRow) === row
93
+ const [edge, setEdge] = useState<SwipeActionsBoxEdge | null>(null)
94
+
95
+ const onOpenChange = useCallback(
96
+ (next: SwipeActionsBoxEdge | null) => {
97
+ setEdge(next)
98
+
99
+ if (next) {
100
+ setOpenRow(row)
101
+ } else if (openRow === row) {
102
+ setOpenRow(null)
103
+ }
104
+ },
105
+ [row]
106
+ )
107
+
108
+ // A row that goes away open must not keep the others from opening.
109
+ useEffect(
110
+ () => () => {
111
+ if (openRow === row) {
112
+ setOpenRow(null)
113
+ }
114
+ },
115
+ [row]
116
+ )
117
+
118
+ return (
119
+ <SwipeActionsBox actionsSpacing={spacing.s12} onOpenChange={onOpenChange} openEdge={isOpenRow ? edge : null}>
120
+ {leading.length > 0 ? (
121
+ <SwipeActionsBox.Leading>
122
+ <Actions actions={leading} onDone={close} />
123
+ </SwipeActionsBox.Leading>
124
+ ) : null}
125
+ {trailing.length > 0 ? (
126
+ <SwipeActionsBox.Trailing>
127
+ {/* The first action sits outermost — at the right-hand end of a trailing row. */}
128
+ <Actions actions={[...trailing].reverse()} onDone={close} />
129
+ </SwipeActionsBox.Trailing>
130
+ ) : null}
131
+ {children}
132
+ </SwipeActionsBox>
133
+ )
134
+ }
@@ -1,18 +1,52 @@
1
- import {SwipeActions as SwiftUISwipeActions} from '@expo/ui/swift-ui'
1
+ import {Button, Image, Label, SwipeActions as SwiftUISwipeActions} from '@expo/ui/swift-ui'
2
+ import {tint} from '@expo/ui/swift-ui/modifiers'
3
+ import {iconResourceName} from '@hzblj/kara-native'
4
+ import {useAppTheme} from '@hzblj/kara-theme'
2
5
 
3
6
  import {useHostGuard} from '../../host-context'
4
- import type {SwipeActionsComponent, SwipeActionsGroupProps, SwipeActionsProps} from './swipe-actions.types'
7
+ import type {SwipeAction, SwipeActionsComponent, SwipeActionsProps} from './swipe-actions.types'
5
8
 
6
- const SwipeActionsRoot = ({children, testID}: SwipeActionsProps) => {
7
- useHostGuard('SwipeActions')
9
+ type ActionsProps = {actions: SwipeAction[]; allowsFullSwipe: boolean; edge: 'leading' | 'trailing'}
10
+
11
+ /**
12
+ * The edge's buttons, a `Label` each — iOS 26 draws its icon on a round button with the title under it; earlier
13
+ * versions, in a row of the usual height, the icon alone. No `destructive` role: SwiftUI would animate the row out by itself, and whether it goes is
14
+ * the list's data to say.
15
+ */
16
+ const Actions = ({actions, allowsFullSwipe, edge}: ActionsProps) => {
17
+ const theme = useAppTheme()
8
18
 
9
- return <SwiftUISwipeActions testID={testID}>{children}</SwiftUISwipeActions>
19
+ return (
20
+ <SwiftUISwipeActions.Actions allowsFullSwipe={allowsFullSwipe} edge={edge}>
21
+ {actions.map(action => (
22
+ <Button
23
+ key={action.label}
24
+ modifiers={[
25
+ tint(theme.color(action.tint ?? (action.destructive ? 'fill.destructive.primary' : 'fill.accent.primary'))),
26
+ ]}
27
+ onPress={action.onPress}
28
+ >
29
+ <Label icon={<Image assetName={iconResourceName(action.icon)} />} title={action.label} />
30
+ </Button>
31
+ ))}
32
+ </SwiftUISwipeActions.Actions>
33
+ )
10
34
  }
11
35
 
12
- const SwipeActionsGroup = ({allowsFullSwipe = true, children, edge = 'trailing'}: SwipeActionsGroupProps) => (
13
- <SwiftUISwipeActions.Actions allowsFullSwipe={allowsFullSwipe} edge={edge}>
14
- {children}
15
- </SwiftUISwipeActions.Actions>
16
- )
36
+ export const SwipeActions: SwipeActionsComponent = ({
37
+ allowsFullSwipe = true,
38
+ children,
39
+ leading = [],
40
+ testID,
41
+ trailing = [],
42
+ }: SwipeActionsProps) => {
43
+ useHostGuard('SwipeActions')
17
44
 
18
- export const SwipeActions: SwipeActionsComponent = Object.assign(SwipeActionsRoot, {Actions: SwipeActionsGroup})
45
+ return (
46
+ <SwiftUISwipeActions testID={testID}>
47
+ {children}
48
+ {leading.length > 0 ? <Actions actions={leading} allowsFullSwipe={allowsFullSwipe} edge="leading" /> : null}
49
+ {trailing.length > 0 ? <Actions actions={trailing} allowsFullSwipe={allowsFullSwipe} edge="trailing" /> : null}
50
+ </SwiftUISwipeActions>
51
+ )
52
+ }
@@ -1,24 +1,47 @@
1
+ import type {ColorToken} from '@hzblj/kara-theme'
1
2
  import type {ReactNode} from 'react'
2
3
 
3
- export type SwipeActionsEdge = 'leading' | 'trailing'
4
+ import type {IconName} from '../../atoms/icon'
5
+
6
+ /** One button a swiped row uncovers. */
7
+ export type SwipeAction = {
8
+ /** Fills the button with `fill.destructive.primary` unless `tint` says otherwise. */
9
+ destructive?: boolean
10
+ /** One of the app's icons, on the button — a round one on iOS 26, as in Mail, a filled stadium on Android. */
11
+ icon: IconName
12
+ /**
13
+ * The action's name, written under the button — before iOS 26 a row of the usual height shows the icon alone and
14
+ * keeps the label for VoiceOver. Actions are keyed by it, so keep labels unique within one row.
15
+ */
16
+ label: string
17
+ /** Called when the button is pressed. The row closes by itself. */
18
+ onPress: () => void
19
+ /**
20
+ * The button's fill — `fill.destructive.primary` for a destructive action, `fill.accent.primary` otherwise. The icon
21
+ * on it is white, so pick a fill that holds white in both appearances.
22
+ */
23
+ tint?: ColorToken
24
+ }
4
25
 
5
26
  /**
6
- * Swipe-to-reveal buttons on a row. iOS only — Compose ships no equivalent, so on Android the
7
- * row renders exactly as it would without this wrapper and the action buttons are dropped.
8
- * Degrading that way rather than to `null` keeps the content on screen.
27
+ * A list row with buttons behind it, uncovered by a swipe — SwiftUI's `swipeActions` on iOS, and on Android, where
28
+ * Compose has none, kara's own: the row slides on an anchored drag and the buttons show in the strip it leaves. One
29
+ * row is open at a time on both; a tap on an open row closes it. A native organism — it belongs inside a `List`.
9
30
  */
10
31
  export type SwipeActionsProps = {
11
- children: ReactNode
12
- testID?: string
13
- }
14
-
15
- export type SwipeActionsGroupProps = {
16
- /** Whether swiping all the way performs the first action in the group. */
32
+ /**
33
+ * iOS: a swipe all the way across performs the edge's first action, as in Mail. Android has no full swipe. `true`
34
+ * by default, as in SwiftUI.
35
+ */
17
36
  allowsFullSwipe?: boolean
37
+ /** The row. */
18
38
  children: ReactNode
19
- edge?: SwipeActionsEdge
39
+ /** The buttons at the leading edge, uncovered by a swipe toward the trailing one. The first sits outermost. */
40
+ leading?: SwipeAction[]
41
+ /** Test identifier. iOS: on the row. Android: the row is a Compose node, reach it by its content. */
42
+ testID?: string
43
+ /** The buttons at the trailing edge, uncovered by a swipe toward the leading one. The first sits outermost. */
44
+ trailing?: SwipeAction[]
20
45
  }
21
46
 
22
- export type SwipeActionsComponent = ((props: SwipeActionsProps) => ReactNode) & {
23
- Actions: (props: SwipeActionsGroupProps) => ReactNode
24
- }
47
+ export type SwipeActionsComponent = (props: SwipeActionsProps) => ReactNode
@@ -1,24 +1,45 @@
1
1
  import {Column, Host} from '@expo/ui/jetpack-compose'
2
+ import {padding} from '@expo/ui/jetpack-compose/modifiers'
2
3
  import {useSxModifiers} from '@hzblj/kara-sx'
3
4
  import {useAppTheme} from '@hzblj/kara-theme'
4
- import {StyleSheet} from 'react-native'
5
+ import {useCallback, useMemo, useRef, useState} from 'react'
6
+ import {StatusBar, StyleSheet, View} from 'react-native'
5
7
  import {HostBoundary} from '../../host-context'
6
8
  import type {ScreenComponent, ScreenProps} from './screen.types'
7
9
 
10
+ /**
11
+ * The content keeps clear of as much of the status bar as the screen covers — all of it when nothing sits above the
12
+ * screen, none under a navigation bar — the way a SwiftUI host keeps to the safe area. Compose would draw under it:
13
+ * the app is edge to edge, and `Host` pads for nothing. The background still runs to the edge.
14
+ */
8
15
  export const Screen: ScreenComponent = ({bg = 'surface.base', children, ignoreKeyboardInset, testID}: ScreenProps) => {
9
16
  const theme = useAppTheme()
10
- const modifiers = useSxModifiers({bg, tall: true, wide: true})
17
+ const frame = useRef<View>(null)
18
+ const [statusBarOverlap, setStatusBarOverlap] = useState(0)
19
+ const sx = useSxModifiers({bg, tall: true, wide: true})
20
+ const modifiers = useMemo(
21
+ () => (statusBarOverlap > 0 ? [...sx, padding(0, statusBarOverlap, 0, 0)] : sx),
22
+ [statusBarOverlap, sx]
23
+ )
24
+
25
+ const onLayout = useCallback(() => {
26
+ frame.current?.measureInWindow((_x, y) => {
27
+ setStatusBarOverlap(Math.max(0, Math.round((StatusBar.currentHeight ?? 0) - y)))
28
+ })
29
+ }, [])
11
30
 
12
31
  return (
13
- <Host
14
- colorScheme={theme.dark ? 'dark' : 'light'}
15
- ignoreSafeAreaKeyboardInsets={ignoreKeyboardInset}
16
- seedColor={theme.materialSeed}
17
- style={StyleSheet.absoluteFill}
18
- >
19
- <Column modifiers={modifiers}>
20
- <HostBoundary>{children}</HostBoundary>
21
- </Column>
22
- </Host>
32
+ <View onLayout={onLayout} ref={frame} style={StyleSheet.absoluteFill} testID={testID}>
33
+ <Host
34
+ colorScheme={theme.dark ? 'dark' : 'light'}
35
+ ignoreSafeAreaKeyboardInsets={ignoreKeyboardInset}
36
+ seedColor={theme.materialSeed}
37
+ style={StyleSheet.absoluteFill}
38
+ >
39
+ <Column modifiers={modifiers}>
40
+ <HostBoundary>{children}</HostBoundary>
41
+ </Column>
42
+ </Host>
43
+ </View>
23
44
  )
24
45
  }
@@ -1,10 +1,19 @@
1
1
  import type {Sx} from '@hzblj/kara-sx'
2
2
  import type {ReactNode} from 'react'
3
3
 
4
+ /**
5
+ * The full-bleed native host a screen starts with — a SwiftUI or Compose `Host` filling its parent, so native atoms
6
+ * render anywhere inside it. The background runs to the edges; the content keeps clear of the status bar where the
7
+ * screen covers it (no navigation bar above), on Android as SwiftUI's safe area does on iOS.
8
+ */
4
9
  export type ScreenProps = {
10
+ /** The screen's colour. `surface.base` by default. */
5
11
  bg?: Sx['bg']
12
+ /** Laid out in a column that fills the screen. */
6
13
  children?: ReactNode
14
+ /** Keeps the content where it is when the keyboard opens, instead of avoiding it. Android reads it once, on mount. */
7
15
  ignoreKeyboardInset?: boolean
16
+ /** Test identifier — on the host view on iOS, on the view around it on Android. */
8
17
  testID?: string
9
18
  }
10
19
 
@@ -1,31 +0,0 @@
1
- "use strict";
2
-
3
- import { useSplitSx, useSx } from '@hzblj/kara-sx';
4
- import { FlashList as ShopifyFlashList } from '@shopify/flash-list';
5
- import { jsx as _jsx } from "react/jsx-runtime";
6
- /** Stands in for an absent `content`, because a hook cannot be called conditionally. */
7
- const NO_SX = {};
8
-
9
- /**
10
- * The default list in this codebase: recycles its rows, so it holds a long list at a steady cost.
11
- *
12
- * `RnFlatList` and `RnSectionList` are the alternatives, and only for rows that host native views —
13
- * see either one for why recycling and absolute positioning cannot carry an `@expo/ui` host.
14
- */
15
- export const FlashList = ({
16
- content,
17
- data,
18
- renderItem,
19
- ...props
20
- }) => {
21
- const [style, rest] = useSplitSx(props);
22
- const contentStyle = useSx(content ?? NO_SX);
23
- return /*#__PURE__*/_jsx(ShopifyFlashList, {
24
- data: data,
25
- renderItem: renderItem,
26
- ...rest,
27
- contentContainerStyle: content === undefined ? undefined : contentStyle,
28
- style: style
29
- });
30
- };
31
- //# sourceMappingURL=flash-list.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["useSplitSx","useSx","FlashList","ShopifyFlashList","jsx","_jsx","NO_SX","content","data","renderItem","props","style","rest","contentStyle","contentContainerStyle","undefined"],"sourceRoot":"../../../../src","sources":["atoms/flash-list/flash-list.tsx"],"mappings":";;AAAA,SAAQA,UAAU,EAAEC,KAAK,QAAO,gBAAgB;AAChD,SAAQC,SAAS,IAAIC,gBAAgB,QAAO,qBAAqB;AAAA,SAAAC,GAAA,IAAAC,IAAA;AAIjE;AACA,MAAMC,KAAK,GAAG,CAAC,CAAC;;AAEhB;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMJ,SAAS,GAAGA,CAAS;EAACK,OAAO;EAAEC,IAAI;EAAEC,UAAU;EAAE,GAAGC;AAA4B,CAAC,KAAK;EACjG,MAAM,CAACC,KAAK,EAAEC,IAAI,CAAC,GAAGZ,UAAU,CAACU,KAAgC,CAAC;EAClE,MAAMG,YAAY,GAAGZ,KAAK,CAACM,OAAO,IAAID,KAAK,CAAC;EAE5C,oBACED,IAAA,CAACF,gBAAgB;IACfK,IAAI,EAAEA,IAAK;IACXC,UAAU,EAAEA,UAAW;IAAA,GACnBG,IAAI;IACRE,qBAAqB,EAAEP,OAAO,KAAKQ,SAAS,GAAGA,SAAS,GAAGF,YAAa;IACxEF,KAAK,EAAEA;EAAM,CACd,CAAC;AAEN,CAAC","ignoreList":[]}
@@ -1,4 +0,0 @@
1
- "use strict";
2
-
3
- export {};
4
- //# sourceMappingURL=flash-list.types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":[],"sourceRoot":"../../../../src","sources":["atoms/flash-list/flash-list.types.ts"],"mappings":"","ignoreList":[]}
@@ -1,5 +0,0 @@
1
- "use strict";
2
-
3
- export * from "./flash-list.js";
4
- export * from "./flash-list.types.js";
5
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":[],"sourceRoot":"../../../../src","sources":["atoms/flash-list/index.ts"],"mappings":";;AAAA,cAAc,iBAAc;AAC5B,cAAc,uBAAoB","ignoreList":[]}
@@ -1,28 +0,0 @@
1
- "use strict";
2
-
3
- import { SegmentedControl as NativeSegmentedControl } from '@expo/ui/community/segmented-control';
4
- import { useAppTheme } from '@hzblj/kara-theme';
5
- import { useCallback } from 'react';
6
- import { jsx as _jsx } from "react/jsx-runtime";
7
- export const SegmentedControl = ({
8
- disabled = false,
9
- onChange,
10
- selectedIndex,
11
- testID,
12
- values
13
- }) => {
14
- const theme = useAppTheme();
15
- const onNativeChange = useCallback(event => {
16
- onChange?.(event.nativeEvent.selectedSegmentIndex, event.nativeEvent.value);
17
- }, [onChange]);
18
- return /*#__PURE__*/_jsx(NativeSegmentedControl, {
19
- appearance: theme.dark ? 'dark' : 'light',
20
- enabled: !disabled,
21
- onChange: onNativeChange,
22
- selectedIndex: selectedIndex,
23
- testID: testID,
24
- tintColor: theme.colorString('surface.primary'),
25
- values: values
26
- });
27
- };
28
- //# sourceMappingURL=segmented-control.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["SegmentedControl","NativeSegmentedControl","useAppTheme","useCallback","jsx","_jsx","disabled","onChange","selectedIndex","testID","values","theme","onNativeChange","event","nativeEvent","selectedSegmentIndex","value","appearance","dark","enabled","tintColor","colorString"],"sourceRoot":"../../../../src","sources":["atoms/segmented-control/segmented-control.tsx"],"mappings":";;AAAA,SAAQA,gBAAgB,IAAIC,sBAAsB,QAAO,sCAAsC;AAC/F,SAAQC,WAAW,QAAO,mBAAmB;AAC7C,SAAQC,WAAW,QAAO,OAAO;AAAA,SAAAC,GAAA,IAAAC,IAAA;AAIjC,OAAO,MAAML,gBAA2C,GAAGA,CAAC;EAC1DM,QAAQ,GAAG,KAAK;EAChBC,QAAQ;EACRC,aAAa;EACbC,MAAM;EACNC;AACqB,CAAC,KAAK;EAC3B,MAAMC,KAAK,GAAGT,WAAW,CAAC,CAAC;EAE3B,MAAMU,cAAc,GAAGT,WAAW,CAC/BU,KAAmE,IAAK;IACvEN,QAAQ,GAAGM,KAAK,CAACC,WAAW,CAACC,oBAAoB,EAAEF,KAAK,CAACC,WAAW,CAACE,KAAK,CAAC;EAC7E,CAAC,EACD,CAACT,QAAQ,CACX,CAAC;EAED,oBACEF,IAAA,CAACJ,sBAAsB;IACrBgB,UAAU,EAAEN,KAAK,CAACO,IAAI,GAAG,MAAM,GAAG,OAAQ;IAC1CC,OAAO,EAAE,CAACb,QAAS;IACnBC,QAAQ,EAAEK,cAAe;IACzBJ,aAAa,EAAEA,aAAc;IAC7BC,MAAM,EAAEA,MAAO;IACfW,SAAS,EAAET,KAAK,CAACU,WAAW,CAAC,iBAAiB,CAAE;IAChDX,MAAM,EAAEA;EAAO,CAChB,CAAC;AAEN,CAAC","ignoreList":[]}
@@ -1,8 +0,0 @@
1
- import type { FlashListProps } from './flash-list.types';
2
- /**
3
- * The default list in this codebase: recycles its rows, so it holds a long list at a steady cost.
4
- *
5
- * `RnFlatList` and `RnSectionList` are the alternatives, and only for rows that host native views —
6
- * see either one for why recycling and absolute positioning cannot carry an `@expo/ui` host.
7
- */
8
- export declare const FlashList: <TItem>({ content, data, renderItem, ...props }: FlashListProps<TItem>) => import("react").JSX.Element;
@@ -1,18 +0,0 @@
1
- import type { ResponsiveViewSx } from '@hzblj/kara-sx';
2
- import type { FlashListRef, FlashListProps as ShopifyFlashListProps } from '@shopify/flash-list';
3
- import type { Ref } from 'react';
4
- /**
5
- * Top-level `sx` styles the viewport; `content` styles the scrolling content container — the same split
6
- * as `RnScrollView` and the React Native lists.
7
- *
8
- * Padding belongs in `content` here and nowhere else. FlashList measures its rows against its own
9
- * `style`, and padding there throws that measurement off; its docs put it as "margin is okay but padding
10
- * is not". `content` has no such limit, because it lands on the scroll view's content container.
11
- *
12
- * `ref` is declared because this is a wrapper rather than the re-export it used to be — React 19 passes
13
- * it as a plain prop, and a `FlashListRef` is how a caller reaches `scrollToIndex` and friends.
14
- */
15
- export type FlashListProps<TItem> = ResponsiveViewSx & Omit<ShopifyFlashListProps<TItem>, 'contentContainerStyle' | 'style'> & {
16
- content?: ResponsiveViewSx;
17
- ref?: Ref<FlashListRef<TItem>>;
18
- };
@@ -1,2 +0,0 @@
1
- export * from './flash-list';
2
- export * from './flash-list.types';
@@ -1,28 +0,0 @@
1
- import {useSplitSx, useSx} from '@hzblj/kara-sx'
2
- import {FlashList as ShopifyFlashList} from '@shopify/flash-list'
3
-
4
- import type {FlashListProps} from './flash-list.types'
5
-
6
- /** Stands in for an absent `content`, because a hook cannot be called conditionally. */
7
- const NO_SX = {}
8
-
9
- /**
10
- * The default list in this codebase: recycles its rows, so it holds a long list at a steady cost.
11
- *
12
- * `RnFlatList` and `RnSectionList` are the alternatives, and only for rows that host native views —
13
- * see either one for why recycling and absolute positioning cannot carry an `@expo/ui` host.
14
- */
15
- export const FlashList = <TItem,>({content, data, renderItem, ...props}: FlashListProps<TItem>) => {
16
- const [style, rest] = useSplitSx(props as Record<string, unknown>)
17
- const contentStyle = useSx(content ?? NO_SX)
18
-
19
- return (
20
- <ShopifyFlashList
21
- data={data}
22
- renderItem={renderItem}
23
- {...rest}
24
- contentContainerStyle={content === undefined ? undefined : contentStyle}
25
- style={style}
26
- />
27
- )
28
- }
@@ -1,20 +0,0 @@
1
- import type {ResponsiveViewSx} from '@hzblj/kara-sx'
2
- import type {FlashListRef, FlashListProps as ShopifyFlashListProps} from '@shopify/flash-list'
3
- import type {Ref} from 'react'
4
-
5
- /**
6
- * Top-level `sx` styles the viewport; `content` styles the scrolling content container — the same split
7
- * as `RnScrollView` and the React Native lists.
8
- *
9
- * Padding belongs in `content` here and nowhere else. FlashList measures its rows against its own
10
- * `style`, and padding there throws that measurement off; its docs put it as "margin is okay but padding
11
- * is not". `content` has no such limit, because it lands on the scroll view's content container.
12
- *
13
- * `ref` is declared because this is a wrapper rather than the re-export it used to be — React 19 passes
14
- * it as a plain prop, and a `FlashListRef` is how a caller reaches `scrollToIndex` and friends.
15
- */
16
- export type FlashListProps<TItem> = ResponsiveViewSx &
17
- Omit<ShopifyFlashListProps<TItem>, 'contentContainerStyle' | 'style'> & {
18
- content?: ResponsiveViewSx
19
- ref?: Ref<FlashListRef<TItem>>
20
- }
@@ -1,2 +0,0 @@
1
- export * from './flash-list'
2
- export * from './flash-list.types'