@planningcenter/chat-react-native 4.4.0-rc.6 → 4.4.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 (350) hide show
  1. package/build/components/conversation/jump_to_bottom_button.d.ts.map +1 -1
  2. package/build/components/conversation/jump_to_bottom_button.js +42 -40
  3. package/build/components/conversation/jump_to_bottom_button.js.map +1 -1
  4. package/build/components/conversation/message.d.ts +2 -1
  5. package/build/components/conversation/message.d.ts.map +1 -1
  6. package/build/components/conversation/message.js +3 -1
  7. package/build/components/conversation/message.js.map +1 -1
  8. package/build/components/conversation/message_form.d.ts.map +1 -1
  9. package/build/components/conversation/message_form.js +3 -1
  10. package/build/components/conversation/message_form.js.map +1 -1
  11. package/build/components/conversation/message_list.d.ts.map +1 -1
  12. package/build/components/conversation/message_list.js +35 -31
  13. package/build/components/conversation/message_list.js.map +1 -1
  14. package/build/components/conversation/message_reaction.d.ts +2 -1
  15. package/build/components/conversation/message_reaction.d.ts.map +1 -1
  16. package/build/components/conversation/message_reaction.js +67 -60
  17. package/build/components/conversation/message_reaction.js.map +1 -1
  18. package/build/components/conversation/messages_disabled_banners.d.ts +7 -5
  19. package/build/components/conversation/messages_disabled_banners.d.ts.map +1 -1
  20. package/build/components/conversation/messages_disabled_banners.js +15 -14
  21. package/build/components/conversation/messages_disabled_banners.js.map +1 -1
  22. package/build/components/conversation/system_message.d.ts +2 -1
  23. package/build/components/conversation/system_message.d.ts.map +1 -1
  24. package/build/components/conversation/system_message.js +133 -100
  25. package/build/components/conversation/system_message.js.map +1 -1
  26. package/build/components/conversations/archived_results_footer.d.ts +6 -0
  27. package/build/components/conversations/archived_results_footer.d.ts.map +1 -0
  28. package/build/components/conversations/archived_results_footer.js +51 -0
  29. package/build/components/conversations/archived_results_footer.js.map +1 -0
  30. package/build/components/conversations/conversation_actions.d.ts.map +1 -1
  31. package/build/components/conversations/conversation_actions.js +241 -206
  32. package/build/components/conversations/conversation_actions.js.map +1 -1
  33. package/build/components/conversations/conversation_preview.d.ts.map +1 -1
  34. package/build/components/conversations/conversation_preview.js +102 -93
  35. package/build/components/conversations/conversation_preview.js.map +1 -1
  36. package/build/components/conversations/conversations.d.ts.map +1 -1
  37. package/build/components/conversations/conversations.js +66 -41
  38. package/build/components/conversations/conversations.js.map +1 -1
  39. package/build/components/conversations/conversations_blank_state.d.ts.map +1 -1
  40. package/build/components/conversations/conversations_blank_state.js +55 -12
  41. package/build/components/conversations/conversations_blank_state.js.map +1 -1
  42. package/build/components/conversations/filtered_results_footer.d.ts +2 -0
  43. package/build/components/conversations/filtered_results_footer.d.ts.map +1 -0
  44. package/build/components/conversations/filtered_results_footer.js +77 -0
  45. package/build/components/conversations/filtered_results_footer.js.map +1 -0
  46. package/build/components/display/action_button.d.ts.map +1 -1
  47. package/build/components/display/action_button.js +18 -16
  48. package/build/components/display/action_button.js.map +1 -1
  49. package/build/components/display/filter_dropdown.d.ts +14 -0
  50. package/build/components/display/filter_dropdown.d.ts.map +1 -0
  51. package/build/components/display/filter_dropdown.js +193 -0
  52. package/build/components/display/filter_dropdown.js.map +1 -0
  53. package/build/components/display/icon_tile.d.ts +13 -0
  54. package/build/components/display/icon_tile.d.ts.map +1 -0
  55. package/build/components/display/icon_tile.js +149 -0
  56. package/build/components/display/icon_tile.js.map +1 -0
  57. package/build/components/display/index.d.ts +2 -0
  58. package/build/components/display/index.d.ts.map +1 -1
  59. package/build/components/display/index.js +2 -0
  60. package/build/components/display/index.js.map +1 -1
  61. package/build/contexts/api_provider.d.ts.map +1 -1
  62. package/build/contexts/api_provider.js +21 -9
  63. package/build/contexts/api_provider.js.map +1 -1
  64. package/build/contexts/conversations_context.d.ts.map +1 -1
  65. package/build/contexts/conversations_context.js +21 -18
  66. package/build/contexts/conversations_context.js.map +1 -1
  67. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts +196 -16
  68. package/build/hooks/groups/use_group_members_for_new_conversation.d.ts.map +1 -1
  69. package/build/hooks/groups/use_group_members_for_new_conversation.js +72 -58
  70. package/build/hooks/groups/use_group_members_for_new_conversation.js.map +1 -1
  71. package/build/hooks/index.d.ts +3 -0
  72. package/build/hooks/index.d.ts.map +1 -1
  73. package/build/hooks/index.js +3 -0
  74. package/build/hooks/index.js.map +1 -1
  75. package/build/hooks/use_animated_message_background_color.d.ts.map +1 -1
  76. package/build/hooks/use_animated_message_background_color.js +5 -3
  77. package/build/hooks/use_animated_message_background_color.js.map +1 -1
  78. package/build/hooks/use_api.d.ts +3 -2
  79. package/build/hooks/use_api.d.ts.map +1 -1
  80. package/build/hooks/use_api.js.map +1 -1
  81. package/build/hooks/use_can_browse_archived_conversations.d.ts +2 -0
  82. package/build/hooks/use_can_browse_archived_conversations.d.ts.map +1 -0
  83. package/build/hooks/use_can_browse_archived_conversations.js +29 -0
  84. package/build/hooks/use_can_browse_archived_conversations.js.map +1 -0
  85. package/build/hooks/use_clear_conversation_filters.d.ts +2 -0
  86. package/build/hooks/use_clear_conversation_filters.d.ts.map +1 -0
  87. package/build/hooks/use_clear_conversation_filters.js +25 -0
  88. package/build/hooks/use_clear_conversation_filters.js.map +1 -0
  89. package/build/hooks/use_conversation.d.ts +73 -1
  90. package/build/hooks/use_conversation.d.ts.map +1 -1
  91. package/build/hooks/use_conversation.js +130 -3
  92. package/build/hooks/use_conversation.js.map +1 -1
  93. package/build/hooks/use_conversation_messages.d.ts.map +1 -1
  94. package/build/hooks/use_conversation_messages.js +29 -26
  95. package/build/hooks/use_conversation_messages.js.map +1 -1
  96. package/build/hooks/use_current_person.d.ts.map +1 -1
  97. package/build/hooks/use_current_person.js +1 -1
  98. package/build/hooks/use_current_person.js.map +1 -1
  99. package/build/hooks/use_features.d.ts +2 -0
  100. package/build/hooks/use_features.d.ts.map +1 -1
  101. package/build/hooks/use_features.js +2 -0
  102. package/build/hooks/use_features.js.map +1 -1
  103. package/build/hooks/use_filter_dropdown_enabled.d.ts +2 -0
  104. package/build/hooks/use_filter_dropdown_enabled.d.ts.map +1 -0
  105. package/build/hooks/use_filter_dropdown_enabled.js +18 -0
  106. package/build/hooks/use_filter_dropdown_enabled.js.map +1 -0
  107. package/build/hooks/use_jolt.d.ts +1 -0
  108. package/build/hooks/use_jolt.d.ts.map +1 -1
  109. package/build/hooks/use_jolt.js +59 -0
  110. package/build/hooks/use_jolt.js.map +1 -1
  111. package/build/hooks/use_manual_archiving_enabled.d.ts +2 -0
  112. package/build/hooks/use_manual_archiving_enabled.d.ts.map +1 -0
  113. package/build/hooks/use_manual_archiving_enabled.js +18 -0
  114. package/build/hooks/use_manual_archiving_enabled.js.map +1 -0
  115. package/build/hooks/use_mark_latest_message_read.d.ts.map +1 -1
  116. package/build/hooks/use_mark_latest_message_read.js +3 -3
  117. package/build/hooks/use_mark_latest_message_read.js.map +1 -1
  118. package/build/hooks/use_reduce_motion.d.ts +2 -0
  119. package/build/hooks/use_reduce_motion.d.ts.map +1 -0
  120. package/build/hooks/use_reduce_motion.js +28 -0
  121. package/build/hooks/use_reduce_motion.js.map +1 -0
  122. package/build/hooks/use_unarchive_conversation_action.d.ts +7 -0
  123. package/build/hooks/use_unarchive_conversation_action.d.ts.map +1 -0
  124. package/build/hooks/use_unarchive_conversation_action.js +52 -0
  125. package/build/hooks/use_unarchive_conversation_action.js.map +1 -0
  126. package/build/screens/attachment_actions/attachment_actions_screen.d.ts.map +1 -1
  127. package/build/screens/attachment_actions/attachment_actions_screen.js +84 -61
  128. package/build/screens/attachment_actions/attachment_actions_screen.js.map +1 -1
  129. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  130. package/build/screens/conversation_details_screen.js +490 -395
  131. package/build/screens/conversation_details_screen.js.map +1 -1
  132. package/build/screens/conversation_filters/components/conversation_filters.d.ts.map +1 -1
  133. package/build/screens/conversation_filters/components/conversation_filters.js +88 -18
  134. package/build/screens/conversation_filters/components/conversation_filters.js.map +1 -1
  135. package/build/screens/conversation_filters/components/rows.d.ts +9 -0
  136. package/build/screens/conversation_filters/components/rows.d.ts.map +1 -1
  137. package/build/screens/conversation_filters/components/rows.js +93 -1
  138. package/build/screens/conversation_filters/components/rows.js.map +1 -1
  139. package/build/screens/conversation_filters/context/conversation_filter_context.d.ts.map +1 -1
  140. package/build/screens/conversation_filters/context/conversation_filter_context.js +8 -4
  141. package/build/screens/conversation_filters/context/conversation_filter_context.js.map +1 -1
  142. package/build/screens/conversation_filters/filter_types.d.ts +1 -0
  143. package/build/screens/conversation_filters/filter_types.d.ts.map +1 -1
  144. package/build/screens/conversation_filters/filter_types.js +1 -0
  145. package/build/screens/conversation_filters/filter_types.js.map +1 -1
  146. package/build/screens/conversation_filters/screen_props.d.ts +1 -0
  147. package/build/screens/conversation_filters/screen_props.d.ts.map +1 -1
  148. package/build/screens/conversation_filters/screen_props.js.map +1 -1
  149. package/build/screens/conversation_filters_screen.d.ts +1 -0
  150. package/build/screens/conversation_filters_screen.d.ts.map +1 -1
  151. package/build/screens/conversation_filters_screen.js +79 -48
  152. package/build/screens/conversation_filters_screen.js.map +1 -1
  153. package/build/screens/conversation_new/components/groups_form.d.ts.map +1 -1
  154. package/build/screens/conversation_new/components/groups_form.js +37 -34
  155. package/build/screens/conversation_new/components/groups_form.js.map +1 -1
  156. package/build/screens/conversation_screen.d.ts.map +1 -1
  157. package/build/screens/conversation_screen.js +13 -10
  158. package/build/screens/conversation_screen.js.map +1 -1
  159. package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts +2 -0
  160. package/build/screens/conversations/components/filter_dropdown_list_header_component.d.ts.map +1 -0
  161. package/build/screens/conversations/components/filter_dropdown_list_header_component.js +580 -0
  162. package/build/screens/conversations/components/filter_dropdown_list_header_component.js.map +1 -0
  163. package/build/screens/conversations/components/list_header_component.d.ts.map +1 -1
  164. package/build/screens/conversations/components/list_header_component.js +95 -90
  165. package/build/screens/conversations/components/list_header_component.js.map +1 -1
  166. package/build/screens/conversations/components/search_list_header_component.d.ts.map +1 -1
  167. package/build/screens/conversations/components/search_list_header_component.js +100 -95
  168. package/build/screens/conversations/components/search_list_header_component.js.map +1 -1
  169. package/build/screens/conversations/conversations_screen.d.ts +1 -0
  170. package/build/screens/conversations/conversations_screen.d.ts.map +1 -1
  171. package/build/screens/conversations/conversations_screen.js +15 -2
  172. package/build/screens/conversations/conversations_screen.js.map +1 -1
  173. package/build/screens/design_system_screen.d.ts.map +1 -1
  174. package/build/screens/design_system_screen.js +275 -215
  175. package/build/screens/design_system_screen.js.map +1 -1
  176. package/build/screens/message_actions_screen.d.ts.map +1 -1
  177. package/build/screens/message_actions_screen.js +112 -92
  178. package/build/screens/message_actions_screen.js.map +1 -1
  179. package/build/screens/search/search_screen.d.ts.map +1 -1
  180. package/build/screens/search/search_screen.js +10 -2
  181. package/build/screens/search/search_screen.js.map +1 -1
  182. package/build/screens/send_giphy_screen.d.ts +1 -0
  183. package/build/screens/send_giphy_screen.d.ts.map +1 -1
  184. package/build/screens/send_giphy_screen.js +108 -106
  185. package/build/screens/send_giphy_screen.js.map +1 -1
  186. package/build/screens/team_conversation_screen.js +1 -1
  187. package/build/screens/team_conversation_screen.js.map +1 -1
  188. package/build/types/jolt_events/message_events.d.ts +2 -0
  189. package/build/types/jolt_events/message_events.d.ts.map +1 -1
  190. package/build/types/jolt_events/message_events.js.map +1 -1
  191. package/build/types/resources/conversation.d.ts +2 -2
  192. package/build/types/resources/conversation.d.ts.map +1 -1
  193. package/build/types/resources/conversation.js.map +1 -1
  194. package/build/types/resources/member_ability.d.ts +2 -0
  195. package/build/types/resources/member_ability.d.ts.map +1 -1
  196. package/build/types/resources/member_ability.js.map +1 -1
  197. package/build/types/resources/message.d.ts +2 -0
  198. package/build/types/resources/message.d.ts.map +1 -1
  199. package/build/types/resources/message.js.map +1 -1
  200. package/build/types/resources/person.d.ts +1 -0
  201. package/build/types/resources/person.d.ts.map +1 -1
  202. package/build/types/resources/person.js.map +1 -1
  203. package/build/utils/archived_conversation.d.ts +5 -0
  204. package/build/utils/archived_conversation.d.ts.map +1 -0
  205. package/build/utils/archived_conversation.js +5 -0
  206. package/build/utils/archived_conversation.js.map +1 -0
  207. package/build/utils/date.d.ts +1 -0
  208. package/build/utils/date.d.ts.map +1 -1
  209. package/build/utils/date.js +9 -0
  210. package/build/utils/date.js.map +1 -1
  211. package/build/utils/index.d.ts +1 -0
  212. package/build/utils/index.d.ts.map +1 -1
  213. package/build/utils/index.js +1 -0
  214. package/build/utils/index.js.map +1 -1
  215. package/build/utils/jolt/transform_message_event_data_to_message_resource.d.ts.map +1 -1
  216. package/build/utils/jolt/transform_message_event_data_to_message_resource.js +2 -1
  217. package/build/utils/jolt/transform_message_event_data_to_message_resource.js.map +1 -1
  218. package/build/utils/native_adapters/configuration.d.ts +3 -0
  219. package/build/utils/native_adapters/configuration.d.ts.map +1 -1
  220. package/build/utils/native_adapters/configuration.js +3 -0
  221. package/build/utils/native_adapters/configuration.js.map +1 -1
  222. package/build/utils/native_adapters/index.d.ts +1 -0
  223. package/build/utils/native_adapters/index.d.ts.map +1 -1
  224. package/build/utils/native_adapters/index.js +1 -0
  225. package/build/utils/native_adapters/index.js.map +1 -1
  226. package/build/utils/native_adapters/menu.d.ts +41 -0
  227. package/build/utils/native_adapters/menu.d.ts.map +1 -0
  228. package/build/utils/native_adapters/menu.js +56 -0
  229. package/build/utils/native_adapters/menu.js.map +1 -0
  230. package/build/utils/request/conversation.d.ts +2 -1
  231. package/build/utils/request/conversation.d.ts.map +1 -1
  232. package/build/utils/request/conversation.js +4 -1
  233. package/build/utils/request/conversation.js.map +1 -1
  234. package/build/utils/request/messages_data_options.d.ts.map +1 -1
  235. package/build/utils/request/messages_data_options.js +1 -1
  236. package/build/utils/request/messages_data_options.js.map +1 -1
  237. package/build/utils/system_messages.d.ts +7 -5
  238. package/build/utils/system_messages.d.ts.map +1 -1
  239. package/build/utils/system_messages.js +10 -2
  240. package/build/utils/system_messages.js.map +1 -1
  241. package/build/vendor/font_awesome_pro_solid_icons.d.ts +1 -0
  242. package/build/vendor/font_awesome_pro_solid_icons.d.ts.map +1 -1
  243. package/build/vendor/font_awesome_pro_solid_icons.js +5 -0
  244. package/build/vendor/font_awesome_pro_solid_icons.js.map +1 -1
  245. package/package.json +3 -3
  246. package/src/__tests__/hooks/use_can_browse_archived_conversations.test.tsx +98 -0
  247. package/src/__tests__/hooks/use_filter_dropdown_enabled.test.tsx +42 -0
  248. package/src/__tests__/screens/conversation_filters/components/conversation_filters.test.tsx +132 -0
  249. package/src/__tests__/screens/conversation_filters_screen.test.tsx +76 -0
  250. package/src/__tests__/screens/conversation_new/components/services_form.test.tsx +1 -0
  251. package/src/__tests__/screens/message_actions_screen.test.tsx +65 -2
  252. package/src/__tests__/screens/send_giphy_screen.test.tsx +102 -0
  253. package/src/__tests__/utils/system_messages.ts +19 -32
  254. package/src/components/conversation/__tests__/message_list.test.tsx +68 -2
  255. package/src/components/conversation/__tests__/system_message.test.tsx +106 -0
  256. package/src/components/conversation/jump_to_bottom_button.tsx +4 -3
  257. package/src/components/conversation/message.tsx +3 -0
  258. package/src/components/conversation/message_form.tsx +3 -1
  259. package/src/components/conversation/message_list.tsx +9 -1
  260. package/src/components/conversation/message_reaction.tsx +7 -2
  261. package/src/components/conversation/messages_disabled_banners.tsx +17 -18
  262. package/src/components/conversation/system_message.tsx +44 -16
  263. package/src/components/conversations/__tests__/conversation_actions.test.tsx +67 -0
  264. package/src/components/conversations/__tests__/conversation_preview.test.tsx +105 -0
  265. package/src/components/conversations/__tests__/conversations.test.tsx +93 -0
  266. package/src/components/conversations/__tests__/conversations_blank_state.test.tsx +73 -0
  267. package/src/components/conversations/archived_results_footer.tsx +36 -0
  268. package/src/components/conversations/conversation_actions.tsx +27 -19
  269. package/src/components/conversations/conversation_preview.tsx +39 -10
  270. package/src/components/conversations/conversations.tsx +26 -3
  271. package/src/components/conversations/conversations_blank_state.tsx +21 -0
  272. package/src/components/conversations/filtered_results_footer.tsx +38 -0
  273. package/src/components/display/__tests__/filter_dropdown.test.tsx +114 -0
  274. package/src/components/display/__tests__/icon_tile.test.tsx +16 -0
  275. package/src/components/display/action_button.tsx +4 -3
  276. package/src/components/display/filter_dropdown.tsx +124 -0
  277. package/src/components/display/icon_tile.tsx +67 -0
  278. package/src/components/display/index.ts +2 -0
  279. package/src/contexts/api_provider.tsx +8 -0
  280. package/src/contexts/conversations_context.tsx +2 -1
  281. package/src/hooks/__tests__/use_conversation_archive.test.tsx +152 -0
  282. package/src/hooks/__tests__/use_conversation_jolt_events.test.tsx +74 -0
  283. package/src/hooks/__tests__/use_conversation_messages.test.tsx +10 -2
  284. package/src/hooks/__tests__/use_jolt.test.tsx +100 -1
  285. package/src/hooks/__tests__/use_mark_latest_message_read.test.tsx +21 -7
  286. package/src/hooks/__tests__/use_reduce_motion.test.tsx +61 -0
  287. package/src/hooks/__tests__/use_unarchive_conversation_action.test.tsx +67 -0
  288. package/src/hooks/groups/use_group_members_for_new_conversation.ts +21 -16
  289. package/src/hooks/index.ts +3 -0
  290. package/src/hooks/use_animated_message_background_color.ts +6 -3
  291. package/src/hooks/use_api.ts +9 -2
  292. package/src/hooks/use_can_browse_archived_conversations.ts +17 -0
  293. package/src/hooks/use_clear_conversation_filters.ts +16 -0
  294. package/src/hooks/use_conversation.ts +44 -3
  295. package/src/hooks/use_conversation_messages.ts +7 -5
  296. package/src/hooks/use_current_person.ts +1 -0
  297. package/src/hooks/use_features.ts +2 -0
  298. package/src/hooks/use_filter_dropdown_enabled.ts +10 -0
  299. package/src/hooks/use_jolt.ts +57 -1
  300. package/src/hooks/use_manual_archiving_enabled.ts +10 -0
  301. package/src/hooks/use_mark_latest_message_read.ts +4 -3
  302. package/src/hooks/use_reduce_motion.ts +19 -0
  303. package/src/hooks/use_unarchive_conversation_action.ts +24 -0
  304. package/src/screens/__tests__/conversation_details_screen.test.tsx +305 -0
  305. package/src/screens/attachment_actions/__tests__/attachment_actions_screen.test.tsx +72 -0
  306. package/src/screens/attachment_actions/attachment_actions_screen.tsx +3 -1
  307. package/src/screens/conversation_details_screen.tsx +80 -16
  308. package/src/screens/conversation_filters/__tests__/conversation_filters.test.tsx +101 -0
  309. package/src/screens/conversation_filters/components/conversation_filters.tsx +40 -7
  310. package/src/screens/conversation_filters/components/rows.tsx +29 -1
  311. package/src/screens/conversation_filters/context/conversation_filter_context.tsx +4 -0
  312. package/src/screens/conversation_filters/filter_types.ts +1 -0
  313. package/src/screens/conversation_filters/screen_props.ts +1 -0
  314. package/src/screens/conversation_filters_screen.tsx +25 -5
  315. package/src/screens/conversation_new/components/groups_form.tsx +1 -0
  316. package/src/screens/conversation_screen.tsx +4 -2
  317. package/src/screens/conversations/__tests__/conversations_screen.test.tsx +52 -0
  318. package/src/screens/conversations/components/__tests__/filter_dropdown_list_header_component.test.tsx +259 -0
  319. package/src/screens/conversations/components/__tests__/list_header_component.test.tsx +21 -4
  320. package/src/screens/conversations/components/filter_dropdown_list_header_component.tsx +286 -0
  321. package/src/screens/conversations/components/list_header_component.tsx +17 -12
  322. package/src/screens/conversations/components/search_list_header_component.tsx +17 -12
  323. package/src/screens/conversations/conversations_screen.tsx +15 -2
  324. package/src/screens/design_system_screen.tsx +26 -0
  325. package/src/screens/message_actions_screen.tsx +29 -20
  326. package/src/screens/search/__tests__/search_screen.test.tsx +13 -1
  327. package/src/screens/search/search_screen.tsx +8 -2
  328. package/src/screens/send_giphy_screen.tsx +6 -3
  329. package/src/screens/team_conversation_screen.tsx +1 -1
  330. package/src/types/jolt_events/message_events.ts +2 -0
  331. package/src/types/resources/conversation.ts +2 -2
  332. package/src/types/resources/member_ability.ts +2 -0
  333. package/src/types/resources/message.ts +3 -0
  334. package/src/types/resources/person.ts +1 -0
  335. package/src/utils/__tests__/conversation.test.ts +1 -0
  336. package/src/utils/__tests__/date.test.ts +23 -0
  337. package/src/utils/__tests__/group_messages.test.ts +1 -0
  338. package/src/utils/archived_conversation.ts +13 -0
  339. package/src/utils/date.ts +8 -0
  340. package/src/utils/index.ts +1 -0
  341. package/src/utils/jolt/__tests__/transform_message_event_data_to_message_resource.test.ts +35 -0
  342. package/src/utils/jolt/transform_message_event_data_to_message_resource.ts +1 -0
  343. package/src/utils/native_adapters/__tests__/menu.test.tsx +46 -0
  344. package/src/utils/native_adapters/configuration.ts +5 -0
  345. package/src/utils/native_adapters/index.ts +1 -0
  346. package/src/utils/native_adapters/menu.tsx +70 -0
  347. package/src/utils/request/conversation.ts +4 -0
  348. package/src/utils/request/messages_data_options.ts +1 -0
  349. package/src/utils/system_messages.ts +19 -10
  350. package/src/vendor/font_awesome_pro_solid_icons.ts +12 -0
@@ -18,7 +18,7 @@ import {
18
18
  useConversationsMute,
19
19
  } from '../../hooks/use_conversations_actions'
20
20
  import { ConversationResource } from '../../types'
21
- import { platformPressedOpacityStyle } from '../../utils'
21
+ import { isArchivedConversation, platformPressedOpacityStyle } from '../../utils'
22
22
  import { tokens } from '../../vendor/tapestry/tokens'
23
23
  import { SwipeableToggleButton } from './swipeable_toggle_button'
24
24
 
@@ -53,6 +53,7 @@ export function ConversationActions({
53
53
  })
54
54
 
55
55
  const isConversationEmpty = conversation.lastMessageCreatedAt === null
56
+ const canMute = !isArchivedConversation(conversation)
56
57
 
57
58
  const handleSwipeableClose = useCallback(() => {
58
59
  setDisabled(false)
@@ -88,13 +89,16 @@ export function ConversationActions({
88
89
  ]
89
90
  : []
90
91
 
91
- const accessibilityActions = [
92
- {
93
- name: muted ? 'unmute' : 'mute',
94
- label: muted ? 'Enable' : 'Silence',
95
- },
96
- ...readAccessibilityAction,
97
- ]
92
+ const muteAccessibilityAction = canMute
93
+ ? [
94
+ {
95
+ name: muted ? 'unmute' : 'mute',
96
+ label: muted ? 'Enable' : 'Silence',
97
+ },
98
+ ]
99
+ : []
100
+
101
+ const accessibilityActions = [...muteAccessibilityAction, ...readAccessibilityAction]
98
102
 
99
103
  const handleAccessibilityAction = useCallback(
100
104
  (event: AccessibilityActionEvent) => {
@@ -136,6 +140,7 @@ export function ConversationActions({
136
140
  renderLeftActions={() => (
137
141
  <LeftActions
138
142
  isConversationEmpty={isConversationEmpty}
143
+ canMute={canMute}
139
144
  handleMute={handleMute}
140
145
  handleLatestMessageUnread={handleLatestMessageUnread}
141
146
  muted={muted}
@@ -163,6 +168,7 @@ export function ConversationActions({
163
168
 
164
169
  interface LeftActionsProps {
165
170
  isConversationEmpty: boolean
171
+ canMute: boolean
166
172
  handleMute: () => void
167
173
  handleLatestMessageUnread: () => void
168
174
  muted: boolean
@@ -173,6 +179,7 @@ interface LeftActionsProps {
173
179
 
174
180
  function LeftActions({
175
181
  isConversationEmpty,
182
+ canMute,
176
183
  handleMute,
177
184
  handleLatestMessageUnread,
178
185
  muted,
@@ -180,7 +187,7 @@ function LeftActions({
180
187
  isMutedPending,
181
188
  isMarkReadPending,
182
189
  }: LeftActionsProps) {
183
- const styles = useStyles()
190
+ const styles = useStyles({ actionButtonCount: canMute ? 2 : 1 })
184
191
 
185
192
  const muteToggleContent = {
186
193
  true: { iconName: 'general.bell', label: 'Enable' },
@@ -202,21 +209,22 @@ function LeftActions({
202
209
  toggleContent={latestMessageUnreadToggleContent}
203
210
  backgroundColor={tokens.fillColorInteractionSwipeDefault}
204
211
  />
205
- <SwipeableToggleButton
206
- loading={isMutedPending}
207
- toggled={muted}
208
- onPress={handleMute}
209
- toggleContent={muteToggleContent}
210
- backgroundColor={tokens.fillColorInteractionSwipeSecondary}
211
- />
212
+ {canMute && (
213
+ <SwipeableToggleButton
214
+ loading={isMutedPending}
215
+ toggled={muted}
216
+ onPress={handleMute}
217
+ toggleContent={muteToggleContent}
218
+ backgroundColor={tokens.fillColorInteractionSwipeSecondary}
219
+ />
220
+ )}
212
221
  </View>
213
222
  )
214
223
  }
215
224
 
216
225
  const ACTION_BUTTON_WIDTH = 120
217
- const ACTION_BUTTON_COUNT = 2
218
226
 
219
- const useStyles = () => {
227
+ const useStyles = ({ actionButtonCount = 2 }: { actionButtonCount?: number } = {}) => {
220
228
  const { colors } = useTheme()
221
229
  return StyleSheet.create({
222
230
  swipeableChildContainer: {
@@ -225,7 +233,7 @@ const useStyles = () => {
225
233
  actionButtonContainer: {
226
234
  flexDirection: 'row',
227
235
  alignItems: 'center',
228
- width: ACTION_BUTTON_WIDTH * ACTION_BUTTON_COUNT,
236
+ width: ACTION_BUTTON_WIDTH * actionButtonCount,
229
237
  alignContent: 'stretch',
230
238
  },
231
239
  swipeableContainer: {
@@ -7,10 +7,19 @@ import {
7
7
  useScalableNumberOfLines,
8
8
  } from '../../hooks'
9
9
  import { ConversationPreviewData, ConversationResource } from '../../types'
10
- import { REACTION_EMOJIS, platformFontWeightBold } from '../../utils'
10
+ import { REACTION_EMOJIS, isArchivedConversation, platformFontWeightBold } from '../../utils'
11
11
  import { formatDatePreview } from '../../utils/date'
12
+ import { faBoxArchive } from '../../vendor/font_awesome_pro_solid_icons'
12
13
  import { tokens } from '../../vendor/tapestry/tokens'
13
- import { AvatarGroup, ConversationAvatar, Heading, Text, Badge, type BadgeProps } from '../display'
14
+ import {
15
+ AvatarGroup,
16
+ ConversationAvatar,
17
+ Heading,
18
+ IconTile,
19
+ Text,
20
+ Badge,
21
+ type BadgeProps,
22
+ } from '../display'
14
23
  import { ConversationActions } from './conversation_actions'
15
24
  import { MuteIndicator } from './mute_indicator'
16
25
  import { UnreadCountBadge } from './unread_count_badge'
@@ -42,6 +51,7 @@ export const ConversationPreview = ({
42
51
  unreadReaction,
43
52
  } = conversation
44
53
 
54
+ const archived = isArchivedConversation(conversation)
45
55
  const emptyConversation = !lastMessageCreatedAt
46
56
  const fallbackIconName = emptyConversation ? 'people.noTextMessage' : 'general.person'
47
57
  const hasMetaContent = lastMessageCreatedAt || unreadCount > 0 || muted
@@ -57,25 +67,35 @@ export const ConversationPreview = ({
57
67
  const conversationAccessibilityLabel = getConversationAccessibilityLabel({
58
68
  conversation,
59
69
  showBadges,
70
+ archived,
60
71
  })
61
72
 
62
73
  return (
63
74
  <ConversationActions
64
75
  conversation={conversation}
65
- style={[styles.previewRow, disabled && styles.disabled, style]}
76
+ style={[styles.previewRow, disabled && !archived && styles.disabled, style]}
66
77
  onPress={onPress}
67
78
  accessibilityLabel={conversationAccessibilityLabel}
68
79
  >
69
- <ConversationAvatar
70
- conversation={conversation}
71
- size="lg"
72
- fallbackIconName={fallbackIconName}
73
- />
80
+ {archived ? (
81
+ <IconTile
82
+ icon={faBoxArchive}
83
+ shape="circle"
84
+ size="lg"
85
+ testID="conversation-archived-icon"
86
+ />
87
+ ) : (
88
+ <ConversationAvatar
89
+ conversation={conversation}
90
+ size="lg"
91
+ fallbackIconName={fallbackIconName}
92
+ />
93
+ )}
74
94
  <View style={styles.conversationBody}>
75
95
  <Heading
76
96
  numberOfLines={numberOfLinesScaledTitle}
77
97
  variant="h3"
78
- style={[styles.title, isUnread && styles.unreadTitle]}
98
+ style={[styles.title, archived && styles.archivedTitle, isUnread && styles.unreadTitle]}
79
99
  >
80
100
  {title}
81
101
  </Heading>
@@ -168,6 +188,8 @@ function reactionEmoji(value: ConversationPreviewData['value']): string | null {
168
188
  }
169
189
 
170
190
  function formatConversationPreviewText(preview: ConversationPreviewData) {
191
+ if (preview.kind === 'system') return preview.text
192
+
171
193
  if (preview.kind === 'reaction') {
172
194
  const emoji = reactionEmoji(preview.value)
173
195
  return `${preview.authorName}${emoji ? ` ${emoji}` : ' reacted'} to: "${preview.text}"`
@@ -179,6 +201,8 @@ function formatConversationPreviewText(preview: ConversationPreviewData) {
179
201
  function formatConversationPreviewAccessibilityLabel(preview: ConversationPreviewData) {
180
202
  const date = formatDatePreview(preview.createdAt)
181
203
 
204
+ if (preview.kind === 'system') return `Latest activity: ${preview.text} at ${date}`
205
+
182
206
  if (preview.kind === 'reaction') {
183
207
  return `${preview.authorName} reacted to your message sent ${date} and says ${preview.text}`
184
208
  }
@@ -189,10 +213,12 @@ function formatConversationPreviewAccessibilityLabel(preview: ConversationPrevie
189
213
  interface GetConversationAccessibilityLabelProps {
190
214
  conversation: ConversationResource
191
215
  showBadges: boolean
216
+ archived: boolean
192
217
  }
193
218
  const getConversationAccessibilityLabel = ({
194
219
  conversation,
195
220
  showBadges,
221
+ archived,
196
222
  }: GetConversationAccessibilityLabelProps) => {
197
223
  const { badges, title, conversationPreview, muted, disabled, unreadCount, unreadReaction } =
198
224
  conversation
@@ -203,7 +229,7 @@ const getConversationAccessibilityLabel = ({
203
229
  const label = {
204
230
  title,
205
231
  badge: hasBadges ? `for ${badges[0]?.pcoResourceType} ${badges[0]?.text || ''}` : '',
206
- disabled: disabled ? 'Disabled' : '',
232
+ disabled: archived ? 'Archived' : disabled ? 'Disabled' : '',
207
233
  muted: muted ? 'Muted' : '',
208
234
  readStatus: hasUnreadMessages
209
235
  ? `${unreadCount} unread messages`
@@ -251,6 +277,9 @@ const useStyles = () => {
251
277
  fontSize: 16,
252
278
  color: colors.textColorDefaultPrimary,
253
279
  },
280
+ archivedTitle: {
281
+ color: colors.textColorDefaultSecondary,
282
+ },
254
283
  unreadTitle: {
255
284
  fontWeight: tokens.fontWeightBold,
256
285
  },
@@ -4,10 +4,14 @@ import { FlatList, StyleSheet, View } from 'react-native'
4
4
  import { useConversationsContext } from '../../contexts/conversations_context'
5
5
  import { useTheme } from '../../hooks'
6
6
  import { useCanCreateConversations } from '../../hooks/use_chat_permissions'
7
+ import { useClearConversationFilters } from '../../hooks/use_clear_conversation_filters'
7
8
  import { useConversationsJoltEvents } from '../../hooks/use_conversations_jolt_events'
9
+ import { useFilterDropdownEnabled } from '../../hooks/use_filter_dropdown_enabled'
8
10
  import { ConversationResource } from '../../types'
11
+ import { ArchivedResultsFooter } from './archived_results_footer'
9
12
  import { ConversationPreview, ConversationPreviewSkeleton } from './conversation_preview'
10
13
  import { ConversationsBlankState } from './conversations_blank_state'
14
+ import { FilteredResultsFooter } from './filtered_results_footer'
11
15
 
12
16
  interface ConversationsProps {
13
17
  ListHeaderComponent?:
@@ -26,11 +30,23 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
26
30
  refetch,
27
31
  isRefetching,
28
32
  isFetched,
29
- args: { chat_group_graph_id, group_source_app_name },
33
+ hasNextPage,
34
+ args: { chat_group_graph_id, group_source_app_name, show_direct_messages, show_archived },
30
35
  } = useConversationsContext()
31
36
  const navigation = useNavigation()
32
37
  const canCreateConversations = useCanCreateConversations()
33
- const isFilterApplied = !!chat_group_graph_id || !!group_source_app_name
38
+ const clearFilters = useClearConversationFilters()
39
+ const filterDropdownEnabled = useFilterDropdownEnabled()
40
+ const isFilterApplied =
41
+ !!chat_group_graph_id || !!group_source_app_name || !!show_direct_messages || !!show_archived
42
+ const showArchivedResultsFooter = !!show_archived && !isLoading && !hasNextPage
43
+ const showFilteredResultsFooter =
44
+ filterDropdownEnabled &&
45
+ isFilterApplied &&
46
+ !show_archived &&
47
+ !hasNextPage &&
48
+ !isLoading &&
49
+ conversations.length > 0
34
50
 
35
51
  const showBadges = !chat_group_graph_id
36
52
 
@@ -62,6 +78,13 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
62
78
  canCreateConversations={canCreateConversations}
63
79
  />
64
80
  }
81
+ ListFooterComponent={
82
+ showArchivedResultsFooter ? (
83
+ <ArchivedResultsFooter onViewActiveConversations={clearFilters} />
84
+ ) : showFilteredResultsFooter ? (
85
+ <FilteredResultsFooter />
86
+ ) : null
87
+ }
65
88
  renderItem={({ item }) => {
66
89
  if (item.type === 'loading') {
67
90
  return <ConversationPreviewSkeleton />
@@ -72,7 +95,7 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
72
95
  onPress={() =>
73
96
  navigation.navigate('Conversation', {
74
97
  conversation_id: item.resource.id,
75
- title: item.resource.title,
98
+ title: item.resource.title ?? undefined,
76
99
  badge: item.resource.badges?.[0],
77
100
  deleted: item.resource.deleted,
78
101
  muted: item.resource.muted,
@@ -1,5 +1,7 @@
1
1
  import React from 'react'
2
2
  import { StyleSheet, View } from 'react-native'
3
+ import { useClearConversationFilters } from '../../hooks/use_clear_conversation_filters'
4
+ import { useFilterDropdownEnabled } from '../../hooks/use_filter_dropdown_enabled'
3
5
  import BlankState from '../primitive/blank_state_primitive'
4
6
 
5
7
  interface ConversationsBlankStateProps {
@@ -11,6 +13,25 @@ export function ConversationsBlankState({
11
13
  isFilterApplied,
12
14
  canCreateConversations,
13
15
  }: ConversationsBlankStateProps) {
16
+ const filterDropdownEnabled = useFilterDropdownEnabled()
17
+ const clearFilters = useClearConversationFilters()
18
+
19
+ if (filterDropdownEnabled && isFilterApplied) {
20
+ return (
21
+ <View style={styles.container}>
22
+ <BlankState.Root>
23
+ <BlankState.Imagery name="general.outlinedTextMessage" />
24
+ <BlankState.Content>
25
+ <BlankState.Heading>No matches</BlankState.Heading>
26
+ </BlankState.Content>
27
+ <BlankState.TextButton onPress={clearFilters}>
28
+ View all conversations
29
+ </BlankState.TextButton>
30
+ </BlankState.Root>
31
+ </View>
32
+ )
33
+ }
34
+
14
35
  return (
15
36
  <View style={styles.container}>
16
37
  <BlankState.Root>
@@ -0,0 +1,38 @@
1
+ import React from 'react'
2
+ import { StyleSheet, View } from 'react-native'
3
+ import { useTheme } from '../../hooks'
4
+ import { useClearConversationFilters } from '../../hooks/use_clear_conversation_filters'
5
+ import { Text, TextButton } from '../display'
6
+
7
+ export function FilteredResultsFooter() {
8
+ const styles = useStyles()
9
+ const clearFilters = useClearConversationFilters()
10
+
11
+ return (
12
+ <View style={styles.container}>
13
+ <Text variant="tertiary" style={styles.text}>
14
+ You&rsquo;re viewing filtered results
15
+ </Text>
16
+ <TextButton variant="tertiary" onPress={clearFilters}>
17
+ View all conversations
18
+ </TextButton>
19
+ </View>
20
+ )
21
+ }
22
+
23
+ const useStyles = () => {
24
+ const { colors } = useTheme()
25
+
26
+ return StyleSheet.create({
27
+ container: {
28
+ alignItems: 'center',
29
+ gap: 8,
30
+ paddingHorizontal: 16,
31
+ paddingVertical: 24,
32
+ },
33
+ text: {
34
+ textAlign: 'center',
35
+ color: colors.textColorDefaultSecondary,
36
+ },
37
+ })
38
+ }
@@ -0,0 +1,114 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react-native'
2
+ import React from 'react'
3
+ import { Pressable, Text } from 'react-native'
4
+ import {
5
+ AudioAdapter,
6
+ ChatAdapters,
7
+ ClipboardAdapter,
8
+ ImagePickerAdapter,
9
+ MenuAction,
10
+ MenuAdapter,
11
+ MenuViewProps,
12
+ VideoAdapter,
13
+ } from '../../../utils/native_adapters'
14
+ import { FilterDropdown } from '../filter_dropdown'
15
+
16
+ // Stands in for the host's native menu: every action, inline sections
17
+ // included, becomes a pressable menu item.
18
+ const FakeMenuView = ({ actions, onPressAction, children }: MenuViewProps) => {
19
+ const flatten = (items: MenuAction[]): MenuAction[] =>
20
+ items.flatMap(item => (item.displayInline && item.subactions ? item.subactions : [item]))
21
+
22
+ return (
23
+ <>
24
+ {children}
25
+ {flatten(actions).map(action => (
26
+ <Pressable
27
+ key={action.id}
28
+ accessibilityRole="menuitem"
29
+ accessibilityState={{ selected: action.state === 'on' }}
30
+ onPress={() => onPressAction?.({ nativeEvent: { event: action.id ?? action.title } })}
31
+ >
32
+ <Text>{action.title}</Text>
33
+ </Pressable>
34
+ ))}
35
+ </>
36
+ )
37
+ }
38
+
39
+ beforeAll(() => {
40
+ ChatAdapters.configure({
41
+ clipboard: new ClipboardAdapter({
42
+ getStringAsync: async () => '',
43
+ setStringAsync: async () => {},
44
+ }),
45
+ audio: new AudioAdapter({ useAudio: () => ({}) as any }),
46
+ video: new VideoAdapter({ Player: (() => null) as any }),
47
+ imagePicker: new ImagePickerAdapter({
48
+ openCameraAsync: async () => ({ canceled: true, assets: null }),
49
+ openImageLibraryAsync: async () => ({ canceled: true, assets: null }),
50
+ }),
51
+ menu: new MenuAdapter({ View: FakeMenuView }),
52
+ })
53
+ })
54
+
55
+ const options = [
56
+ { id: 'all', label: 'All' },
57
+ { id: 'groups', label: 'Groups' },
58
+ { id: 'teams', label: 'Teams' },
59
+ ]
60
+
61
+ const renderFilterDropdown = ({ showMoreFilters }: { showMoreFilters?: boolean } = {}) => {
62
+ const onSelect = jest.fn()
63
+ const onMoreFilters = jest.fn()
64
+
65
+ render(
66
+ <FilterDropdown
67
+ options={options}
68
+ selectedId="groups"
69
+ onSelect={onSelect}
70
+ onMoreFilters={onMoreFilters}
71
+ triggerLabel="Groups"
72
+ showMoreFilters={showMoreFilters}
73
+ />
74
+ )
75
+
76
+ return { onSelect, onMoreFilters }
77
+ }
78
+
79
+ describe('FilterDropdown', () => {
80
+ it('hands the menu every option, the selected one checked, then More filters', () => {
81
+ renderFilterDropdown()
82
+
83
+ expect(screen.getByRole('button', { name: 'Groups' })).toBeTruthy()
84
+ expect(
85
+ screen.getAllByRole('menuitem').map(item => [item.props.accessibilityState.selected])
86
+ ).toEqual([[false], [true], [false], [false]])
87
+ expect(screen.getByRole('menuitem', { name: 'More filters' })).toBeTruthy()
88
+ })
89
+
90
+ it('calls onSelect with the id of the pressed option', () => {
91
+ const { onSelect, onMoreFilters } = renderFilterDropdown()
92
+
93
+ fireEvent.press(screen.getByRole('menuitem', { name: 'Teams' }))
94
+
95
+ expect(onSelect).toHaveBeenCalledWith('teams')
96
+ expect(onMoreFilters).not.toHaveBeenCalled()
97
+ })
98
+
99
+ it('leaves More filters out when showMoreFilters is false', () => {
100
+ renderFilterDropdown({ showMoreFilters: false })
101
+
102
+ expect(screen.queryByRole('menuitem', { name: 'More filters' })).toBeNull()
103
+ expect(screen.getAllByRole('menuitem')).toHaveLength(options.length)
104
+ })
105
+
106
+ it('calls onMoreFilters for the More filters action', () => {
107
+ const { onSelect, onMoreFilters } = renderFilterDropdown()
108
+
109
+ fireEvent.press(screen.getByRole('menuitem', { name: 'More filters' }))
110
+
111
+ expect(onMoreFilters).toHaveBeenCalledTimes(1)
112
+ expect(onSelect).not.toHaveBeenCalled()
113
+ })
114
+ })
@@ -0,0 +1,16 @@
1
+ import { render } from '@testing-library/react-native'
2
+ import React from 'react'
3
+ import { faBoxArchive } from '../../../vendor/font_awesome_pro_solid_icons'
4
+ import { IconTile } from '../icon_tile'
5
+
6
+ describe('IconTile', () => {
7
+ it('rounds the corners of a square tile instead of masking it to a circle', () => {
8
+ const view = render(
9
+ <IconTile icon={faBoxArchive} shape="square" maxFontSizeMultiplier={1} testID="tile" />
10
+ )
11
+
12
+ expect(view.getByTestId('tile', { includeHiddenElements: true })).toHaveStyle({
13
+ borderRadius: 4,
14
+ })
15
+ })
16
+ })
@@ -7,7 +7,7 @@ import Animated, {
7
7
  ReduceMotion,
8
8
  } from 'react-native-reanimated'
9
9
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
10
- import { useTheme } from '../../hooks'
10
+ import { useReduceMotion, useTheme } from '../../hooks'
11
11
  import { ConversationWarning } from '../../types'
12
12
  import { MAX_FONT_SIZE_MULTIPLIER_LANDMARK } from '../../utils'
13
13
  import { Banner } from './banner'
@@ -37,13 +37,14 @@ export const ActionButton = ({
37
37
  warnings?: ConversationWarning[]
38
38
  }) => {
39
39
  const styles = useStyles()
40
+ const reduceMotion = useReduceMotion() ? ReduceMotion.Always : ReduceMotion.Never
40
41
 
41
42
  return (
42
43
  <LayoutAnimationConfig skipEntering>
43
44
  {visible && (
44
45
  <Animated.View
45
- entering={FadeIn.duration(300).reduceMotion(ReduceMotion.System)}
46
- exiting={FadeOut.duration(300).reduceMotion(ReduceMotion.System)}
46
+ entering={FadeIn.duration(300).reduceMotion(reduceMotion)}
47
+ exiting={FadeOut.duration(300).reduceMotion(reduceMotion)}
47
48
  style={styles.container}
48
49
  >
49
50
  {warnings.length > 0 && (
@@ -0,0 +1,124 @@
1
+ import React from 'react'
2
+ import { StyleSheet, View } from 'react-native'
3
+ import { useTheme } from '../../hooks'
4
+ import {
5
+ MAX_FONT_SIZE_MULTIPLIER_LANDMARK,
6
+ Menu,
7
+ MenuAction,
8
+ MenuActionEvent,
9
+ platformFontWeightBold,
10
+ } from '../../utils'
11
+ import { tokens } from '../../vendor/tapestry/tokens'
12
+ import { Icon } from './icon'
13
+ import { Text } from './text'
14
+
15
+ // =================================
16
+ // ====== Constants ================
17
+ // =================================
18
+
19
+ const MORE_FILTERS_ID = 'more_filters'
20
+ const MORE_FILTERS_LABEL = 'More filters'
21
+
22
+ // =================================
23
+ // ====== Component ================
24
+ // =================================
25
+
26
+ export interface FilterDropdownOption {
27
+ id: string
28
+ label: string
29
+ }
30
+
31
+ export interface FilterDropdownProps {
32
+ options: FilterDropdownOption[]
33
+ selectedId: string
34
+ onSelect: (id: string) => void
35
+ onMoreFilters: () => void
36
+ triggerLabel: string
37
+ showMoreFilters?: boolean
38
+ }
39
+
40
+ export function FilterDropdown({
41
+ options,
42
+ selectedId,
43
+ onSelect,
44
+ onMoreFilters,
45
+ triggerLabel,
46
+ showMoreFilters = true,
47
+ }: FilterDropdownProps) {
48
+ const styles = useStyles()
49
+
50
+ const optionActions: MenuAction[] = options.map(option => ({
51
+ id: option.id,
52
+ title: option.label,
53
+ state: option.id === selectedId ? ('on' as const) : ('off' as const),
54
+ }))
55
+ const moreFiltersAction: MenuAction = {
56
+ id: MORE_FILTERS_ID,
57
+ title: '',
58
+ displayInline: true,
59
+ subactions: [{ id: MORE_FILTERS_ID, title: MORE_FILTERS_LABEL }],
60
+ }
61
+ const actions = showMoreFilters ? [...optionActions, moreFiltersAction] : optionActions
62
+
63
+ const handlePressAction = ({ nativeEvent }: MenuActionEvent) => {
64
+ if (nativeEvent.event === MORE_FILTERS_ID) {
65
+ onMoreFilters()
66
+ } else {
67
+ onSelect(nativeEvent.event)
68
+ }
69
+ }
70
+
71
+ return (
72
+ <Menu.View actions={actions} onPressAction={handlePressAction} style={styles.container}>
73
+ <View
74
+ style={styles.trigger}
75
+ accessible
76
+ accessibilityRole="button"
77
+ accessibilityHint="Opens filter options"
78
+ >
79
+ <Text
80
+ style={styles.triggerLabel}
81
+ numberOfLines={1}
82
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
83
+ >
84
+ {triggerLabel}
85
+ </Text>
86
+ <Icon
87
+ name="general.downChevron"
88
+ style={styles.triggerIcon}
89
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
90
+ accessibilityElementsHidden
91
+ />
92
+ </View>
93
+ </Menu.View>
94
+ )
95
+ }
96
+
97
+ // =================================
98
+ // ====== Styles ===================
99
+ // =================================
100
+
101
+ const useStyles = () => {
102
+ const { colors } = useTheme()
103
+
104
+ return StyleSheet.create({
105
+ container: {
106
+ alignSelf: 'flex-start',
107
+ },
108
+ trigger: {
109
+ flexDirection: 'row',
110
+ alignItems: 'center',
111
+ gap: 6,
112
+ paddingVertical: 4,
113
+ },
114
+ triggerLabel: {
115
+ flexShrink: 1,
116
+ fontWeight: platformFontWeightBold,
117
+ color: colors.textColorDefaultPrimary,
118
+ },
119
+ triggerIcon: {
120
+ fontSize: tokens.fontSizeSm,
121
+ color: colors.iconColorDefaultPrimary,
122
+ },
123
+ })
124
+ }