Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2bd5e66
feat: create poll re-design
khushal87 Feb 18, 2026
d8ed922
feat: create poll re-design
khushal87 Feb 18, 2026
2139413
Merge branch 'develop' into feat/create-poll
isekovanic Feb 18, 2026
ac3715c
fix: introduce dynamic height handling
isekovanic Feb 18, 2026
52096f3
fix: add ability to have gaps between polls
isekovanic Feb 18, 2026
67deb59
chore: simplify logic and remove unnecessary bootstrapping from earlier
isekovanic Feb 18, 2026
4019054
fix: duplicate option overflow
isekovanic Feb 18, 2026
75416c4
fix: broken animation to nearest option when errored
isekovanic Feb 18, 2026
34b0820
fix: wrong layout when error is newly created option
isekovanic Feb 18, 2026
d8a0ba8
fix: issues with neighbour reordering animation frame race
isekovanic Feb 18, 2026
7b9b864
fix: simplify code
isekovanic Feb 18, 2026
1a10414
fix: remove poll create option fixed height completely
isekovanic Feb 18, 2026
9997abb
fix: origin position of new options
isekovanic Feb 18, 2026
ed8f9a1
fix: add animation options
isekovanic Feb 18, 2026
9cc40e9
fix: transition timing
isekovanic Feb 18, 2026
be02313
perf: optimize option rerenders
isekovanic Feb 18, 2026
25b33e0
Merge branch 'develop' into fix/create-poll-option-dyn-height
isekovanic Feb 19, 2026
50fb908
fix: lint issues
isekovanic Feb 19, 2026
2fc6552
fix: switch margin
khushal87 Feb 19, 2026
bb6773f
fix: switch margin in poll content (#3414)
khushal87 Feb 19, 2026
bda5d16
Merge branch 'fix/create-poll-option-dyn-height' of github.com:GetStr…
khushal87 Feb 19, 2026
0a69699
Merge branch 'develop' into fix/create-poll-option-dyn-height
isekovanic Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package/src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
compressImageQuality,
CooldownTimer = CooldownTimerDefault,
CreatePollContent,
createPollOptionGap,
customMessageSwipeAction,
DateHeader = DateHeaderDefault,
deletedMessagesVisibilityType = 'always',
Expand Down Expand Up @@ -1828,6 +1829,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
compressImageQuality,
CooldownTimer,
CreatePollContent,
createPollOptionGap,
doFileUploadRequest,
editMessage,
FileAttachmentUploadPreview,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const useCreateInputMessageInputContext = ({
compressImageQuality,
CooldownTimer,
CreatePollContent,
createPollOptionGap,
doFileUploadRequest,
editMessage,
FileAttachmentUploadPreview,
Expand Down Expand Up @@ -89,6 +90,7 @@ export const useCreateInputMessageInputContext = ({
compressImageQuality,
CooldownTimer,
CreatePollContent,
createPollOptionGap,
doFileUploadRequest,
editMessage,
FileAttachmentUploadPreview,
Expand Down
6 changes: 6 additions & 0 deletions package/src/components/MessageInput/MessageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ type MessageInputPropsWithContext = Pick<ChatContextValue, 'isOnline'> &
| 'showPollCreationDialog'
| 'sendMessage'
| 'CreatePollContent'
| 'createPollOptionGap'
| 'StopMessageStreamingButton'
> &
Pick<MessagesContextValue, 'Reply'> &
Expand Down Expand Up @@ -206,6 +207,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => {
closeAttachmentPicker,
closePollCreationDialog,
CreatePollContent,
createPollOptionGap,
editing,
messageInputFloating,
messageInputHeightStore,
Expand Down Expand Up @@ -463,6 +465,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => {
<CreatePoll
closePollCreationDialog={closePollCreationDialog}
CreatePollContent={CreatePollContent}
createPollOptionGap={createPollOptionGap}
sendMessage={sendMessage}
/>
</SafeAreaViewWrapper>
Expand All @@ -486,6 +489,7 @@ const areEqual = (
audioRecordingEnabled: prevAsyncMessagesEnabled,
channel: prevChannel,
closePollCreationDialog: prevClosePollCreationDialog,
createPollOptionGap: prevCreatePollOptionGap,
editing: prevEditing,
isKeyboardVisible: prevIsKeyboardVisible,
isOnline: prevIsOnline,
Expand All @@ -505,6 +509,7 @@ const areEqual = (
audioRecordingEnabled: nextAsyncMessagesEnabled,
channel: nextChannel,
closePollCreationDialog: nextClosePollCreationDialog,
createPollOptionGap: nextCreatePollOptionGap,
editing: nextEditing,
isKeyboardVisible: nextIsKeyboardVisible,
isOnline: nextIsOnline,
Expand All @@ -525,6 +530,7 @@ const areEqual = (
const pollCreationInputPropsEqual =
prevOpenPollCreationDialog === nextOpenPollCreationDialog &&
prevClosePollCreationDialog === nextClosePollCreationDialog &&
prevCreatePollOptionGap === nextCreatePollOptionGap &&
prevShowPollCreationDialog === nextShowPollCreationDialog;
if (!pollCreationInputPropsEqual) {
return false;
Expand Down
62 changes: 48 additions & 14 deletions package/src/components/Poll/CreatePollContent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { StyleSheet, Switch, Text, View } from 'react-native';

import { ScrollView } from 'react-native-gesture-handler';
Expand All @@ -23,7 +23,6 @@ import {
import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer';
import { useStateStore } from '../../hooks/useStateStore';
import { primitives } from '../../theme';
import { POLL_OPTION_HEIGHT } from '../../utils/constants';

const pollComposerStateSelector = (state: PollComposerState) => ({
options: state.data.options,
Expand All @@ -40,14 +39,23 @@ export const CreatePollContent = () => {
const { pollComposer } = messageComposer;
const { options } = useStateStore(pollComposer.state, pollComposerStateSelector);

const { createPollOptionHeight, closePollCreationDialog, createAndSendPoll } =
useCreatePollContentContext();
const {
createPollOptionGap = 8,
closePollCreationDialog,
createAndSendPoll,
} = useCreatePollContentContext();
const normalizedCreatePollOptionGap =
Number.isFinite(createPollOptionGap) && createPollOptionGap > 0 ? createPollOptionGap : 0;
const optionIdsKey = useMemo(() => options.map((option) => option.id).join('|'), [options]);
const optionsRef = useRef(options);
optionsRef.current = options;

// positions and index lookup map
// TODO: Please rethink the structure of this, bidirectional data flow is not great
const currentOptionPositions = useSharedValue<CurrentOptionPositionsCache>({
inverseIndexCache: {},
positionCache: {},
totalHeight: 0,
});

const {
Expand All @@ -61,20 +69,43 @@ export const CreatePollContent = () => {
const styles = useStyles();

useEffect(() => {
if (!createPollOptionHeight) return;
const latestOptions = optionsRef.current;
const currentPositions = currentOptionPositions.value;
const isCacheAlignedWithOptions =
latestOptions.length === Object.keys(currentPositions.inverseIndexCache).length &&
latestOptions.every(
(option, index) =>
currentPositions.inverseIndexCache[index] === option.id &&
currentPositions.positionCache[option.id] !== undefined,
);

// Avoid overwriting freshly measured heights/tops from CreatePollOptions onLayout.
// We only need this effect when options ids/order introduced missing cache entries.
if (isCacheAlignedWithOptions) {
return;
}

const previousPositionCache = currentOptionPositions.value.positionCache;
const newCurrentOptionPositions: CurrentOptionPositionsCache = {
inverseIndexCache: {},
positionCache: {},
totalHeight: 0,
};
options.forEach((option, index) => {
let runningTop = 0;
latestOptions.forEach((option, index) => {
const preservedHeight = previousPositionCache[option.id]?.updatedHeight ?? 0;
newCurrentOptionPositions.inverseIndexCache[index] = option.id;
newCurrentOptionPositions.positionCache[option.id] = {
updatedHeight: preservedHeight,
updatedIndex: index,
updatedTop: index * createPollOptionHeight,
updatedTop: runningTop,
};
const gap = index === latestOptions.length - 1 ? 0 : normalizedCreatePollOptionGap;
runningTop += preservedHeight + gap;
newCurrentOptionPositions.totalHeight = runningTop;
});
currentOptionPositions.value = newCurrentOptionPositions;
}, [createPollOptionHeight, currentOptionPositions, options]);
}, [currentOptionPositions, normalizedCreatePollOptionGap, optionIdsKey]);

const onBackPressHandler = useCallback(() => {
pollComposer.initState();
Expand Down Expand Up @@ -174,11 +205,11 @@ export const CreatePollContent = () => {
export const CreatePoll = ({
closePollCreationDialog,
CreatePollContent: CreatePollContentOverride,
createPollOptionHeight = POLL_OPTION_HEIGHT,
createPollOptionGap = 8,
sendMessage,
}: Pick<
CreatePollContentContextValue,
'createPollOptionHeight' | 'closePollCreationDialog' | 'sendMessage'
'createPollOptionGap' | 'closePollCreationDialog' | 'sendMessage'
> &
Pick<InputMessageInputContextValue, 'CreatePollContent'>) => {
const messageComposer = useMessageComposer();
Expand All @@ -199,7 +230,12 @@ export const CreatePoll = ({

return (
<CreatePollContentProvider
value={{ closePollCreationDialog, createAndSendPoll, createPollOptionHeight, sendMessage }}
value={{
closePollCreationDialog,
createAndSendPoll,
createPollOptionGap,
sendMessage,
}}
>
{CreatePollContentOverride ? <CreatePollContentOverride /> : <CreatePollContent />}
</CreatePollContentProvider>
Expand Down Expand Up @@ -241,9 +277,7 @@ const useStyles = () => {
optionCardWrapper: {
gap: primitives.spacingMd,
},
optionCardSwitch: {
marginRight: primitives.spacingMd,
},
optionCardSwitch: {},
});
}, [semantics]);
};
Loading