From d786211b659f457e34001765614582587f9690ae Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com> Date: Thu, 9 Jul 2026 23:40:41 +0200 Subject: [PATCH 1/9] fix: yarn workspaces ci issues (#3724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal This PR fixes a regression introduced with [this change](https://github.com/GetStream/stream-chat-react-native/pull/3717). Namely, restoring the functionality in #3717 essentially forced [this](https://github.com/GetStream/stream-chat-react-native/commit/e19a1864891b0d6c1d33e9c615cc85058e26e09e) to happen as well. We in fact want this to only be the case whenever we release a new tarball to `npm` and not in every scenario. While bumping `version` is fine in `package.json`, workspace dependencies need to be resolved through the `workspace:^` protocol. ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes
iOS
Before After
Android
Before After
## ๐Ÿงช Testing ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- package/bin/before-tag.sh | 15 +++++++++++---- package/bin/release.sh | 18 ++++++++++++++++-- package/expo-package/package.json | 2 +- package/native-package/package.json | 2 +- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/package/bin/before-tag.sh b/package/bin/before-tag.sh index 893161faaa..b99e8b1a39 100644 --- a/package/bin/before-tag.sh +++ b/package/bin/before-tag.sh @@ -10,15 +10,22 @@ set -eux PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]') PACKAGE_VERSION_WITHOUT_SHA=$(echo "$PACKAGE_VERSION" | cut -d"+" -f1) +# We bump ONLY the `version` field of the internal packages here and deliberately leave their +# `stream-chat-react-native-core` dependency as `workspace:^`. This script runs during +# semantic-release's *prepare* step, before the release commit + `git push origin main` (see +# `release/prod.js`), so whatever these files look like on disk here is what gets committed. +# +# Bumping `version` is safe to commit: Yarn resolves workspace packages to a `0.0.0-use.local` +# placeholder in the lockfile and keys on the `workspace:^` descriptor, so the version field +# never affects yarn.lock. Resolving `workspace:^` to a concrete version, however, WOULD change +# the lockfile descriptor and break `yarn install --immutable` and local workspace linking. +# That resolution is therefore done only in the published npm tarball, at publish time, in +# `release.sh` (which runs after this commit) and is never committed back to git. cd native-package npm version --no-workspaces --allow-same-version --no-git-tag-version "$PACKAGE_VERSION_WITHOUT_SHA" -sed -e 's|"stream-chat-react-native-core": "[^"]*"|"stream-chat-react-native-core": "'"$PACKAGE_VERSION_WITHOUT_SHA"'"|g' -i.bak package.json -rm package.json.bak cd ../expo-package npm version --no-workspaces --allow-same-version --no-git-tag-version "$PACKAGE_VERSION_WITHOUT_SHA" -sed -e 's|"stream-chat-react-native-core": "[^"]*"|"stream-chat-react-native-core": "'"$PACKAGE_VERSION_WITHOUT_SHA"'"|g' -i.bak package.json -rm package.json.bak cd .. sed -e 's|"version": "[^"]*"|"version": "'"$PACKAGE_VERSION_WITHOUT_SHA"'"|g' -i.bak src/version.json diff --git a/package/bin/release.sh b/package/bin/release.sh index f17991bc16..986973a0e2 100755 --- a/package/bin/release.sh +++ b/package/bin/release.sh @@ -6,21 +6,35 @@ set -eux PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]') +PACKAGE_VERSION_WITHOUT_SHA=$(echo "$PACKAGE_VERSION" | cut -d"+" -f1) PACKAGE_TAG=$(sed 's/.*-\(.*\)\..*/\1/' <<< "$PACKAGE_VERSION") +# Resolve the `workspace:^` protocol to the concrete published version. npm cannot understand +# Yarn's `workspace:` protocol, so the tarball must carry a real version. We do this ONLY here, +# at publish time as this runs after `semantic-release` has already committed and pushed the release +# commit (see release/prod.js), on a throwaway CI checkout, so the rewrite is never committed. +# That keeps `workspace:^` intact in git for local dev and `yarn install --immutable`, while the +# published packages still depend on a concrete `stream-chat-react-native-core` version. +resolve_workspace_dep() { + sed -e 's|"stream-chat-react-native-core": "[^"]*"|"stream-chat-react-native-core": "'"$PACKAGE_VERSION_WITHOUT_SHA"'"|g' -i.bak package.json + rm package.json.bak +} + # If tag === version it means that its not a prerelease and shouuld set things to latest if [[ "${PACKAGE_TAG}" != "${PACKAGE_VERSION}" ]]; then cd native-package + resolve_workspace_dep npm publish --no-workspaces --tag="$PACKAGE_TAG" cd ../expo-package + resolve_workspace_dep npm publish --no-workspaces --tag="$PACKAGE_TAG" else cd native-package + resolve_workspace_dep npm publish --no-workspaces cd ../expo-package + resolve_workspace_dep npm publish --no-workspaces fi - - diff --git a/package/expo-package/package.json b/package/expo-package/package.json index ffc9f5071b..2bc19bd2b9 100644 --- a/package/expo-package/package.json +++ b/package/expo-package/package.json @@ -27,7 +27,7 @@ "types": "types/index.d.ts", "dependencies": { "mime": "^4.0.7", - "stream-chat-react-native-core": "9.6.1" + "stream-chat-react-native-core": "workspace:^" }, "peerDependencies": { "expo": ">=52.0.0", diff --git a/package/native-package/package.json b/package/native-package/package.json index befce3a703..d7ba58849c 100644 --- a/package/native-package/package.json +++ b/package/native-package/package.json @@ -30,7 +30,7 @@ "dependencies": { "es6-symbol": "^3.1.3", "mime": "^4.0.7", - "stream-chat-react-native-core": "9.6.1" + "stream-chat-react-native-core": "workspace:^" }, "peerDependencies": { "@react-native-camera-roll/camera-roll": ">=7.9.0", From c79da0069c3eeb90b17b20e282c817010a66d8df Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Fri, 10 Jul 2026 11:52:56 +0200 Subject: [PATCH 2/9] ci: restore SampleApp Android APK upload to S3 (#3725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal Restore the public download link for the SampleApp Android build: `https://getstream.io/downloads/rn-sample-app.apk`. This S3 upload was removed in #3334 ("chore: add android build and deploy workflows and improve ios workflow"), which replaced the S3-based Android job with Firebase App Distribution. Firebase serves internal QA testers, but it dropped the public, unauthenticated download link that lives on our own domain. This PR brings that link back **alongside** Firebase. ## ๐Ÿ›  Implementation details Re-adds the S3 upload as two steps in the existing `build_and_deploy_android_firebase` job in [`sample-distribution.yml`](.github/workflows/sample-distribution.yml): - **Configure AWS credentials** โ€” `aws-actions/configure-aws-credentials@v4` (the previous code used the now-EOL `v1`), authenticating with the `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` secrets. - **Upload APK to S3** โ€” copies the APK to `rn-sample-app.apk` and runs `aws s3 cp ... --sse AES256`. Notable choices: - **No rebuild.** The upload reuses the APK the Fastlane step already produces at `examples/SampleApp/app-build/reactnativesampleapp.apk`, so the previous manual `react-native bundle` + `gradlew assembleRelease` steps are not reintroduced. - **Object key unchanged** (`rn-sample-app.apk`) so the existing public URL keeps working. - **`develop`-only**, matching the original behavior. - **Firebase distribution is kept** โ€” this is additive. ## ๐ŸŽจ UI Changes N/A โ€” CI/workflow-only change, no UI impact. ## ๐Ÿงช Testing CI-only change; verified by the `develop` pipeline rather than the example apps: 1. On merge to `develop`, the `build_and_deploy_android_firebase` job runs the two new steps. 2. Confirm the `Upload APK to S3` step succeeds in the Actions logs. 3. Confirm `https://getstream.io/downloads/rn-sample-app.apk` downloads the latest build. ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [x] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- .github/workflows/sample-distribution.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/sample-distribution.yml b/.github/workflows/sample-distribution.yml index c0a5b51255..d6179e2793 100644 --- a/.github/workflows/sample-distribution.yml +++ b/.github/workflows/sample-distribution.yml @@ -94,3 +94,19 @@ jobs: env: ANDROID_FIREBASE_APP_ID: ${{ secrets.ANDROID_FIREBASE_APP_ID }} FIREBASE_CREDENTIALS_JSON: ${{ secrets.FIREBASE_CREDENTIALS_JSON }} + # Publish the same APK to S3 for the public download link. Reuses the + # artifact the Fastlane step above already built (app-build/), no rebuild. + - name: Configure AWS credentials + if: github.ref == 'refs/heads/develop' + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + - name: Upload APK to S3 + if: github.ref == 'refs/heads/develop' + working-directory: examples/SampleApp + # Public download link: https://getstream.io/downloads/rn-sample-app.apk + run: | + cp app-build/reactnativesampleapp.apk rn-sample-app.apk + aws s3 cp rn-sample-app.apk s3://${{ secrets.AWS_S3_BUCKET }} --sse AES256 From 364d30a7886f709f68a8aa19ee92c1bf0619ccc1 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Fri, 10 Jul 2026 12:25:31 +0200 Subject: [PATCH 3/9] ci: install AWS CLI on self-hosted runner for S3 upload (#3726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal Follow-up to #3725 (merged), which restored the SampleApp Android APK upload to S3 that powers `https://getstream.io/downloads/rn-sample-app.apk`. That upload fails on the self-hosted `public` runner because the AWS CLI is not installed there (the original S3 job ran on GitHub-hosted `ubuntu-latest`, where `aws` is preinstalled). This PR makes the job install the AWS CLI when it is missing so the upload can succeed. ## ๐Ÿ›  Implementation details Adds an `Ensure AWS CLI is available` step to the `build_and_deploy_android_firebase` job in [`sample-distribution.yml`](.github/workflows/sample-distribution.yml), before the AWS credentials/upload steps: - No-op when `aws` is already on `PATH` (so it costs nothing if the runner image gains the CLI later). - Otherwise installs AWS CLI v2 from the official bundle, architecture-aware (`x86_64` / `aarch64`). - Installs to a user-writable location (`$HOME/.local/...`) with no `sudo`, then appends the bin dir to `$GITHUB_PATH` so the subsequent `Upload APK to S3` step picks it up. - Runs on `develop` only, matching the rest of the S3 flow. ## ๐ŸŽจ UI Changes N/A โ€” CI/workflow-only change, no UI impact. ## ๐Ÿงช Testing CI-only change; verified by the `develop` pipeline rather than the example apps: 1. On merge to `develop`, the `build_and_deploy_android_firebase` job runs the new install step and logs the resolved `aws --version`. 2. Confirm the `Upload APK to S3` step now succeeds in the Actions logs. 3. Confirm `https://getstream.io/downloads/rn-sample-app.apk` serves the latest build. ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [x] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- .github/workflows/sample-distribution.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/sample-distribution.yml b/.github/workflows/sample-distribution.yml index d6179e2793..363f9efd8e 100644 --- a/.github/workflows/sample-distribution.yml +++ b/.github/workflows/sample-distribution.yml @@ -96,6 +96,26 @@ jobs: FIREBASE_CREDENTIALS_JSON: ${{ secrets.FIREBASE_CREDENTIALS_JSON }} # Publish the same APK to S3 for the public download link. Reuses the # artifact the Fastlane step above already built (app-build/), no rebuild. + - name: Ensure AWS CLI is available + if: github.ref == 'refs/heads/develop' + # The self-hosted `public` runner does not ship the AWS CLI. Install v2 + # only when missing, to a user-writable dir (no sudo), then expose it. + run: | + if command -v aws >/dev/null 2>&1; then + echo "AWS CLI already installed: $(aws --version)" + exit 0 + fi + echo "AWS CLI not found; installing v2..." + case "$(uname -m)" in + x86_64) zip="awscli-exe-linux-x86_64.zip" ;; + aarch64|arm64) zip="awscli-exe-linux-aarch64.zip" ;; + *) echo "Unsupported architecture: $(uname -m)"; exit 1 ;; + esac + curl -fsSL "https://awscli.amazonaws.com/${zip}" -o /tmp/awscliv2.zip + unzip -q /tmp/awscliv2.zip -d /tmp + /tmp/aws/install --bin-dir "$HOME/.local/bin" --install-dir "$HOME/.local/aws-cli" --update + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + "$HOME/.local/bin/aws" --version - name: Configure AWS credentials if: github.ref == 'refs/heads/develop' uses: aws-actions/configure-aws-credentials@v4 From eb94ff8d3832ecbd8c8ff4eb93197d6bc0f20db5 Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Fri, 10 Jul 2026 10:06:54 -0500 Subject: [PATCH 4/9] fix: export OutputButtons from the SDK (#3727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal Docs PR: https://github.com/GetStream/docs-content/pull/1418 ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes
iOS
Before After
Android
Before After
## ๐Ÿงช Testing ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- package/src/components/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/package/src/components/index.ts b/package/src/components/index.ts index 58f07e9441..4f906d2d19 100644 --- a/package/src/components/index.ts +++ b/package/src/components/index.ts @@ -130,6 +130,7 @@ export * from './MessageInput/MessageInputFooterView'; export * from './MessageInput/MessageInputHeaderView'; export * from './MessageInput/MessageInputLeadingView'; export * from './MessageInput/MessageInputTrailingView'; +export * from './MessageInput/components/OutputButtons'; export * from './MessageInput/components/OutputButtons/SendButton'; export * from './MessageInput/SendMessageDisallowedIndicator'; export * from './MessageInput/ShowThreadMessageInChannelButton'; From dbecc8ef6d12506cd1a9fcbd3db6a7c15dcc862d Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Fri, 10 Jul 2026 10:57:28 -0500 Subject: [PATCH 5/9] fix: allow moving text inside message content (#3723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal Even though we had for reordering message content (text, gallery, poll etc) text had a fixed position. This PR enables updating text's position too. https://github.com/GetStream/docs-content/pull/1417/changes ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes No UI changes, the following URL contains before-after shots verifying no change: https://claude.ai/code/artifact/c071bb00-30f3-48ba-ac12-708204549007?via=auto_preview (if the link doesn't work, try the attached screenshots) [message-content-ordering-report.zip](https://github.com/user-attachments/files/29860209/message-content-ordering-report.zip) ## ๐Ÿงช Testing Tested manually on android and ios too ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- package/src/components/Channel/Channel.tsx | 2 +- .../MessageItemView/MessageContent.tsx | 28 ++- .../MessageItemView/MessageTextContainer.tsx | 2 +- .../__tests__/MessageContent.test.tsx | 116 +++++++++ .../MessageTextContainer.test.tsx.snap | 2 +- .../__snapshots__/Thread.test.tsx.snap | 236 +++++++++--------- 6 files changed, 263 insertions(+), 123 deletions(-) diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 4e4848ca7b..1c7bfe054e 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -468,8 +468,8 @@ const ChannelWithContext = (props: PropsWithChildren) = 'poll', 'ai_text', 'attachments', - 'text', 'location', + 'text', ], messageOverlayTargetId, messageInputFloating = false, diff --git a/package/src/components/Message/MessageItemView/MessageContent.tsx b/package/src/components/Message/MessageItemView/MessageContent.tsx index 8f699f06ee..57787cd56c 100644 --- a/package/src/components/Message/MessageItemView/MessageContent.tsx +++ b/package/src/components/Message/MessageItemView/MessageContent.tsx @@ -220,12 +220,25 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { const { setNativeScrollability } = useMessageListItemContext(); const hasContentSideViews = !!(MessageContentLeadingView || MessageContentTrailingView); + const gap = primitives.spacingXs; + + const messageTextContainerStyles = useMemo(() => { + return { + textContainer: { + // Cancel the container's 8px inter-item gap so only the caption's own + // paragraph marginTop shows. Skip for the first item: there is no gap to + // cancel + marginTop: -gap, + }, + }; + }, [gap]); + const contentBody = ( <> { key={`ai_message_text_container_${messageContentOrderIndex}`} /> ) : null; + case 'text': { + const suppressed = + (otherAttachments.length && otherAttachments[0].actions) || isAIGenerated; + return suppressed ? null : ( + + ); + } default: return null; } })} - {(otherAttachments.length && otherAttachments[0].actions) || isAIGenerated ? null : ( - - )} ); const a11yPressableLabel = useMemo(() => { diff --git a/package/src/components/Message/MessageItemView/MessageTextContainer.tsx b/package/src/components/Message/MessageItemView/MessageTextContainer.tsx index ff01cc5d04..32b00d37a3 100644 --- a/package/src/components/Message/MessageItemView/MessageTextContainer.tsx +++ b/package/src/components/Message/MessageItemView/MessageTextContainer.tsx @@ -21,7 +21,7 @@ import { useTranslatedMessage } from '../../../hooks/useTranslatedMessage'; import { primitives } from '../../../theme'; const styles = StyleSheet.create({ - textContainer: { maxWidth: 256, paddingHorizontal: primitives.spacingSm }, + textContainer: { maxWidth: 256, paddingHorizontal: primitives.spacingXxs }, }); export type MessageTextProps = MessageTextContainerProps & { diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx index 7d44fbe0d3..bac4032f1a 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx @@ -445,6 +445,122 @@ describe('MessageContent', () => { }); }); + // Collects the given testIDs in the order they appear in the rendered tree. + const collectOrderedTestIDs = ( + root: ReturnType, + targets: string[], + ) => { + const found: string[] = []; + const walk = (node: typeof root | string | null) => { + if (!node || typeof node === 'string') { + return; + } + const testID = node.props?.testID; + if (testID && targets.includes(testID) && !found.includes(testID)) { + found.push(testID); + } + (node.children ?? []).forEach((child) => walk(child as typeof root | string)); + }; + walk(root); + return found; + }; + + it('renders text after attachments with the default messageContentOrder', async () => { + const user = generateUser(); + const message = generateMessage({ + attachments: [ + { image_url: 'https://i.imgur.com/SLx06PP.png', type: 'image' }, + { image_url: 'https://i.imgur.com/iNaC3K7.jpg', type: 'image' }, + ], + text: 'a caption', + user, + }); + + renderMessage({ message }); + + await waitFor(() => { + expect(screen.getByTestId('gallery-container')).toBeTruthy(); + expect(screen.getByTestId('message-text-container')).toBeTruthy(); + }); + + expect( + collectOrderedTestIDs(screen.getByTestId('message-content-wrapper'), [ + 'gallery-container', + 'message-text-container', + ]), + ).toEqual(['gallery-container', 'message-text-container']); + }); + + it('renders text before attachments when messageContentOrder puts text first', async () => { + const user = generateUser(); + const message = generateMessage({ + attachments: [ + { image_url: 'https://i.imgur.com/SLx06PP.png', type: 'image' }, + { image_url: 'https://i.imgur.com/iNaC3K7.jpg', type: 'image' }, + ], + text: 'a caption', + user, + }); + + render( + + + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId('gallery-container')).toBeTruthy(); + expect(screen.getByTestId('message-text-container')).toBeTruthy(); + }); + + expect( + collectOrderedTestIDs(screen.getByTestId('message-content-wrapper'), [ + 'gallery-container', + 'message-text-container', + ]), + ).toEqual(['message-text-container', 'gallery-container']); + }); + + it('suppresses the text container for an ephemeral giphy preview with actions', async () => { + const user = generateUser(); + const message = generateMessage({ + attachments: [ + { + ...generateGiphyAttachment(), + actions: [ + generateAttachmentAction(), + generateAttachmentAction(), + generateAttachmentAction(), + ], + }, + ], + text: '/giphy hello', + user, + }); + + renderMessage({ message }); + + await waitFor(() => { + expect(screen.getByTestId('giphy-action-attachment')).toBeTruthy(); + }); + + expect(screen.queryByTestId('message-text-container')).toBeFalsy(); + }); + it('renders the ReactionList when the message has reactions', async () => { const user = generateUser(); const reaction = generateReaction(); diff --git a/package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap b/package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap index 99b414f7a2..f016eb6247 100644 --- a/package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap +++ b/package/src/components/Message/MessageItemView/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap @@ -6,7 +6,7 @@ exports[`MessageTextContainer should render message text container 1`] = ` [ { "maxWidth": 256, - "paddingHorizontal": 12, + "paddingHorizontal": 4, }, {}, undefined, diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap index 0881547ce7..a477d74f15 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap @@ -530,57 +530,58 @@ exports[`Thread should match thread snapshot 1`] = ` {}, ] } - /> - - - Message6 + + Message6 + - + @@ -864,57 +865,58 @@ exports[`Thread should match thread snapshot 1`] = ` {}, ] } - /> - - - Message5 + + Message5 + - + @@ -1232,57 +1234,58 @@ exports[`Thread should match thread snapshot 1`] = ` {}, ] } - /> - - - Message4 + + Message4 + - + @@ -1560,57 +1563,58 @@ exports[`Thread should match thread snapshot 1`] = ` {}, ] } - /> - - - Message3 + + Message3 + - + From a740c7a641e205edff116736e5236838af2a8cae Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Mon, 13 Jul 2026 10:01:28 -0500 Subject: [PATCH 6/9] fix: theming fixes (avatar, reaction pill, force message alignment) (#3728) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal A few small fixes for issues spotted during AI skill tests: - `avatar` theming key wasn't used and had an outdated shape. It's technically a breaking change, but since the shape is broken currently, it should be fine - Adds theme keys to fully style the reaction pill's selected and pressed states (previously only the background could've been changed) - Two small fix for `forceAlignMessages="'left'"` setting ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes
iOS
Before After
Android
Before After
## ๐Ÿงช Testing ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- .../MessageItemView/MessageContent.tsx | 4 ++-- .../MessageItemView/MessageItemView.tsx | 2 +- .../ReactionList/ReactionListItemWrapper.tsx | 12 ++++++++-- package/src/components/ui/Avatar/Avatar.tsx | 10 +++++--- .../src/components/ui/Avatar/AvatarGroup.tsx | 2 -- .../src/contexts/themeContext/utils/theme.ts | 24 ++++++------------- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/package/src/components/Message/MessageItemView/MessageContent.tsx b/package/src/components/Message/MessageItemView/MessageContent.tsx index 57787cd56c..97267a3876 100644 --- a/package/src/components/Message/MessageItemView/MessageContent.tsx +++ b/package/src/components/Message/MessageItemView/MessageContent.tsx @@ -190,7 +190,7 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { let computedBottomRightRadius = components.messageBubbleRadiusGroupBottom; if (isBottomOrSingle) { // add relevant sharp corner (the "tail") - if (isMyMessage) { + if (alignment === 'right') { computedBottomRightRadius = components.messageBubbleRadiusTail; } else { computedBottomLeftRadius = components.messageBubbleRadiusTail; @@ -208,6 +208,7 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { return style; }, [ + alignment, backgroundColor, borderBottomLeftRadius, borderBottomRightRadius, @@ -215,7 +216,6 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { borderTopLeftRadius, borderTopRightRadius, groupStyles, - isMyMessage, ]); const { setNativeScrollability } = useMessageListItemContext(); diff --git a/package/src/components/Message/MessageItemView/MessageItemView.tsx b/package/src/components/Message/MessageItemView/MessageItemView.tsx index 28e7719d3a..4d9a842873 100644 --- a/package/src/components/Message/MessageItemView/MessageItemView.tsx +++ b/package/src/components/Message/MessageItemView/MessageItemView.tsx @@ -221,7 +221,7 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { ? semantics.backgroundUtilityPressed : semantics.reactionBg, }, + selected && styles.containerSelected, + pressed && styles.containerPressed, style, ]} {...rest} @@ -43,7 +45,7 @@ const useStyles = () => { theme: { semantics, messageItemView: { - reactionListItemWrapper: { wrapper, container }, + reactionListItemWrapper: { wrapper, container, containerSelected, containerPressed }, }, }, } = useTheme(); @@ -61,11 +63,17 @@ const useStyles = () => { gap: primitives.spacingXxs, ...container, }, + containerPressed: { + ...containerPressed, + }, + containerSelected: { + ...containerSelected, + }, wrapper: { backgroundColor: semantics.reactionBg, borderRadius: primitives.radiusMax, ...wrapper, }, }); - }, [semantics, wrapper, container]); + }, [semantics, wrapper, container, containerSelected, containerPressed]); }; diff --git a/package/src/components/ui/Avatar/Avatar.tsx b/package/src/components/ui/Avatar/Avatar.tsx index d5a64d0522..42df9afa3d 100644 --- a/package/src/components/ui/Avatar/Avatar.tsx +++ b/package/src/components/ui/Avatar/Avatar.tsx @@ -85,7 +85,7 @@ export const Avatar = (props: AvatarProps) => { const useStyles = () => { const { - theme: { semantics }, + theme: { avatar, semantics }, } = useTheme(); const { borderCoreOpacitySubtle } = semantics; return useMemo( @@ -94,15 +94,19 @@ const useStyles = () => { border: { borderColor: borderCoreOpacitySubtle, borderWidth: 1, + ...avatar.border, }, container: { alignItems: 'center', borderRadius: primitives.radiusMax, justifyContent: 'center', overflow: 'hidden', + ...avatar.container, + }, + image: { + ...avatar.image, }, - image: {}, }), - [borderCoreOpacitySubtle], + [avatar.border, avatar.container, avatar.image, borderCoreOpacitySubtle], ); }; diff --git a/package/src/components/ui/Avatar/AvatarGroup.tsx b/package/src/components/ui/Avatar/AvatarGroup.tsx index 1d272ea4c3..aae521f752 100644 --- a/package/src/components/ui/Avatar/AvatarGroup.tsx +++ b/package/src/components/ui/Avatar/AvatarGroup.tsx @@ -11,7 +11,6 @@ import { UserAvatar, UserAvatarProps } from './UserAvatar'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { PeopleIcon } from '../../../icons/users'; -import { primitives } from '../../../theme'; import { BadgeCount, BadgeCountProps, OnlineIndicator, OnlineIndicatorProps } from '../Badge'; export type AvatarGroupProps = { @@ -217,7 +216,6 @@ const useUserAvatarGroupStyles = () => { userAvatarWrapper: { borderWidth: 2, borderColor: semantics.borderCoreInverse, - borderRadius: primitives.radiusMax, }, onlineIndicatorWrapper: { position: 'absolute', diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts index 66ce7e8354..4bb03625c8 100644 --- a/package/src/contexts/themeContext/utils/theme.ts +++ b/package/src/contexts/themeContext/utils/theme.ts @@ -5,7 +5,6 @@ import { type TextStyle, type ViewStyle, } from 'react-native'; -import type { CircleProps } from 'react-native-svg'; import type { IconProps } from '../../../icons/utils/base'; import { primitives, lightSemantics, darkSemantics } from '../../../theme'; @@ -139,11 +138,9 @@ export type Theme = { speedChangeButtonText: TextStyle; }; avatar: { - BASE_AVATAR_SIZE: number; + border: ViewStyle; container: ViewStyle; image: ImageStyle; - presenceIndicator: CircleProps; - presenceIndicatorContainer: ViewStyle; }; avatarStack: { userAvatarWrapper: ViewStyle; @@ -967,6 +964,8 @@ export type Theme = { reactionListItemWrapper: { wrapper: ViewStyle; container: ViewStyle; + containerSelected: ViewStyle; + containerPressed: ViewStyle; }; reactionListTop: { container: ViewStyle; @@ -1256,20 +1255,9 @@ export const defaultTheme: Theme = { speedChangeButtonText: {}, }, avatar: { - BASE_AVATAR_SIZE, + border: {}, container: {}, - image: { - borderRadius: 16, - height: 32, - width: 32, - }, - presenceIndicator: { - cx: 6, - cy: 6, - r: 5, - strokeWidth: 2, - }, - presenceIndicatorContainer: {}, + image: {}, }, avatarStack: { userAvatarWrapper: {}, @@ -2062,6 +2050,8 @@ export const defaultTheme: Theme = { reactionListItemWrapper: { wrapper: {}, container: {}, + containerSelected: {}, + containerPressed: {}, }, reactionListTop: { container: {}, From 0a38205cf9a19331a8f1fae2e976aa6ad610e032 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Wed, 15 Jul 2026 15:07:44 +0200 Subject: [PATCH 7/9] fix: export missing base UI components and correct stale JSDoc/comments (#3730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal Fix issues surfaced by a documentation audit of the RN v9 docs against the SDK source: several documented base-UI primitives were not exported from the package root, and a few source doc-comments were stale (and were the origin of doc errors). ## ๐Ÿ›  Implementation details **Missing package-root exports** (documented components/hooks that failed to import from `stream-chat-react-native`): - `components/ui/index.ts` โ€” export `Input`, `SpeedSettingsButton`, `GiphyChip` - `components/index.ts` โ€” export `EmptyList`, `EmptySearchResult`, `SearchInput`, `SelectionCircle` (were in `UIComponents/index.ts` but never re-exported, since `components/index.ts` cherry-picks `UIComponents/*` rather than re-exporting the barrel), and `useCooldownRemaining` **Stale source doc-comments corrected** (each caused a corresponding docs bug): - `contexts/pollContext/pollContext.tsx` โ€” `usePollContext` error message referenced a non-existent `useCreatePollContext` hook - `contexts/attachmentPickerContext/AttachmentPickerContext.tsx` โ€” JSDoc referenced a non-existent `setBottomInset` setter (the context exposes only `topInset`/`bottomInset` values) - `components/Chat/Chat.tsx` โ€” `useNativeMultipartUpload` JSDoc `@default true` โ†’ `@default false` (matches the runtime default) - `contexts/channelsContext/ChannelsContext.tsx` โ€” `numberOfSkeletons` comment default `6` โ†’ `8` (matches the runtime default in `ChannelList.tsx`) ## ๐ŸŽจ UI Changes None โ€” export additions and doc-comment corrections only. No runtime/UI behavior change. ## ๐Ÿงช Testing - `tsc --noEmit -p package/tsconfig.json` passes clean (0 errors) โ€” confirms all new exports resolve without name collisions. - No behavior changes; the exported symbols were already used internally. ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [x] PR targets the `develop` branch - [x] Documentation is updated (companion docs PR: GetStream/docs-content#1435) - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- package/src/components/Chat/Chat.tsx | 2 +- package/src/components/index.ts | 5 +++++ package/src/components/ui/index.ts | 3 +++ .../attachmentPickerContext/AttachmentPickerContext.tsx | 2 +- package/src/contexts/channelsContext/ChannelsContext.tsx | 2 +- package/src/contexts/pollContext/pollContext.tsx | 2 +- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/package/src/components/Chat/Chat.tsx b/package/src/components/Chat/Chat.tsx index 89df8a737e..6e9b3079cc 100644 --- a/package/src/components/Chat/Chat.tsx +++ b/package/src/components/Chat/Chat.tsx @@ -51,7 +51,7 @@ export type ChatProps = Pick & * This only controls whether the native adapter gets installed by this Chat instance. * It does not uninstall an adapter that was already installed on the client. * - * @default true + * @default false */ useNativeMultipartUpload?: boolean; /** diff --git a/package/src/components/index.ts b/package/src/components/index.ts index 4f906d2d19..7195904f8d 100644 --- a/package/src/components/index.ts +++ b/package/src/components/index.ts @@ -148,6 +148,7 @@ export * from './MessageInput/components/AttachmentPreview/AudioAttachmentUpload export * from './MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview'; export * from './MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview'; export * from './MessageInput/hooks/useAudioRecorder'; +export * from './MessageInput/hooks/useCooldownRemaining'; export * from './MessageList/DateHeader'; export * from './MessageList/hooks/useMessageList'; @@ -196,6 +197,10 @@ export * from './UIComponents/SvgAwareImage'; export * from './UIComponents/Spinner'; export * from './UIComponents/SwipableWrapper'; export * from './UIComponents/PortalWhileClosingView'; +export * from './UIComponents/EmptyList'; +export * from './UIComponents/EmptySearchResult'; +export * from './UIComponents/SearchInput'; +export * from './UIComponents/SelectionCircle'; export * from './Thread/Thread'; export * from './Thread/components/ThreadFooterComponent'; diff --git a/package/src/components/ui/index.ts b/package/src/components/ui/index.ts index 7daf9d6d37..afe40c071a 100644 --- a/package/src/components/ui/index.ts +++ b/package/src/components/ui/index.ts @@ -2,3 +2,6 @@ export * from './Avatar'; export * from './VideoPlayIndicator'; export * from './Button'; export * from './Badge'; +export * from './Input/Input'; +export * from './SpeedSettingsButton'; +export * from './GiphyChip'; diff --git a/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx b/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx index 063d1f6f05..71ccda2f50 100644 --- a/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx +++ b/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx @@ -22,7 +22,7 @@ export type AttachmentPickerContextValue = Pick< > & { /** * `bottomInset` determine the height of the `AttachmentPicker` and the underlying shift to the `MessageList` when it is opened. - * This can also be set via the `setBottomInset` function provided by the `useAttachmentPickerContext` hook. + * This can be set via the `bottomInset` prop on the `Channel` component. * * Please check [OverlayProvider](https://github.com/GetStream/stream-chat-react-native/wiki/Cookbook-v3.0#overlayprovider) section in Cookbook * for more details. diff --git a/package/src/contexts/channelsContext/ChannelsContext.tsx b/package/src/contexts/channelsContext/ChannelsContext.tsx index 48e66f26aa..c6aa806d22 100644 --- a/package/src/contexts/channelsContext/ChannelsContext.tsx +++ b/package/src/contexts/channelsContext/ChannelsContext.tsx @@ -69,7 +69,7 @@ export type ChannelsContextValue = { */ maxUnreadCount: number; /** - * Number of skeletons that should show when loading. Default: 6 + * Number of skeletons that should show when loading. Default: 8 */ numberOfSkeletons: number; /** diff --git a/package/src/contexts/pollContext/pollContext.tsx b/package/src/contexts/pollContext/pollContext.tsx index ec0ef46e8d..bfb1f07797 100644 --- a/package/src/contexts/pollContext/pollContext.tsx +++ b/package/src/contexts/pollContext/pollContext.tsx @@ -29,7 +29,7 @@ export const usePollContext = () => { if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) { throw new Error( - 'The useCreatePollContext hook was called outside of the PollContext provider. Make sure you have configured the Poll component correctly - https://getstream.io/chat/docs/sdk/reactnative/basics/hello_stream_chat/#channel', + 'The usePollContext hook was called outside of the PollContext provider. Make sure you have configured the Poll component correctly - https://getstream.io/chat/docs/sdk/reactnative/basics/hello_stream_chat/#channel', ); } From 230cfa104d616f3753dc06bbe34640fd20868f15 Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Thu, 16 Jul 2026 13:49:11 -0500 Subject: [PATCH 8/9] feat: allow custom icons (#3731) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐ŸŽฏ Goal Allow providing custom icons Docs: https://github.com/GetStream/docs-content/pull/1438 Fixes https://github.com/GetStream/stream-chat-react-native/issues/3449 ## ๐Ÿ›  Implementation details ## ๐ŸŽจ UI Changes
iOS
Before After
Android
Before After
## ๐Ÿงช Testing ## โ˜‘๏ธ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android --- .../Attachment/Audio/PlayPauseButton.tsx | 8 +- .../src/components/Attachment/FileIcon.tsx | 46 ++--- .../src/components/Attachment/Giphy/Giphy.tsx | 5 +- .../Attachment/UrlPreview/URLPreview.tsx | 5 +- .../UrlPreview/URLPreviewCompact.tsx | 5 +- .../AttachmentMediaPicker.tsx | 5 +- .../AttachmentPickerItem.tsx | 4 +- .../components/AttachmentPickerContent.tsx | 13 +- .../components/AttachmentTypePickerButton.tsx | 28 ++- .../AutoCompleteSuggestionCommandIcon.tsx | 21 +- .../AutoCompleteSuggestionHeader.tsx | 5 +- .../mentionItems/MentionBroadcastItem.tsx | 5 +- .../mentionItems/MentionRoleItem.tsx | 5 +- .../mentionItems/MentionUserGroupItem.tsx | 23 ++- .../components/ChannelDetailsNavHeader.tsx | 5 +- .../ChannelDetailsNavigationSection.tsx | 12 +- .../components/ChannelDetailsProfile.tsx | 5 +- .../ChannelEditDetailsFormHeader.tsx | 5 +- .../components/ChannelEditImageSheet.tsx | 16 +- .../members/ChannelAddMembersButton.tsx | 5 +- .../members/ChannelAddMembersFormHeader.tsx | 5 +- .../components/members/ChannelMemberItem.tsx | 5 +- .../components/modal/ModalHeader.tsx | 5 +- .../navigation-section/FileAttachmentList.tsx | 9 +- .../navigation-section/MediaItem.tsx | 5 +- .../navigation-section/MediaList.tsx | 5 +- .../navigation-section/PinnedMessageList.tsx | 5 +- .../hooks/useChannelDetailsNavigationItems.ts | 25 ++- .../ChannelMessagePreviewDeliveryStatus.tsx | 21 +- .../ChannelPreview/ChannelPreviewMessage.tsx | 4 +- .../ChannelPreviewMutedStatus.tsx | 5 +- .../ChannelPreviewPinnedStatus.tsx | 5 +- .../ChannelPreview/ChannelSwipableWrapper.tsx | 4 +- .../components/ImageGalleryFooter.tsx | 9 +- .../components/ImageGalleryHeader.tsx | 5 +- .../components/ImageGalleryVideoControl.tsx | 10 +- .../Indicators/EmptyStateIndicator.tsx | 9 +- .../Headers/MessagePinnedHeader.tsx | 5 +- .../Headers/MessageReminderHeader.tsx | 5 +- .../Headers/MessageSavedForLaterHeader.tsx | 5 +- .../Headers/SentToChannelHeader.tsx | 5 +- .../MessageItemView/MessageDeleted.tsx | 5 +- .../MessageItemView/MessageReplies.tsx | 8 +- .../Message/MessageItemView/MessageStatus.tsx | 23 ++- .../MessageItemView/MessageSwipeContent.tsx | 5 +- .../ReactionList/ReactionListClustered.tsx | 6 +- .../ReactionList/ReactionListItem.tsx | 6 +- .../Message/hooks/useMessageActions.tsx | 49 ++--- .../ShowThreadMessageInChannelButton.tsx | 10 +- .../StopMessageStreamingButton.tsx | 5 +- .../AttachmentRemoveControl.tsx | 5 +- .../AttachmentUploadProgressIndicator.tsx | 11 +- .../VideoAttachmentUploadPreview.tsx | 5 +- .../AudioRecorder/AudioRecorder.tsx | 27 ++- .../AudioRecorder/AudioRecordingButton.tsx | 5 +- .../AudioRecordingInProgress.tsx | 5 +- .../AudioRecordingLockIndicator.tsx | 11 +- .../AudioRecorder/AudioRecordingPreview.tsx | 8 +- .../components/InputButtons/AttachButton.tsx | 5 +- .../components/LinkPreviewList.tsx | 5 +- .../components/OutputButtons/EditButton.tsx | 5 +- .../components/OutputButtons/SendButton.tsx | 5 +- .../MessageList/ScrollToBottomButton.tsx | 5 +- .../UnreadMessagesNotification.tsx | 8 +- .../MessageMenu/MessageReactionPicker.tsx | 5 +- .../MessageMenu/MessageUserReactions.tsx | 7 +- .../MessageMenu/MessageUserReactionsItem.tsx | 5 +- .../MessageUserReactionsItem.test.tsx | 2 +- .../components/Notifications/Notification.tsx | 35 ++-- .../Poll/components/CreatePollHeader.tsx | 11 +- .../Poll/components/CreatePollOptions.tsx | 11 +- .../Poll/components/MultipleVotesSettings.tsx | 7 +- .../Poll/components/PollModalHeader.tsx | 5 +- .../components/Poll/components/PollOption.tsx | 4 +- .../ThreadList/ThreadListUnreadBanner.tsx | 10 +- .../ThreadMessagePreviewDeliveryStatus.tsx | 16 +- .../UIComponents/EmptySearchResult.tsx | 5 +- .../components/UIComponents/SearchInput.tsx | 12 +- .../UIComponents/SelectionCircle.tsx | 5 +- .../src/components/UIComponents/Spinner.tsx | 5 +- .../src/components/ui/Avatar/AvatarGroup.tsx | 7 +- .../src/components/ui/Avatar/UserAvatar.tsx | 11 +- .../src/components/ui/Badge/ErrorBadge.tsx | 5 +- .../src/components/ui/Badge/GiphyBadge.tsx | 5 +- .../src/components/ui/Badge/ImgurBadge.tsx | 5 +- .../src/components/ui/Badge/RetryBadge.tsx | 5 +- package/src/components/ui/GiphyChip.tsx | 8 +- package/src/components/ui/Input/Input.tsx | 8 +- .../src/components/ui/VideoPlayIndicator.tsx | 5 +- .../componentsContext/ComponentsContext.tsx | 48 ++++- .../componentsContext/defaultComponents.ts | 193 ++++++++++++++++++ .../__tests__/useChannelActionItems.test.tsx | 15 ++ .../useChannelMemberActionItems.test.tsx | 9 + .../hooks/actions/useChannelActionItems.tsx | 42 +++- .../actions/useChannelMemberActionItems.tsx | 35 +++- .../messagePreview/useMessagePreviewIcon.tsx | 28 +-- package/src/icons/arrow-left.tsx | 23 --- 97 files changed, 758 insertions(+), 448 deletions(-) delete mode 100644 package/src/icons/arrow-left.tsx diff --git a/package/src/components/Attachment/Audio/PlayPauseButton.tsx b/package/src/components/Attachment/Audio/PlayPauseButton.tsx index 5786b02bda..1e0ac70459 100644 --- a/package/src/components/Attachment/Audio/PlayPauseButton.tsx +++ b/package/src/components/Attachment/Audio/PlayPauseButton.tsx @@ -2,8 +2,7 @@ import React, { useMemo } from 'react'; import { Pressable, PressableProps, StyleProp, StyleSheet, ViewStyle } from 'react-native'; import { useTheme } from '../../../contexts'; -import { Pause } from '../../../icons/pause-fill'; -import { Play } from '../../../icons/play-fill'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { primitives } from '../../../theme'; import { buttonSizes } from '../../ui/Button/constants'; @@ -31,6 +30,7 @@ export const PlayPauseButton = ({ const { theme: { semantics }, } = useTheme(); + const { icons } = useComponentsContext(); const styles = useStyles(); return ( {isPlaying ? ( - + ) : ( - + )} ); diff --git a/package/src/components/Attachment/FileIcon.tsx b/package/src/components/Attachment/FileIcon.tsx index 8e9157e1d8..304ecde815 100644 --- a/package/src/components/Attachment/FileIcon.tsx +++ b/package/src/components/Attachment/FileIcon.tsx @@ -1,18 +1,9 @@ import React from 'react'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; +import type { IconsMap } from '../../contexts/componentsContext/defaultComponents'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import { Audio } from '../../icons/filetype-audio-xl'; -import { Code } from '../../icons/filetype-code-xl'; -import { ZIP } from '../../icons/filetype-compression-xl'; -import { OtherFileIcon } from '../../icons/filetype-other-xl'; -import { PDF } from '../../icons/filetype-pdf-xl'; -import { Presentation } from '../../icons/filetype-presentation-xl'; -import { SpreadSheet } from '../../icons/filetype-spreadsheet-xl'; -import { DOC } from '../../icons/filetype-text-xl'; -import { Video } from '../../icons/filetype-video-xl'; -import type { IconProps } from '../../icons/utils/base'; - // https://www.iana.org/assignments/media-types/media-types.xhtml#audio const audioFileTypes = [ 'audio/1d-interleaved-parityfec', @@ -349,49 +340,49 @@ const codeFileTypes = [ 'text/plain', ]; -const mimeTypeToIconMap: Record> = { - 'application/pdf': PDF, // .pdf +const mimeTypeToIconKeyMap: Record = { + 'application/pdf': 'PDF', // .pdf }; for (const type of audioFileTypes) { - mimeTypeToIconMap[type] = Audio; + mimeTypeToIconKeyMap[type] = 'Audio'; } for (const type of docMimeTypes) { - mimeTypeToIconMap[type] = DOC; + mimeTypeToIconKeyMap[type] = 'DOC'; } for (const type of excelMimeTypes) { - mimeTypeToIconMap[type] = SpreadSheet; + mimeTypeToIconKeyMap[type] = 'SpreadSheet'; } for (const type of powerpointMimeTypes) { - mimeTypeToIconMap[type] = Presentation; + mimeTypeToIconKeyMap[type] = 'Presentation'; } for (const type of zipFileTypes) { - mimeTypeToIconMap[type] = ZIP; + mimeTypeToIconKeyMap[type] = 'ZIP'; } for (const type of videoFileTypes) { - mimeTypeToIconMap[type] = Video; + mimeTypeToIconKeyMap[type] = 'Video'; } for (const type of codeFileTypes) { - mimeTypeToIconMap[type] = Code; + mimeTypeToIconKeyMap[type] = 'Code'; } -function mimeTypeToIcon(mimeType?: string): React.ComponentType { +function mimeTypeToIconKey(mimeType?: string): keyof IconsMap { if (!mimeType) { - return OtherFileIcon; + return 'OtherFileIcon'; } - const Icon = mimeTypeToIconMap[mimeType]; - if (Icon) { - return Icon; + const iconKey = mimeTypeToIconKeyMap[mimeType]; + if (iconKey) { + return iconKey; } - return OtherFileIcon; + return 'OtherFileIcon'; } export type FileIconProps = { @@ -426,8 +417,9 @@ export const FileIcon = ({ mimeType, size = 'md' }: FileIconProps) => { }, }, } = useTheme(); + const { icons } = useComponentsContext(); - const Icon = mimeTypeToIcon(mimeType); + const Icon = icons[mimeTypeToIconKey(mimeType)]; return ; }; diff --git a/package/src/components/Attachment/Giphy/Giphy.tsx b/package/src/components/Attachment/Giphy/Giphy.tsx index 836aee2350..8c954ce7d7 100644 --- a/package/src/components/Attachment/Giphy/Giphy.tsx +++ b/package/src/components/Attachment/Giphy/Giphy.tsx @@ -5,6 +5,7 @@ import type { Attachment } from 'stream-chat'; import { GiphyImage } from './GiphyImage'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, @@ -16,7 +17,6 @@ import { import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; -import { EyeOpen } from '../../../icons/EyeOpen'; import { components, primitives } from '../../../theme'; import { Button } from '../../ui/'; @@ -43,6 +43,7 @@ const GiphyWithContext = (props: GiphyPropsWithContext) => { const { actions, image_url, thumb_url } = attachment; + const { icons } = useComponentsContext(); const { t } = useTranslationContext(); const { @@ -68,7 +69,7 @@ const GiphyWithContext = (props: GiphyPropsWithContext) => { testID='giphy-action-attachment' > - + {t('Only visible to you')} diff --git a/package/src/components/Attachment/UrlPreview/URLPreview.tsx b/package/src/components/Attachment/UrlPreview/URLPreview.tsx index 424c2d1d1b..cac7956894 100644 --- a/package/src/components/Attachment/UrlPreview/URLPreview.tsx +++ b/package/src/components/Attachment/UrlPreview/URLPreview.tsx @@ -25,7 +25,6 @@ import { useMessagesContext, } from '../../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; -import { Link } from '../../../icons/link'; import { primitives } from '../../../theme'; import { FileTypes } from '../../../types/types'; import { makeImageCompatibleUrl } from '../../../utils/utils'; @@ -67,6 +66,8 @@ const URLPreviewWithContext = (props: URLPreviewPropsWithContext) => { styles: stylesProp = {}, } = props; + const { icons } = useComponentsContext(); + const { theme: { semantics }, } = useTheme(); @@ -155,7 +156,7 @@ const URLPreviewWithContext = (props: URLPreviewPropsWithContext) => { ) : null} - + ) : null} - + { const { theme: { semantics }, } = useTheme(); + const { icons } = useComponentsContext(); - return ; + return ; }; export const AttachmentMediaPicker = (props: AttachmentPickerContentProps) => { diff --git a/package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx b/package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx index 3f6c83fdd5..2af5aed769 100644 --- a/package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx +++ b/package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx @@ -15,7 +15,6 @@ import { useMessageInputContext } from '../../../../contexts/messageInputContext import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../../contexts/translationContext/TranslationContext'; import { useViewport } from '../../../../hooks/useViewport'; -import { Plus } from '../../../../icons/plus'; import { NativeHandlers } from '../../../../native'; import { primitives } from '../../../../theme'; import type { File } from '../../../../types/types'; @@ -163,6 +162,7 @@ const AttachmentImage = (props: AttachmentPickerItemType) => { const AttachmentIosLimited = () => { const { numberOfAttachmentPickerImageColumns } = useAttachmentPickerContext(); + const { icons } = useComponentsContext(); const { vw } = useViewport(); const { t } = useTranslationContext(); const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2; @@ -178,7 +178,7 @@ const AttachmentIosLimited = () => { ]} onPress={NativeHandlers.iOS14RefreshGallerySelection} > - + {t('Add more')} ); diff --git a/package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx b/package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx index 91610b5798..c02a4ad876 100644 --- a/package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx +++ b/package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx @@ -19,9 +19,9 @@ import { useMessageInputContext, useTranslationContext, } from '../../../contexts'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useAttachmentPickerState, useStableCallback } from '../../../hooks'; -import { Camera, FilePickerIcon, PollThumbnail, VideoIcon } from '../../../icons'; import { primitives } from '../../../theme'; import { CommandSuggestionItem } from '../../AutoCompleteInput/AutoCompleteSuggestionItem'; @@ -149,6 +149,7 @@ export const AttachmentCommandPicker = () => { export const AttachmentPollPicker = (props: AttachmentPickerContentProps) => { const { t } = useTranslationContext(); + const { icons } = useComponentsContext(); const { height } = props; const { openPollCreationDialog, sendMessage } = useMessageInputContext(); @@ -158,7 +159,7 @@ export const AttachmentPollPicker = (props: AttachmentPickerContentProps) => { return ( { const [permissionDenied, setPermissionDenied] = useState(false); const { t } = useTranslationContext(); + const { icons } = useComponentsContext(); const { height, videoOnly } = props; const { takeAndUploadImage } = useMessageInputContext(); @@ -198,7 +200,7 @@ export const AttachmentCameraPicker = ( return permissionDenied ? ( ) : ( { const { t } = useTranslationContext(); + const { icons } = useComponentsContext(); const { height } = props; const { pickFile } = useMessageInputContext(); return ( { const { hasImagePicker, pickAndUploadImageFromNativePicker } = useMessageInputContext(); const { attachmentPickerStore, disableAttachmentPicker } = useAttachmentPickerContext(); + const { icons } = useComponentsContext(); const { selectedPicker } = useAttachmentPickerState(); const setImagePicker = useStableCallback(() => { @@ -85,7 +79,7 @@ export const MediaPickerButton = () => { @@ -94,6 +88,7 @@ export const MediaPickerButton = () => { export const CameraPickerButton = () => { const { attachmentPickerStore, disableAttachmentPicker } = useAttachmentPickerContext(); + const { icons } = useComponentsContext(); const { selectedPicker } = useAttachmentPickerState(); const { hasCameraPicker, takeAndUploadImage } = useMessageInputContext(); @@ -119,14 +114,14 @@ export const CameraPickerButton = () => { {Platform.OS === 'android' ? ( @@ -137,6 +132,7 @@ export const CameraPickerButton = () => { export const FilePickerButton = () => { const { attachmentPickerStore, disableAttachmentPicker } = useAttachmentPickerContext(); + const { icons } = useComponentsContext(); const { selectedPicker } = useAttachmentPickerState(); const { hasFilePicker, pickFile } = useMessageInputContext(); @@ -152,7 +148,7 @@ export const FilePickerButton = () => { @@ -164,6 +160,7 @@ export const PollPickerButton = () => { const { selectedPicker } = useAttachmentPickerState(); const { threadList } = useChannelContext(); + const { icons } = useComponentsContext(); const { hasCreatePoll } = useMessagesContext(); const ownCapabilities = useOwnCapabilitiesContext(); @@ -180,7 +177,7 @@ export const PollPickerButton = () => { @@ -191,6 +188,7 @@ export const CommandsPickerButton = () => { const [showCommandsSheet, setShowCommandsSheet] = useState(false); const { hasCommands } = useMessageInputContext(); const { attachmentPickerStore, disableAttachmentPicker } = useAttachmentPickerContext(); + const { icons } = useComponentsContext(); const { selectedPicker } = useAttachmentPickerState(); const setCommandsPicker = useStableCallback(() => { @@ -208,7 +206,7 @@ export const CommandsPickerButton = () => { diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionCommandIcon.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionCommandIcon.tsx index c14a695f1d..e457544782 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionCommandIcon.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionCommandIcon.tsx @@ -3,32 +3,31 @@ import { StyleSheet, View } from 'react-native'; import { CommandVariants } from 'stream-chat'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import { Flag, GiphyIcon, Mute, Sound, UserAdd, UserDelete } from '../../icons'; -import { Lightning } from '../../icons/bolt'; -import { Imgur } from '../../icons/imgur'; export const SuggestionCommandIcon = ({ name }: { name: CommandVariants }) => { const { theme: { semantics }, } = useTheme(); + const { icons } = useComponentsContext(); if (name === 'ban') { - return ; + return ; } else if (name === 'flag') { - return ; + return ; } else if (name === 'giphy') { - return ; + return ; } else if (name === 'imgur') { - return ; + return ; } else if (name === 'mute') { - return ; + return ; } else if (name === 'unban') { - return ; + return ; } else if (name === 'unmute') { - return ; + return ; } else { - return ; + return ; } }; diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionHeader.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionHeader.tsx index a912d5c57c..b778c5af6e 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionHeader.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionHeader.tsx @@ -1,9 +1,9 @@ import React, { useMemo } from 'react'; import { StyleSheet, Text, View } from 'react-native'; +import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import { Smile } from '../../icons/emoji'; import { primitives } from '../../theme'; export type AutoCompleteSuggestionHeaderProps = { @@ -48,11 +48,12 @@ export const EmojiHeader: React.FC = ({ query semantics, }, } = useTheme(); + const { icons } = useComponentsContext(); const styles = useStyles(); return ( - + {`Emoji matching "${queryText}"`} diff --git a/package/src/components/AutoCompleteInput/mentionItems/MentionBroadcastItem.tsx b/package/src/components/AutoCompleteInput/mentionItems/MentionBroadcastItem.tsx index 5b90332e61..0a6db47aba 100644 --- a/package/src/components/AutoCompleteInput/mentionItems/MentionBroadcastItem.tsx +++ b/package/src/components/AutoCompleteInput/mentionItems/MentionBroadcastItem.tsx @@ -6,8 +6,8 @@ import { EnhancedMentionContent } from './EnhancedMentionContent'; import { EnhancedMentionIcon } from './EnhancedMentionIcon'; import { MentionItem } from './MentionItem'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; -import { Megaphone } from '../../../icons/megaphone'; export type MentionBroadcastItemProps = { entity: ChannelMentionSuggestion | HereMentionSuggestion; @@ -24,8 +24,9 @@ const SUBTITLE_KEY = { export const MentionBroadcastItem = ({ entity }: MentionBroadcastItemProps) => { const { t } = useTranslationContext(); + const { icons } = useComponentsContext(); return ( - }> + }> { const { t } = useTranslationContext(); + const { icons } = useComponentsContext(); return ( - }> + }> ( - }> - - -); +export const MentionUserGroupItem = ({ entity }: MentionUserGroupItemProps) => { + const { icons } = useComponentsContext(); + return ( + }> + + + ); +}; diff --git a/package/src/components/ChannelDetails/components/ChannelDetailsNavHeader.tsx b/package/src/components/ChannelDetails/components/ChannelDetailsNavHeader.tsx index 439dbb30fa..9f00da6a0f 100644 --- a/package/src/components/ChannelDetails/components/ChannelDetailsNavHeader.tsx +++ b/package/src/components/ChannelDetails/components/ChannelDetailsNavHeader.tsx @@ -4,10 +4,10 @@ import { I18nManager, StyleSheet, Text, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useChannelDetailsContext } from '../../../contexts/channelDetailsContext/channelDetailsContext'; +import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { useIsDirectChat } from '../../../hooks/useIsDirectChat'; -import { ChevronLeft } from '../../../icons/chevron-left'; import { primitives } from '../../../theme'; import { Button } from '../../ui/Button/Button'; @@ -26,6 +26,7 @@ export const ChannelDetailsNavHeader = ({ title, }: ChannelDetailsNavHeaderProps) => { const { channel } = useChannelDetailsContext(); + const { icons } = useComponentsContext(); const { t } = useTranslationContext(); const { theme: { @@ -54,7 +55,7 @@ export const ChannelDetailsNavHeader = ({