@hzblj/kara-components 0.10.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 (185) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/lib/module/atoms/activity-indicator/activity-indicator.android.js +6 -1
  3. package/lib/module/atoms/activity-indicator/activity-indicator.android.js.map +1 -1
  4. package/lib/module/atoms/activity-indicator/activity-indicator.ios.js +2 -0
  5. package/lib/module/atoms/activity-indicator/activity-indicator.ios.js.map +1 -1
  6. package/lib/module/atoms/avatar/avatar.js +5 -0
  7. package/lib/module/atoms/avatar/avatar.js.map +1 -1
  8. package/lib/module/atoms/button/button-tokens.js +2 -2
  9. package/lib/module/atoms/button/button-tokens.js.map +1 -1
  10. package/lib/module/atoms/button/button.android.js +6 -2
  11. package/lib/module/atoms/button/button.android.js.map +1 -1
  12. package/lib/module/atoms/button/button.ios.js +7 -2
  13. package/lib/module/atoms/button/button.ios.js.map +1 -1
  14. package/lib/module/atoms/card/card.android.js +5 -3
  15. package/lib/module/atoms/card/card.android.js.map +1 -1
  16. package/lib/module/atoms/card/card.ios.js +7 -3
  17. package/lib/module/atoms/card/card.ios.js.map +1 -1
  18. package/lib/module/atoms/checkbox/checkbox.android.js +1 -1
  19. package/lib/module/atoms/checkbox/checkbox.android.js.map +1 -1
  20. package/lib/module/atoms/fab/fab.android.js +9 -1
  21. package/lib/module/atoms/fab/fab.android.js.map +1 -1
  22. package/lib/module/atoms/fab/fab.ios.js +9 -3
  23. package/lib/module/atoms/fab/fab.ios.js.map +1 -1
  24. package/lib/module/atoms/glass-view/glass-view.android.js +1 -1
  25. package/lib/module/atoms/glass-view/glass-view.android.js.map +1 -1
  26. package/lib/module/atoms/h-stack/h-stack.ios.js +5 -3
  27. package/lib/module/atoms/h-stack/h-stack.ios.js.map +1 -1
  28. package/lib/module/atoms/icon-button/icon-button.android.js +9 -0
  29. package/lib/module/atoms/icon-button/icon-button.android.js.map +1 -1
  30. package/lib/module/atoms/icon-button/icon-button.ios.js +9 -2
  31. package/lib/module/atoms/icon-button/icon-button.ios.js.map +1 -1
  32. package/lib/module/atoms/image/image.js +4 -4
  33. package/lib/module/atoms/image/image.js.map +1 -1
  34. package/lib/module/atoms/pressable/pressable.js +26 -25
  35. package/lib/module/atoms/pressable/pressable.js.map +1 -1
  36. package/lib/module/atoms/progress/progress.android.js +7 -0
  37. package/lib/module/atoms/progress/progress.android.js.map +1 -1
  38. package/lib/module/atoms/progress/progress.ios.js +6 -1
  39. package/lib/module/atoms/progress/progress.ios.js.map +1 -1
  40. package/lib/module/atoms/segmented-control/segmented-control.android.js +1 -1
  41. package/lib/module/atoms/segmented-control/segmented-control.android.js.map +1 -1
  42. package/lib/module/atoms/segmented-control/segmented-control.ios.js +1 -1
  43. package/lib/module/atoms/segmented-control/segmented-control.ios.js.map +1 -1
  44. package/lib/module/atoms/skeleton/skeleton.js +20 -6
  45. package/lib/module/atoms/skeleton/skeleton.js.map +1 -1
  46. package/lib/module/atoms/surface/surface.android.js +5 -3
  47. package/lib/module/atoms/surface/surface.android.js.map +1 -1
  48. package/lib/module/atoms/surface/surface.ios.js +7 -3
  49. package/lib/module/atoms/surface/surface.ios.js.map +1 -1
  50. package/lib/module/atoms/switch/switch.android.js +1 -1
  51. package/lib/module/atoms/switch/switch.android.js.map +1 -1
  52. package/lib/module/atoms/text/text.android.js +5 -3
  53. package/lib/module/atoms/text/text.android.js.map +1 -1
  54. package/lib/module/atoms/text/text.ios.js +6 -4
  55. package/lib/module/atoms/text/text.ios.js.map +1 -1
  56. package/lib/module/atoms/v-stack/v-stack.ios.js +5 -3
  57. package/lib/module/atoms/v-stack/v-stack.ios.js.map +1 -1
  58. package/lib/module/atoms/z-stack/z-stack.ios.js +5 -3
  59. package/lib/module/atoms/z-stack/z-stack.ios.js.map +1 -1
  60. package/lib/module/glyph/glyph.android.js +2 -0
  61. package/lib/module/glyph/glyph.android.js.map +1 -1
  62. package/lib/module/glyph/glyph.ios.js +5 -2
  63. package/lib/module/glyph/glyph.ios.js.map +1 -1
  64. package/lib/module/molecules/collapsible/collapsible.android.js +18 -8
  65. package/lib/module/molecules/collapsible/collapsible.android.js.map +1 -1
  66. package/lib/module/molecules/labeled-content/labeled-content.js +4 -1
  67. package/lib/module/molecules/labeled-content/labeled-content.js.map +1 -1
  68. package/lib/module/molecules/link/link.js +4 -1
  69. package/lib/module/molecules/link/link.js.map +1 -1
  70. package/lib/module/molecules/list-item/list-item.js +4 -1
  71. package/lib/module/molecules/list-item/list-item.js.map +1 -1
  72. package/lib/module/molecules/navigation-bar/navigation-bar.android.js +1 -1
  73. package/lib/module/molecules/navigation-bar/navigation-bar.android.js.map +1 -1
  74. package/lib/module/molecules/navigation-bar/navigation-bar.ios.js +1 -1
  75. package/lib/module/molecules/navigation-bar/navigation-bar.ios.js.map +1 -1
  76. package/lib/module/molecules/rn-list-item/rn-list-item.js +10 -2
  77. package/lib/module/molecules/rn-list-item/rn-list-item.js.map +1 -1
  78. package/lib/module/molecules/scroll-edge-container/scroll-edge-container.android.js +3 -4
  79. package/lib/module/molecules/scroll-edge-container/scroll-edge-container.android.js.map +1 -1
  80. package/lib/module/molecules/stepper/stepper.ios.js +6 -2
  81. package/lib/module/molecules/stepper/stepper.ios.js.map +1 -1
  82. package/lib/module/motion.js +19 -0
  83. package/lib/module/motion.js.map +1 -0
  84. package/lib/module/organisms/fab-menu/fab-menu.android.js +8 -5
  85. package/lib/module/organisms/fab-menu/fab-menu.android.js.map +1 -1
  86. package/lib/module/organisms/fab-menu/fab-menu.ios.js +5 -2
  87. package/lib/module/organisms/fab-menu/fab-menu.ios.js.map +1 -1
  88. package/lib/module/organisms/snackbar/snackbar.js +142 -17
  89. package/lib/module/organisms/snackbar/snackbar.js.map +1 -1
  90. package/lib/module/organisms/swipe-actions/swipe-actions.android.js +1 -1
  91. package/lib/module/organisms/swipe-actions/swipe-actions.android.js.map +1 -1
  92. package/lib/module/use-stack-modifiers.js +10 -8
  93. package/lib/module/use-stack-modifiers.js.map +1 -1
  94. package/lib/typescript/atoms/activity-indicator/activity-indicator.types.d.ts +2 -0
  95. package/lib/typescript/atoms/avatar/avatar.types.d.ts +5 -0
  96. package/lib/typescript/atoms/button/button-tokens.d.ts +1 -1
  97. package/lib/typescript/atoms/button/button.types.d.ts +2 -0
  98. package/lib/typescript/atoms/card/card.types.d.ts +2 -2
  99. package/lib/typescript/atoms/fab/fab.types.d.ts +8 -1
  100. package/lib/typescript/atoms/h-stack/h-stack.types.d.ts +6 -3
  101. package/lib/typescript/atoms/icon-button/icon-button.types.d.ts +7 -0
  102. package/lib/typescript/atoms/image/image.d.ts +2 -2
  103. package/lib/typescript/atoms/pressable/pressable.types.d.ts +9 -9
  104. package/lib/typescript/atoms/progress/progress.types.d.ts +2 -0
  105. package/lib/typescript/atoms/skeleton/skeleton.types.d.ts +3 -2
  106. package/lib/typescript/atoms/surface/surface.types.d.ts +2 -2
  107. package/lib/typescript/atoms/text/text.types.d.ts +2 -2
  108. package/lib/typescript/atoms/v-stack/v-stack.types.d.ts +3 -3
  109. package/lib/typescript/atoms/z-stack/z-stack.types.d.ts +3 -3
  110. package/lib/typescript/glyph/glyph.types.d.ts +2 -0
  111. package/lib/typescript/molecules/labeled-content/labeled-content.types.d.ts +4 -2
  112. package/lib/typescript/molecules/link/link.types.d.ts +2 -1
  113. package/lib/typescript/molecules/list-item/list-item.types.d.ts +5 -3
  114. package/lib/typescript/molecules/stepper/stepper.ios.d.ts +1 -1
  115. package/lib/typescript/motion.d.ts +8 -0
  116. package/lib/typescript/organisms/fab-menu/fab-menu.types.d.ts +5 -0
  117. package/lib/typescript/organisms/snackbar/snackbar.types.d.ts +38 -5
  118. package/lib/typescript/use-stack-modifiers.d.ts +6 -5
  119. package/package.json +4 -4
  120. package/src/atoms/activity-indicator/activity-indicator.android.tsx +7 -1
  121. package/src/atoms/activity-indicator/activity-indicator.ios.tsx +9 -1
  122. package/src/atoms/activity-indicator/activity-indicator.types.ts +2 -0
  123. package/src/atoms/avatar/avatar.tsx +5 -1
  124. package/src/atoms/avatar/avatar.types.ts +5 -0
  125. package/src/atoms/button/button-tokens.ts +2 -2
  126. package/src/atoms/button/button.android.tsx +5 -3
  127. package/src/atoms/button/button.ios.tsx +10 -2
  128. package/src/atoms/button/button.types.ts +2 -0
  129. package/src/atoms/card/card.android.tsx +6 -4
  130. package/src/atoms/card/card.ios.tsx +7 -3
  131. package/src/atoms/card/card.types.ts +8 -7
  132. package/src/atoms/checkbox/checkbox.android.tsx +1 -1
  133. package/src/atoms/fab/fab.android.tsx +5 -3
  134. package/src/atoms/fab/fab.ios.tsx +8 -4
  135. package/src/atoms/fab/fab.types.ts +8 -1
  136. package/src/atoms/glass-view/glass-view.android.tsx +1 -1
  137. package/src/atoms/h-stack/h-stack.ios.tsx +6 -4
  138. package/src/atoms/h-stack/h-stack.types.ts +17 -13
  139. package/src/atoms/icon-button/icon-button.android.tsx +16 -3
  140. package/src/atoms/icon-button/icon-button.ios.tsx +13 -4
  141. package/src/atoms/icon-button/icon-button.types.ts +7 -0
  142. package/src/atoms/image/image.tsx +3 -3
  143. package/src/atoms/pressable/pressable.tsx +26 -18
  144. package/src/atoms/pressable/pressable.types.ts +30 -29
  145. package/src/atoms/progress/progress.android.tsx +5 -3
  146. package/src/atoms/progress/progress.ios.tsx +5 -2
  147. package/src/atoms/progress/progress.types.ts +2 -0
  148. package/src/atoms/segmented-control/segmented-control.android.tsx +1 -1
  149. package/src/atoms/segmented-control/segmented-control.ios.tsx +1 -1
  150. package/src/atoms/skeleton/skeleton.tsx +18 -4
  151. package/src/atoms/skeleton/skeleton.types.ts +3 -2
  152. package/src/atoms/surface/surface.android.tsx +6 -4
  153. package/src/atoms/surface/surface.ios.tsx +7 -3
  154. package/src/atoms/surface/surface.types.ts +10 -9
  155. package/src/atoms/switch/switch.android.tsx +1 -1
  156. package/src/atoms/text/text.android.tsx +6 -4
  157. package/src/atoms/text/text.ios.tsx +6 -4
  158. package/src/atoms/text/text.types.ts +23 -22
  159. package/src/atoms/v-stack/v-stack.ios.tsx +6 -4
  160. package/src/atoms/v-stack/v-stack.types.ts +17 -16
  161. package/src/atoms/z-stack/z-stack.ios.tsx +6 -4
  162. package/src/atoms/z-stack/z-stack.types.ts +15 -14
  163. package/src/glyph/glyph.android.tsx +14 -2
  164. package/src/glyph/glyph.ios.tsx +13 -3
  165. package/src/glyph/glyph.types.ts +2 -0
  166. package/src/molecules/collapsible/collapsible.android.tsx +27 -7
  167. package/src/molecules/labeled-content/labeled-content.tsx +12 -1
  168. package/src/molecules/labeled-content/labeled-content.types.ts +4 -2
  169. package/src/molecules/link/link.tsx +2 -1
  170. package/src/molecules/link/link.types.ts +2 -1
  171. package/src/molecules/list-item/list-item.tsx +20 -2
  172. package/src/molecules/list-item/list-item.types.ts +5 -3
  173. package/src/molecules/navigation-bar/navigation-bar.android.tsx +1 -1
  174. package/src/molecules/navigation-bar/navigation-bar.ios.tsx +1 -1
  175. package/src/molecules/rn-list-item/rn-list-item.tsx +18 -3
  176. package/src/molecules/scroll-edge-container/scroll-edge-container.android.tsx +3 -2
  177. package/src/molecules/stepper/stepper.ios.tsx +8 -2
  178. package/src/motion.ts +13 -0
  179. package/src/organisms/fab-menu/fab-menu.android.tsx +9 -5
  180. package/src/organisms/fab-menu/fab-menu.ios.tsx +6 -2
  181. package/src/organisms/fab-menu/fab-menu.types.ts +5 -0
  182. package/src/organisms/snackbar/snackbar.tsx +143 -19
  183. package/src/organisms/snackbar/snackbar.types.ts +41 -5
  184. package/src/organisms/swipe-actions/swipe-actions.android.tsx +1 -1
  185. package/src/use-stack-modifiers.ts +20 -7
@@ -9,6 +9,11 @@ import type {MenuAction} from '../menu'
9
9
  * label of a SwiftUI `Menu`. A native atom: it renders inside `<Screen>` or `<Island>`.
10
10
  */
11
11
  export type FabMenuProps = {
12
+ /**
13
+ * What VoiceOver and TalkBack say for the button, since an icon has no words of its own: "Create". Required for that
14
+ * reason.
15
+ */
16
+ accessibilityLabel: string
12
17
  /** The actions the button unfolds into. */
13
18
  actions: MenuAction[]
14
19
  /** The closed button's icon, one of the app's. A plus by default. */
@@ -1,41 +1,165 @@
1
- import {FadeInDown, FadeOutDown} from 'react-native-reanimated'
1
+ import {duration as durations, easing, spacing, useReducedMotion} from '@hzblj/kara-theme'
2
+ import {useEffect, useRef} from 'react'
3
+ import {AccessibilityInfo, Platform} from 'react-native'
4
+ import {
5
+ Easing,
6
+ type EntryExitAnimationFunction,
7
+ FadeIn,
8
+ FadeInDown,
9
+ FadeInUp,
10
+ FadeOut,
11
+ FadeOutDown,
12
+ FadeOutUp,
13
+ ReduceMotion,
14
+ withTiming,
15
+ } from 'react-native-reanimated'
2
16
 
3
- import {ABox} from '../../atoms/box'
17
+ import {ABox, Box} from '../../atoms/box'
4
18
  import {Pressable} from '../../atoms/pressable'
5
19
  import {RnText} from '../../atoms/rn-text'
6
- import type {SnackbarComponent, SnackbarProps} from './snackbar.types'
20
+ import {RnGlyph} from '../../glyph'
21
+ import {timing} from '../../motion'
22
+ import type {SnackbarAnimation, SnackbarComponent, SnackbarDuration, SnackbarProps} from './snackbar.types'
23
+
24
+ /** Material's `SnackbarDuration.Short` and `Long`. */
25
+ const DURATIONS = {long: 10_000, short: 4_000} as const
26
+
27
+ const PLATFORM_ANIMATION: SnackbarAnimation = Platform.OS === 'android' ? 'scale' : 'slide'
28
+
29
+ /** The close glyph at the label's height: an SF Symbol fills its box, Material's icon sits inside a padded one. */
30
+ const CLOSE_SIZE = Platform.OS === 'ios' ? 's12' : 's16'
31
+
32
+ const SMOOTH_OUT = Easing.bezier(...easing.smoothOut)
33
+ const GROW = timing('quick')
34
+
35
+ /** Material's snackbar enter: it fades in while it grows from 80 %, in 150 ms. */
36
+ const scaleIn: EntryExitAnimationFunction = () => {
37
+ 'worklet'
38
+
39
+ return {
40
+ animations: {opacity: withTiming(1, GROW), transform: [{scale: withTiming(1, GROW)}]},
41
+ initialValues: {opacity: 0, transform: [{scale: 0.8}]},
42
+ }
43
+ }
44
+
45
+ /**
46
+ * The presets in and out: arriving in `fast`, leaving in `quick`, both on `smoothOut`. `ReduceMotion.Never`, because
47
+ * `useReducedMotion` has already picked the fade by then, and a fade is what should still run.
48
+ */
49
+ const TRANSITIONS = {
50
+ fade: {
51
+ entering: FadeIn.duration(durations.fast).easing(SMOOTH_OUT).reduceMotion(ReduceMotion.Never),
52
+ exiting: FadeOut.duration(durations.quick).easing(SMOOTH_OUT).reduceMotion(ReduceMotion.Never),
53
+ },
54
+ // Material's exit is a 75 ms fade: `micro`.
55
+ scale: {entering: scaleIn, exiting: FadeOut.duration(durations.micro).reduceMotion(ReduceMotion.Never)},
56
+ // Slide: in from the edge it sits on, and back out toward it.
57
+ slideBottom: {
58
+ entering: FadeInDown.duration(durations.fast).easing(SMOOTH_OUT).reduceMotion(ReduceMotion.Never),
59
+ exiting: FadeOutDown.duration(durations.quick).easing(SMOOTH_OUT).reduceMotion(ReduceMotion.Never),
60
+ },
61
+ slideTop: {
62
+ entering: FadeInUp.duration(durations.fast).easing(SMOOTH_OUT).reduceMotion(ReduceMotion.Never),
63
+ exiting: FadeOutUp.duration(durations.quick).easing(SMOOTH_OUT).reduceMotion(ReduceMotion.Never),
64
+ },
65
+ }
66
+
67
+ const transitionsFor = (animation: SnackbarAnimation, position: 'bottom' | 'top') => {
68
+ if (animation === 'slide') {
69
+ return position === 'top' ? TRANSITIONS.slideTop : TRANSITIONS.slideBottom
70
+ }
71
+
72
+ return TRANSITIONS[animation]
73
+ }
74
+
75
+ const millisecondsOf = (duration: SnackbarDuration) =>
76
+ typeof duration === 'number' ? duration : duration === 'indefinite' ? undefined : DURATIONS[duration]
77
+
78
+ export const Snackbar: SnackbarComponent = ({
79
+ actionLabel,
80
+ actionOnNewLine = false,
81
+ animation = PLATFORM_ANIMATION,
82
+ dismissible = false,
83
+ duration = 'indefinite',
84
+ entering,
85
+ exiting,
86
+ message,
87
+ offset = 's24',
88
+ onAction,
89
+ onDismiss,
90
+ position = 'bottom',
91
+ visible,
92
+ }: SnackbarProps) => {
93
+ const reduced = useReducedMotion()
94
+ // A caller's inline `onDismiss` is a new function every render; the timer must not restart for it.
95
+ const dismiss = useRef(onDismiss)
96
+ dismiss.current = onDismiss
97
+
98
+ // biome-ignore lint/correctness/useExhaustiveDependencies: a new `message` restarts the timer, as a new snackbar
99
+ useEffect(() => {
100
+ const milliseconds = millisecondsOf(duration)
101
+
102
+ if (!visible || milliseconds === undefined) {
103
+ return
104
+ }
105
+
106
+ const timer = setTimeout(() => dismiss.current?.(), milliseconds)
107
+
108
+ return () => clearTimeout(timer)
109
+ }, [duration, message, visible])
110
+
111
+ // Material's snackbar is announced; a React Native view is not unless asked.
112
+ useEffect(() => {
113
+ if (visible) {
114
+ AccessibilityInfo.announceForAccessibility(message)
115
+ }
116
+ }, [message, visible])
7
117
 
8
- export const Snackbar: SnackbarComponent = ({actionLabel, message, onAction, visible}: SnackbarProps) => {
9
118
  if (!visible) {
10
119
  return null
11
120
  }
12
121
 
122
+ const edge = position === 'top' ? {top: spacing[offset]} : {bottom: spacing[offset]}
123
+ const hasTrailing = Boolean(actionLabel) || dismissible
124
+ // With motion reduced it only fades: nothing slides or grows.
125
+ const preset = transitionsFor(reduced ? 'fade' : animation, position)
126
+
13
127
  return (
14
128
  <ABox
15
- align="center"
129
+ align={actionOnNewLine ? 'stretch' : 'center'}
16
130
  bg="surface.high.contrast"
17
131
  borderRadius="medium"
18
- bottom={24}
19
- entering={FadeInDown}
20
- exiting={FadeOutDown}
21
- flexDirection="row"
22
- gap="s12"
23
- left={16}
132
+ flexDirection={actionOnNewLine ? 'column' : 'row'}
133
+ gap={actionOnNewLine ? 's8' : 's12'}
134
+ left={spacing.s16}
24
135
  position="absolute"
25
136
  px="s16"
26
137
  py="s12"
27
- right={16}
138
+ right={spacing.s16}
28
139
  shadow="snackbar"
140
+ {...edge}
141
+ entering={entering ?? preset.entering}
142
+ exiting={exiting ?? preset.exiting}
29
143
  >
30
- <RnText color="content.on.high.contrast" variant="footnote">
144
+ {/* It takes the room, so the action sits at the trailing end, as far in from it as the message is from the start. */}
145
+ <RnText color="content.on.high.contrast" flex={actionOnNewLine ? undefined : 1} variant="footnote">
31
146
  {message}
32
147
  </RnText>
33
- {actionLabel ? (
34
- <Pressable onPress={onAction}>
35
- <RnText color="content.accent" variant="footnoteMedium">
36
- {actionLabel}
37
- </RnText>
38
- </Pressable>
148
+ {hasTrailing ? (
149
+ <Box align="center" alignSelf={actionOnNewLine ? 'flex-end' : undefined} flexDirection="row" gap="s16">
150
+ {actionLabel ? (
151
+ <Pressable onPress={onAction} pressEffect="opacity" ripple={false}>
152
+ <RnText color="content.accent" variant="footnoteMedium">
153
+ {actionLabel}
154
+ </RnText>
155
+ </Pressable>
156
+ ) : null}
157
+ {dismissible ? (
158
+ <Pressable accessibilityLabel="Dismiss" onPress={onDismiss} pressEffect="opacity" ripple={false}>
159
+ <RnGlyph color="content.on.high.contrast" name="close" size={CLOSE_SIZE} />
160
+ </Pressable>
161
+ ) : null}
162
+ </Box>
39
163
  ) : null}
40
164
  </ABox>
41
165
  )
@@ -1,17 +1,53 @@
1
- import type {ReactNode} from 'react'
1
+ import type {SpaceSize} from '@hzblj/kara-theme'
2
+ import type {ComponentProps, ReactNode} from 'react'
3
+ import type Animated from 'react-native-reanimated'
4
+
5
+ type LayoutAnimation = ComponentProps<typeof Animated.View>['entering']
6
+
7
+ /** How it comes and goes. */
8
+ export type SnackbarAnimation = 'fade' | 'scale' | 'slide'
9
+
10
+ /** How long it stays: Material's short (4 s) and long (10 s), never, or a number of milliseconds. */
11
+ export type SnackbarDuration = 'indefinite' | 'long' | 'short' | number
12
+
13
+ /** The edge of its parent it sits on. */
14
+ export type SnackbarPosition = 'bottom' | 'top'
2
15
 
3
16
  /**
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.
17
+ * A short message laid over an edge of its parent, with an optional action at its trailing end. The React Native tier,
18
+ * drawn the same on both platforms (iOS has no snackbar of its own), so it needs no host. Material's options are all
19
+ * here: a duration, a dismiss button, the action on a line of its own. It is announced to VoiceOver and TalkBack when
20
+ * it appears.
6
21
  */
7
22
  export type SnackbarProps = {
8
- /** The action button's text, on the trailing side. No button without it. */
23
+ /** The action button's text, at the trailing end. No button without it. */
9
24
  actionLabel?: string
25
+ /** Puts the action under the message, at its trailing end: Material's layout for a long message or action. */
26
+ actionOnNewLine?: boolean
27
+ /**
28
+ * How it comes and goes. `scale` is Material's own (a fade and a scale from 80 %), `slide` rises from its edge as it
29
+ * fades in, `fade` only fades. `scale` on Android and `slide` on iOS by default. With motion reduced, it fades.
30
+ */
31
+ animation?: SnackbarAnimation
32
+ /** A close button at the trailing end, Material's dismiss action; it calls `onDismiss`. */
33
+ dismissible?: boolean
34
+ /** How long it stays up before `onDismiss` is called. `indefinite` by default: it never hides itself. */
35
+ duration?: SnackbarDuration
36
+ /** A Reanimated entering animation of your own, e.g. `FadeInDown.springify()`. It wins over `animation`. */
37
+ entering?: LayoutAnimation
38
+ /** A Reanimated exiting animation of your own. It wins over `animation`. */
39
+ exiting?: LayoutAnimation
10
40
  /** The message. */
11
41
  message: string
42
+ /** Between the snackbar and its edge, a space token. `s24` by default; raise it above a tab bar. */
43
+ offset?: SpaceSize
12
44
  /** The action was pressed. The snackbar stays up; turn `visible` off to hide it. */
13
45
  onAction?: () => void
14
- /** Shows the snackbar while true, fading it in and out. It sets no timer of its own. */
46
+ /** It asks to go: its `duration` ran out, or its dismiss button was pressed. Turn `visible` off to hide it. */
47
+ onDismiss?: () => void
48
+ /** The edge of its parent it sits on. `bottom` by default, Material's place for it. */
49
+ position?: SnackbarPosition
50
+ /** Shows the snackbar while true, animating it in and out. */
15
51
  visible: boolean
16
52
  }
17
53
 
@@ -49,7 +49,7 @@ type ActionButtonProps = {action: SwipeAction; onDone: () => void}
49
49
  const ActionButton = ({action, onDone}: ActionButtonProps) => {
50
50
  const theme = useAppTheme()
51
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'
52
+ const content = !action.tint && action.destructive ? 'content.on.destructive' : 'content.on.accent'
53
53
 
54
54
  const onClick = useCallback(() => {
55
55
  onDone()
@@ -1,24 +1,37 @@
1
1
  import {clickable, type ModifierConfig, testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
2
- import {type ResponsiveSx, useSxModifiers} from '@hzblj/kara-sx'
2
+ import {
3
+ type AccessibilityProps,
4
+ type ResponsiveSx,
5
+ splitAccessibility,
6
+ useAccessibilityModifiers,
7
+ useSxModifiers,
8
+ } from '@hzblj/kara-sx'
3
9
  import {useMemo} from 'react'
4
10
 
5
11
  /**
6
- * A Compose stack's modifiers in the order a pressable one needs: its frame, shape and background, then the press,
7
- * then its padding. Compose clips a `clickable` to what comes after it, so with the padding first the ripple would stop
8
- * at the padding and a row pressed by its edge would light up only in the middle.
12
+ * A Compose stack's modifiers in the order a pressable one needs: its frame, shape and background, then the press, its
13
+ * semantics, then its padding. Compose clips a `clickable` to what comes after it, so with the padding first the ripple
14
+ * would stop at the padding and a row pressed by its edge would light up only in the middle. The semantics come after
15
+ * the click, because a label that replaces the children's clears whatever semantics are inside it.
9
16
  */
10
- export const useStackModifiers = (sx: ResponsiveSx, onPress?: () => void, testID?: string): ModifierConfig[] => {
11
- const {p, pb, pl, pr, pt, px, py, ...frame} = sx
17
+ export const useStackModifiers = (
18
+ props: ResponsiveSx & AccessibilityProps,
19
+ onPress?: () => void,
20
+ testID?: string
21
+ ): ModifierConfig[] => {
22
+ const [accessibility, {p, pb, pl, pr, pt, px, py, ...frame}] = splitAccessibility(props)
12
23
  const frameModifiers = useSxModifiers(frame)
13
24
  const paddingModifiers = useSxModifiers({p, pb, pl, pr, pt, px, py})
25
+ const accessibilityModifiers = useAccessibilityModifiers(accessibility, onPress !== undefined)
14
26
 
15
27
  return useMemo(
16
28
  () => [
17
29
  ...frameModifiers,
18
30
  ...(testID ? [testIdModifier(testID)] : []),
19
31
  ...(onPress ? [clickable(onPress)] : []),
32
+ ...accessibilityModifiers,
20
33
  ...paddingModifiers,
21
34
  ],
22
- [frameModifiers, onPress, paddingModifiers, testID]
35
+ [accessibilityModifiers, frameModifiers, onPress, paddingModifiers, testID]
23
36
  )
24
37
  }