@planningcenter/chat-react-native 3.5.0-rc.1 → 3.5.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 (217) hide show
  1. package/build/components/conversation/attachments/audio_attachment.d.ts +2 -2
  2. package/build/components/conversation/attachments/audio_attachment.d.ts.map +1 -1
  3. package/build/components/conversation/attachments/audio_attachment.js +2 -2
  4. package/build/components/conversation/attachments/audio_attachment.js.map +1 -1
  5. package/build/components/conversation/attachments/expanded_link.d.ts +2 -2
  6. package/build/components/conversation/attachments/expanded_link.d.ts.map +1 -1
  7. package/build/components/conversation/attachments/expanded_link.js +2 -2
  8. package/build/components/conversation/attachments/expanded_link.js.map +1 -1
  9. package/build/components/conversation/attachments/generic_file_attachment.d.ts +2 -2
  10. package/build/components/conversation/attachments/generic_file_attachment.d.ts.map +1 -1
  11. package/build/components/conversation/attachments/generic_file_attachment.js +2 -2
  12. package/build/components/conversation/attachments/generic_file_attachment.js.map +1 -1
  13. package/build/components/conversation/attachments/giphy_attachment.d.ts +2 -2
  14. package/build/components/conversation/attachments/giphy_attachment.d.ts.map +1 -1
  15. package/build/components/conversation/attachments/giphy_attachment.js +18 -5
  16. package/build/components/conversation/attachments/giphy_attachment.js.map +1 -1
  17. package/build/components/conversation/attachments/image_attachment.d.ts +2 -2
  18. package/build/components/conversation/attachments/image_attachment.d.ts.map +1 -1
  19. package/build/components/conversation/attachments/image_attachment.js +3 -3
  20. package/build/components/conversation/attachments/image_attachment.js.map +1 -1
  21. package/build/components/conversation/attachments/video_attachment.d.ts +2 -2
  22. package/build/components/conversation/attachments/video_attachment.d.ts.map +1 -1
  23. package/build/components/conversation/attachments/video_attachment.js +2 -2
  24. package/build/components/conversation/attachments/video_attachment.js.map +1 -1
  25. package/build/components/conversation/empty_conversation_blank_state.js +1 -1
  26. package/build/components/conversation/empty_conversation_blank_state.js.map +1 -1
  27. package/build/components/conversation/message.d.ts +5 -2
  28. package/build/components/conversation/message.d.ts.map +1 -1
  29. package/build/components/conversation/message.js +11 -9
  30. package/build/components/conversation/message.js.map +1 -1
  31. package/build/components/conversation/message_attachments.d.ts +3 -2
  32. package/build/components/conversation/message_attachments.d.ts.map +1 -1
  33. package/build/components/conversation/message_attachments.js +9 -9
  34. package/build/components/conversation/message_attachments.js.map +1 -1
  35. package/build/components/conversations/conversation_actions.d.ts.map +1 -1
  36. package/build/components/conversations/conversation_actions.js +1 -1
  37. package/build/components/conversations/conversation_actions.js.map +1 -1
  38. package/build/components/conversations/conversation_preview.d.ts.map +1 -1
  39. package/build/components/conversations/conversation_preview.js +13 -3
  40. package/build/components/conversations/conversation_preview.js.map +1 -1
  41. package/build/components/conversations/conversations.js +3 -3
  42. package/build/components/conversations/conversations.js.map +1 -1
  43. package/build/components/display/action_button.d.ts +4 -1
  44. package/build/components/display/action_button.d.ts.map +1 -1
  45. package/build/components/display/action_button.js +20 -5
  46. package/build/components/display/action_button.js.map +1 -1
  47. package/build/components/display/avatar.d.ts +4 -1
  48. package/build/components/display/avatar.d.ts.map +1 -1
  49. package/build/components/display/avatar.js +3 -2
  50. package/build/components/display/avatar.js.map +1 -1
  51. package/build/components/display/avatar_group.d.ts +4 -1
  52. package/build/components/display/avatar_group.d.ts.map +1 -1
  53. package/build/components/display/avatar_group.js +6 -3
  54. package/build/components/display/avatar_group.js.map +1 -1
  55. package/build/components/display/blank_state.d.ts +7 -2
  56. package/build/components/display/blank_state.d.ts.map +1 -1
  57. package/build/components/display/blank_state.js +6 -5
  58. package/build/components/display/blank_state.js.map +1 -1
  59. package/build/components/display/child_notice.d.ts +2 -1
  60. package/build/components/display/child_notice.d.ts.map +1 -1
  61. package/build/components/display/child_notice.js +4 -4
  62. package/build/components/display/child_notice.js.map +1 -1
  63. package/build/components/display/heading.d.ts +2 -3
  64. package/build/components/display/heading.d.ts.map +1 -1
  65. package/build/components/display/heading.js.map +1 -1
  66. package/build/components/display/icon.d.ts +2 -1
  67. package/build/components/display/icon.d.ts.map +1 -1
  68. package/build/components/display/icon.js +3 -0
  69. package/build/components/display/icon.js.map +1 -1
  70. package/build/components/display/icon_button.d.ts +4 -0
  71. package/build/components/display/icon_button.d.ts.map +1 -1
  72. package/build/components/display/icon_button.js +32 -0
  73. package/build/components/display/icon_button.js.map +1 -1
  74. package/build/components/display/image_attachment_preview.d.ts +14 -0
  75. package/build/components/display/image_attachment_preview.d.ts.map +1 -0
  76. package/build/components/display/image_attachment_preview.js +42 -0
  77. package/build/components/display/image_attachment_preview.js.map +1 -0
  78. package/build/components/display/index.d.ts +2 -0
  79. package/build/components/display/index.d.ts.map +1 -1
  80. package/build/components/display/index.js +2 -0
  81. package/build/components/display/index.js.map +1 -1
  82. package/build/components/display/keyboard_view.d.ts +2 -1
  83. package/build/components/display/keyboard_view.d.ts.map +1 -1
  84. package/build/components/display/keyboard_view.js +13 -11
  85. package/build/components/display/keyboard_view.js.map +1 -1
  86. package/build/components/display/person.d.ts.map +1 -1
  87. package/build/components/display/person.js +4 -1
  88. package/build/components/display/person.js.map +1 -1
  89. package/build/components/display/platform_modal_header_buttons.d.ts +15 -0
  90. package/build/components/display/platform_modal_header_buttons.d.ts.map +1 -0
  91. package/build/components/display/platform_modal_header_buttons.js +43 -0
  92. package/build/components/display/platform_modal_header_buttons.js.map +1 -0
  93. package/build/components/display/video_attachment_preview.d.ts +8 -0
  94. package/build/components/display/video_attachment_preview.d.ts.map +1 -0
  95. package/build/components/display/video_attachment_preview.js +71 -0
  96. package/build/components/display/video_attachment_preview.js.map +1 -0
  97. package/build/components/group_conversation_list.d.ts +1 -1
  98. package/build/components/group_conversation_list.d.ts.map +1 -1
  99. package/build/components/group_conversation_list.js +8 -6
  100. package/build/components/group_conversation_list.js.map +1 -1
  101. package/build/components/page/error_boundary.d.ts.map +1 -1
  102. package/build/components/page/error_boundary.js +4 -1
  103. package/build/components/page/error_boundary.js.map +1 -1
  104. package/build/components/primitive/avatar_primitive.d.ts +6 -1
  105. package/build/components/primitive/avatar_primitive.d.ts.map +1 -1
  106. package/build/components/primitive/avatar_primitive.js +24 -3
  107. package/build/components/primitive/avatar_primitive.js.map +1 -1
  108. package/build/contexts/chat_context.d.ts +1 -0
  109. package/build/contexts/chat_context.d.ts.map +1 -1
  110. package/build/contexts/chat_context.js +5 -3
  111. package/build/contexts/chat_context.js.map +1 -1
  112. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts +14 -196
  113. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts.map +1 -1
  114. package/build/hooks/groups/use_group_members_for_new_conversation.js +2 -2
  115. package/build/hooks/groups/use_group_members_for_new_conversation.js.map +1 -1
  116. package/build/hooks/use_conversation.d.ts.map +1 -1
  117. package/build/hooks/use_conversation.js +7 -1
  118. package/build/hooks/use_conversation.js.map +1 -1
  119. package/build/hooks/use_giphy.js.map +1 -1
  120. package/build/hooks/use_report_bug_action.d.ts +5 -0
  121. package/build/hooks/use_report_bug_action.d.ts.map +1 -0
  122. package/build/hooks/use_report_bug_action.js +30 -0
  123. package/build/hooks/use_report_bug_action.js.map +1 -0
  124. package/build/navigation/index.d.ts +5 -0
  125. package/build/navigation/index.d.ts.map +1 -1
  126. package/build/navigation/index.js +5 -0
  127. package/build/navigation/index.js.map +1 -1
  128. package/build/screens/attachment_actions/attachment_actions_screen.d.ts +6 -2
  129. package/build/screens/attachment_actions/attachment_actions_screen.d.ts.map +1 -1
  130. package/build/screens/attachment_actions/attachment_actions_screen.js +16 -16
  131. package/build/screens/attachment_actions/attachment_actions_screen.js.map +1 -1
  132. package/build/screens/attachment_actions/hooks/useDeleteAttachment.d.ts +10 -0
  133. package/build/screens/attachment_actions/hooks/useDeleteAttachment.d.ts.map +1 -0
  134. package/build/screens/attachment_actions/hooks/useDeleteAttachment.js +29 -0
  135. package/build/screens/attachment_actions/hooks/useDeleteAttachment.js.map +1 -0
  136. package/build/screens/bug_report_screen.d.ts +5 -0
  137. package/build/screens/bug_report_screen.d.ts.map +1 -0
  138. package/build/screens/bug_report_screen.js +237 -0
  139. package/build/screens/bug_report_screen.js.map +1 -0
  140. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  141. package/build/screens/conversation_details_screen.js +29 -10
  142. package/build/screens/conversation_details_screen.js.map +1 -1
  143. package/build/screens/conversation_new/components/form_list.d.ts +3 -1
  144. package/build/screens/conversation_new/components/form_list.d.ts.map +1 -1
  145. package/build/screens/conversation_new/components/form_list.js +6 -2
  146. package/build/screens/conversation_new/components/form_list.js.map +1 -1
  147. package/build/screens/conversation_new/components/groups_form.d.ts.map +1 -1
  148. package/build/screens/conversation_new/components/groups_form.js +3 -6
  149. package/build/screens/conversation_new/components/groups_form.js.map +1 -1
  150. package/build/screens/conversation_new/components/services_form.js +1 -1
  151. package/build/screens/conversation_new/components/services_form.js.map +1 -1
  152. package/build/screens/conversation_screen.d.ts.map +1 -1
  153. package/build/screens/conversation_screen.js +3 -5
  154. package/build/screens/conversation_screen.js.map +1 -1
  155. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  156. package/build/screens/conversations/conversations_screen.js +7 -2
  157. package/build/screens/conversations/conversations_screen.js.map +1 -1
  158. package/build/screens/design_system_screen.d.ts.map +1 -1
  159. package/build/screens/design_system_screen.js +29 -4
  160. package/build/screens/design_system_screen.js.map +1 -1
  161. package/build/types/resources/denormalized_attachment_resource.d.ts +2 -2
  162. package/build/types/resources/denormalized_attachment_resource.js.map +1 -1
  163. package/build/types/resources/member_ability.d.ts +1 -0
  164. package/build/types/resources/member_ability.d.ts.map +1 -1
  165. package/build/types/resources/member_ability.js.map +1 -1
  166. package/build/utils/request/conversation.d.ts +2 -1
  167. package/build/utils/request/conversation.d.ts.map +1 -1
  168. package/build/utils/request/conversation.js.map +1 -1
  169. package/package.json +2 -2
  170. package/src/components/conversation/attachments/audio_attachment.tsx +3 -3
  171. package/src/components/conversation/attachments/expanded_link.tsx +3 -8
  172. package/src/components/conversation/attachments/generic_file_attachment.tsx +3 -3
  173. package/src/components/conversation/attachments/giphy_attachment.tsx +23 -8
  174. package/src/components/conversation/attachments/image_attachment.tsx +4 -4
  175. package/src/components/conversation/attachments/video_attachment.tsx +3 -3
  176. package/src/components/conversation/empty_conversation_blank_state.tsx +1 -1
  177. package/src/components/conversation/message.tsx +20 -8
  178. package/src/components/conversation/message_attachments.tsx +18 -11
  179. package/src/components/conversations/conversation_actions.tsx +1 -0
  180. package/src/components/conversations/conversation_preview.tsx +24 -3
  181. package/src/components/conversations/conversations.tsx +3 -3
  182. package/src/components/display/action_button.tsx +32 -4
  183. package/src/components/display/avatar.tsx +17 -2
  184. package/src/components/display/avatar_group.tsx +19 -3
  185. package/src/components/display/blank_state.tsx +21 -8
  186. package/src/components/display/child_notice.tsx +7 -4
  187. package/src/components/display/heading.tsx +2 -2
  188. package/src/components/display/icon.tsx +4 -1
  189. package/src/components/display/icon_button.tsx +32 -0
  190. package/src/components/display/image_attachment_preview.tsx +74 -0
  191. package/src/components/display/index.ts +2 -0
  192. package/src/components/display/keyboard_view.tsx +16 -17
  193. package/src/components/display/person.tsx +4 -1
  194. package/src/components/display/platform_modal_header_buttons.tsx +83 -0
  195. package/src/components/display/video_attachment_preview.tsx +105 -0
  196. package/src/components/group_conversation_list.tsx +14 -7
  197. package/src/components/page/error_boundary.tsx +4 -1
  198. package/src/components/primitive/avatar_primitive.tsx +42 -4
  199. package/src/contexts/chat_context.tsx +6 -3
  200. package/src/hooks/groups/use_group_members_for_new_conversation.ts +6 -4
  201. package/src/hooks/use_conversation.ts +7 -1
  202. package/src/hooks/use_giphy.ts +2 -2
  203. package/src/hooks/use_report_bug_action.ts +37 -0
  204. package/src/navigation/index.tsx +5 -0
  205. package/src/screens/attachment_actions/attachment_actions_screen.tsx +35 -16
  206. package/src/screens/attachment_actions/hooks/useDeleteAttachment.tsx +46 -0
  207. package/src/screens/bug_report_screen.tsx +334 -0
  208. package/src/screens/conversation_details_screen.tsx +41 -7
  209. package/src/screens/conversation_new/components/form_list.tsx +17 -1
  210. package/src/screens/conversation_new/components/groups_form.tsx +6 -5
  211. package/src/screens/conversation_new/components/services_form.tsx +3 -1
  212. package/src/screens/conversation_screen.tsx +9 -5
  213. package/src/screens/conversations/conversations_screen.tsx +11 -1
  214. package/src/screens/design_system_screen.tsx +68 -3
  215. package/src/types/resources/denormalized_attachment_resource.ts +2 -2
  216. package/src/types/resources/member_ability.ts +1 -0
  217. package/src/utils/request/conversation.ts +2 -1
@@ -23,6 +23,8 @@ import * as logomark from '@planningcenter/icons/paths/logomark'
23
23
  import * as people from '@planningcenter/icons/paths/people'
24
24
  // @ts-ignore
25
25
  import * as services from '@planningcenter/icons/paths/services'
26
+ // @ts-ignore
27
+ import * as publishing from '@planningcenter/icons/paths/publishing'
26
28
 
27
29
  // =================================
28
30
  // ====== Constants ================
@@ -40,9 +42,10 @@ const ICONS = {
40
42
  logomark,
41
43
  people,
42
44
  services,
45
+ publishing,
43
46
  } as const
44
47
 
45
- type IconStyle = ViewStyle & {
48
+ export type IconStyle = ViewStyle & {
46
49
  fontSize?: number
47
50
  color?: string
48
51
  }
@@ -14,6 +14,10 @@ import type { IconButtonAppearanceUnion } from './utils/button_colors'
14
14
  // =================================
15
15
 
16
16
  const SIZES = {
17
+ xxxs: 'xxxs',
18
+ xxs: 'xxs',
19
+ xs: 'xs',
20
+ sm: 'sm',
17
21
  md: 'md',
18
22
  lg: 'lg',
19
23
  xl: 'xl',
@@ -98,6 +102,18 @@ export function IconButton({
98
102
  const androidRippleColor = useCreateAndroidRippleColor({ color: color })
99
103
 
100
104
  const androidRadiusSizeMap: AndroidRadiusSize = {
105
+ [SIZES.xxxs]: {
106
+ radius: 8,
107
+ },
108
+ [SIZES.xxs]: {
109
+ radius: 10,
110
+ },
111
+ [SIZES.xs]: {
112
+ radius: 12,
113
+ },
114
+ [SIZES.sm]: {
115
+ radius: 14,
116
+ },
101
117
  [SIZES.md]: {
102
118
  radius: 16,
103
119
  },
@@ -163,6 +179,22 @@ const useStyles = ({
163
179
  const colorKey = getColorKey({ disabled, loading, appearance })
164
180
 
165
181
  const sizeStyleMap: SizeStyle = {
182
+ [SIZES.xxxs]: {
183
+ fontSize: 8,
184
+ height: 8 * fontScale,
185
+ },
186
+ [SIZES.xxs]: {
187
+ fontSize: 10,
188
+ height: 10 * fontScale,
189
+ },
190
+ [SIZES.xs]: {
191
+ fontSize: 12,
192
+ height: 12 * fontScale,
193
+ },
194
+ [SIZES.sm]: {
195
+ fontSize: 14,
196
+ height: 14 * fontScale,
197
+ },
166
198
  [SIZES.md]: {
167
199
  fontSize: 16,
168
200
  height: 16 * fontScale,
@@ -0,0 +1,74 @@
1
+ import { View, StyleSheet } from 'react-native'
2
+ import { Image } from './image'
3
+ import { IconButton } from './icon_button'
4
+ import { useTheme } from '../../hooks'
5
+
6
+ const SIZES = {
7
+ sm: 'sm',
8
+ md: 'md',
9
+ } as const
10
+ type ImageSizeUnion = (typeof SIZES)[keyof typeof SIZES]
11
+
12
+ interface ImageAttachmentPreviewProps {
13
+ fileName: string
14
+ uri: string
15
+ size?: ImageSizeUnion
16
+ onClosePress: () => void
17
+ }
18
+
19
+ export const ImageAttachmentPreview = ({
20
+ uri,
21
+ fileName,
22
+ size = 'md',
23
+ onClosePress,
24
+ }: ImageAttachmentPreviewProps) => {
25
+ const styles = useStyles({ size })
26
+
27
+ return (
28
+ <View style={styles.container}>
29
+ <IconButton
30
+ name="general.x"
31
+ onPress={onClosePress}
32
+ size="xxs"
33
+ appearance="neutral"
34
+ style={styles.closeButton}
35
+ accessibilityLabel="Remove image attachment"
36
+ />
37
+ <Image
38
+ source={{ uri }}
39
+ style={styles.image}
40
+ alt={`Attached ${fileName}`}
41
+ resizeMode="cover"
42
+ />
43
+ </View>
44
+ )
45
+ }
46
+
47
+ const useStyles = ({ size = 'md' }: { size: ImageSizeUnion }) => {
48
+ const { colors } = useTheme()
49
+
50
+ const imageSizeMap = {
51
+ sm: 60,
52
+ md: 80,
53
+ }
54
+
55
+ return StyleSheet.create({
56
+ container: {
57
+ width: imageSizeMap[size],
58
+ height: imageSizeMap[size],
59
+ },
60
+ image: {
61
+ borderRadius: 8,
62
+ },
63
+ closeButton: {
64
+ position: 'absolute',
65
+ top: 4,
66
+ right: 4,
67
+ zIndex: 2,
68
+ backgroundColor: colors.fillColorNeutral050Base,
69
+ borderRadius: 16,
70
+ height: 20,
71
+ width: 20,
72
+ },
73
+ })
74
+ }
@@ -3,12 +3,14 @@ export * from './avatar'
3
3
  export * from './badge'
4
4
  export * from './banner_collapsible'
5
5
  export * from './banner'
6
+ export * from './blank_state'
6
7
  export * from './button'
7
8
  export * from './child_notice'
8
9
  export * from './heading'
9
10
  export * from './icon_button'
10
11
  export * from './icon'
11
12
  export * from './image'
13
+ export * from './image_attachment_preview'
12
14
  export * from './person'
13
15
  export * from './spinner'
14
16
  export * from './switch'
@@ -1,41 +1,40 @@
1
- import React, { useEffect, useMemo, useState } from 'react'
1
+ import React, { useEffect, useState } from 'react'
2
2
  import {
3
+ Dimensions,
3
4
  Keyboard,
4
5
  KeyboardAvoidingView,
5
6
  Platform,
6
7
  StyleSheet,
7
- useWindowDimensions,
8
8
  View,
9
9
  ViewProps,
10
10
  } from 'react-native'
11
11
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
12
+ import { useChatContext } from '../../contexts/chat_context'
12
13
 
13
- export function KeyboardView({ children }: ViewProps) {
14
+ export function KeyboardView({ children }: Pick<ViewProps, 'children'>) {
14
15
  const styles = useStyles()
15
16
  const isKeyboardOpen = useKeyboardOpen()
16
- const { height } = useWindowDimensions()
17
- const insets = useSafeAreaInsets()
17
+ const screenHeight = Dimensions.get('screen').height
18
18
  const [pageSheetGap, setPageSheetGap] = useState<number>(0)
19
- const keyboardVerticalOffset = useMemo(
20
- () =>
21
- Platform.select({
22
- ios: pageSheetGap - insets.bottom,
23
- android: pageSheetGap + insets.bottom,
24
- }),
25
- [pageSheetGap, insets.bottom]
26
- )
19
+ const { bottom } = useSafeAreaInsets()
20
+ const { edgeToEdge } = useChatContext()
21
+
22
+ const behavior: 'padding' | 'height' | 'position' | undefined = Platform.select({
23
+ android: edgeToEdge ? 'padding' : undefined,
24
+ ios: 'padding',
25
+ })
27
26
 
28
27
  return (
29
28
  <View
30
29
  style={styles.pageGapView}
31
30
  onLayout={event => {
32
31
  const { height: viewHeight } = event.nativeEvent.layout
33
- setPageSheetGap(height - viewHeight)
32
+ setPageSheetGap(screenHeight - viewHeight)
34
33
  }}
35
34
  >
36
35
  <KeyboardAvoidingView
37
- keyboardVerticalOffset={keyboardVerticalOffset}
38
- behavior={'padding'}
36
+ keyboardVerticalOffset={pageSheetGap - bottom}
37
+ behavior={behavior}
39
38
  style={styles.keyboardView}
40
39
  enabled={Platform.select({ android: isKeyboardOpen, ios: true })}
41
40
  >
@@ -45,7 +44,7 @@ export function KeyboardView({ children }: ViewProps) {
45
44
  )
46
45
  }
47
46
 
48
- const useKeyboardOpen = () => {
47
+ export const useKeyboardOpen = () => {
49
48
  const [open, setOpen] = useState(false)
50
49
 
51
50
  useEffect(() => {
@@ -23,7 +23,9 @@ export function Person({ person, style }: PersonProps) {
23
23
  <View style={[styles.wrapper, style]}>
24
24
  <Avatar sourceUri={person.avatar} />
25
25
  <View style={styles.content}>
26
- <Text style={styles.name}>{person.name}</Text>
26
+ <Text style={styles.name} numberOfLines={2}>
27
+ {person.name}
28
+ </Text>
27
29
  <View style={styles.badges}>
28
30
  {person.badges?.map((badge, index) => <Badge key={index} label={badge.title} />)}
29
31
  </View>
@@ -45,6 +47,7 @@ const useStyles = () => {
45
47
  },
46
48
  content: {
47
49
  gap: space(0.25),
50
+ flexShrink: 1,
48
51
  },
49
52
  name: {
50
53
  fontWeight: platformFontWeightMedium,
@@ -0,0 +1,83 @@
1
+ import { ColorValue, Platform, StyleSheet, PlatformColor } from 'react-native'
2
+ import { TextButton } from './text_button'
3
+ import { HeaderButton } from '@react-navigation/elements'
4
+ import { Icon } from './icon'
5
+ import { useTheme } from '../../hooks'
6
+
7
+ interface HeaderSubmitButtonProps {
8
+ onPress: () => void
9
+ title?: string
10
+ disabled?: boolean
11
+ }
12
+
13
+ export const HeaderSubmitButton = ({
14
+ onPress,
15
+ title = 'Submit',
16
+ ...props
17
+ }: HeaderSubmitButtonProps) => {
18
+ const styles = useStyles()
19
+
20
+ return (
21
+ <TextButton
22
+ onPress={onPress}
23
+ textStyle={[styles.interactionColor, props.disabled && styles.disabledColor]}
24
+ {...props}
25
+ >
26
+ {title}
27
+ </TextButton>
28
+ )
29
+ }
30
+
31
+ interface HeaderCancelButtonProps {
32
+ onPress: () => void
33
+ title?: string
34
+ tintColor?: ColorValue
35
+ }
36
+
37
+ export const HeaderCancelButton = ({
38
+ onPress,
39
+ title = 'Cancel',
40
+ tintColor,
41
+ ...props
42
+ }: HeaderCancelButtonProps) => {
43
+ const styles = useStyles()
44
+
45
+ return Platform.select({
46
+ ios: (
47
+ <TextButton
48
+ onPress={onPress}
49
+ textStyle={[styles.interactionColor, styles.iosCancelButton]}
50
+ {...props}
51
+ >
52
+ {title}
53
+ </TextButton>
54
+ ),
55
+ default: (
56
+ <HeaderButton onPress={onPress} style={[styles.androidCancelButton]} {...props}>
57
+ <Icon name="general.x" size={16} color={tintColor} />
58
+ </HeaderButton>
59
+ ),
60
+ })
61
+ }
62
+
63
+ const useStyles = () => {
64
+ const { colors } = useTheme()
65
+
66
+ return StyleSheet.create({
67
+ interactionColor: {
68
+ color: Platform.select({
69
+ ios: PlatformColor('link'),
70
+ android: colors.fillColorInteractionDefault as ColorValue,
71
+ }),
72
+ },
73
+ androidCancelButton: {
74
+ marginRight: 16,
75
+ },
76
+ iosCancelButton: {
77
+ fontWeight: 'normal',
78
+ },
79
+ disabledColor: {
80
+ color: colors.textColorDefaultDisabled,
81
+ },
82
+ })
83
+ }
@@ -0,0 +1,105 @@
1
+ import { View, StyleSheet } from 'react-native'
2
+ import { IconButton } from './icon_button'
3
+ import { useTheme } from '../../hooks'
4
+ import { Icon } from './icon'
5
+ import { Text } from './text'
6
+ import { platformFontWeightMedium } from '../../utils'
7
+
8
+ interface VideoAttachmentPreviewProps {
9
+ name: string
10
+ duration?: string
11
+ onClosePress: () => void
12
+ }
13
+
14
+ export const VideoAttachmentPreview = ({
15
+ name,
16
+ duration,
17
+ onClosePress,
18
+ }: VideoAttachmentPreviewProps) => {
19
+ const styles = useStyles()
20
+
21
+ return (
22
+ <View style={styles.container}>
23
+ <View style={styles.contentContainer}>
24
+ <Icon name="general.outlinedVideoFile" size={18} style={styles.fileIcon} />
25
+ <View style={styles.textContainer}>
26
+ <Text
27
+ variant="tertiary"
28
+ numberOfLines={1}
29
+ style={styles.nameText}
30
+ accessibilityLabel={`Video attachment: ${name}`}
31
+ >
32
+ {name}
33
+ </Text>
34
+ {duration && (
35
+ <Text
36
+ variant="tertiary"
37
+ numberOfLines={1}
38
+ style={styles.durationText}
39
+ accessibilityLabel={`Duration: ${duration}`}
40
+ >
41
+ {duration}
42
+ </Text>
43
+ )}
44
+ </View>
45
+ </View>
46
+ <IconButton
47
+ name="general.x"
48
+ onPress={onClosePress}
49
+ size="xxs"
50
+ appearance="neutral"
51
+ style={styles.closeButton}
52
+ accessibilityLabel="Remove video attachment"
53
+ />
54
+ </View>
55
+ )
56
+ }
57
+
58
+ const useStyles = () => {
59
+ const { colors } = useTheme()
60
+
61
+ return StyleSheet.create({
62
+ container: {
63
+ height: 60,
64
+ flexDirection: 'row',
65
+ justifyContent: 'space-between',
66
+ alignItems: 'center',
67
+ gap: 8,
68
+ backgroundColor: colors.fillColorNeutral070,
69
+ borderColor: colors.borderColorDefaultBase,
70
+ borderWidth: 1,
71
+ borderRadius: 8,
72
+ padding: 4,
73
+ },
74
+ contentContainer: {
75
+ flexDirection: 'row',
76
+ gap: 8,
77
+ alignItems: 'center',
78
+ flexShrink: 1,
79
+ paddingVertical: 4,
80
+ paddingLeft: 8,
81
+ },
82
+ textContainer: {
83
+ flexShrink: 1,
84
+ flexDirection: 'column',
85
+ },
86
+ fileIcon: {
87
+ color: colors.iconColorDefaultPrimary,
88
+ },
89
+ nameText: {
90
+ color: colors.textColorDefaultPrimary,
91
+ fontWeight: platformFontWeightMedium,
92
+ flexShrink: 1,
93
+ },
94
+ durationText: {
95
+ color: colors.textColorDefaultPrimary,
96
+ },
97
+ closeButton: {
98
+ backgroundColor: colors.fillColorNeutral050Base,
99
+ borderRadius: 16,
100
+ height: 20,
101
+ width: 20,
102
+ alignSelf: 'flex-start',
103
+ },
104
+ })
105
+ }
@@ -1,10 +1,13 @@
1
1
  import React from 'react'
2
2
  import { StyleSheet, View, ViewStyle } from 'react-native'
3
- import { useConversations } from '../hooks/use_conversations'
4
3
  import { ConversationPreview } from './conversations/conversation_preview'
5
4
  import { ConversationRequestArgs } from '../utils/request/conversation'
6
5
  import { useTheme } from '../hooks'
7
6
  import { Icon, Text } from './display'
7
+ import {
8
+ ConversationsContextProvider,
9
+ useConversationsContext,
10
+ } from '../contexts/conversations_context'
8
11
 
9
12
  interface GroupConversationsProps extends Partial<ConversationRequestArgs> {
10
13
  limit?: number
@@ -24,19 +27,23 @@ interface GroupConversationsProps extends Partial<ConversationRequestArgs> {
24
27
  *
25
28
  * Originally designed for use in CCA.
26
29
  */
27
- export const GroupConversations = ({
30
+ export const GroupConversations = ({ chat_group_graph_id, ...rest }: GroupConversationsProps) => {
31
+ return (
32
+ <ConversationsContextProvider args={{ chat_group_graph_id, group_source_app_name: undefined }}>
33
+ <GroupConversationList {...rest} />
34
+ </ConversationsContextProvider>
35
+ )
36
+ }
37
+
38
+ const GroupConversationList = ({
28
39
  limit,
29
40
  onConversationPress,
30
41
  style,
31
42
  ListHeaderComponent,
32
43
  ListOverflowFooterComponent,
33
- chat_group_graph_id,
34
44
  }: GroupConversationsProps) => {
35
45
  const styles = useStyles()
36
- const { conversations = [] } = useConversations({
37
- chat_group_graph_id,
38
- group_source_app_name: undefined,
39
- })
46
+ const { conversations = [] } = useConversationsContext()
40
47
 
41
48
  return (
42
49
  <View style={style}>
@@ -3,7 +3,7 @@ import { useQueryErrorResetBoundary } from '@tanstack/react-query'
3
3
  import React, { PropsWithChildren, useEffect, useMemo } from 'react'
4
4
  import { StyleSheet, View } from 'react-native'
5
5
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
6
- import { Button, Heading, Icon, Text } from '../display'
6
+ import { Button, Heading, Icon, Text, TextButton } from '../display'
7
7
  import { useTheme } from '../../hooks'
8
8
  import { ResponseError } from '../../utils/response_error'
9
9
 
@@ -98,6 +98,9 @@ function ErrorContent({ heading, body }: { heading: string; body: string }) {
98
98
  </Heading>
99
99
  <Text style={styles.body}>{body}</Text>
100
100
  </View>
101
+ <TextButton variant="tertiary" onPress={() => navigation.navigate('BugReport')}>
102
+ Report a bug
103
+ </TextButton>
101
104
  <Button variant="outline" onPress={navigation.goBack} title="Go back" size="md" />
102
105
  </View>
103
106
  )
@@ -1,6 +1,7 @@
1
1
  import React, { createContext, useContext, useEffect, useState } from 'react'
2
2
  import { StyleSheet, View, ViewProps } from 'react-native'
3
3
  import { useTheme } from '../../hooks'
4
+ import { Icon, IconString } from '../display/icon'
4
5
  import { Image, ImageProps } from '../display/image'
5
6
  import { Spinner } from '../display/spinner'
6
7
 
@@ -11,6 +12,7 @@ import { Spinner } from '../display/spinner'
11
12
  const Avatar = {
12
13
  Root: AvatarRoot,
13
14
  Image: AvatarImage,
15
+ ImageFallback: AvatarImageFallback,
14
16
  Presence: AvatarPresence,
15
17
  Group: AvatarGroup,
16
18
  GroupLoader: AvatarGroupLoader,
@@ -20,6 +22,7 @@ const Avatar = {
20
22
  type AvatarComponents = {
21
23
  Root: React.FC<AvatarRootProps>
22
24
  Image: React.FC<AvatarImageProps>
25
+ ImageFallback: React.FC<AvatarImageFallbackProps>
23
26
  Presence: React.FC<AvatarPresenceProps>
24
27
  Group: React.FC<AvatarGroupProps>
25
28
  GroupLoader: React.FC<Record<string, never>>
@@ -29,6 +32,7 @@ type AvatarComponents = {
29
32
  export default Avatar as AvatarComponents
30
33
  export type {
31
34
  AvatarImageProps,
35
+ AvatarImageFallbackProps,
32
36
  AvatarPresenceProps,
33
37
  AvatarGroupProps,
34
38
  AvatarMaskProps,
@@ -66,6 +70,12 @@ const AVATAR_PRESENCE_PX: Record<AvatarSize, number> = {
66
70
  [AVATAR_SIZES.lg]: 14,
67
71
  }
68
72
 
73
+ const AVATAR_FALLBACK_ICON_PX: Record<AvatarSize, number> = {
74
+ [AVATAR_SIZES.sm]: 12,
75
+ [AVATAR_SIZES.md]: 16,
76
+ [AVATAR_SIZES.lg]: 20,
77
+ }
78
+
69
79
  // =================================
70
80
  // ====== Context ==================
71
81
  // =================================
@@ -160,6 +170,28 @@ function AvatarGroupImage({ sourceUri, style, onLoad }: AvatarGroupImageProps) {
160
170
  )
161
171
  }
162
172
 
173
+ interface AvatarImageFallbackProps {
174
+ name?: IconString
175
+ }
176
+
177
+ function AvatarImageFallback({ name = 'general.person' }: AvatarImageFallbackProps) {
178
+ const { size } = useAvatarContext()
179
+ const styles = useStyles()
180
+
181
+ return (
182
+ <View style={styles.fallbackContainer}>
183
+ <Icon
184
+ name={name}
185
+ size={AVATAR_FALLBACK_ICON_PX[size]}
186
+ style={styles.fallbackIcon}
187
+ accessibilityElementsHidden={true}
188
+ />
189
+ </View>
190
+ )
191
+ }
192
+
193
+ AvatarImageFallback.displayName = 'Avatar.ImageFallback'
194
+
163
195
  // =================================
164
196
  // ====== AvatarGroup ============
165
197
  // =================================
@@ -169,6 +201,7 @@ interface AvatarGroupProps {
169
201
  }
170
202
 
171
203
  type AvatarIndex = 0 | 1 | 2 | 3
204
+
172
205
  function AvatarGroup({ sourceUris }: AvatarGroupProps) {
173
206
  const styles = useStyles()
174
207
  const { setAllImagesLoaded } = useAvatarContext()
@@ -196,10 +229,6 @@ function AvatarGroup({ sourceUris }: AvatarGroupProps) {
196
229
  setAllImagesLoaded(allImagesLoaded)
197
230
  }, [displayUris, hasDisplayUris, loadingStatus, setAllImagesLoaded])
198
231
 
199
- if (!hasDisplayUris) {
200
- return null
201
- }
202
-
203
232
  if (displayUris.length === 1) {
204
233
  return <AvatarGroupImage sourceUri={displayUris[0]} onLoad={() => handleImageLoaded(0)} />
205
234
  }
@@ -381,5 +410,14 @@ const useStyles = ({ size = 'md', presence = 'offline' }: Styles = {}) => {
381
410
  width: '100%',
382
411
  height: '50%',
383
412
  },
413
+ fallbackContainer: {
414
+ flex: 1,
415
+ alignItems: 'center',
416
+ justifyContent: 'center',
417
+ backgroundColor: colors.fillColorNeutral070,
418
+ },
419
+ fallbackIcon: {
420
+ color: colors.iconColorDefaultDim,
421
+ },
384
422
  })
385
423
  }
@@ -12,6 +12,7 @@ export interface ChatProviderProps {
12
12
  token?: PartialToken
13
13
  tokenType?: OauthType
14
14
  theme: CreateChatThemeProps
15
+ edgeToEdge?: boolean
15
16
  }
16
17
 
17
18
  export interface ChatContextValue extends Omit<ChatProviderProps, 'theme'> {
@@ -26,10 +27,11 @@ export const ChatContext = createContext<ChatContextValue>({
26
27
  session: new Session(),
27
28
  theme: defaultTheme('light'),
28
29
  token: undefined,
30
+ edgeToEdge: true,
29
31
  })
30
32
 
31
33
  export function ChatProvider({ children, value }: { children: any; value: ChatProviderProps }) {
32
- const { env, token, onUnauthorizedResponse, giphyApiKey, tokenType } = value
34
+ const { env, token, onUnauthorizedResponse, giphyApiKey, tokenType, edgeToEdge = true } = value
33
35
  const theme = useCreateChatTheme(value.theme || {})
34
36
  const session = useMemo(
35
37
  () => new Session({ token, env, type: tokenType }),
@@ -37,12 +39,13 @@ export function ChatProvider({ children, value }: { children: any; value: ChatPr
37
39
  )
38
40
 
39
41
  const contextValue: ChatContextValue = {
42
+ edgeToEdge,
40
43
  env,
41
- token,
44
+ giphyApiKey,
42
45
  onUnauthorizedResponse,
43
46
  session,
44
47
  theme,
45
- giphyApiKey,
48
+ token,
46
49
  }
47
50
 
48
51
  return <ChatContext.Provider value={contextValue}>{children}</ChatContext.Provider>
@@ -1,12 +1,14 @@
1
1
  import { MemberBadge, MemberResource, ResourceObject } from '../../types'
2
2
  import { GroupsGroupMemberResource } from '../../types/resources/groups/groups_member_resource_with_person'
3
- import { useApiPaginator } from '../use_api'
4
3
  import { useCurrentPerson } from '../use_current_person'
4
+ import { useSuspensePaginator } from '../use_suspense_api'
5
5
 
6
- type UseApiPaginatorResult<T extends ResourceObject> = ReturnType<typeof useApiPaginator<T>>
6
+ type UseSuspensePaginatorResult<T extends ResourceObject> = ReturnType<
7
+ typeof useSuspensePaginator<T>
8
+ >
7
9
 
8
10
  export type GroupMembersForNewConversationResult = Omit<
9
- UseApiPaginatorResult<GroupsGroupMemberResource>, // We are passing our own "members" data shape
11
+ UseSuspensePaginatorResult<GroupsGroupMemberResource>, // We are passing our own "members" data shape
10
12
  'data'
11
13
  > & {
12
14
  data: MemberResource[]
@@ -20,7 +22,7 @@ export type GroupMembersForNewConversationResult = Omit<
20
22
  */
21
23
  export function useGroupMembersForNewConversation({ id }: { id: number }) {
22
24
  const currentPerson = useCurrentPerson()
23
- const response = useApiPaginator<GroupsGroupMemberResource>({
25
+ const response = useSuspensePaginator<GroupsGroupMemberResource>({
24
26
  url: `/me/groups/${id}/memberships`,
25
27
  data: {
26
28
  perPage: 100,
@@ -26,7 +26,13 @@ export const getConversationRequestArgs = ({ conversation_id }: { conversation_i
26
26
  'unread_count',
27
27
  'updated_at',
28
28
  ],
29
- MemberAbility: ['can_update', 'can_delete', 'leader', 'can_reply'],
29
+ MemberAbility: [
30
+ 'can_update',
31
+ 'can_delete',
32
+ 'leader',
33
+ 'can_reply',
34
+ 'can_delete_non_authored_messages',
35
+ ],
30
36
  ConversationBadge: ['app_name', 'pco_resource_type', 'text'],
31
37
  Group: ['type', 'id', 'links', 'name', 'source_app_name', 'source_type'],
32
38
  },