stream-chat-react-native-core 9.0.0-beta.27 → 9.0.0-beta.29

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 (187) hide show
  1. package/lib/commonjs/components/Attachment/Gallery.js +29 -9
  2. package/lib/commonjs/components/Attachment/Gallery.js.map +1 -1
  3. package/lib/commonjs/components/Attachment/Giphy/GiphyImage.js +4 -4
  4. package/lib/commonjs/components/Attachment/Giphy/GiphyImage.js.map +1 -1
  5. package/lib/commonjs/components/Attachment/ImageLoadingFailedIndicator.js +3 -3
  6. package/lib/commonjs/components/Attachment/ImageLoadingFailedIndicator.js.map +1 -1
  7. package/lib/commonjs/components/Attachment/ImageLoadingIndicator.js +1 -1
  8. package/lib/commonjs/components/Attachment/ImageLoadingIndicator.js.map +1 -1
  9. package/lib/commonjs/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js +2 -2
  10. package/lib/commonjs/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js.map +1 -1
  11. package/lib/commonjs/components/Channel/Channel.js +1 -1
  12. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  13. package/lib/commonjs/components/ChannelList/Skeleton.js +1 -1
  14. package/lib/commonjs/components/ChannelList/Skeleton.js.map +1 -1
  15. package/lib/commonjs/components/ChannelList/hooks/useChannelActionItems.js +0 -15
  16. package/lib/commonjs/components/ChannelList/hooks/useChannelActionItems.js.map +1 -1
  17. package/lib/commonjs/components/ChannelPreview/ChannelDetailsBottomSheet.js +14 -12
  18. package/lib/commonjs/components/ChannelPreview/ChannelDetailsBottomSheet.js.map +1 -1
  19. package/lib/commonjs/components/ChannelPreview/ChannelPreviewView.js +1 -0
  20. package/lib/commonjs/components/ChannelPreview/ChannelPreviewView.js.map +1 -1
  21. package/lib/commonjs/components/ChannelPreview/ChannelSwipableWrapper.js +11 -9
  22. package/lib/commonjs/components/ChannelPreview/ChannelSwipableWrapper.js.map +1 -1
  23. package/lib/commonjs/components/ImageGallery/ImageGallery.js +3 -3
  24. package/lib/commonjs/components/ImageGallery/ImageGallery.js.map +1 -1
  25. package/lib/commonjs/components/Message/Message.js +2 -1
  26. package/lib/commonjs/components/Message/Message.js.map +1 -1
  27. package/lib/commonjs/components/Message/MessageItemView/MessageItemView.js +7 -3
  28. package/lib/commonjs/components/Message/MessageItemView/MessageItemView.js.map +1 -1
  29. package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js +3 -1
  30. package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js.map +1 -1
  31. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js +1 -1
  32. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js.map +1 -1
  33. package/lib/commonjs/components/MessageMenu/MessageUserReactions.js +0 -1
  34. package/lib/commonjs/components/MessageMenu/MessageUserReactions.js.map +1 -1
  35. package/lib/commonjs/components/Poll/components/PollInputDialog.js +1 -1
  36. package/lib/commonjs/components/Poll/components/PollInputDialog.js.map +1 -1
  37. package/lib/commonjs/components/Reply/Reply.js +2 -2
  38. package/lib/commonjs/components/Reply/Reply.js.map +1 -1
  39. package/lib/commonjs/components/ThreadList/ThreadListItemSkeleton.js +1 -1
  40. package/lib/commonjs/components/ThreadList/ThreadListItemSkeleton.js.map +1 -1
  41. package/lib/commonjs/components/UIComponents/BottomSheetModal.js +145 -44
  42. package/lib/commonjs/components/UIComponents/BottomSheetModal.js.map +1 -1
  43. package/lib/commonjs/components/UIComponents/BottomSheetModal.utils.js +53 -0
  44. package/lib/commonjs/components/UIComponents/BottomSheetModal.utils.js.map +1 -0
  45. package/lib/commonjs/components/UIComponents/StreamBottomSheetModalFlatList.js +9 -5
  46. package/lib/commonjs/components/UIComponents/StreamBottomSheetModalFlatList.js.map +1 -1
  47. package/lib/commonjs/components/ui/Avatar/ChannelAvatar.js +1 -1
  48. package/lib/commonjs/components/ui/Avatar/ChannelAvatar.js.map +1 -1
  49. package/lib/commonjs/contexts/bottomSheetContext/BottomSheetContext.js.map +1 -1
  50. package/lib/commonjs/contexts/messageComposerContext/MessageComposerContext.js +4 -4
  51. package/lib/commonjs/contexts/messageComposerContext/MessageComposerContext.js.map +1 -1
  52. package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
  53. package/lib/commonjs/contexts/overlayContext/ClosingPortalHostsLayer.js +10 -1
  54. package/lib/commonjs/contexts/overlayContext/ClosingPortalHostsLayer.js.map +1 -1
  55. package/lib/commonjs/contexts/overlayContext/MessageOverlayHostLayer.js +16 -7
  56. package/lib/commonjs/contexts/overlayContext/MessageOverlayHostLayer.js.map +1 -1
  57. package/lib/commonjs/hooks/messagePreview/useMessagePreviewText.js +4 -4
  58. package/lib/commonjs/hooks/messagePreview/useMessagePreviewText.js.map +1 -1
  59. package/lib/commonjs/version.json +1 -1
  60. package/lib/module/components/Attachment/Gallery.js +29 -9
  61. package/lib/module/components/Attachment/Gallery.js.map +1 -1
  62. package/lib/module/components/Attachment/Giphy/GiphyImage.js +4 -4
  63. package/lib/module/components/Attachment/Giphy/GiphyImage.js.map +1 -1
  64. package/lib/module/components/Attachment/ImageLoadingFailedIndicator.js +3 -3
  65. package/lib/module/components/Attachment/ImageLoadingFailedIndicator.js.map +1 -1
  66. package/lib/module/components/Attachment/ImageLoadingIndicator.js +1 -1
  67. package/lib/module/components/Attachment/ImageLoadingIndicator.js.map +1 -1
  68. package/lib/module/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js +2 -2
  69. package/lib/module/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js.map +1 -1
  70. package/lib/module/components/Channel/Channel.js +1 -1
  71. package/lib/module/components/Channel/Channel.js.map +1 -1
  72. package/lib/module/components/ChannelList/Skeleton.js +1 -1
  73. package/lib/module/components/ChannelList/Skeleton.js.map +1 -1
  74. package/lib/module/components/ChannelList/hooks/useChannelActionItems.js +0 -15
  75. package/lib/module/components/ChannelList/hooks/useChannelActionItems.js.map +1 -1
  76. package/lib/module/components/ChannelPreview/ChannelDetailsBottomSheet.js +14 -12
  77. package/lib/module/components/ChannelPreview/ChannelDetailsBottomSheet.js.map +1 -1
  78. package/lib/module/components/ChannelPreview/ChannelPreviewView.js +1 -0
  79. package/lib/module/components/ChannelPreview/ChannelPreviewView.js.map +1 -1
  80. package/lib/module/components/ChannelPreview/ChannelSwipableWrapper.js +11 -9
  81. package/lib/module/components/ChannelPreview/ChannelSwipableWrapper.js.map +1 -1
  82. package/lib/module/components/ImageGallery/ImageGallery.js +3 -3
  83. package/lib/module/components/ImageGallery/ImageGallery.js.map +1 -1
  84. package/lib/module/components/Message/Message.js +2 -1
  85. package/lib/module/components/Message/Message.js.map +1 -1
  86. package/lib/module/components/Message/MessageItemView/MessageItemView.js +7 -3
  87. package/lib/module/components/Message/MessageItemView/MessageItemView.js.map +1 -1
  88. package/lib/module/components/Message/hooks/useCreateMessageContext.js +3 -1
  89. package/lib/module/components/Message/hooks/useCreateMessageContext.js.map +1 -1
  90. package/lib/module/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js +1 -1
  91. package/lib/module/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js.map +1 -1
  92. package/lib/module/components/MessageMenu/MessageUserReactions.js +0 -1
  93. package/lib/module/components/MessageMenu/MessageUserReactions.js.map +1 -1
  94. package/lib/module/components/Poll/components/PollInputDialog.js +1 -1
  95. package/lib/module/components/Poll/components/PollInputDialog.js.map +1 -1
  96. package/lib/module/components/Reply/Reply.js +2 -2
  97. package/lib/module/components/Reply/Reply.js.map +1 -1
  98. package/lib/module/components/ThreadList/ThreadListItemSkeleton.js +1 -1
  99. package/lib/module/components/ThreadList/ThreadListItemSkeleton.js.map +1 -1
  100. package/lib/module/components/UIComponents/BottomSheetModal.js +145 -44
  101. package/lib/module/components/UIComponents/BottomSheetModal.js.map +1 -1
  102. package/lib/module/components/UIComponents/BottomSheetModal.utils.js +53 -0
  103. package/lib/module/components/UIComponents/BottomSheetModal.utils.js.map +1 -0
  104. package/lib/module/components/UIComponents/StreamBottomSheetModalFlatList.js +9 -5
  105. package/lib/module/components/UIComponents/StreamBottomSheetModalFlatList.js.map +1 -1
  106. package/lib/module/components/ui/Avatar/ChannelAvatar.js +1 -1
  107. package/lib/module/components/ui/Avatar/ChannelAvatar.js.map +1 -1
  108. package/lib/module/contexts/bottomSheetContext/BottomSheetContext.js.map +1 -1
  109. package/lib/module/contexts/messageComposerContext/MessageComposerContext.js +4 -4
  110. package/lib/module/contexts/messageComposerContext/MessageComposerContext.js.map +1 -1
  111. package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
  112. package/lib/module/contexts/overlayContext/ClosingPortalHostsLayer.js +10 -1
  113. package/lib/module/contexts/overlayContext/ClosingPortalHostsLayer.js.map +1 -1
  114. package/lib/module/contexts/overlayContext/MessageOverlayHostLayer.js +16 -7
  115. package/lib/module/contexts/overlayContext/MessageOverlayHostLayer.js.map +1 -1
  116. package/lib/module/hooks/messagePreview/useMessagePreviewText.js +4 -4
  117. package/lib/module/hooks/messagePreview/useMessagePreviewText.js.map +1 -1
  118. package/lib/module/version.json +1 -1
  119. package/lib/typescript/components/Attachment/Giphy/GiphyImage.d.ts.map +1 -1
  120. package/lib/typescript/components/ChannelList/hooks/useChannelActionItems.d.ts.map +1 -1
  121. package/lib/typescript/components/ChannelPreview/ChannelDetailsBottomSheet.d.ts +2 -2
  122. package/lib/typescript/components/ChannelPreview/ChannelDetailsBottomSheet.d.ts.map +1 -1
  123. package/lib/typescript/components/ChannelPreview/ChannelPreviewStatus.d.ts +1 -1
  124. package/lib/typescript/components/ChannelPreview/ChannelPreviewStatus.d.ts.map +1 -1
  125. package/lib/typescript/components/ChannelPreview/ChannelPreviewView.d.ts +2 -2
  126. package/lib/typescript/components/ChannelPreview/ChannelPreviewView.d.ts.map +1 -1
  127. package/lib/typescript/components/ChannelPreview/ChannelSwipableWrapper.d.ts +3 -3
  128. package/lib/typescript/components/ChannelPreview/ChannelSwipableWrapper.d.ts.map +1 -1
  129. package/lib/typescript/components/Message/Message.d.ts.map +1 -1
  130. package/lib/typescript/components/Message/MessageItemView/MessageItemView.d.ts +1 -1
  131. package/lib/typescript/components/Message/MessageItemView/MessageItemView.d.ts.map +1 -1
  132. package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts +1 -1
  133. package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts.map +1 -1
  134. package/lib/typescript/components/MessageMenu/MessageUserReactions.d.ts.map +1 -1
  135. package/lib/typescript/components/UIComponents/BottomSheetModal.d.ts +5 -1
  136. package/lib/typescript/components/UIComponents/BottomSheetModal.d.ts.map +1 -1
  137. package/lib/typescript/components/UIComponents/BottomSheetModal.utils.d.ts +28 -0
  138. package/lib/typescript/components/UIComponents/BottomSheetModal.utils.d.ts.map +1 -0
  139. package/lib/typescript/components/UIComponents/StreamBottomSheetModalFlatList.d.ts.map +1 -1
  140. package/lib/typescript/components/ui/Avatar/ChannelAvatar.d.ts.map +1 -1
  141. package/lib/typescript/contexts/bottomSheetContext/BottomSheetContext.d.ts +1 -0
  142. package/lib/typescript/contexts/bottomSheetContext/BottomSheetContext.d.ts.map +1 -1
  143. package/lib/typescript/contexts/messageContext/MessageContext.d.ts +4 -2
  144. package/lib/typescript/contexts/messageContext/MessageContext.d.ts.map +1 -1
  145. package/lib/typescript/contexts/overlayContext/MessageOverlayHostLayer.d.ts.map +1 -1
  146. package/lib/typescript/hooks/messagePreview/useMessagePreviewText.d.ts.map +1 -1
  147. package/package.json +2 -4
  148. package/src/components/Attachment/Gallery.tsx +21 -5
  149. package/src/components/Attachment/Giphy/GiphyImage.tsx +6 -3
  150. package/src/components/Attachment/ImageLoadingFailedIndicator.tsx +1 -2
  151. package/src/components/Attachment/ImageLoadingIndicator.tsx +1 -1
  152. package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx +2 -2
  153. package/src/components/Channel/Channel.tsx +1 -1
  154. package/src/components/ChannelList/Skeleton.tsx +1 -1
  155. package/src/components/ChannelList/hooks/__tests__/useChannelActionItems.test.tsx +12 -45
  156. package/src/components/ChannelList/hooks/useChannelActionItems.tsx +1 -19
  157. package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx +9 -7
  158. package/src/components/ChannelPreview/ChannelPreviewStatus.tsx +1 -1
  159. package/src/components/ChannelPreview/ChannelPreviewView.tsx +3 -2
  160. package/src/components/ChannelPreview/ChannelSwipableWrapper.tsx +8 -7
  161. package/src/components/ChannelPreview/__tests__/ChannelDetailsBottomSheet.test.tsx +1 -1
  162. package/src/components/ChannelPreview/__tests__/ChannelLastMessagePreview.test.tsx +32 -0
  163. package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx +5 -2
  164. package/src/components/ImageGallery/ImageGallery.tsx +3 -3
  165. package/src/components/Message/Message.tsx +2 -1
  166. package/src/components/Message/MessageItemView/MessageItemView.tsx +8 -3
  167. package/src/components/Message/MessageItemView/__tests__/MessageContent.test.js +28 -0
  168. package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.js +32 -1
  169. package/src/components/Message/hooks/useCreateMessageContext.ts +3 -0
  170. package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx +1 -1
  171. package/src/components/MessageMenu/MessageUserReactions.tsx +1 -5
  172. package/src/components/Poll/components/PollInputDialog.tsx +1 -1
  173. package/src/components/Reply/Reply.tsx +2 -2
  174. package/src/components/ThreadList/ThreadListItemSkeleton.tsx +1 -1
  175. package/src/components/UIComponents/BottomSheetModal.tsx +242 -56
  176. package/src/components/UIComponents/BottomSheetModal.utils.ts +82 -0
  177. package/src/components/UIComponents/StreamBottomSheetModalFlatList.tsx +14 -5
  178. package/src/components/UIComponents/__tests__/BottomSheetModal.utils.test.ts +111 -0
  179. package/src/components/ui/Avatar/ChannelAvatar.tsx +5 -1
  180. package/src/contexts/bottomSheetContext/BottomSheetContext.tsx +1 -0
  181. package/src/contexts/messageComposerContext/MessageComposerContext.tsx +1 -1
  182. package/src/contexts/messageContext/MessageContext.tsx +4 -2
  183. package/src/contexts/overlayContext/ClosingPortalHostsLayer.tsx +11 -1
  184. package/src/contexts/overlayContext/MessageOverlayHostLayer.tsx +17 -10
  185. package/src/hooks/messagePreview/useMessagePreviewText.tsx +5 -6
  186. package/src/version.json +1 -1
  187. package/bin/patch-bottom-sheet-5.1.8-pr-2561.js +0 -246
@@ -10,6 +10,7 @@ import {
10
10
  EventSubscription,
11
11
  Keyboard,
12
12
  KeyboardEvent,
13
+ LayoutChangeEvent,
13
14
  Modal,
14
15
  Platform,
15
16
  Pressable,
@@ -23,12 +24,20 @@ import Animated, {
23
24
  Easing,
24
25
  FadeIn,
25
26
  runOnJS,
27
+ useAnimatedReaction,
26
28
  useAnimatedStyle,
29
+ useDerivedValue,
27
30
  useSharedValue,
28
31
  withTiming,
29
32
  } from 'react-native-reanimated';
30
33
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
31
34
 
35
+ import {
36
+ getBottomSheetBaseHeight,
37
+ getBottomSheetSnapPointTranslateY,
38
+ getBottomSheetTopSnapIndex,
39
+ } from './BottomSheetModal.utils';
40
+
32
41
  import { BottomSheetProvider } from '../../contexts/bottomSheetContext/BottomSheetContext';
33
42
  import { useTheme } from '../../contexts/themeContext/ThemeContext';
34
43
  import { useStableCallback } from '../../hooks';
@@ -49,6 +58,10 @@ export type BottomSheetModalProps = {
49
58
  * The height of the modal.
50
59
  */
51
60
  height?: number;
61
+ /**
62
+ * Whether the sheet should wrap its content up to the provided `height`.
63
+ */
64
+ enableDynamicSizing?: boolean;
52
65
  /**
53
66
  * Whether the sheet content should be lazy loaded or not. Particularly
54
67
  * useful when the content is something heavy and we don't want to disrupt
@@ -57,10 +70,17 @@ export type BottomSheetModalProps = {
57
70
  lazy?: boolean;
58
71
  };
59
72
 
60
- export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>) => {
73
+ const BottomSheetModalInner = (props: PropsWithChildren<BottomSheetModalProps>) => {
61
74
  const { height: windowHeight } = useWindowDimensions();
62
75
  const { top: topInset, bottom: bottomInset } = useSafeAreaInsets();
63
- const { children, height = windowHeight / 2, onClose, visible, lazy = false } = props;
76
+ const {
77
+ children,
78
+ enableDynamicSizing = false,
79
+ height = windowHeight / 2,
80
+ onClose,
81
+ visible,
82
+ lazy = false,
83
+ } = props;
64
84
 
65
85
  const {
66
86
  theme: {
@@ -73,12 +93,26 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
73
93
  0,
74
94
  windowHeight - topInset - (Platform.OS === 'android' ? bottomInset + 16 : 0),
75
95
  );
96
+ const fixedBaseHeight = Math.min(height, maxHeight);
76
97
 
77
- const baseHeight = Math.min(height, maxHeight);
78
- const snapPoints = useMemo(() => [baseHeight, maxHeight], [baseHeight, maxHeight]);
79
- const snapPointsTranslateY = useMemo(
80
- () => snapPoints.map((point) => maxHeight - point),
81
- [maxHeight, snapPoints],
98
+ const contentHeight = useSharedValue<number | undefined>(undefined);
99
+ const baseHeight = useDerivedValue(
100
+ () =>
101
+ getBottomSheetBaseHeight({
102
+ contentHeight: contentHeight.value,
103
+ enableDynamicSizing,
104
+ height,
105
+ maxHeight,
106
+ }),
107
+ [contentHeight, enableDynamicSizing, height, maxHeight],
108
+ );
109
+ const topSnapIndex = useDerivedValue<number>(
110
+ () =>
111
+ getBottomSheetTopSnapIndex({
112
+ baseHeight: baseHeight.value,
113
+ maxHeight,
114
+ }),
115
+ [baseHeight, maxHeight],
82
116
  );
83
117
 
84
118
  const sheetTranslateY = useSharedValue(maxHeight);
@@ -100,6 +134,36 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
100
134
  }
101
135
  });
102
136
 
137
+ const handleDynamicContentLayout = useStableCallback((event: LayoutChangeEvent) => {
138
+ if (!enableDynamicSizing) {
139
+ return;
140
+ }
141
+
142
+ const nextContentHeight = Math.ceil(event.nativeEvent.layout.height);
143
+
144
+ if (contentHeight.value === nextContentHeight) {
145
+ return;
146
+ }
147
+
148
+ contentHeight.value = nextContentHeight;
149
+ });
150
+
151
+ const open = useStableCallback((shouldShowContentAfterOpen = true) => {
152
+ sheetTranslateY.value = withTiming(
153
+ maxHeight - fixedBaseHeight,
154
+ { duration: 250, easing: Easing.out(Easing.cubic) },
155
+ (finished) => {
156
+ if (!finished) return;
157
+
158
+ isOpening.value = false;
159
+
160
+ if (shouldShowContentAfterOpen) {
161
+ runOnJS(showContent)();
162
+ }
163
+ },
164
+ );
165
+ });
166
+
103
167
  const finishClose = useStableCallback((closeAnimationFinishedCallback?: () => void) => {
104
168
  onClose();
105
169
  if (closeAnimationFinishedCallback) {
@@ -161,27 +225,66 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
161
225
  currentSnapIndex.value = 0;
162
226
  sheetTranslateY.value = maxHeight;
163
227
 
164
- sheetTranslateY.value = withTiming(
165
- snapPointsTranslateY[0],
166
- { duration: 250, easing: Easing.out(Easing.cubic) },
167
- (finished) => {
168
- if (!finished) return;
228
+ if (enableDynamicSizing) {
229
+ setRenderContent(true);
230
+ return;
231
+ }
169
232
 
170
- isOpening.value = false;
171
- runOnJS(showContent)();
172
- },
173
- );
233
+ open();
174
234
  }, [
235
+ enableDynamicSizing,
175
236
  visible,
176
237
  isOpen,
177
238
  isOpening,
178
239
  maxHeight,
179
- showContent,
240
+ open,
180
241
  sheetTranslateY,
181
242
  currentSnapIndex,
182
- snapPointsTranslateY,
183
243
  ]);
184
244
 
245
+ useAnimatedReaction(
246
+ () => {
247
+ if (
248
+ !visible ||
249
+ !enableDynamicSizing ||
250
+ !isOpening.value ||
251
+ contentHeight.value === undefined
252
+ ) {
253
+ return undefined;
254
+ }
255
+
256
+ return getBottomSheetSnapPointTranslateY({
257
+ baseHeight: baseHeight.value,
258
+ maxHeight,
259
+ snapIndex: 0,
260
+ });
261
+ },
262
+ (nextTranslateY, prevTranslateY) => {
263
+ if (nextTranslateY === undefined || nextTranslateY === prevTranslateY) {
264
+ return;
265
+ }
266
+
267
+ sheetTranslateY.value = withTiming(
268
+ nextTranslateY,
269
+ { duration: 250, easing: Easing.out(Easing.cubic) },
270
+ (finished) => {
271
+ if (finished) {
272
+ isOpening.value = false;
273
+ }
274
+ },
275
+ );
276
+ },
277
+ [
278
+ baseHeight,
279
+ contentHeight,
280
+ enableDynamicSizing,
281
+ isOpening,
282
+ maxHeight,
283
+ sheetTranslateY,
284
+ visible,
285
+ ],
286
+ );
287
+
185
288
  // if `visible` gets hard changed, we force a cleanup
186
289
  useEffect(() => {
187
290
  if (visible) return;
@@ -191,8 +294,10 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
191
294
  keyboardOffset.value = 0;
192
295
  currentSnapIndex.value = 0;
193
296
  sheetTranslateY.value = maxHeight;
297
+ contentHeight.value = undefined;
194
298
  setRenderContent(!lazy);
195
299
  }, [
300
+ contentHeight,
196
301
  visible,
197
302
  lazy,
198
303
  isOpen,
@@ -204,16 +309,52 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
204
309
  ]);
205
310
 
206
311
  // Keep the sheet aligned with the active snap if dimensions change while visible.
207
- useEffect(() => {
208
- if (!visible || !isOpen.value || isOpening.value) {
209
- return;
210
- }
211
-
212
- sheetTranslateY.value = withTiming(snapPointsTranslateY[currentSnapIndex.value], {
213
- duration: 250,
214
- easing: Easing.inOut(Easing.ease),
215
- });
216
- }, [visible, isOpen, isOpening, sheetTranslateY, currentSnapIndex, snapPointsTranslateY]);
312
+ useAnimatedReaction(
313
+ () => {
314
+ if (!visible || !isOpen.value || isOpening.value) {
315
+ return undefined;
316
+ }
317
+
318
+ const clampedSnapIndex = Math.min(currentSnapIndex.value, topSnapIndex.value);
319
+
320
+ return {
321
+ snapIndex: clampedSnapIndex,
322
+ translateY: getBottomSheetSnapPointTranslateY({
323
+ baseHeight: baseHeight.value,
324
+ maxHeight,
325
+ snapIndex: clampedSnapIndex,
326
+ }),
327
+ };
328
+ },
329
+ (nextTarget) => {
330
+ if (!nextTarget) {
331
+ return;
332
+ }
333
+
334
+ if (currentSnapIndex.value !== nextTarget.snapIndex) {
335
+ currentSnapIndex.value = nextTarget.snapIndex;
336
+ }
337
+
338
+ if (Math.abs(sheetTranslateY.value - nextTarget.translateY) < 1) {
339
+ return;
340
+ }
341
+
342
+ sheetTranslateY.value = withTiming(nextTarget.translateY, {
343
+ duration: 250,
344
+ easing: Easing.inOut(Easing.ease),
345
+ });
346
+ },
347
+ [
348
+ baseHeight,
349
+ currentSnapIndex,
350
+ isOpen,
351
+ isOpening,
352
+ maxHeight,
353
+ sheetTranslateY,
354
+ topSnapIndex,
355
+ visible,
356
+ ],
357
+ );
217
358
 
218
359
  const animateKeyboardOffset = useStableCallback((offset: number) => {
219
360
  keyboardOffset.value = withTiming(offset, {
@@ -258,10 +399,10 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
258
399
 
259
400
  const overlayAnimatedStyle = useAnimatedStyle(() => {
260
401
  const visibleHeight = Math.max(0, maxHeight - sheetTranslateY.value);
261
- const threshold = Math.max(1, Math.min(baseHeight, maxHeight));
402
+ const threshold = Math.max(1, Math.min(baseHeight.value, maxHeight));
262
403
  const progress = Math.min(1, visibleHeight / threshold);
263
404
  return { opacity: progress };
264
- });
405
+ }, [baseHeight, maxHeight]);
265
406
 
266
407
  const panGesture = useMemo(
267
408
  () =>
@@ -275,11 +416,24 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
275
416
  sheetTranslateY.value = Math.min(Math.max(nextTranslateY, 0), maxHeight);
276
417
  })
277
418
  .onEnd((event) => {
278
- const openTranslateY = snapPointsTranslateY[currentSnapIndex.value];
419
+ const openTranslateY = getBottomSheetSnapPointTranslateY({
420
+ baseHeight: baseHeight.value,
421
+ maxHeight,
422
+ snapIndex: currentSnapIndex.value,
423
+ });
279
424
  const draggedDown = Math.max(sheetTranslateY.value - openTranslateY, 0);
280
- const topSnapIndex = snapPoints.length - 1;
281
- const isAtTopSnap = currentSnapIndex.value === topSnapIndex;
282
- const snap0TranslateY = snapPointsTranslateY[0];
425
+ const hasMultipleSnapPoints = topSnapIndex.value > 0;
426
+ const isAtTopSnap = currentSnapIndex.value === topSnapIndex.value;
427
+ const snap0TranslateY = getBottomSheetSnapPointTranslateY({
428
+ baseHeight: baseHeight.value,
429
+ maxHeight,
430
+ snapIndex: 0,
431
+ });
432
+ const topSnapTranslateY = getBottomSheetSnapPointTranslateY({
433
+ baseHeight: baseHeight.value,
434
+ maxHeight,
435
+ snapIndex: topSnapIndex.value,
436
+ });
283
437
  const projectedTranslateY = sheetTranslateY.value + event.velocityY * 0.2;
284
438
 
285
439
  const shouldCloseFromLowerSnap = event.velocityY > 500 || draggedDown > maxHeight / 2;
@@ -287,49 +441,61 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
287
441
  event.velocityY > 2200 ||
288
442
  projectedTranslateY > snap0TranslateY + (maxHeight - snap0TranslateY) * 0.96;
289
443
 
290
- const shouldClose = isAtTopSnap ? shouldCloseFromTopSnap : shouldCloseFromLowerSnap;
444
+ const shouldClose = !hasMultipleSnapPoints
445
+ ? shouldCloseFromLowerSnap
446
+ : isAtTopSnap
447
+ ? shouldCloseFromTopSnap
448
+ : shouldCloseFromLowerSnap;
291
449
 
292
450
  if (shouldClose) {
293
451
  runOnJS(closeFromGesture)();
294
452
  } else {
295
453
  isOpen.value = true;
296
454
  let nearestIndex = 0;
297
- let minDistance = Number.POSITIVE_INFINITY;
298
- for (let i = 0; i < snapPointsTranslateY.length; i += 1) {
299
- const candidate = snapPointsTranslateY[i];
300
- const distance = Math.abs(sheetTranslateY.value - candidate);
301
- if (distance < minDistance) {
302
- minDistance = distance;
303
- nearestIndex = i;
455
+ let minDistance = Math.abs(sheetTranslateY.value - snap0TranslateY);
456
+
457
+ if (hasMultipleSnapPoints) {
458
+ const topDistance = Math.abs(sheetTranslateY.value - topSnapTranslateY);
459
+ if (topDistance < minDistance) {
460
+ minDistance = topDistance;
461
+ nearestIndex = topSnapIndex.value;
304
462
  }
305
463
  }
464
+
306
465
  // velocity-based snapping, fast upward swipe goes to top snap point
307
- if (event.velocityY < -800) {
308
- nearestIndex = snapPointsTranslateY.length - 1;
466
+ if (hasMultipleSnapPoints && event.velocityY < -800) {
467
+ nearestIndex = topSnapIndex.value;
309
468
  }
310
469
  // From top snap, a gentle downward flick should settle to snap 0
311
470
  // without requiring a large drag distance.
312
- if (isAtTopSnap && event.velocityY > 120) {
471
+ if (hasMultipleSnapPoints && isAtTopSnap && event.velocityY > 120) {
313
472
  nearestIndex = 0;
314
473
  }
315
474
 
316
475
  currentSnapIndex.value = nearestIndex;
317
- sheetTranslateY.value = withTiming(snapPointsTranslateY[nearestIndex], {
318
- duration: 250,
319
- easing: Easing.inOut(Easing.ease),
320
- });
476
+ sheetTranslateY.value = withTiming(
477
+ getBottomSheetSnapPointTranslateY({
478
+ baseHeight: baseHeight.value,
479
+ maxHeight,
480
+ snapIndex: nearestIndex,
481
+ }),
482
+ {
483
+ duration: 250,
484
+ easing: Easing.inOut(Easing.ease),
485
+ },
486
+ );
321
487
  }
322
488
  }),
323
489
  [
490
+ baseHeight,
324
491
  currentSnapIndex,
325
492
  isOpen,
326
493
  maxHeight,
327
494
  closeFromGesture,
328
495
  panStartTranslateY,
329
496
  renderContent,
330
- snapPoints,
331
- snapPointsTranslateY,
332
497
  sheetTranslateY,
498
+ topSnapIndex,
333
499
  ],
334
500
  );
335
501
 
@@ -339,16 +505,20 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
339
505
  () => ({
340
506
  close,
341
507
  currentSnapIndex,
508
+ topSnapIndex,
342
509
  }),
343
- [close, currentSnapIndex],
510
+ [close, currentSnapIndex, topSnapIndex],
344
511
  );
345
512
 
346
513
  return (
347
514
  <Modal onRequestClose={onClose} transparent visible={visible}>
348
515
  <GestureHandlerRootView style={styles.sheetContentContainer}>
349
516
  <View style={[styles.overlay, overlayTheme]}>
350
- <Animated.View pointerEvents='none' style={[styles.backdrop, overlayAnimatedStyle]} />
351
- <Pressable onPress={onBackdropPress} style={StyleSheet.absoluteFillObject} />
517
+ <Animated.View
518
+ pointerEvents='none'
519
+ style={[StyleSheet.absoluteFill, styles.backdrop, overlayAnimatedStyle]}
520
+ />
521
+ <Pressable onPress={onBackdropPress} style={StyleSheet.absoluteFill} />
352
522
 
353
523
  <Animated.View
354
524
  pointerEvents='box-none'
@@ -364,7 +534,16 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
364
534
  entering={FadeIn.duration(250)}
365
535
  style={styles.sheetContentContainer}
366
536
  >
367
- {children}
537
+ {enableDynamicSizing ? (
538
+ <View
539
+ onLayout={handleDynamicContentLayout}
540
+ style={{ paddingBottom: bottomInset }}
541
+ >
542
+ {children}
543
+ </View>
544
+ ) : (
545
+ children
546
+ )}
368
547
  </Animated.View>
369
548
  </BottomSheetProvider>
370
549
  ) : null}
@@ -378,6 +557,14 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
378
557
  );
379
558
  };
380
559
 
560
+ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>) => {
561
+ if (!props.visible) {
562
+ return null;
563
+ }
564
+
565
+ return <BottomSheetModalInner {...props} />;
566
+ };
567
+
381
568
  const useStyles = () => {
382
569
  const {
383
570
  theme: { semantics },
@@ -406,7 +593,6 @@ const useStyles = () => {
406
593
  justifyContent: 'flex-end',
407
594
  },
408
595
  backdrop: {
409
- ...StyleSheet.absoluteFillObject,
410
596
  backgroundColor: semantics.backgroundCoreScrim,
411
597
  },
412
598
  sheetContentContainer: {
@@ -0,0 +1,82 @@
1
+ export const BOTTOM_SHEET_HANDLE_HEIGHT = 4;
2
+ export const BOTTOM_SHEET_HANDLE_MARGIN_VERTICAL = 8;
3
+ export const BOTTOM_SHEET_HANDLE_TOTAL_HEIGHT =
4
+ BOTTOM_SHEET_HANDLE_HEIGHT + BOTTOM_SHEET_HANDLE_MARGIN_VERTICAL * 2;
5
+
6
+ type GetBottomSheetBaseHeightParams = {
7
+ contentHeight: number | undefined;
8
+ enableDynamicSizing: boolean;
9
+ height: number;
10
+ maxHeight: number;
11
+ };
12
+
13
+ export const getBottomSheetBaseHeight = ({
14
+ contentHeight,
15
+ enableDynamicSizing,
16
+ height,
17
+ maxHeight,
18
+ }: GetBottomSheetBaseHeightParams) => {
19
+ 'worklet';
20
+
21
+ const cappedHeight = Math.min(height, maxHeight);
22
+
23
+ if (!enableDynamicSizing || contentHeight === undefined) {
24
+ return cappedHeight;
25
+ }
26
+
27
+ const measuredHeight = Math.max(0, contentHeight) + BOTTOM_SHEET_HANDLE_TOTAL_HEIGHT;
28
+
29
+ return Math.min(measuredHeight, cappedHeight);
30
+ };
31
+
32
+ type GetBottomSheetSnapPointsParams = {
33
+ baseHeight: number;
34
+ maxHeight: number;
35
+ };
36
+
37
+ export const getBottomSheetSnapPoints = ({
38
+ baseHeight,
39
+ maxHeight,
40
+ }: GetBottomSheetSnapPointsParams) => {
41
+ 'worklet';
42
+
43
+ if (baseHeight === maxHeight) {
44
+ return [baseHeight];
45
+ }
46
+
47
+ return [baseHeight, maxHeight];
48
+ };
49
+
50
+ type GetBottomSheetTopSnapIndexParams = {
51
+ baseHeight: number;
52
+ maxHeight: number;
53
+ };
54
+
55
+ export const getBottomSheetTopSnapIndex = ({
56
+ baseHeight,
57
+ maxHeight,
58
+ }: GetBottomSheetTopSnapIndexParams) => {
59
+ 'worklet';
60
+
61
+ return baseHeight === maxHeight ? 0 : 1;
62
+ };
63
+
64
+ type GetBottomSheetSnapPointTranslateYParams = {
65
+ baseHeight: number;
66
+ maxHeight: number;
67
+ snapIndex: number;
68
+ };
69
+
70
+ export const getBottomSheetSnapPointTranslateY = ({
71
+ baseHeight,
72
+ maxHeight,
73
+ snapIndex,
74
+ }: GetBottomSheetSnapPointTranslateYParams) => {
75
+ 'worklet';
76
+
77
+ if (getBottomSheetTopSnapIndex({ baseHeight, maxHeight }) === 0) {
78
+ return 0;
79
+ }
80
+
81
+ return snapIndex <= 0 ? maxHeight - baseHeight : 0;
82
+ };
@@ -12,7 +12,7 @@ export const StreamBottomSheetModalFlatList = <ItemT,>({
12
12
  scrollEnabled: scrollEnabledProp,
13
13
  ...props
14
14
  }: StreamBottomSheetModalFlatListProps<ItemT>) => {
15
- const { currentSnapIndex } = useBottomSheetContext();
15
+ const { currentSnapIndex, topSnapIndex } = useBottomSheetContext();
16
16
  const listRef = useRef<FlatList<ItemT>>(null);
17
17
 
18
18
  const setNativeScrollEnabled = useStableCallback((value: boolean) =>
@@ -20,14 +20,23 @@ export const StreamBottomSheetModalFlatList = <ItemT,>({
20
20
  );
21
21
 
22
22
  useAnimatedReaction(
23
- () => currentSnapIndex.value,
23
+ () => ({
24
+ currentSnapIndex: currentSnapIndex.value,
25
+ topSnapIndex: topSnapIndex.value,
26
+ }),
24
27
  (value, prev) => {
25
- if (value === prev || scrollEnabledProp !== undefined) {
28
+ if (
29
+ scrollEnabledProp !== undefined ||
30
+ (prev &&
31
+ value.currentSnapIndex === prev.currentSnapIndex &&
32
+ value.topSnapIndex === prev.topSnapIndex)
33
+ ) {
26
34
  return;
27
35
  }
28
- runOnJS(setNativeScrollEnabled)(value === 1);
36
+
37
+ runOnJS(setNativeScrollEnabled)(value.currentSnapIndex === value.topSnapIndex);
29
38
  },
30
- [currentSnapIndex],
39
+ [currentSnapIndex, topSnapIndex],
31
40
  );
32
41
 
33
42
  return <FlatList ref={listRef} {...props} scrollEnabled={false} />;
@@ -0,0 +1,111 @@
1
+ import {
2
+ BOTTOM_SHEET_HANDLE_TOTAL_HEIGHT,
3
+ getBottomSheetBaseHeight,
4
+ getBottomSheetSnapPointTranslateY,
5
+ getBottomSheetSnapPoints,
6
+ getBottomSheetTopSnapIndex,
7
+ } from '../BottomSheetModal.utils';
8
+
9
+ describe('BottomSheetModal.utils', () => {
10
+ it('caps fixed bottom sheet height by maxHeight', () => {
11
+ expect(
12
+ getBottomSheetBaseHeight({
13
+ contentHeight: undefined,
14
+ enableDynamicSizing: false,
15
+ height: 500,
16
+ maxHeight: 420,
17
+ }),
18
+ ).toBe(420);
19
+ });
20
+
21
+ it('uses measured content height plus handle and safe area when dynamic sizing is enabled', () => {
22
+ expect(
23
+ getBottomSheetBaseHeight({
24
+ contentHeight: 120,
25
+ enableDynamicSizing: true,
26
+ height: 400,
27
+ maxHeight: 500,
28
+ }),
29
+ ).toBe(120 + BOTTOM_SHEET_HANDLE_TOTAL_HEIGHT);
30
+ });
31
+
32
+ it('caps dynamic content height by the provided height', () => {
33
+ expect(
34
+ getBottomSheetBaseHeight({
35
+ contentHeight: 500,
36
+ enableDynamicSizing: true,
37
+ height: 300,
38
+ maxHeight: 600,
39
+ }),
40
+ ).toBe(300);
41
+ });
42
+
43
+ it('keeps the expanded snap point when the sheet is shorter than maxHeight', () => {
44
+ expect(
45
+ getBottomSheetSnapPoints({
46
+ baseHeight: 280,
47
+ maxHeight: 640,
48
+ }),
49
+ ).toEqual([280, 640]);
50
+ });
51
+
52
+ it('returns two snap points for fixed sizing when maxHeight exceeds baseHeight', () => {
53
+ expect(
54
+ getBottomSheetSnapPoints({
55
+ baseHeight: 280,
56
+ maxHeight: 640,
57
+ }),
58
+ ).toEqual([280, 640]);
59
+ });
60
+
61
+ it('returns a single snap point when the sheet is already at maxHeight', () => {
62
+ expect(
63
+ getBottomSheetSnapPoints({
64
+ baseHeight: 640,
65
+ maxHeight: 640,
66
+ }),
67
+ ).toEqual([640]);
68
+ });
69
+
70
+ it('returns the correct top snap index', () => {
71
+ expect(
72
+ getBottomSheetTopSnapIndex({
73
+ baseHeight: 280,
74
+ maxHeight: 640,
75
+ }),
76
+ ).toBe(1);
77
+
78
+ expect(
79
+ getBottomSheetTopSnapIndex({
80
+ baseHeight: 640,
81
+ maxHeight: 640,
82
+ }),
83
+ ).toBe(0);
84
+ });
85
+
86
+ it('returns the correct translateY for each snap point', () => {
87
+ expect(
88
+ getBottomSheetSnapPointTranslateY({
89
+ baseHeight: 280,
90
+ maxHeight: 640,
91
+ snapIndex: 0,
92
+ }),
93
+ ).toBe(360);
94
+
95
+ expect(
96
+ getBottomSheetSnapPointTranslateY({
97
+ baseHeight: 280,
98
+ maxHeight: 640,
99
+ snapIndex: 1,
100
+ }),
101
+ ).toBe(0);
102
+
103
+ expect(
104
+ getBottomSheetSnapPointTranslateY({
105
+ baseHeight: 640,
106
+ maxHeight: 640,
107
+ snapIndex: 0,
108
+ }),
109
+ ).toBe(0);
110
+ });
111
+ });
@@ -58,7 +58,11 @@ export const ChannelAvatar = (props: ChannelAvatarProps) => {
58
58
 
59
59
  if (usersWithoutSelf.length > 1) {
60
60
  return (
61
- <UserAvatarGroup size='xl' users={usersForGroup} showOnlineIndicator={showOnlineIndicator} />
61
+ <UserAvatarGroup
62
+ size={size}
63
+ users={usersForGroup}
64
+ showOnlineIndicator={showOnlineIndicator}
65
+ />
62
66
  );
63
67
  } else {
64
68
  return (