From 719e592927d5ea6de4f9658913a8d252fa0e4bde Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Mon, 6 Jul 2026 14:39:07 +0200 Subject: [PATCH] fix: add optional toggling between keyboard and picker --- package/src/components/Channel/Channel.tsx | 3 ++ .../useCreateInputMessageInputContext.ts | 2 ++ .../__tests__/AttachButton.test.tsx | 31 +++++++++++++++++++ .../components/InputButtons/AttachButton.tsx | 14 +++++++-- .../MessageInputContext.tsx | 9 ++++++ .../hooks/useCreateMessageInputContext.ts | 2 ++ 6 files changed, 59 insertions(+), 2 deletions(-) diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index f972ba4fb1..4e4848ca7b 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -218,6 +218,7 @@ export type ChannelPropsWithContext = Pick & | 'compressImageQuality' | 'createPollOptionGap' | 'doFileUploadRequest' + | 'focusInputOnPickerClose' | 'handleAttachButtonPress' | 'hasCameraPicker' | 'hasCommands' @@ -423,6 +424,7 @@ const ChannelWithContext = (props: PropsWithChildren) = enableSwipeToReply = true, enforceUniqueReaction = false, FlatList = NativeHandlers.FlatList, + focusInputOnPickerClose = true, forceAlignMessages, getMessageGroupStyle, handleAttachButtonPress, @@ -1651,6 +1653,7 @@ const ChannelWithContext = (props: PropsWithChildren) = createPollOptionGap, doFileUploadRequest, editMessage, + focusInputOnPickerClose, handleAttachButtonPress, hasCameraPicker, hasCommands: hasCommands ?? !!clientChannelConfig?.commands?.length, diff --git a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts index 2ea779f443..af3b17d2b9 100644 --- a/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts +++ b/package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts @@ -17,6 +17,7 @@ export const useCreateInputMessageInputContext = ({ createPollOptionGap, doFileUploadRequest, editMessage, + focusInputOnPickerClose, handleAttachButtonPress, hasCameraPicker, hasCommands, @@ -49,6 +50,7 @@ export const useCreateInputMessageInputContext = ({ createPollOptionGap, doFileUploadRequest, editMessage, + focusInputOnPickerClose, handleAttachButtonPress, hasCameraPicker, hasCommands, diff --git a/package/src/components/MessageInput/__tests__/AttachButton.test.tsx b/package/src/components/MessageInput/__tests__/AttachButton.test.tsx index a28203cdcf..32be1025f6 100644 --- a/package/src/components/MessageInput/__tests__/AttachButton.test.tsx +++ b/package/src/components/MessageInput/__tests__/AttachButton.test.tsx @@ -180,4 +180,35 @@ describe('AttachButton', () => { expect(queryByTestId('attachment-picker-list')).toBeTruthy(); }); }); + + it('should close the attachment picker on second press when focusInputOnPickerClose is false', async () => { + jest.spyOn(NativeHandler, 'isImageMediaLibraryAvailable').mockImplementation(() => true); + + const channelProps = { channel, focusInputOnPickerClose: false }; + const props = {}; + + renderComponent({ channelProps, client, props }); + + const { queryByTestId } = screen; + + await waitFor(() => { + expect(queryByTestId('attach-button')).toBeTruthy(); + }); + + act(() => { + fireEvent.press(screen.getByTestId('attach-button')); + }); + + await waitFor(() => { + expect(queryByTestId('attachment-picker-list')).toBeTruthy(); + }); + + act(() => { + fireEvent.press(screen.getByTestId('attach-button')); + }); + + await waitFor(() => { + expect(queryByTestId('attachment-picker-list')).toBeNull(); + }); + }); }); diff --git a/package/src/components/MessageInput/components/InputButtons/AttachButton.tsx b/package/src/components/MessageInput/components/InputButtons/AttachButton.tsx index b11fb5d2fd..59fe362ab8 100644 --- a/package/src/components/MessageInput/components/InputButtons/AttachButton.tsx +++ b/package/src/components/MessageInput/components/InputButtons/AttachButton.tsx @@ -92,13 +92,23 @@ export type AttachButtonProps = Partial; */ export const AttachButton = (props: AttachButtonProps) => { const { disableAttachmentPicker } = useAttachmentPickerContext(); - const { inputBoxRef, handleAttachButtonPress, openAttachmentPicker } = useMessageInputContext(); + const { + closeAttachmentPicker, + focusInputOnPickerClose, + handleAttachButtonPress, + inputBoxRef, + openAttachmentPicker, + } = useMessageInputContext(); const { attachmentPickerStore } = useAttachmentPickerContext(); const { selectedPicker } = useAttachmentPickerState(); const toggleAttachmentPicker = useStableCallback(() => { if (attachmentPickerStore.state.getLatestValue().selectedPicker) { - inputBoxRef.current?.focus(); + if (focusInputOnPickerClose) { + inputBoxRef.current?.focus(); + } else { + closeAttachmentPicker(); + } } else { openAttachmentPicker(); } diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index c8ed9ecfc5..bca66a675a 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -128,6 +128,15 @@ export type InputMessageInputContextValue = { options?: UpdateMessageOptions; }) => ReturnType; + /** + * Controls what happens when the attach button is pressed while the attachment picker is open. + * When true (default), the message input is focused, toggling from the picker to the keyboard. + * When false, the attachment picker is closed without focusing the input. + * @type boolean + * @default true + */ + focusInputOnPickerClose: boolean; + /** When false, CameraSelectorIcon will be hidden */ hasCameraPicker: boolean; diff --git a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts index 6b0b681ad7..83cdd1bef4 100644 --- a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +++ b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts @@ -18,6 +18,7 @@ export const useCreateMessageInputContext = ({ compressImageQuality, createPollOptionGap, editMessage, + focusInputOnPickerClose, handleAttachButtonPress, hasCameraPicker, hasCommands, @@ -61,6 +62,7 @@ export const useCreateMessageInputContext = ({ compressImageQuality, createPollOptionGap, editMessage, + focusInputOnPickerClose, handleAttachButtonPress, hasCameraPicker, hasCommands,