├── .babelrc.js ├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── actions │ └── setup-node │ │ └── action.yml ├── auto-merge.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── e2e.yml │ ├── pr-check.yml │ ├── release.yml │ └── size.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .ladle ├── components.tsx ├── config.mjs └── styles.css ├── .lintstagedrc.fix.json ├── .lintstagedrc.json ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .releaserc.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── SECURITY.md ├── assetsTransformer.js ├── axe-helper.js ├── babel.config.js ├── codecov.yml ├── commitlint.config.mjs ├── developers ├── BRANCHES.md ├── COMMIT.md ├── DEPRECATIONS.md ├── DEVELOPMENT.md ├── DOCUMENTATION.md ├── E2E.md ├── PR.md └── RELEASE.md ├── e2e ├── .eslintrc.json ├── add-message.test.ts ├── attachment-sizing.test.ts ├── autocomplete-mention.test.ts ├── edit-message.test.ts ├── fixtures │ ├── data │ │ └── attachment.mjs │ ├── fixtures.mjs │ └── utils.mjs ├── mark-read.test.ts ├── navigate-long-message-lists.test.ts ├── navigate-long-message-lists.test.ts-snapshots │ ├── thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-chromium-linux.png │ ├── thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-firefox-linux.png │ ├── thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-linux.png │ ├── thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-webkit-linux.png │ ├── thread-autoscroll-on-new-message-only-if-I-send-a-message-1-chromium-linux.png │ ├── thread-autoscroll-on-new-message-only-if-I-send-a-message-1-firefox-linux.png │ ├── thread-autoscroll-on-new-message-only-if-I-send-a-message-1-linux.png │ └── thread-autoscroll-on-new-message-only-if-I-send-a-message-1-webkit-linux.png ├── pin-message.test.ts ├── scripts │ ├── run_e2e.sh │ └── run_in_container.sh └── user │ ├── Controller.ts │ ├── User.ts │ ├── components │ ├── Attachment │ │ └── Attachment.ts │ ├── AutocompleteSuggestionList.ts │ ├── ChannelPreview.ts │ ├── EditMessageForm │ │ └── EditMessageForm.ts │ ├── Message │ │ ├── MessageSimple.ts │ │ └── QuotedMessage.ts │ ├── MessageActions │ │ ├── MessageActions.ts │ │ └── MessageActionsBox.ts │ ├── MessageInput.ts │ ├── MessageList │ │ ├── MessageList.ts │ │ └── MessageNotification.ts │ └── Thread │ │ └── Thread.ts │ ├── selectors.ts │ └── test.ts ├── eslint.config.mjs ├── examples ├── capacitor │ ├── .gitignore │ ├── android │ │ ├── .gitignore │ │ ├── app │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── capacitor.build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src │ │ │ │ ├── androidTest │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── getcapacitor │ │ │ │ │ └── myapp │ │ │ │ │ └── ExampleInstrumentedTest.java │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── assets │ │ │ │ │ ├── capacitor.config.json │ │ │ │ │ └── capacitor.plugins.json │ │ │ │ ├── java │ │ │ │ │ └── capacitor │ │ │ │ │ │ └── test │ │ │ │ │ │ └── MainActivity.java │ │ │ │ └── res │ │ │ │ │ ├── drawable-land-hdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-mdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-xhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-xxhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-xxxhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-hdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-mdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-xhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-xxhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-xxxhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ │ ├── drawable │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ └── splash.png │ │ │ │ │ ├── layout │ │ │ │ │ └── activity_main.xml │ │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── values │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ │ └── xml │ │ │ │ │ ├── config.xml │ │ │ │ │ └── file_paths.xml │ │ │ │ └── test │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── getcapacitor │ │ │ │ └── myapp │ │ │ │ └── ExampleUnitTest.java │ │ ├── build.gradle │ │ ├── capacitor.settings.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle │ │ └── variables.gradle │ ├── capacitor.config.ts │ ├── ios │ │ ├── .gitignore │ │ └── App │ │ │ ├── App.xcodeproj │ │ │ ├── project.pbxproj │ │ │ └── project.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ │ ├── App.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ ├── App │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ ├── AppIcon-20x20@1x.png │ │ │ │ │ ├── AppIcon-20x20@2x-1.png │ │ │ │ │ ├── AppIcon-20x20@2x.png │ │ │ │ │ ├── AppIcon-20x20@3x.png │ │ │ │ │ ├── AppIcon-29x29@1x.png │ │ │ │ │ ├── AppIcon-29x29@2x-1.png │ │ │ │ │ ├── AppIcon-29x29@2x.png │ │ │ │ │ ├── AppIcon-29x29@3x.png │ │ │ │ │ ├── AppIcon-40x40@1x.png │ │ │ │ │ ├── AppIcon-40x40@2x-1.png │ │ │ │ │ ├── AppIcon-40x40@2x.png │ │ │ │ │ ├── AppIcon-40x40@3x.png │ │ │ │ │ ├── AppIcon-512@2x.png │ │ │ │ │ ├── AppIcon-60x60@2x.png │ │ │ │ │ ├── AppIcon-60x60@3x.png │ │ │ │ │ ├── AppIcon-76x76@1x.png │ │ │ │ │ ├── AppIcon-76x76@2x.png │ │ │ │ │ ├── AppIcon-83.5x83.5@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── Splash.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── splash-2732x2732-1.png │ │ │ │ │ ├── splash-2732x2732-2.png │ │ │ │ │ └── splash-2732x2732.png │ │ │ ├── Base.lproj │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ ├── capacitor.config.json │ │ │ └── config.xml │ │ │ └── Podfile │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── App.css │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── components │ │ │ ├── ChannelInner.tsx │ │ │ ├── CustomMessage.scss │ │ │ └── CustomMessage.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── react-app-env.d.ts │ │ ├── serviceWorker.ts │ │ └── setupTests.ts │ ├── tsconfig.json │ └── yarn.lock ├── nextjs │ ├── .gitignore │ ├── .nvmrc │ ├── README.md │ ├── next.config.js │ ├── package.json │ ├── pages │ │ ├── App.css │ │ ├── _app.jsx │ │ └── index.jsx │ ├── public │ │ ├── favicon.ico │ │ └── vercel.svg │ └── yarn.lock ├── tutorial │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── eslint.config.js │ ├── index.html │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── 1-client-setup │ │ │ ├── App.tsx │ │ │ ├── credentials.ts │ │ │ ├── index.html │ │ │ └── main.tsx │ │ ├── 2-core-component-setup │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── layout.css │ │ │ ├── main.tsx │ │ │ └── stream-chat.d.ts │ │ ├── 3-channel-list │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── layout.css │ │ │ └── main.tsx │ │ ├── 4-custom-ui-components │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── layout.css │ │ │ └── main.tsx │ │ ├── 5-custom-attachment-type │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── layout.css │ │ │ ├── main.tsx │ │ │ └── stream-chat.d.ts │ │ ├── 6-emoji-picker │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── layout.css │ │ │ └── main.tsx │ │ ├── 7-livestream │ │ │ ├── App.tsx │ │ │ ├── index.html │ │ │ ├── layout.css │ │ │ └── main.tsx │ │ ├── App.tsx │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── yarn.lock ├── typescript │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── App.tsx │ │ ├── components │ │ │ ├── ChannelInner.tsx │ │ │ ├── CustomMessage.scss │ │ │ └── CustomMessage.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── react-app-env.d.ts │ │ ├── serviceWorker.ts │ │ └── setupTests.ts │ ├── tsconfig.json │ └── yarn.lock ├── vite │ ├── .env.example │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.tsx │ │ ├── index.scss │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── yarn.lock └── website-demos │ ├── README.md │ ├── social-messaging │ └── README.md │ └── virtual-event │ └── README.md ├── i18next-parser.config.js ├── jest-global-setup.js ├── jest.config.js ├── jest.env.setup.js ├── package.json ├── playwright.config.ts ├── scripts ├── bundle-cjs.mjs ├── bundle-esm.mjs ├── check-sidebar.mjs ├── copy-css.sh ├── get-package-version.mjs ├── merge-stream-chat-css-docs.sh ├── validate-cjs-browser-bundle.cjs ├── validate-cjs-node-bundle.cjs └── validate-translations.js ├── src ├── @types │ ├── stream-chat-custom-data.d.ts │ └── vite-env.d.ts ├── components │ ├── AIStateIndicator │ │ ├── AIStateIndicator.tsx │ │ ├── hooks │ │ │ └── useAIState.ts │ │ └── index.ts │ ├── Attachment │ │ ├── Attachment.tsx │ │ ├── AttachmentActions.tsx │ │ ├── AttachmentContainer.tsx │ │ ├── Audio.tsx │ │ ├── Card.tsx │ │ ├── FileAttachment.tsx │ │ ├── UnsupportedAttachment.tsx │ │ ├── VoiceRecording.tsx │ │ ├── __tests__ │ │ │ ├── Attachment.test.js │ │ │ ├── AttachmentActions.test.js │ │ │ ├── Audio.test.js │ │ │ ├── Card.test.js │ │ │ ├── File.test.js │ │ │ ├── VoiceRecording.test.js │ │ │ ├── WaveProgressBar.test.js │ │ │ ├── __snapshots__ │ │ │ │ ├── Attachment.test.js.snap │ │ │ │ ├── AttachmentActions.test.js.snap │ │ │ │ ├── Card.test.js.snap │ │ │ │ ├── File.test.js.snap │ │ │ │ ├── VoiceRecording.test.js.snap │ │ │ │ └── WaveProgressBar.test.js.snap │ │ │ ├── audioSampling.test.js │ │ │ └── utils.test.js │ │ ├── attachment-sizing.tsx │ │ ├── audioSampling.ts │ │ ├── components │ │ │ ├── DownloadButton.tsx │ │ │ ├── FileSizeIndicator.tsx │ │ │ ├── PlayButton.tsx │ │ │ ├── PlaybackRateButton.tsx │ │ │ ├── ProgressBar.tsx │ │ │ ├── WaveProgressBar.tsx │ │ │ └── index.ts │ │ ├── hooks │ │ │ └── useAudioController.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ └── utils.tsx │ ├── Avatar │ │ ├── Avatar.tsx │ │ ├── ChannelAvatar.tsx │ │ ├── GroupAvatar.tsx │ │ ├── __tests__ │ │ │ └── Avatar.test.js │ │ └── index.ts │ ├── Channel │ │ ├── Channel.tsx │ │ ├── LoadingChannel.tsx │ │ ├── __tests__ │ │ │ ├── Channel.test.js │ │ │ ├── __snapshots__ │ │ │ │ └── Channel.test.js.snap │ │ │ └── useIsMounted.test.js │ │ ├── channelState.ts │ │ ├── constants.ts │ │ ├── hooks │ │ │ ├── useChannelContainerClasses.ts │ │ │ ├── useCreateChannelStateContext.ts │ │ │ ├── useCreateTypingContext.ts │ │ │ ├── useEditMessageHandler.ts │ │ │ ├── useIsMounted.ts │ │ │ └── useMentionsHandlers.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── ChannelHeader │ │ ├── ChannelHeader.tsx │ │ ├── __tests__ │ │ │ └── ChannelHeader.test.js │ │ ├── icons.tsx │ │ └── index.ts │ ├── ChannelList │ │ ├── ChannelList.tsx │ │ ├── ChannelListMessenger.tsx │ │ ├── __tests__ │ │ │ ├── ChannelList.test.js │ │ │ ├── ChannelListMessenger.test.js │ │ │ └── __snapshots__ │ │ │ │ └── ChannelListMessenger.test.js.snap │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useChannelDeletedListener.ts │ │ │ ├── useChannelHiddenListener.ts │ │ │ ├── useChannelListShape.ts │ │ │ ├── useChannelMembershipState.ts │ │ │ ├── useChannelTruncatedListener.ts │ │ │ ├── useChannelUpdatedListener.ts │ │ │ ├── useChannelVisibleListener.ts │ │ │ ├── useConnectionRecoveredListener.ts │ │ │ ├── useMessageNewListener.ts │ │ │ ├── useMobileNavigation.ts │ │ │ ├── useNotificationAddedToChannelListener.ts │ │ │ ├── useNotificationMessageNewListener.ts │ │ │ ├── useNotificationRemovedFromChannelListener.ts │ │ │ ├── usePaginatedChannels.ts │ │ │ ├── useSelectedChannelState.ts │ │ │ └── useUserPresenceChangedListener.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── ChannelPreview │ │ ├── ChannelPreview.tsx │ │ ├── ChannelPreviewActionButtons.tsx │ │ ├── ChannelPreviewMessenger.tsx │ │ ├── __tests__ │ │ │ ├── ChannelPreview.test.js │ │ │ ├── ChannelPreviewMessenger.test.js │ │ │ ├── __snapshots__ │ │ │ │ └── ChannelPreviewMessenger.test.js.snap │ │ │ └── utils.test.js │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ └── useMessageDeliveryStatus.test.js │ │ │ ├── index.ts │ │ │ ├── useChannelPreviewInfo.ts │ │ │ ├── useIsChannelMuted.ts │ │ │ └── useMessageDeliveryStatus.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ └── utils.tsx │ ├── ChannelSearch │ │ ├── ChannelSearch.tsx │ │ ├── SearchBar.tsx │ │ ├── SearchInput.tsx │ │ ├── SearchResults.tsx │ │ ├── __tests__ │ │ │ ├── ChannelSearch.test.js │ │ │ ├── SearchBar.test.js │ │ │ ├── SearchResults.test.js │ │ │ └── __snapshots__ │ │ │ │ ├── ChannelSearch.test.js.snap │ │ │ │ └── SearchBar.test.js.snap │ │ ├── hooks │ │ │ └── useChannelSearch.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ └── utils.ts │ ├── Chat │ │ ├── Chat.tsx │ │ ├── __tests__ │ │ │ └── Chat.test.js │ │ ├── hooks │ │ │ ├── useChannelsQueryState.ts │ │ │ ├── useChat.ts │ │ │ ├── useCreateChatClient.ts │ │ │ └── useCreateChatContext.ts │ │ └── index.ts │ ├── ChatView │ │ ├── ChatView.tsx │ │ └── index.tsx │ ├── DateSeparator │ │ ├── DateSeparator.tsx │ │ ├── __tests__ │ │ │ └── DateSeparator.test.js │ │ └── index.ts │ ├── Dialog │ │ ├── DialogAnchor.tsx │ │ ├── DialogManager.ts │ │ ├── DialogMenu.tsx │ │ ├── DialogPortal.tsx │ │ ├── FormDialog.tsx │ │ ├── PromptDialog.tsx │ │ ├── __tests__ │ │ │ └── DialogsManager.test.js │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useDialog.ts │ │ └── index.ts │ ├── DragAndDrop │ │ └── DragAndDropContainer.tsx │ ├── EmptyStateIndicator │ │ ├── EmptyStateIndicator.tsx │ │ ├── __tests__ │ │ │ └── EmptyStateIndicator.test.js │ │ ├── icons.tsx │ │ └── index.ts │ ├── EventComponent │ │ ├── EventComponent.tsx │ │ ├── __tests__ │ │ │ └── EventComponent.test.js │ │ └── index.ts │ ├── Form │ │ ├── FieldError.tsx │ │ └── SwitchField.tsx │ ├── Gallery │ │ ├── BaseImage.tsx │ │ ├── Gallery.tsx │ │ ├── Image.tsx │ │ ├── ModalGallery.tsx │ │ ├── __tests__ │ │ │ ├── BaseImage.test.js │ │ │ ├── Gallery.test.js │ │ │ ├── Image.test.js │ │ │ ├── ModalGallery.test.js │ │ │ └── __snapshots__ │ │ │ │ ├── Gallery.test.js.snap │ │ │ │ └── Image.test.js.snap │ │ └── index.tsx │ ├── InfiniteScrollPaginator │ │ ├── InfiniteScroll.tsx │ │ ├── InfiniteScrollPaginator.tsx │ │ ├── __tests__ │ │ │ └── InfiniteScroll.test.js │ │ ├── hooks │ │ │ └── useCursorPaginator.ts │ │ └── index.ts │ ├── LoadMore │ │ ├── LoadMoreButton.tsx │ │ ├── LoadMorePaginator.tsx │ │ ├── __tests__ │ │ │ ├── LoadMoreButton.test.js │ │ │ └── LoadMorePaginator.test.js │ │ └── index.ts │ ├── Loading │ │ ├── LoadingChannels.tsx │ │ ├── LoadingErrorIndicator.tsx │ │ ├── LoadingIndicator.tsx │ │ ├── __tests__ │ │ │ ├── LoadingChannels.test.js │ │ │ ├── LoadingErrorIndicator.test.js │ │ │ ├── LoadingIndicator.test.js │ │ │ └── __snapshots__ │ │ │ │ ├── LoadingChannels.test.js.snap │ │ │ │ └── LoadingIndicator.test.js.snap │ │ └── index.ts │ ├── MML │ │ ├── MML.tsx │ │ ├── __tests__ │ │ │ └── MML.test.js │ │ └── index.ts │ ├── MediaRecorder │ │ ├── AudioRecorder │ │ │ ├── AudioRecorder.tsx │ │ │ ├── AudioRecordingButtons.tsx │ │ │ ├── AudioRecordingInProgress.tsx │ │ │ ├── AudioRecordingPreview.tsx │ │ │ ├── RecordingTimer.tsx │ │ │ ├── __tests__ │ │ │ │ ├── AudioRecorder.test.js │ │ │ │ ├── AudioRecordingPreview.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── AudioRecorder.test.js.snap │ │ │ ├── hooks │ │ │ │ └── useTimeElapsed.ts │ │ │ └── index.ts │ │ ├── RecordingPermissionDeniedNotification.tsx │ │ ├── classes │ │ │ ├── AmplitudeRecorder.ts │ │ │ ├── BrowserPermission.ts │ │ │ ├── MediaRecorderController.ts │ │ │ ├── __tests__ │ │ │ │ ├── AmplitudeRecorder.test.js │ │ │ │ ├── BrowserPermission.test.js │ │ │ │ └── MediaRecorderController.test.js │ │ │ └── index.ts │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ └── useMediaRecorder.test.js │ │ │ ├── index.ts │ │ │ └── useMediaRecorder.ts │ │ ├── index.ts │ │ ├── observable │ │ │ ├── BehaviorSubject.ts │ │ │ ├── Observable.ts │ │ │ ├── Observer.ts │ │ │ ├── Subject.ts │ │ │ ├── Subscription.ts │ │ │ ├── __tests__ │ │ │ │ ├── BehaviorSubject.test.js │ │ │ │ └── Subject.test.js │ │ │ └── index.ts │ │ └── transcode │ │ │ ├── audioProcessing.ts │ │ │ ├── index.ts │ │ │ └── wav.ts │ ├── Message │ │ ├── FixedHeightMessage.tsx │ │ ├── Message.tsx │ │ ├── MessageBlocked.tsx │ │ ├── MessageDeleted.tsx │ │ ├── MessageEditedTimestamp.tsx │ │ ├── MessageErrorText.tsx │ │ ├── MessageOptions.tsx │ │ ├── MessageRepliesCountButton.tsx │ │ ├── MessageSimple.tsx │ │ ├── MessageStatus.tsx │ │ ├── MessageText.tsx │ │ ├── MessageTimestamp.tsx │ │ ├── QuotedMessage.tsx │ │ ├── StreamedMessageText.tsx │ │ ├── Timestamp.tsx │ │ ├── __tests__ │ │ │ ├── FixedHeightMessage.test.js │ │ │ ├── Message.test.js │ │ │ ├── MessageDeleted.test.js │ │ │ ├── MessageOptions.test.js │ │ │ ├── MessageRepliesCountButton.test.js │ │ │ ├── MessageSimple.test.js │ │ │ ├── MessageStatus.test.js │ │ │ ├── MessageText.test.js │ │ │ ├── MessageTimestamp.test.js │ │ │ ├── QuotedMessage.test.js │ │ │ ├── __snapshots__ │ │ │ │ ├── MessageStatus.test.js.snap │ │ │ │ └── MessageText.test.js.snap │ │ │ └── utils.test.js │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ ├── useActionHandler.test.js │ │ │ │ ├── useDeleteHandler.test.js │ │ │ │ ├── useEditHandler.test.js │ │ │ │ ├── useFlagHandler.test.js │ │ │ │ ├── useMentionsHandler.test.js │ │ │ │ ├── useMuteHandler.test.js │ │ │ │ ├── useOpenThreadHandler.test.js │ │ │ │ ├── useReactionHandler.test.js │ │ │ │ ├── useReactionsFetcher.js │ │ │ │ ├── useRetryHandler.test.js │ │ │ │ ├── useUserHandler.test.js │ │ │ │ ├── useUserRole.test.js │ │ │ │ └── userMarkUnreadHandler.test.js │ │ │ ├── index.ts │ │ │ ├── useActionHandler.ts │ │ │ ├── useDeleteHandler.ts │ │ │ ├── useEditHandler.ts │ │ │ ├── useFlagHandler.ts │ │ │ ├── useMarkUnreadHandler.ts │ │ │ ├── useMentionsHandler.ts │ │ │ ├── useMessageTextStreaming.ts │ │ │ ├── useMuteHandler.ts │ │ │ ├── useOpenThreadHandler.ts │ │ │ ├── usePinHandler.ts │ │ │ ├── useReactionHandler.ts │ │ │ ├── useReactionsFetcher.ts │ │ │ ├── useRetryHandler.ts │ │ │ ├── useUserHandler.ts │ │ │ └── useUserRole.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── renderText │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── renderText.test.js.snap │ │ │ │ └── renderText.test.js │ │ │ ├── componentRenderers │ │ │ │ ├── Anchor.tsx │ │ │ │ ├── Emoji.tsx │ │ │ │ ├── Mention.tsx │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── regex.ts │ │ │ ├── rehypePlugins │ │ │ │ ├── emojiMarkdownPlugin.ts │ │ │ │ ├── index.ts │ │ │ │ └── mentionsMarkdownPlugin.ts │ │ │ ├── remarkPlugins │ │ │ │ ├── htmlToTextPlugin.ts │ │ │ │ ├── index.ts │ │ │ │ └── keepLineBreaksPlugin.ts │ │ │ ├── renderText.tsx │ │ │ └── types.ts │ │ ├── types.ts │ │ └── utils.tsx │ ├── MessageActions │ │ ├── CustomMessageActionsList.tsx │ │ ├── MessageActions.tsx │ │ ├── MessageActionsBox.tsx │ │ ├── __tests__ │ │ │ ├── CustomMessageActionsList.test.js │ │ │ ├── MessageActions.test.js │ │ │ └── MessageActionsBox.test.js │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useMessageActionsBoxPopper.ts │ │ └── index.ts │ ├── MessageBounce │ │ ├── MessageBounceModal.tsx │ │ ├── MessageBouncePrompt.tsx │ │ └── index.ts │ ├── MessageInput │ │ ├── AttachmentPreviewList │ │ │ ├── AttachmentPreviewList.tsx │ │ │ ├── FileAttachmentPreview.tsx │ │ │ ├── ImageAttachmentPreview.tsx │ │ │ ├── UnsupportedAttachmentPreview.tsx │ │ │ ├── VoiceRecordingPreview.tsx │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── AttachmentSelector.tsx │ │ ├── CooldownTimer.tsx │ │ ├── EditMessageForm.tsx │ │ ├── LinkPreviewList.tsx │ │ ├── MessageInput.tsx │ │ ├── MessageInputFlat.tsx │ │ ├── QuotedMessagePreview.tsx │ │ ├── SendButton.tsx │ │ ├── StopAIGenerationButton.tsx │ │ ├── WithDragAndDropUpload.tsx │ │ ├── __tests__ │ │ │ ├── AttachmentPreviewList.test.js │ │ │ ├── AttachmentSelector.test.js │ │ │ ├── CooldownTimer.test.js │ │ │ ├── EditMessageForm.test.js │ │ │ ├── LinkPreviewList.test.js │ │ │ ├── MessageInput.test.js │ │ │ ├── SendButton.test.js │ │ │ └── __snapshots__ │ │ │ │ ├── AttachmentPreviewList.test.js.snap │ │ │ │ └── LinkPreviewList.test.js.snap │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ └── useCooldownTimer.test.js │ │ │ ├── index.ts │ │ │ ├── useAttachmentManagerState.ts │ │ │ ├── useCanCreatePoll.ts │ │ │ ├── useCooldownTimer.tsx │ │ │ ├── useCreateMessageInputContext.ts │ │ │ ├── useMessageComposer.ts │ │ │ ├── useMessageComposerHasSendableData.ts │ │ │ ├── useMessageInputControls.ts │ │ │ ├── usePasteHandler.ts │ │ │ ├── useSubmitHandler.ts │ │ │ ├── useTextareaRef.ts │ │ │ ├── useTimer.ts │ │ │ └── utils.ts │ │ ├── icons.tsx │ │ └── index.ts │ ├── MessageList │ │ ├── ConnectionStatus.tsx │ │ ├── CustomNotification.tsx │ │ ├── GiphyPreviewMessage.tsx │ │ ├── MessageList.tsx │ │ ├── MessageListMainPanel.tsx │ │ ├── MessageListNotifications.tsx │ │ ├── MessageNotification.tsx │ │ ├── ScrollToBottomButton.tsx │ │ ├── UnreadMessagesNotification.tsx │ │ ├── UnreadMessagesSeparator.tsx │ │ ├── VirtualizedMessageList.tsx │ │ ├── VirtualizedMessageListComponents.tsx │ │ ├── __tests__ │ │ │ ├── ConnectionStatus.test.js │ │ │ ├── CustomNotification.test.js │ │ │ ├── MessageList.test.js │ │ │ ├── MessageNotification.test.js │ │ │ ├── ScrollToBottomButton.test.js │ │ │ ├── VirtualizedMessageList.test.js │ │ │ ├── VirtualizedMessageListComponents.test.js │ │ │ ├── __snapshots__ │ │ │ │ ├── VirtualizedMessageList.test.js.snap │ │ │ │ └── VirtualizedMessageListComponents.test.js.snap │ │ │ └── utils.test.js │ │ ├── hooks │ │ │ ├── MessageList │ │ │ │ ├── index.ts │ │ │ │ ├── useEnrichedMessages.ts │ │ │ │ ├── useMessageListElements.tsx │ │ │ │ ├── useMessageListScrollManager.ts │ │ │ │ ├── useScrollLocationLogic.tsx │ │ │ │ └── useUnreadMessagesNotification.ts │ │ │ ├── VirtualizedMessageList │ │ │ │ ├── index.ts │ │ │ │ ├── useGiphyPreview.ts │ │ │ │ ├── useMessageSetKey.ts │ │ │ │ ├── useNewMessageNotification.ts │ │ │ │ ├── usePrependMessagesCount.ts │ │ │ │ ├── useScrollToBottomOnNewMessage.ts │ │ │ │ ├── useShouldForceScrollToBottom.ts │ │ │ │ └── useUnreadMessagesNotificationVirtualized.ts │ │ │ ├── __tests__ │ │ │ │ ├── useGiphyPreview.test.js │ │ │ │ ├── useMarkRead.test.js │ │ │ │ ├── useMessageListScrollManager.test.js │ │ │ │ ├── usePrependMessagesCount.test.js │ │ │ │ └── useUnreadMessagesNotificationVirtualized.test.js │ │ │ ├── index.ts │ │ │ ├── useLastReadData.ts │ │ │ └── useMarkRead.ts │ │ ├── icons.tsx │ │ ├── index.ts │ │ ├── renderMessages.tsx │ │ └── utils.ts │ ├── Modal │ │ ├── Modal.tsx │ │ ├── ModalHeader.tsx │ │ ├── __tests__ │ │ │ └── Modal.test.js │ │ ├── icons.tsx │ │ └── index.ts │ ├── Poll │ │ ├── Poll.tsx │ │ ├── PollActions │ │ │ ├── AddCommentForm.tsx │ │ │ ├── EndPollDialog.tsx │ │ │ ├── PollAction.tsx │ │ │ ├── PollActions.tsx │ │ │ ├── PollAnswerList.tsx │ │ │ ├── PollOptionsFullList.tsx │ │ │ ├── PollResults │ │ │ │ ├── PollOptionVotesList.tsx │ │ │ │ ├── PollOptionWithLatestVotes.tsx │ │ │ │ ├── PollOptionWithVotesHeader.tsx │ │ │ │ ├── PollResults.tsx │ │ │ │ └── index.ts │ │ │ ├── SuggestPollOptionForm.tsx │ │ │ └── index.ts │ │ ├── PollContent.tsx │ │ ├── PollCreationDialog │ │ │ ├── MultipleAnswersField.tsx │ │ │ ├── NameField.tsx │ │ │ ├── OptionFieldSet.tsx │ │ │ ├── PollCreationDialog.tsx │ │ │ ├── PollCreationDialogControls.tsx │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── PollHeader.tsx │ │ ├── PollOptionList.tsx │ │ ├── PollOptionSelector.tsx │ │ ├── PollVote.tsx │ │ ├── QuotedPoll.tsx │ │ ├── __tests__ │ │ │ ├── AddCommentForm.test.js │ │ │ ├── Poll.test.js │ │ │ ├── PollActions.test.js │ │ │ ├── PollCreationDialog.test.js │ │ │ ├── PollHeader.test.js │ │ │ ├── PollOptionList.test.js │ │ │ └── SuggestPollOptionForm.test.js │ │ ├── constants.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useManagePollVotesRealtime.ts │ │ │ ├── usePollAnswerPagination.ts │ │ │ └── usePollOptionVotesPagination.ts │ │ └── index.ts │ ├── Portal │ │ └── Portal.ts │ ├── ReactFileUtilities │ │ ├── FileIcon │ │ │ ├── FileIcon.tsx │ │ │ ├── FileIconSet.tsx │ │ │ ├── iconMap.ts │ │ │ ├── index.ts │ │ │ └── mimeTypes.ts │ │ ├── LoadingIndicator.tsx │ │ ├── UploadButton.tsx │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── Reactions │ │ ├── ReactionSelector.tsx │ │ ├── ReactionSelectorWithButton.tsx │ │ ├── ReactionsList.tsx │ │ ├── ReactionsListModal.tsx │ │ ├── SimpleReactionsList.tsx │ │ ├── SpriteImage.tsx │ │ ├── StreamEmoji.tsx │ │ ├── __tests__ │ │ │ ├── ReactionSelector.test.js │ │ │ ├── ReactionsList.test.js │ │ │ ├── ReactionsListModal.test.js │ │ │ ├── SimpleReactionsList.test.js │ │ │ └── __snapshots__ │ │ │ │ └── SimpleReactionsList.test.js.snap │ │ ├── hooks │ │ │ ├── useFetchReactions.ts │ │ │ └── useProcessReactions.tsx │ │ ├── index.ts │ │ ├── reactionOptions.tsx │ │ ├── types.ts │ │ └── utils │ │ │ └── utils.ts │ ├── SafeAnchor │ │ ├── SafeAnchor.tsx │ │ ├── __tests__ │ │ │ └── SafeAnchor.test.js │ │ └── index.ts │ ├── TextareaComposer │ │ ├── SuggestionList │ │ │ ├── CommandItem.tsx │ │ │ ├── EmoticonItem.tsx │ │ │ ├── SuggestionList.tsx │ │ │ ├── SuggestionListItem.tsx │ │ │ ├── UserItem.tsx │ │ │ └── index.ts │ │ ├── TextareaComposer.tsx │ │ ├── __tests__ │ │ │ ├── CommandItem.test.js │ │ │ ├── EmoticonItem.test.js │ │ │ └── UserItem.test.js │ │ └── index.ts │ ├── Thread │ │ ├── Thread.tsx │ │ ├── ThreadHead.tsx │ │ ├── ThreadHeader.tsx │ │ ├── ThreadStart.tsx │ │ ├── __tests__ │ │ │ ├── Thread.test.js │ │ │ └── ThreadStart.test.js │ │ ├── icons.tsx │ │ └── index.ts │ ├── Threads │ │ ├── ThreadContext.tsx │ │ ├── ThreadList │ │ │ ├── ThreadList.tsx │ │ │ ├── ThreadListEmptyPlaceholder.tsx │ │ │ ├── ThreadListItem.tsx │ │ │ ├── ThreadListItemUI.tsx │ │ │ ├── ThreadListLoadingIndicator.tsx │ │ │ ├── ThreadListUnseenThreadsBanner.tsx │ │ │ └── index.ts │ │ ├── UnreadCountBadge.tsx │ │ ├── hooks │ │ │ ├── useThreadManagerState.ts │ │ │ └── useThreadState.ts │ │ ├── icons.tsx │ │ └── index.ts │ ├── Tooltip │ │ ├── Tooltip.tsx │ │ ├── __tests__ │ │ │ └── Tooltip.test.js │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useEnterLeaveHandlers.ts │ │ └── index.ts │ ├── TypingIndicator │ │ ├── TypingIndicator.tsx │ │ ├── __tests__ │ │ │ └── TypingIndicator.test.js │ │ └── index.ts │ ├── UtilityComponents │ │ ├── ErrorBoundary.tsx │ │ ├── NullComponent.tsx │ │ ├── index.ts │ │ └── useStableId.ts │ ├── Window │ │ ├── Window.tsx │ │ ├── __tests__ │ │ │ └── Window.test.js │ │ └── index.ts │ └── index.ts ├── constants │ ├── limits.ts │ └── messageTypes.ts ├── context │ ├── AttachmentSelectorContext.tsx │ ├── ChannelActionContext.tsx │ ├── ChannelListContext.tsx │ ├── ChannelStateContext.tsx │ ├── ChatContext.tsx │ ├── ComponentContext.tsx │ ├── DialogManagerContext.tsx │ ├── MessageBounceContext.tsx │ ├── MessageContext.tsx │ ├── MessageInputContext.tsx │ ├── MessageListContext.tsx │ ├── PollContext.tsx │ ├── TranslationContext.tsx │ ├── TypingContext.tsx │ ├── VirtualizedMessageListContext.tsx │ ├── WithComponents.tsx │ ├── index.ts │ └── utils │ │ └── getDisplayName.ts ├── experimental │ ├── MessageActions │ │ ├── MessageActions.tsx │ │ ├── defaults.tsx │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useBaseMessageActionSetFilter.ts │ │ │ └── useSplitMessageActionSet.ts │ │ └── index.ts │ ├── Search │ │ ├── Search.tsx │ │ ├── SearchBar │ │ │ ├── SearchBar.tsx │ │ │ └── index.ts │ │ ├── SearchContext.tsx │ │ ├── SearchResults │ │ │ ├── SearchResultItem.tsx │ │ │ ├── SearchResults.tsx │ │ │ ├── SearchResultsHeader.tsx │ │ │ ├── SearchResultsPresearch.tsx │ │ │ ├── SearchSourceResultList.tsx │ │ │ ├── SearchSourceResultListFooter.tsx │ │ │ ├── SearchSourceResults.tsx │ │ │ ├── SearchSourceResultsEmpty.tsx │ │ │ ├── SearchSourceResultsHeader.tsx │ │ │ ├── SearchSourceResultsLoadingIndicator.tsx │ │ │ └── index.ts │ │ ├── SearchSourceResultsContext.tsx │ │ ├── __tests__ │ │ │ ├── Search.test.js │ │ │ ├── SearchBar.test.js │ │ │ ├── SearchResultItem.test.js │ │ │ ├── SearchResults.test.js │ │ │ ├── SearchResultsHeader.test.js │ │ │ ├── SearchSourceResultList.test.js │ │ │ ├── SearchSourceResultListFooter.test.js │ │ │ └── SearchSourceResults.test.js │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useSearchFocusedMessage.ts │ │ │ └── useSearchQueriesInProgress.ts │ │ └── index.ts │ └── index.ts ├── i18n │ ├── Streami18n.ts │ ├── __tests__ │ │ ├── Streami18n.test.js │ │ └── utils.test.js │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── hi.json │ ├── index.ts │ ├── it.json │ ├── ja.json │ ├── ko.json │ ├── nl.json │ ├── pt.json │ ├── ru.json │ ├── tr.json │ ├── translations.ts │ ├── types.ts │ └── utils.ts ├── index.ts ├── mock-builders │ ├── __test__ │ │ └── translator.test.js │ ├── api │ │ ├── error.js │ │ ├── getOrCreateChannel.js │ │ ├── index.js │ │ ├── markRead.js │ │ ├── queryChannels.js │ │ ├── queryMembers.js │ │ ├── queryUsers.js │ │ ├── sendMessage.js │ │ ├── threadReplies.js │ │ └── utils.js │ ├── browser │ │ ├── AnalyserNode.js │ │ ├── AudioContext.js │ │ ├── EventEmitter.js │ │ ├── HTMLMediaElement.js │ │ ├── MediaRecorder.js │ │ ├── ResizeObserver.js │ │ ├── events │ │ │ └── dataavailable.js │ │ └── index.js │ ├── event │ │ ├── channelDeleted.js │ │ ├── channelHidden.js │ │ ├── channelTruncated.js │ │ ├── channelUpdated.js │ │ ├── channelVisible.js │ │ ├── connectionChanged.js │ │ ├── connectionRecovered.js │ │ ├── draftDeleted.ts │ │ ├── draftUpdated.ts │ │ ├── index.js │ │ ├── messageDeleted.js │ │ ├── messageNew.js │ │ ├── messageRead.js │ │ ├── messageUndeleted.js │ │ ├── messageUpdated.js │ │ ├── notificationAddedToChannel.js │ │ ├── notificationMarkRead.js │ │ ├── notificationMarkUnread.js │ │ ├── notificationMessageNew.js │ │ ├── notificationMutesUpdated.js │ │ ├── notificationRemovedFromChannel.js │ │ └── userUpdated.js │ ├── generator │ │ ├── attachment.js │ │ ├── channel.js │ │ ├── index.js │ │ ├── member.js │ │ ├── message.js │ │ ├── messageDraft.ts │ │ ├── poll.js │ │ ├── reaction.js │ │ └── user.js │ ├── index.js │ ├── translator.js │ └── utils.js ├── plugins │ ├── Emojis │ │ ├── EmojiPicker.tsx │ │ ├── icons.tsx │ │ ├── index.ts │ │ └── middleware │ │ │ ├── index.ts │ │ │ └── textComposerEmojiMiddleware.ts │ └── encoders │ │ └── mp3.ts ├── store │ ├── hooks │ │ ├── index.ts │ │ └── useStateStore.ts │ └── index.ts ├── stories │ ├── add-message.stories.tsx │ ├── attachment-sizing.stories.tsx │ ├── edit-message.stories.tsx │ ├── hello.stories.tsx │ ├── jump-to-message.stories.tsx │ ├── mark-read.stories.tsx │ ├── message-status-readby-tooltip.stories.tsx │ ├── navigate-long-message-lists.stories.tsx │ ├── pin-message.stories.tsx │ ├── toggle-message-actions.stories.tsx │ └── utils.tsx ├── types │ ├── defaultDataInterfaces.ts │ ├── index.ts │ └── types.ts └── utils │ ├── __tests__ │ └── getChannel.test.js │ ├── browsers.ts │ ├── deprecationWarning.ts │ ├── getChannel.ts │ ├── getWholeChar.ts │ ├── index.ts │ └── mergeDeep.ts ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | E2E_APP_KEY='my-stream-api-key' 2 | E2E_APP_SECRET='my-stream-api-secret' 3 | E2E_TEST_USER_1='test-user-1' 4 | E2E_TEST_USER_1_TOKEN='test-user-1-jwt' 5 | E2E_TEST_USER_2='test-user-2' 6 | E2E_TEST_USER_2_TOKEN='test-user-2-jwt' 7 | E2E_JUMP_TO_MESSAGE_CHANNEL='jump-to-message' 8 | E2E_ATTACHMENT_SIZING_CHANNEL='attachment-sizing' 9 | E2E_ADD_MESSAGE_CHANNEL='add-message' 10 | E2E_ADDITIONAL_CHANNELS="mr-channel-1,mr-channel-2,edit-message-channel,pin-message-channel" 11 | E2E_LONG_MESSAGE_LISTS_CHANNEL='navigate-long-message-lists' 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Describe a new feature 4 | title: '' 5 | labels: feature 6 | assignees: '' 7 | --- 8 | 9 | **Motivation** 10 | A clear and concise description of what the problem/opportunity is. Ex. I'm always frustrated when [...] 11 | 12 | **Proposed solution** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Acceptance Criteria** 16 | A set of predefined requirements that must be met to mark a user story complete. 17 | -------------------------------------------------------------------------------- /.github/actions/setup-node/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Sets up Node and installs dependencies 3 | 4 | inputs: 5 | node-version: 6 | description: 'Specify Node version' 7 | required: false 8 | default: '22' 9 | 10 | runs: 11 | using: 'composite' 12 | steps: 13 | - name: Setup Node 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: ${{ inputs.node-version }} 17 | 18 | - name: Set NODE_VERSION env 19 | shell: bash 20 | run: echo "NODE_VERSION=$(node --version)" >> $GITHUB_ENV 21 | 22 | - name: Cache dependencies 23 | uses: actions/cache@v4 24 | with: 25 | path: ./node_modules 26 | key: ${{ runner.os }}-${{ env.NODE_VERSION }}-modules-${{ hashFiles('./yarn.lock') }} 27 | 28 | - name: Install dependencies 29 | run: yarn install --frozen-lockfile 30 | shell: bash 31 | -------------------------------------------------------------------------------- /.github/auto-merge.yml: -------------------------------------------------------------------------------- 1 | minApprovals: 2 | NONE: 0 3 | requiredLabels: 4 | - stream-dependencies 5 | updateBranch: true 6 | mergeMethod: rebase 7 | deleteBranchAfterMerge: true 8 | requiredTitleRegex: stream-chat-css 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'daily' 7 | allow: 8 | - dependency-name: '@stream-io/stream-chat-css' 9 | - dependency-name: 'stream-chat' 10 | labels: 11 | - stream-dependencies 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | tsc: 7 | runs-on: ubuntu-latest 8 | name: TypeScript 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - uses: ./.github/actions/setup-node 13 | 14 | - name: 🧪 tsc 15 | run: yarn types 16 | 17 | test: 18 | runs-on: ubuntu-latest 19 | name: Lint & test with Node 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: ./.github/actions/setup-node 24 | 25 | - name: Build SDK 26 | run: yarn build 27 | 28 | - name: 🧪 Lint and test with Node ${{ env.NODE_VERSION }} 29 | env: 30 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 31 | run: | 32 | yarn lint 33 | yarn coverage 34 | yarn validate-translations 35 | 36 | - name: 🧪 Validate CommonJS bundle with Node ${{ env.NODE_VERSION }} 37 | run: yarn validate-cjs 38 | -------------------------------------------------------------------------------- /.github/workflows/pr-check.yml: -------------------------------------------------------------------------------- 1 | name: Check PR title 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited, synchronize, reopened] 6 | 7 | jobs: 8 | pr-title: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - uses: ./.github/actions/setup-node 14 | 15 | - name: commitlint 16 | run: echo "${{ github.event.pull_request.title }}" | npx commitlint --verbose 17 | -------------------------------------------------------------------------------- /.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: Compressed Size 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '**.test.js' 9 | - '**.md' 10 | env: 11 | NODE_OPTIONS: --max_old_space_size=4096 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: preactjs/compressed-size-action@v2 19 | with: 20 | repo-token: '${{ secrets.GITHUB_TOKEN }}' 21 | pattern: './dist/**/*.{js,cjs,css,json}' 22 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | set -e 5 | 6 | if ! yarn run lint-staged; then 7 | echo 8 | echo "Some files were not formatted correctly (see output above), commit aborted!" 9 | echo "Consider running \"yarn run fix-staged\" to attempt auto-fix." 10 | exit 1 11 | fi -------------------------------------------------------------------------------- /.ladle/components.tsx: -------------------------------------------------------------------------------- 1 | import type { GlobalProvider } from '@ladle/react'; 2 | import React from 'react'; 3 | 4 | import '@stream-io/stream-chat-css/dist/v2/css/index.css'; 5 | import './styles.css'; 6 | 7 | // https://ladle.dev/docs/providers 8 | // At the moment used to provide the styles from ./styles.css to all the stories 9 | export const Provider: GlobalProvider = ({ children }) => <>{children}; 10 | -------------------------------------------------------------------------------- /.ladle/config.mjs: -------------------------------------------------------------------------------- 1 | // https://www.ladle.dev/docs/config 2 | export default { 3 | envPrefix: 'E2E_', 4 | }; 5 | -------------------------------------------------------------------------------- /.ladle/styles.css: -------------------------------------------------------------------------------- 1 | .ladle-main { 2 | padding: 0 1rem 3rem; 3 | } 4 | 5 | .chat-wrapper { 6 | display: flex; 7 | flex-direction: row; 8 | } 9 | 10 | .str-chat { 11 | height: 700px !important; 12 | } 13 | .str-chat-channel { 14 | flex-grow: 1; 15 | } 16 | 17 | .str-chat-channel-list { 18 | overflow: auto; 19 | } 20 | -------------------------------------------------------------------------------- /.lintstagedrc.fix.json: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.{js,ts,tsx,md}": "eslint --fix", 3 | "**/*.{js,mjs,ts,mts,jsx,tsx,md,json,yml}": "prettier --write" 4 | } 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.{js,ts,tsx,md}": "eslint --max-warnings 0 --no-warn-ignored", 3 | "**/*.{js,mjs,ts,mts,jsx,tsx,md,json,yml}": "prettier --list-different", 4 | "src/i18n/*.json": "yarn run validate-translations" 5 | } 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | CHANGELOG.md 3 | examples/**/serviceWorker.ts 4 | ./dist/ 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "jsxSingleQuote": true, 4 | "printWidth": 90, 5 | "singleQuote": true, 6 | "tabWidth": 2, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Submit a pull request 2 | 3 | ### 🎯 Goal 4 | 5 | _Describe why we are making this change_ 6 | 7 | ### 🛠 Implementation details 8 | 9 | _Provide a description of the implementation_ 10 | 11 | ### 🎨 UI Changes 12 | 13 | _Add relevant screenshots_ 14 | -------------------------------------------------------------------------------- /assetsTransformer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | process(src, filename) { 6 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /axe-helper.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | const { configureAxe } = require('jest-axe'); 3 | 4 | module.exports.axe = configureAxe(); 5 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | module.exports = { 3 | env: { 4 | production: { 5 | presets: [ 6 | [ 7 | '@babel/env', 8 | { 9 | modules: false, 10 | }, 11 | ], 12 | ], 13 | }, 14 | test: { 15 | plugins: ['transform-es2015-modules-commonjs'], 16 | presets: [ 17 | [ 18 | '@babel/preset-env', 19 | { 20 | modules: 'commonjs', 21 | }, 22 | ], 23 | ], 24 | }, 25 | }, 26 | ignore: ['src/@types/*'], 27 | plugins: [ 28 | '@babel/plugin-proposal-class-properties', 29 | '@babel/plugin-transform-runtime', 30 | 'babel-plugin-dynamic-import-node', 31 | ], 32 | presets: ['@babel/preset-typescript', '@babel/env', '@babel/preset-react'], 33 | }; 34 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 70% # the required coverage value 6 | threshold: 10% # the leniency in hitting the target 7 | patch: 8 | default: 9 | target: 10% 10 | threshold: 10% 11 | -------------------------------------------------------------------------------- /commitlint.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | [ 8 | 'build', 9 | 'chore', 10 | 'ci', 11 | 'docs', 12 | 'feat', 13 | 'fix', 14 | 'perf', 15 | 'refactor', 16 | 'revert', 17 | 'style', 18 | 'test', 19 | 'deprecate', 20 | ], 21 | ], 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /developers/BRANCHES.md: -------------------------------------------------------------------------------- 1 | # Branches 2 | 3 | We have one (base) branch, `master`. If you want to implement a solution, you are required to create branches from `master` or its derivatives. The `master` branch serves for release and should receive updates only through squash-merging feature branches - each of these squashed-commits should be up to date with `master` branch and should be passing CI requirements. 4 | 5 | The branch architecture in this repository is as follows: 6 | 7 | ```shell 8 | NPM <--- master <--- branch_solution_x 9 | <--- branch_solution_y 10 | <--- branch_solution_x 11 | ... 12 | ``` 13 | -------------------------------------------------------------------------------- /developers/DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | # Documenting the SDK 2 | 3 | If you are introducing changes impacting the API or behavior of the SDK parts, you should document these, see [GetStream/docs](https://github.com/GetStream/docs) repository for more information. 4 | -------------------------------------------------------------------------------- /e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc.json", 3 | "rules": { 4 | "jest/require-top-level-describe": "off", 5 | "jest/no-done-callback": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-firefox-linux.png -------------------------------------------------------------------------------- /e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-linux.png -------------------------------------------------------------------------------- /e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-webkit-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-not-if-I-receive-a-message-1-webkit-linux.png -------------------------------------------------------------------------------- /e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-only-if-I-send-a-message-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-only-if-I-send-a-message-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-only-if-I-send-a-message-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-only-if-I-send-a-message-1-firefox-linux.png -------------------------------------------------------------------------------- /e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-only-if-I-send-a-message-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-only-if-I-send-a-message-1-linux.png -------------------------------------------------------------------------------- /e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-only-if-I-send-a-message-1-webkit-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/e2e/navigate-long-message-lists.test.ts-snapshots/thread-autoscroll-on-new-message-only-if-I-send-a-message-1-webkit-linux.png -------------------------------------------------------------------------------- /e2e/scripts/run_e2e.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | for browser in "webkit" "chromium" "firefox"; do 3 | yarn e2e-fixtures && yarn e2e --browser $browser $@ 4 | done 5 | -------------------------------------------------------------------------------- /e2e/scripts/run_in_container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # generate snapshots in a container to match the CI env 3 | 4 | docker run --rm --network host -v $(pwd):/work/ -w /work/ mcr.microsoft.com/playwright:v1.22.0-focal /bin/bash e2e/scripts/run_e2e.sh $@ 5 | -------------------------------------------------------------------------------- /e2e/user/User.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from '@playwright/test'; 2 | 3 | export type TestingUser = ReturnType; 4 | 5 | type F = ( 6 | page: Page, 7 | ) => Partial>; 8 | 9 | export function makeUser(page: Page) { 10 | return { 11 | clicks(f: T): ReturnType['click'] { 12 | return f(page).click; 13 | }, 14 | get(f: T): ReturnType['get'] { 15 | return f(page).get; 16 | }, 17 | sees(f: T): ReturnType['see'] { 18 | return f(page).see; 19 | }, 20 | submits(f: T): ReturnType['submit'] { 21 | return f(page).submit; 22 | }, 23 | typesTo(f: T): ReturnType['typeTo'] { 24 | return f(page).typeTo; 25 | }, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /e2e/user/components/AutocompleteSuggestionList.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from '@playwright/test'; 2 | 3 | export const getAutocompleteSuggestionItem = (page: Page, num: number) => 4 | page.locator( 5 | `.str-chat__suggestion-list-item >> :nth-match(span.str-chat__user-item--name, ${num})`, 6 | ); 7 | 8 | export default (page: Page) => ({ 9 | click: { 10 | async nth(num: number) { 11 | const item = await getAutocompleteSuggestionItem(page, num); 12 | item.click(); 13 | return item; 14 | }, 15 | }, 16 | see: {}, 17 | }); 18 | -------------------------------------------------------------------------------- /e2e/user/components/EditMessageForm/EditMessageForm.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | 3 | import selectors from '../../selectors'; 4 | 5 | export function getEditMessageForm(page: Page) { 6 | return page.locator(selectors.modalOpen); 7 | } 8 | 9 | export default (page: Page) => ({ 10 | click: { 11 | cancel() { 12 | return page.click(selectors.buttonCancel); 13 | }, 14 | removeAttachment(index: number) { 15 | // used keyboard press instead of a mouseclick as mouseclick causes unwanted Modal cancelation 16 | // which is a bug that should be addressed 17 | return page.locator(selectors.buttonCancelUpload).nth(index).press('Enter'); 18 | }, 19 | send() { 20 | return page.click(selectors.buttonSend); 21 | }, 22 | }, 23 | get: () => getEditMessageForm(page), 24 | }); 25 | -------------------------------------------------------------------------------- /e2e/user/components/Message/QuotedMessage.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from '@playwright/test'; 2 | 3 | import selectors from '../../selectors'; 4 | 5 | export function getQuotedMessage(page: Page, text: string) { 6 | return page.locator(`${selectors.quotedMessage} :text("${text}")`); 7 | } 8 | 9 | export default (page: Page) => ({ 10 | click: { 11 | //"nth-match" engine expects a one-based index as the last argument 12 | nth(text: string, index = 1) { 13 | return page.click( 14 | `${selectors.quotedMessage} :nth-match(:text("${text}"),${index})`, 15 | { 16 | force: true, // onClickCapture registered on the quoted message intercepts pointer events 17 | }, 18 | ); 19 | }, 20 | }, 21 | get: (text: string) => getQuotedMessage(page, text), 22 | }); 23 | -------------------------------------------------------------------------------- /e2e/user/components/MessageActions/MessageActions.ts: -------------------------------------------------------------------------------- 1 | import { getMessage } from '../Message/MessageSimple'; 2 | import selectors from '../../selectors'; 3 | import type { Page } from '@playwright/test'; 4 | 5 | export default (page: Page) => ({ 6 | click: { 7 | async reply(targetMessageText: string, nthMessageWithText?: number) { 8 | const message = getMessage(page, targetMessageText, nthMessageWithText); 9 | await message.hover(); 10 | await message.locator(selectors.buttonOpenThread).click(); 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /e2e/user/components/MessageInput.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from '@playwright/test'; 2 | 3 | import selectors from '../selectors'; 4 | 5 | export function getMessageInput(page: Page) { 6 | return page.locator(selectors.messageInput); 7 | } 8 | 9 | export default (page: Page) => ({ 10 | see: {}, 11 | submit: { 12 | async message(text: string) { 13 | await page.fill(selectors.messageInput, text); 14 | return page.keyboard.press('Enter'); 15 | }, 16 | async reply(text: string) { 17 | await page.fill(selectors.messageInputTextareaThread, text); 18 | return page.keyboard.press('Enter'); 19 | }, 20 | }, 21 | typeTo: { 22 | text(text: string) { 23 | return page.fill(selectors.messageInput, text); 24 | }, 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /e2e/user/components/MessageList/MessageNotification.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from '@playwright/test'; 2 | 3 | import selectors from '../../selectors'; 4 | 5 | export function getMessageNotificationSelector(text: string) { 6 | return `${selectors.messageNotification} >> text="${text}"`; 7 | } 8 | 9 | function getMessageNotification(page: Page, text: string) { 10 | return page.locator(getMessageNotificationSelector(text)); 11 | } 12 | 13 | export default (page: Page) => ({ 14 | click: { 15 | text(text: string) { 16 | return page.click(getMessageNotificationSelector(text)); 17 | }, 18 | }, 19 | get: (text: string) => getMessageNotification(page, text), 20 | }); 21 | -------------------------------------------------------------------------------- /e2e/user/test.ts: -------------------------------------------------------------------------------- 1 | import { test as _test } from '@playwright/test'; 2 | import { Controller } from './Controller'; 3 | import { makeUser, TestingUser } from './User'; 4 | 5 | export type CustomTestContext = { 6 | controller: Controller; 7 | user: TestingUser; 8 | }; 9 | 10 | export const test = _test.extend({ 11 | controller: async ({ baseURL, page }, use) => { 12 | await use(new Controller(baseURL, page)); 13 | }, 14 | user: async ({ page }, use) => { 15 | await use(makeUser(page)); 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /examples/capacitor/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/.npmkeep 3 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_1_8 6 | targetCompatibility JavaVersion.VERSION_1_8 7 | } 8 | } 9 | 10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" 11 | dependencies { 12 | 13 | 14 | } 15 | 16 | 17 | if (hasProperty('postBuildExtras')) { 18 | postBuildExtras() 19 | } 20 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import android.content.Context; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import androidx.test.platform.app.InstrumentationRegistry; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | 24 | assertEquals("com.getcapacitor.app", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/assets/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "capacitor.test", 3 | "appName": "capacitor", 4 | "webDir": "build", 5 | "bundledWebRuntime": false 6 | } 7 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/assets/capacitor.plugins.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/java/capacitor/test/MainActivity.java: -------------------------------------------------------------------------------- 1 | package capacitor.test; 2 | 3 | import com.getcapacitor.BridgeActivity; 4 | 5 | public class MainActivity extends BridgeActivity {} 6 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-land-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-land-hdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-land-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-land-mdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-land-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-land-xhdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-land-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-land-xxhdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-land-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-land-xxxhdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-port-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-port-hdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-port-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-port-mdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-port-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-port-xhdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-port-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-port-xxhdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable-port-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable-port-xxxhdpi/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | capacitor 4 | capacitor 5 | capacitor.test 6 | capacitor.test 7 | 8 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 17 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/xml/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/capacitor/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/capacitor/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:7.0.2' 11 | classpath 'com.google.gms:google-services:4.3.5' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | apply from: "variables.gradle" 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | jcenter() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /examples/capacitor/android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') 4 | -------------------------------------------------------------------------------- /examples/capacitor/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/capacitor/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /examples/capacitor/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' -------------------------------------------------------------------------------- /examples/capacitor/android/variables.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 21 3 | compileSdkVersion = 30 4 | targetSdkVersion = 30 5 | androidxActivityVersion = '1.2.0' 6 | androidxAppCompatVersion = '1.2.0' 7 | androidxCoordinatorLayoutVersion = '1.1.0' 8 | androidxCoreVersion = '1.3.2' 9 | androidxFragmentVersion = '1.3.0' 10 | junitVersion = '4.13.1' 11 | androidxJunitVersion = '1.1.2' 12 | androidxEspressoCoreVersion = '3.3.0' 13 | cordovaAndroidVersion = '7.0.0' 14 | } -------------------------------------------------------------------------------- /examples/capacitor/capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | const config: CapacitorConfig = { 4 | appId: 'capacitor.test', 5 | appName: 'capacitor', 6 | webDir: 'build', 7 | bundledWebRuntime: false, 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /examples/capacitor/ios/.gitignore: -------------------------------------------------------------------------------- 1 | App/build 2 | App/Pods 3 | App/Podfile.lock 4 | App/App/public 5 | DerivedData 6 | xcuserdata 7 | 8 | # Cordova plugins for Capacitor 9 | capacitor-cordova-ios-plugins 10 | -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "splash-2732x2732-2.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "filename": "splash-2732x2732-1.png", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "universal", 15 | "filename": "splash-2732x2732.png", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "version": 1, 21 | "author": "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "capacitor.test", 3 | "appName": "capacitor", 4 | "webDir": "build", 5 | "bundledWebRuntime": false 6 | } 7 | -------------------------------------------------------------------------------- /examples/capacitor/ios/App/App/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/capacitor/ios/App/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '12.0' 2 | use_frameworks! 3 | 4 | # workaround to avoid Xcode caching of Pods that requires 5 | # Product -> Clean Build Folder after new Cordova plugins installed 6 | # Requires CocoaPods 1.6 or newer 7 | install! 'cocoapods', :disable_input_output_paths => true 8 | 9 | def capacitor_pods 10 | pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' 11 | pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' 12 | 13 | end 14 | 15 | target 'App' do 16 | capacitor_pods 17 | # Add your Pods here 18 | end 19 | -------------------------------------------------------------------------------- /examples/capacitor/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/public/favicon.ico -------------------------------------------------------------------------------- /examples/capacitor/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/public/logo192.png -------------------------------------------------------------------------------- /examples/capacitor/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/capacitor/public/logo512.png -------------------------------------------------------------------------------- /examples/capacitor/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/capacitor/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/capacitor/src/App.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | height: 100%; 6 | } 7 | 8 | #root { 9 | height: 100%; 10 | } 11 | 12 | .str-chat-channel-list { 13 | height: 100%; 14 | } 15 | 16 | .str-chat-channel { 17 | height: 100%; 18 | } 19 | -------------------------------------------------------------------------------- /examples/capacitor/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/capacitor/src/components/ChannelInner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | ChannelHeader, 5 | MessageList, 6 | MessageInput, 7 | Thread, 8 | Window, 9 | } from 'stream-chat-react'; 10 | 11 | export const ChannelInner = () => { 12 | return ( 13 | <> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /examples/capacitor/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/capacitor/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | , 11 | ); 12 | 13 | // If you want your app to work offline and load faster, you can change 14 | // unregister() to register() below. Note this comes with some pitfalls. 15 | // Learn more about service workers: https://bit.ly/CRA-PWA 16 | serviceWorker.unregister(); 17 | -------------------------------------------------------------------------------- /examples/capacitor/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/capacitor/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /examples/capacitor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react" 17 | }, 18 | "include": ["src"] 19 | } 20 | -------------------------------------------------------------------------------- /examples/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | -------------------------------------------------------------------------------- /examples/nextjs/.nvmrc: -------------------------------------------------------------------------------- 1 | lts/gallium 2 | -------------------------------------------------------------------------------- /examples/nextjs/README.md: -------------------------------------------------------------------------------- 1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn). 2 | -------------------------------------------------------------------------------- /examples/nextjs/next.config.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/nextjs/next.config.js -------------------------------------------------------------------------------- /examples/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@stream-io/stream-chat-css": "link:../../node_modules/@stream-io/stream-chat-css", 12 | "next": "^12.0.9", 13 | "react": "link:../../node_modules/react", 14 | "react-app-polyfill": "^1.0.2", 15 | "react-dom": "link:../../node_modules/react-dom", 16 | "react-scripts": "2.1.4", 17 | "stream-chat": "link:../../node_modules/stream-chat", 18 | "stream-chat-react": "link:../../" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/nextjs/pages/App.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | height: 100%; 6 | } 7 | 8 | #root { 9 | height: 100%; 10 | } 11 | 12 | .str-chat-channel-list { 13 | overflow: scroll; 14 | } 15 | -------------------------------------------------------------------------------- /examples/nextjs/pages/_app.jsx: -------------------------------------------------------------------------------- 1 | // pages/_app.js 2 | import '@stream-io/stream-chat-css/dist/css/index.css'; 3 | import './App.css'; 4 | 5 | export default function MyApp({ Component, pageProps }) { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /examples/nextjs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/nextjs/public/favicon.ico -------------------------------------------------------------------------------- /examples/tutorial/.env.example: -------------------------------------------------------------------------------- 1 | VITE_API_KEY=REPLACE_WITH_API_KEY 2 | VITE_USER_ID=REPLACE_WITH_USER_ID 3 | VITE_USER_NAME=REPLACE_WITH_USER_NAME 4 | VITE_USER_TOKEN=REPLACE_WITH_USER_TOKEN 5 | -------------------------------------------------------------------------------- /examples/tutorial/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | !env.example 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | pnpm-debug.log* 11 | lerna-debug.log* 12 | 13 | node_modules 14 | dist 15 | dist-ssr 16 | *.local 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | -------------------------------------------------------------------------------- /examples/tutorial/README.md: -------------------------------------------------------------------------------- 1 | This folder contains the source code for [Chat React tutorial](https://github.com/GetStream/getstream.io-tutorials/blob/main/chat/tutorials/react-tutorial.mdx). It contains multiple versions of apps representing the tutorial steps. 2 | 3 | ## Setup 4 | 5 | 1. Copy create a `.env` file next to the `.env.example` file. 6 | 2. Copy the contents of `.env.example` file into `.env` file and populate the credentials 7 | 8 | ## Run individual app examples 9 | 10 | ```shell 11 | yarn dev:client-setup 12 | yarn dev:client-setup 13 | yarn dev:core-component-setup 14 | yarn dev:channel-list 15 | yarn dev:custom-ui-components 16 | yarn dev:custom-attachment-type 17 | yarn dev:emoji-picker 18 | yarn dev:livestream 19 | ``` 20 | -------------------------------------------------------------------------------- /examples/tutorial/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import globals from 'globals'; 3 | import reactHooks from 'eslint-plugin-react-hooks'; 4 | import reactRefresh from 'eslint-plugin-react-refresh'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], 23 | }, 24 | }, 25 | ); 26 | -------------------------------------------------------------------------------- /examples/tutorial/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/tutorial/src/1-client-setup/App.tsx: -------------------------------------------------------------------------------- 1 | import { Chat, useCreateChatClient } from 'stream-chat-react'; 2 | 3 | // your Stream app information 4 | const apiKey = 'REPLACE_WITH_API_KEY'; 5 | const userId = 'REPLACE_WITH_USER_ID'; 6 | const userName = 'REPLACE_WITH_USER_NAME'; 7 | const userToken = 'REPLACE_WITH_USER_TOKEN'; 8 | 9 | const App = () => { 10 | const client = useCreateChatClient({ 11 | apiKey, 12 | tokenOrProvider: userToken, 13 | userData: { id: userId, name: userName }, 14 | }); 15 | 16 | if (!client) return
Setting up client & connection...
; 17 | 18 | return Chat with client is ready!; 19 | }; 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /examples/tutorial/src/1-client-setup/credentials.ts: -------------------------------------------------------------------------------- 1 | // your Stream app information 2 | export const apiKey = import.meta.env.VITE_API_KEY; 3 | export const userId = import.meta.env.VITE_USER_ID; 4 | export const userName = import.meta.env.VITE_USER_NAME; 5 | export const userToken = import.meta.env.VITE_USER_TOKEN; 6 | -------------------------------------------------------------------------------- /examples/tutorial/src/1-client-setup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/tutorial/src/1-client-setup/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /examples/tutorial/src/2-core-component-setup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/tutorial/src/2-core-component-setup/layout.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root { 4 | height: 100%; 5 | } 6 | body { 7 | margin: 0; 8 | } 9 | #root { 10 | display: flex; 11 | } 12 | 13 | .str-chat__channel-list { 14 | width: 30%; 15 | } 16 | .str-chat__channel { 17 | width: 100%; 18 | } 19 | .str-chat__thread { 20 | width: 45%; 21 | } -------------------------------------------------------------------------------- /examples/tutorial/src/2-core-component-setup/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /examples/tutorial/src/2-core-component-setup/stream-chat.d.ts: -------------------------------------------------------------------------------- 1 | import { DefaultChannelData } from 'stream-chat-react'; 2 | 3 | declare module 'stream-chat' { 4 | interface CustomChannelData extends DefaultChannelData { 5 | image?: string; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/tutorial/src/3-channel-list/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/tutorial/src/3-channel-list/layout.css: -------------------------------------------------------------------------------- 1 | @layer base, theme; 2 | @import 'stream-chat-react/dist/css/v2/index.css' layer(base); 3 | 4 | @layer theme { 5 | .str-chat__theme-custom { 6 | --str-chat__primary-color: #009688; 7 | --str-chat__active-primary-color: #004d40; 8 | --str-chat__surface-color: #f5f5f5; 9 | --str-chat__secondary-surface-color: #fafafa; 10 | --str-chat__primary-surface-color: #e0f2f1; 11 | --str-chat__primary-surface-color-low-emphasis: #edf7f7; 12 | --str-chat__border-radius-circle: 6px; 13 | } 14 | } 15 | 16 | html, 17 | body, 18 | #root { 19 | height: 100%; 20 | } 21 | body { 22 | margin: 0; 23 | } 24 | #root { 25 | display: flex; 26 | } 27 | 28 | .str-chat__channel-list { 29 | width: 30%; 30 | } 31 | .str-chat__channel { 32 | width: 100%; 33 | } 34 | .str-chat__thread { 35 | width: 45%; 36 | } -------------------------------------------------------------------------------- /examples/tutorial/src/3-channel-list/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /examples/tutorial/src/4-custom-ui-components/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/tutorial/src/4-custom-ui-components/layout.css: -------------------------------------------------------------------------------- 1 | @layer base, theme; 2 | @import 'stream-chat-react/dist/css/v2/index.css' layer(base); 3 | 4 | @layer theme { 5 | .str-chat__theme-custom { 6 | --str-chat__primary-color: #009688; 7 | --str-chat__active-primary-color: #004d40; 8 | --str-chat__surface-color: #f5f5f5; 9 | --str-chat__secondary-surface-color: #fafafa; 10 | --str-chat__primary-surface-color: #e0f2f1; 11 | --str-chat__primary-surface-color-low-emphasis: #edf7f7; 12 | --str-chat__border-radius-circle: 6px; 13 | } 14 | } 15 | 16 | html, 17 | body, 18 | #root { 19 | height: 100%; 20 | } 21 | body { 22 | margin: 0; 23 | } 24 | #root { 25 | display: flex; 26 | } 27 | 28 | .str-chat__channel-list { 29 | width: 30%; 30 | } 31 | .str-chat__channel { 32 | width: 100%; 33 | } 34 | .str-chat__thread { 35 | width: 45%; 36 | } -------------------------------------------------------------------------------- /examples/tutorial/src/4-custom-ui-components/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /examples/tutorial/src/5-custom-attachment-type/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/tutorial/src/5-custom-attachment-type/layout.css: -------------------------------------------------------------------------------- 1 | @layer base, theme; 2 | @import 'stream-chat-react/dist/css/v2/index.css' layer(base); 3 | 4 | @layer theme { 5 | .str-chat__theme-custom { 6 | --str-chat__primary-color: #009688; 7 | --str-chat__active-primary-color: #004d40; 8 | --str-chat__surface-color: #f5f5f5; 9 | --str-chat__secondary-surface-color: #fafafa; 10 | --str-chat__primary-surface-color: #e0f2f1; 11 | --str-chat__primary-surface-color-low-emphasis: #edf7f7; 12 | --str-chat__border-radius-circle: 6px; 13 | } 14 | } 15 | 16 | html, 17 | body, 18 | #root { 19 | height: 100%; 20 | } 21 | body { 22 | margin: 0; 23 | } 24 | #root { 25 | display: flex; 26 | } 27 | 28 | .str-chat__channel-list { 29 | width: 30%; 30 | } 31 | .str-chat__channel { 32 | width: 100%; 33 | } 34 | .str-chat__thread { 35 | width: 45%; 36 | } -------------------------------------------------------------------------------- /examples/tutorial/src/5-custom-attachment-type/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /examples/tutorial/src/5-custom-attachment-type/stream-chat.d.ts: -------------------------------------------------------------------------------- 1 | import { DefaultAttachmentData, DefaultChannelData } from 'stream-chat-react'; 2 | 3 | declare module 'stream-chat' { 4 | interface CustomAttachmentData extends DefaultAttachmentData { 5 | image?: string; 6 | name?: string; 7 | url?: string; 8 | } 9 | 10 | interface CustomChannelData extends DefaultChannelData { 11 | image?: string; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/tutorial/src/6-emoji-picker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/tutorial/src/6-emoji-picker/layout.css: -------------------------------------------------------------------------------- 1 | @layer base, theme; 2 | @import 'stream-chat-react/dist/css/v2/index.css' layer(base); 3 | 4 | @layer theme { 5 | .str-chat__theme-custom { 6 | --str-chat__primary-color: #009688; 7 | --str-chat__active-primary-color: #004d40; 8 | --str-chat__surface-color: #f5f5f5; 9 | --str-chat__secondary-surface-color: #fafafa; 10 | --str-chat__primary-surface-color: #e0f2f1; 11 | --str-chat__primary-surface-color-low-emphasis: #edf7f7; 12 | --str-chat__border-radius-circle: 6px; 13 | } 14 | } 15 | 16 | html, 17 | body, 18 | #root { 19 | height: 100%; 20 | } 21 | body { 22 | margin: 0; 23 | } 24 | #root { 25 | display: flex; 26 | } 27 | 28 | .str-chat__channel-list { 29 | width: 30%; 30 | } 31 | .str-chat__channel { 32 | width: 100%; 33 | } 34 | .str-chat__thread { 35 | width: 45%; 36 | } -------------------------------------------------------------------------------- /examples/tutorial/src/6-emoji-picker/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /examples/tutorial/src/7-livestream/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/tutorial/src/7-livestream/layout.css: -------------------------------------------------------------------------------- 1 | @layer base, theme; 2 | @import 'stream-chat-react/dist/css/v2/index.css' layer(base); 3 | 4 | @layer theme { 5 | .str-chat__theme-custom { 6 | --str-chat__primary-color: #009688; 7 | --str-chat__active-primary-color: #004d40; 8 | --str-chat__surface-color: #f5f5f5; 9 | --str-chat__secondary-surface-color: #fafafa; 10 | --str-chat__primary-surface-color: #e0f2f1; 11 | --str-chat__primary-surface-color-low-emphasis: #edf7f7; 12 | --str-chat__border-radius-circle: 6px; 13 | } 14 | } 15 | 16 | html, 17 | body, 18 | #root { 19 | height: 100%; 20 | } 21 | body { 22 | margin: 0; 23 | } 24 | #root { 25 | display: flex; 26 | } 27 | 28 | .str-chat__channel-list { 29 | width: 30%; 30 | } 31 | .str-chat__channel { 32 | width: 100%; 33 | } 34 | .str-chat__thread { 35 | width: 45%; 36 | } -------------------------------------------------------------------------------- /examples/tutorial/src/7-livestream/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /examples/tutorial/src/App.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/tutorial/src/App.tsx -------------------------------------------------------------------------------- /examples/tutorial/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | createRoot(document.getElementById('root')!).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /examples/tutorial/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/tutorial/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "verbatimModuleSyntax": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "erasableSyntaxOnly": true, 23 | "noFallthroughCasesInSwitch": true, 24 | "noUncheckedSideEffectImports": true 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /examples/tutorial/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] 4 | } 5 | -------------------------------------------------------------------------------- /examples/tutorial/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "verbatimModuleSyntax": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "erasableSyntaxOnly": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUncheckedSideEffectImports": true 23 | }, 24 | "include": ["vite.config.ts"] 25 | } 26 | -------------------------------------------------------------------------------- /examples/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/typescript/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/typescript/public/favicon.ico -------------------------------------------------------------------------------- /examples/typescript/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/typescript/public/logo192.png -------------------------------------------------------------------------------- /examples/typescript/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-chat-react/e7708fdae001b7b1a47c9e3fc2ce560f2412455c/examples/typescript/public/logo512.png -------------------------------------------------------------------------------- /examples/typescript/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/typescript/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/typescript/src/components/ChannelInner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | ChannelHeader, 5 | MessageList, 6 | MessageInput, 7 | Thread, 8 | Window, 9 | } from 'stream-chat-react'; 10 | 11 | export const ChannelInner = () => { 12 | return ( 13 | <> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /examples/typescript/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import 'stream-chat-react/dist/css/v2/index.css'; 4 | import './index.scss'; 5 | import App from './App'; 6 | import * as serviceWorker from './serviceWorker'; 7 | 8 | createRoot(document.getElementById('root')!).render( 9 | 10 | 11 | , 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /examples/typescript/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/typescript/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /examples/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react" 17 | }, 18 | "include": ["src"] 19 | } 20 | -------------------------------------------------------------------------------- /examples/vite/.env.example: -------------------------------------------------------------------------------- 1 | VITE_STREAM_KEY="" 2 | VITE_USER_TOKEN="" 3 | VITE_USER_ID="" -------------------------------------------------------------------------------- /examples/vite/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .vercel 26 | -------------------------------------------------------------------------------- /examples/vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "link:../../node_modules/react", 13 | "react-dom": "link:../../node_modules/react-dom", 14 | "stream-chat-react": "link:../../" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "link:../../node_modules/@types/react", 18 | "@types/react-dom": "link:../../node_modules/@types/react-dom", 19 | "@typescript-eslint/eslint-plugin": "^7.2.0", 20 | "@typescript-eslint/parser": "^7.2.0", 21 | "@vitejs/plugin-react-swc": "^3.5.0", 22 | "eslint": "^8.57.0", 23 | "eslint-plugin-react-hooks": "^4.6.0", 24 | "eslint-plugin-react-refresh": "^0.4.6", 25 | "sass": "^1.75.0", 26 | "typescript": "^5.4.5", 27 | "vite": "^5.2.10" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | import './index.scss'; 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ); 11 | -------------------------------------------------------------------------------- /examples/vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react-jsx", 14 | "allowImportingTsExtensions": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "noUnusedLocals": false, 17 | "noUnusedParameters": false 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /examples/vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /examples/vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite'; 2 | import react from '@vitejs/plugin-react-swc'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig(() => { 6 | const rootDir = process.cwd(); 7 | 8 | // Load shared .env file 9 | const env = loadEnv('', rootDir, ''); 10 | return { 11 | plugins: [react()], 12 | define: { 13 | 'process.env': env, // need `process.env` access 14 | }, 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /examples/website-demos/README.md: -------------------------------------------------------------------------------- 1 | The demos have a new home. Check out the [website-react-examples](https://github.com/GetStream/website-react-examples) repository for the latest React sample apps. 2 | -------------------------------------------------------------------------------- /examples/website-demos/social-messaging/README.md: -------------------------------------------------------------------------------- 1 | This demo [has a new home](https://github.com/GetStream/website-react-examples/tree/master/social-messenger-ts). 2 | 3 | Check out the [website-react-examples](https://github.com/GetStream/website-react-examples) repository for the latest React sample apps. 4 | -------------------------------------------------------------------------------- /examples/website-demos/virtual-event/README.md: -------------------------------------------------------------------------------- 1 | This demo [has a new home](https://github.com/GetStream/website-react-examples/tree/master/virtual-event). 2 | 3 | Check out the [website-react-examples](https://github.com/GetStream/website-react-examples) repository for the latest React sample apps. 4 | -------------------------------------------------------------------------------- /i18next-parser.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // https://github.com/i18next/i18next-parser#options 3 | module.exports = { 4 | createOldCatalogs: false, 5 | input: ['./src/**/*.{tsx,ts}'], 6 | keepRemoved: true, 7 | keySeparator: false, 8 | locales: ['de', 'en', 'es', 'fr', 'hi', 'it', 'ja', 'ko', 'nl', 'pt', 'ru', 'tr'], 9 | namespaceSeparator: false, 10 | output: 'src/i18n/$LOCALE.json', 11 | sort(a, b) { 12 | return a < b ? -1 : 1; // alfabetical order 13 | }, 14 | useKeysAsDefaultValue: true, 15 | verbose: true, 16 | }; 17 | -------------------------------------------------------------------------------- /jest-global-setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef, require-await */ 2 | module.exports = async () => { 3 | process.env.TZ = 'UTC'; 4 | }; 5 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | // https://playwright.dev/docs/test-configuration 4 | const config: PlaywrightTestConfig = { 5 | expect: { 6 | toHaveScreenshot: { 7 | maxDiffPixels: 100, 8 | }, 9 | }, 10 | maxFailures: 1, 11 | retries: 2, 12 | testDir: './e2e', 13 | use: { 14 | headless: true, 15 | screenshot: 'only-on-failure', 16 | viewport: { height: 920, width: 1280 }, 17 | }, 18 | webServer: { 19 | command: 'npx ladle serve --open none', 20 | port: 61000, 21 | reuseExistingServer: !process.env.CI, 22 | timeout: 120 * 1000, 23 | }, 24 | workers: 1, 25 | }; 26 | 27 | export default config; 28 | -------------------------------------------------------------------------------- /scripts/copy-css.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p dist/assets dist/css/v2 dist/scss/v2 4 | 5 | cp -r node_modules/@stream-io/stream-chat-css/dist/assets/* dist/assets 6 | cp -r node_modules/@stream-io/stream-chat-css/dist/v2/css/*.css dist/css/v2 7 | cp -r node_modules/@stream-io/stream-chat-css/dist/v2/scss/* dist/scss/v2 8 | -------------------------------------------------------------------------------- /scripts/merge-stream-chat-css-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | usage() { 4 | echo "Missing path to stream-chat-css directory" 5 | echo "Usage: $(basename $0) " 6 | } 7 | 8 | main() { 9 | if [ $# -eq 0 ]; then 10 | usage 11 | exit 1 12 | fi 13 | 14 | STREAM_CHAT_CSS_DOCS_PATH=$1; 15 | cp -r "$STREAM_CHAT_CSS_DOCS_PATH"/* ./docusaurus/docs/React; 16 | } 17 | 18 | 19 | main $* 20 | exit 0 21 | -------------------------------------------------------------------------------- /scripts/validate-cjs-browser-bundle.cjs: -------------------------------------------------------------------------------- 1 | // As the community transitions to ESM, we can easily break our CJS bundle. 2 | // This smoke test can help to detect this early. 3 | 4 | const { JSDOM } = require('jsdom'); 5 | const dom = new JSDOM('', { 6 | url: 'https://localhost', 7 | }); 8 | 9 | global.window = dom.window; 10 | 11 | for (const key of Object.keys(window)) { 12 | if (global[key] === undefined) { 13 | global[key] = window[key]; 14 | } 15 | } 16 | 17 | require('../dist/index.browser.cjs'); 18 | -------------------------------------------------------------------------------- /scripts/validate-cjs-node-bundle.cjs: -------------------------------------------------------------------------------- 1 | // As the community transitions to ESM, we can easily break our CJS bundle. 2 | // This smoke test can help to detect this early. 3 | 4 | require('../dist/index.node.cjs'); 5 | -------------------------------------------------------------------------------- /src/@types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly E2E_APP_KEY: string; 5 | readonly E2E_APP_SECRET: string; 6 | readonly E2E_TEST_USER_1: string; 7 | readonly E2E_TEST_USER_1_TOKEN: string; 8 | readonly E2E_TEST_USER_2: string; 9 | readonly E2E_TEST_USER_2_TOKEN: string; 10 | readonly E2E_JUMP_TO_MESSAGE_CHANNEL: string; 11 | readonly E2E_ADD_MESSAGE_CHANNEL: string; 12 | } 13 | 14 | interface ImportMeta { 15 | readonly env: ImportMetaEnv; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/AIStateIndicator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AIStateIndicator'; 2 | export * from './hooks/useAIState'; 3 | -------------------------------------------------------------------------------- /src/components/Attachment/__tests__/__snapshots__/AttachmentActions.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AttachmentActions should render AttachmentActions component 1`] = ` 4 |
5 |
8 |
11 | 12 | 19 | 26 |
27 |
28 |
29 | `; 30 | -------------------------------------------------------------------------------- /src/components/Attachment/components/DownloadButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { DownloadIcon } from '../icons'; 4 | import { SafeAnchor } from '../../SafeAnchor'; 5 | 6 | type DownloadButtonProps = { 7 | assetUrl?: string; 8 | }; 9 | 10 | export const DownloadButton = ({ assetUrl }: DownloadButtonProps) => ( 11 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /src/components/Attachment/components/PlayButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PauseIcon, PlayTriangleIcon } from '../icons'; 3 | 4 | type PlayButtonProps = { 5 | isPlaying: boolean; 6 | onClick: () => void; 7 | }; 8 | 9 | export const PlayButton = ({ isPlaying, onClick }: PlayButtonProps) => ( 10 | 17 | ); 18 | -------------------------------------------------------------------------------- /src/components/Attachment/components/PlaybackRateButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export type PlaybackRateButtonProps = React.ComponentProps<'button'>; 4 | 5 | export const PlaybackRateButton = ({ children, onClick }: PlaybackRateButtonProps) => ( 6 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/components/Attachment/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DownloadButton'; 2 | export * from './FileSizeIndicator'; 3 | export * from './ProgressBar'; 4 | export * from './PlaybackRateButton'; 5 | export * from './PlayButton'; 6 | export * from './WaveProgressBar'; 7 | -------------------------------------------------------------------------------- /src/components/Attachment/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Attachment'; 2 | export * from './AttachmentActions'; 3 | export * from './AttachmentContainer'; 4 | export * from './Audio'; 5 | export * from './audioSampling'; 6 | export * from './Card'; 7 | export * from './components'; 8 | export * from './UnsupportedAttachment'; 9 | export * from './FileAttachment'; 10 | export * from './utils'; 11 | export { useAudioController } from './hooks/useAudioController'; 12 | -------------------------------------------------------------------------------- /src/components/Avatar/ChannelAvatar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Avatar, GroupAvatar } from './'; 4 | import type { AvatarProps, GroupAvatarProps } from './'; 5 | 6 | export type ChannelAvatarProps = Partial & AvatarProps; 7 | 8 | export const ChannelAvatar = ({ 9 | groupChannelDisplayInfo, 10 | image, 11 | name, 12 | user, 13 | ...sharedProps 14 | }: ChannelAvatarProps) => { 15 | if (groupChannelDisplayInfo) { 16 | return ( 17 | 18 | ); 19 | } 20 | return ; 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/Avatar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Avatar'; 2 | export * from './ChannelAvatar'; 3 | export * from './GroupAvatar'; 4 | -------------------------------------------------------------------------------- /src/components/Channel/__tests__/useIsMounted.test.js: -------------------------------------------------------------------------------- 1 | import { act, renderHook } from '@testing-library/react'; 2 | import { useIsMounted } from '../hooks/useIsMounted'; 3 | 4 | describe('useIsMounted hook', () => { 5 | it('should set the value to false after unmounting', () => { 6 | const renderResult = renderHook(() => useIsMounted()); 7 | const ref = renderResult.result.current; 8 | expect(ref.current).toBe(true); 9 | act(() => { 10 | renderResult.unmount(); 11 | }); 12 | expect(ref.current).toBe(false); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/components/Channel/constants.ts: -------------------------------------------------------------------------------- 1 | export const CHANNEL_CONTAINER_ID = 'str-chat__channel'; 2 | -------------------------------------------------------------------------------- /src/components/Channel/hooks/useCreateTypingContext.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | 3 | import type { TypingContextValue } from '../../../context/TypingContext'; 4 | 5 | export const useCreateTypingContext = (value: TypingContextValue) => { 6 | const { typing } = value; 7 | 8 | const typingValue = Object.keys(typing || {}).join(); 9 | 10 | const typingContext: TypingContextValue = useMemo( 11 | () => ({ 12 | typing, 13 | }), 14 | // eslint-disable-next-line react-hooks/exhaustive-deps 15 | [typingValue], 16 | ); 17 | 18 | return typingContext; 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/Channel/hooks/useIsMounted.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export const useIsMounted = () => { 4 | const isMounted = useRef(false); 5 | 6 | useEffect(() => { 7 | isMounted.current = true; 8 | return () => { 9 | isMounted.current = false; 10 | }; 11 | }, []); 12 | 13 | return isMounted; 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/Channel/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Channel'; 2 | export { useEditMessageHandler as useChannelEditMessageHandler } from './hooks/useEditMessageHandler'; 3 | export { useMentionsHandlers as useChannelMentionsHandler } from './hooks/useMentionsHandlers'; 4 | -------------------------------------------------------------------------------- /src/components/ChannelHeader/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useTranslationContext } from '../../context'; 4 | 5 | export const MenuIcon = ({ title }: { title?: string }) => { 6 | const { t } = useTranslationContext('MenuIcon'); 7 | 8 | return ( 9 | 10 | {title ?? t('Menu')} 11 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/ChannelHeader/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ChannelHeader'; 2 | -------------------------------------------------------------------------------- /src/components/ChannelList/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useChannelDeletedListener'; 2 | export * from './useChannelHiddenListener'; 3 | export * from './useChannelTruncatedListener'; 4 | export * from './useChannelUpdatedListener'; 5 | export * from './useChannelVisibleListener'; 6 | export * from './useConnectionRecoveredListener'; 7 | export * from './useMessageNewListener'; 8 | export * from './useMobileNavigation'; 9 | export * from './useNotificationAddedToChannelListener'; 10 | export * from './useNotificationMessageNewListener'; 11 | export * from './useNotificationRemovedFromChannelListener'; 12 | export * from './usePaginatedChannels'; 13 | export * from './useUserPresenceChangedListener'; 14 | export * from './useChannelMembershipState'; 15 | -------------------------------------------------------------------------------- /src/components/ChannelList/hooks/useChannelMembershipState.ts: -------------------------------------------------------------------------------- 1 | import type { Channel, ChannelMemberResponse, EventTypes } from 'stream-chat'; 2 | import { useSelectedChannelState } from './useSelectedChannelState'; 3 | 4 | const selector = (c: Channel) => c.state.membership; 5 | const keys: EventTypes[] = ['member.updated']; 6 | 7 | export function useChannelMembershipState(channel: Channel): ChannelMemberResponse; 8 | export function useChannelMembershipState( 9 | channel?: Channel | undefined, 10 | ): ChannelMemberResponse | undefined; 11 | export function useChannelMembershipState(channel?: Channel) { 12 | return useSelectedChannelState({ channel, selector, stateChangeEventKeys: keys }); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/ChannelList/hooks/useConnectionRecoveredListener.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | import { useChatContext } from '../../../context/ChatContext'; 4 | 5 | export const useConnectionRecoveredListener = (forceUpdate?: () => void) => { 6 | const { client } = useChatContext('useConnectionRecoveredListener'); 7 | 8 | useEffect(() => { 9 | const handleEvent = () => { 10 | if (forceUpdate) { 11 | forceUpdate(); 12 | } 13 | }; 14 | 15 | client.on('connection.recovered', handleEvent); 16 | 17 | return () => { 18 | client.off('connection.recovered', handleEvent); 19 | }; 20 | }, [client, forceUpdate]); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/ChannelList/hooks/useMobileNavigation.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | export const useMobileNavigation = ( 4 | channelListRef: React.RefObject, 5 | navOpen: boolean, 6 | closeMobileNav?: () => void, 7 | ) => { 8 | useEffect(() => { 9 | const handleClickOutside = (event: MouseEvent) => { 10 | if ( 11 | closeMobileNav && 12 | channelListRef.current && 13 | !channelListRef.current.contains(event.target as Node) && 14 | navOpen 15 | ) { 16 | closeMobileNav(); 17 | } 18 | }; 19 | 20 | document.addEventListener('click', handleClickOutside); 21 | 22 | return () => { 23 | document.removeEventListener('click', handleClickOutside); 24 | }; 25 | }, [channelListRef, closeMobileNav, navOpen]); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/ChannelList/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ChannelList'; 2 | export * from './ChannelListMessenger'; 3 | export * from './hooks'; 4 | export * from './utils'; 5 | -------------------------------------------------------------------------------- /src/components/ChannelPreview/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useChannelPreviewInfo } from './useChannelPreviewInfo'; 2 | export { MessageDeliveryStatus } from './useMessageDeliveryStatus'; 3 | -------------------------------------------------------------------------------- /src/components/ChannelPreview/hooks/useIsChannelMuted.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import { useChatContext } from '../../../context/ChatContext'; 4 | 5 | import type { Channel } from 'stream-chat'; 6 | 7 | export const useIsChannelMuted = (channel: Channel) => { 8 | const { client } = useChatContext('useIsChannelMuted'); 9 | 10 | const [muted, setMuted] = useState(channel.muteStatus()); 11 | 12 | useEffect(() => { 13 | const handleEvent = () => setMuted(channel.muteStatus()); 14 | 15 | client.on('notification.channel_mutes_updated', handleEvent); 16 | return () => client.off('notification.channel_mutes_updated', handleEvent); 17 | // eslint-disable-next-line react-hooks/exhaustive-deps 18 | }, [muted]); 19 | 20 | return muted; 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/ChannelPreview/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ChannelPreview'; 2 | export * from './ChannelPreviewMessenger'; 3 | export * from './ChannelPreviewActionButtons'; 4 | export * from './hooks'; 5 | export * from './utils'; 6 | -------------------------------------------------------------------------------- /src/components/ChannelSearch/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ChannelSearch'; 2 | export * from './SearchBar'; 3 | export * from './SearchInput'; 4 | export * from './SearchResults'; 5 | export * from './utils'; 6 | export type { 7 | ChannelSearchFunctionParams, 8 | ChannelSearchParams, 9 | } from './hooks/useChannelSearch'; 10 | -------------------------------------------------------------------------------- /src/components/ChannelSearch/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Channel, UserResponse } from 'stream-chat'; 2 | 3 | export type ChannelOrUserResponse = Channel | UserResponse; 4 | 5 | export const isChannel = (output: ChannelOrUserResponse): output is Channel => 6 | (output as Channel).cid != null; 7 | -------------------------------------------------------------------------------- /src/components/Chat/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Chat'; 2 | export * from './hooks/useChat'; 3 | export * from './hooks/useCreateChatClient'; 4 | -------------------------------------------------------------------------------- /src/components/ChatView/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './ChatView'; 2 | -------------------------------------------------------------------------------- /src/components/DateSeparator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DateSeparator'; 2 | -------------------------------------------------------------------------------- /src/components/Dialog/DialogMenu.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps } from 'react'; 2 | import React from 'react'; 3 | import clsx from 'clsx'; 4 | 5 | export type DialogMenuButtonProps = ComponentProps<'button'>; 6 | 7 | export const DialogMenuButton = ({ 8 | children, 9 | className, 10 | ...props 11 | }: DialogMenuButtonProps) => ( 12 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/components/Dialog/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useDialog'; 2 | -------------------------------------------------------------------------------- /src/components/Dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DialogAnchor'; 2 | export * from './DialogManager'; 3 | export * from './DialogPortal'; 4 | export * from './hooks'; 5 | -------------------------------------------------------------------------------- /src/components/EmptyStateIndicator/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const ChatBubble = () => ( 4 | 12 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /src/components/EmptyStateIndicator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './EmptyStateIndicator'; 2 | -------------------------------------------------------------------------------- /src/components/EventComponent/index.ts: -------------------------------------------------------------------------------- 1 | export * from './EventComponent'; 2 | -------------------------------------------------------------------------------- /src/components/Form/FieldError.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import type { ComponentProps } from 'react'; 3 | import React from 'react'; 4 | 5 | type FieldErrorProps = ComponentProps<'div'> & { 6 | text?: string; 7 | }; 8 | export const FieldError = ({ className, text, ...props }: FieldErrorProps) => ( 9 |
10 | {text} 11 |
12 | ); 13 | -------------------------------------------------------------------------------- /src/components/Gallery/__tests__/__snapshots__/Image.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Image should render component with default props 1`] = ` 4 |
5 | 11 |
12 | `; 13 | -------------------------------------------------------------------------------- /src/components/Gallery/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './BaseImage'; 2 | export * from './Gallery'; 3 | export * from './Image'; 4 | export * from './ModalGallery'; 5 | -------------------------------------------------------------------------------- /src/components/InfiniteScrollPaginator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './InfiniteScroll'; 2 | -------------------------------------------------------------------------------- /src/components/LoadMore/index.ts: -------------------------------------------------------------------------------- 1 | export * from './LoadMoreButton'; 2 | export * from './LoadMorePaginator'; 3 | -------------------------------------------------------------------------------- /src/components/Loading/LoadingChannels.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LoadingItems = () => ( 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ); 12 | 13 | const UnMemoizedLoadingChannels = () => ( 14 |
15 | 16 | 17 | 18 |
19 | ); 20 | 21 | /** 22 | * Loading indicator for the ChannelList 23 | */ 24 | export const LoadingChannels = React.memo(UnMemoizedLoadingChannels); 25 | -------------------------------------------------------------------------------- /src/components/Loading/LoadingErrorIndicator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useTranslationContext } from '../../context/TranslationContext'; 4 | 5 | export type LoadingErrorIndicatorProps = { 6 | /** Error object */ 7 | error?: Error; 8 | }; 9 | 10 | /** 11 | * UI component for error indicator in a Channel 12 | */ 13 | const UnMemoizedLoadingErrorIndicator = ({ error }: LoadingErrorIndicatorProps) => { 14 | const { t } = useTranslationContext('LoadingErrorIndicator'); 15 | 16 | if (!error) return null; 17 | 18 | return ( 19 |
{t('Error: {{ errorMessage }}', { errorMessage: error.message })}
20 | ); 21 | }; 22 | 23 | export const LoadingErrorIndicator = React.memo( 24 | UnMemoizedLoadingErrorIndicator, 25 | (prevProps, nextProps) => prevProps.error?.message === nextProps.error?.message, 26 | ) as typeof UnMemoizedLoadingErrorIndicator; 27 | -------------------------------------------------------------------------------- /src/components/Loading/__tests__/LoadingChannels.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { cleanup, render } from '@testing-library/react'; 4 | import '@testing-library/jest-dom'; 5 | 6 | import { LoadingChannels } from '../LoadingChannels'; 7 | 8 | afterEach(cleanup); 9 | 10 | describe('LoadingChannels', () => { 11 | it('should render component with default props', () => { 12 | const { container } = render(); 13 | expect(container).toMatchSnapshot(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/Loading/index.ts: -------------------------------------------------------------------------------- 1 | export * from './LoadingChannels'; 2 | export * from './LoadingErrorIndicator'; 3 | export * from './LoadingIndicator'; 4 | -------------------------------------------------------------------------------- /src/components/MML/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MML'; 2 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/AudioRecorder/AudioRecordingButtons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MicIcon } from '../../MessageInput/icons'; 3 | 4 | export type StartRecordingAudioButtonProps = React.ComponentProps<'button'>; 5 | 6 | export const StartRecordingAudioButton = (props: StartRecordingAudioButtonProps) => ( 7 | 15 | ); 16 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/AudioRecorder/RecordingTimer.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import { displayDuration } from '../../Attachment'; 3 | import React from 'react'; 4 | 5 | export type RecordingTimerProps = { 6 | durationSeconds: number; 7 | }; 8 | 9 | export const RecordingTimer = ({ durationSeconds }: RecordingTimerProps) => ( 10 |
= 3600, 13 | })} 14 | > 15 | {displayDuration(durationSeconds)} 16 |
17 | ); 18 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/AudioRecorder/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AudioRecorder'; 2 | export * from './AudioRecordingButtons'; 3 | export * from './RecordingTimer'; 4 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/classes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BrowserPermission'; 2 | export * from './MediaRecorderController'; 3 | export type { AmplitudeRecorderConfig } from './AmplitudeRecorder'; 4 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export type { RecordingController } from './useMediaRecorder'; 2 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RecordingPermissionDeniedNotification'; 2 | export * from './AudioRecorder'; 3 | export * from './hooks'; 4 | export { MediaRecordingState } from './classes/MediaRecorderController'; 5 | export { RecordingPermission } from './classes/BrowserPermission'; 6 | export type { CustomAudioRecordingConfig } from './classes'; 7 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/observable/BehaviorSubject.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from './Subject'; 2 | import type { ObserverOrNext } from './Observer'; 3 | import { createObserver } from './Observer'; 4 | import type { Subscription } from './Subscription'; 5 | 6 | export class BehaviorSubject extends Subject { 7 | constructor(private _value: T) { 8 | super(); 9 | } 10 | 11 | get value(): T { 12 | const { _value, thrownError } = this; 13 | if (thrownError) { 14 | throw thrownError; 15 | } 16 | return _value; 17 | } 18 | 19 | subscribe(observerOrNext: ObserverOrNext): Subscription { 20 | const observer = createObserver(observerOrNext); 21 | const subscription = super.subscribe(observerOrNext); 22 | if (!subscription.closed) observer.next(this._value); 23 | return subscription; 24 | } 25 | 26 | next(value: T): void { 27 | super.next((this._value = value)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/observable/Observer.ts: -------------------------------------------------------------------------------- 1 | type Next = (value: T) => void; 2 | export type Observer = { 3 | next(value: T): void; 4 | complete?(): void; 5 | error?(error: Error): void; 6 | }; 7 | export type ObserverOrNext = Next | Observer; 8 | 9 | export function createObserver(observerOrNext: ObserverOrNext): Observer { 10 | return typeof observerOrNext === 'function' ? { next: observerOrNext } : observerOrNext; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/observable/Subscription.ts: -------------------------------------------------------------------------------- 1 | export interface SubscriptionLike { 2 | closed: boolean; 3 | 4 | unsubscribe(): void; 5 | } 6 | 7 | export class Subscription implements SubscriptionLike { 8 | closed = false; 9 | private _unsubscribe: (() => void) | undefined; 10 | 11 | constructor(unsubscribe?: () => void) { 12 | this._unsubscribe = unsubscribe; 13 | } 14 | 15 | unsubscribe() { 16 | this.closed = true; 17 | this._unsubscribe?.(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/observable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BehaviorSubject'; 2 | export * from './Observable'; 3 | export * from './Observer'; 4 | export * from './Subject'; 5 | export * from './Subscription'; 6 | -------------------------------------------------------------------------------- /src/components/MediaRecorder/transcode/index.ts: -------------------------------------------------------------------------------- 1 | import { encodeToWaw } from './wav'; 2 | import { createFileFromBlobs, getExtensionFromMimeType } from '../../ReactFileUtilities'; 3 | 4 | export type TranscoderConfig = { 5 | // defaults to 16000Hz 6 | sampleRate: number; 7 | // Custom encoder function that converts the recorded audio file into a blob with the desired MIME type 8 | encoder?: (file: File, sampleRate: number) => Promise; 9 | }; 10 | 11 | export type TranscodeParams = TranscoderConfig & { 12 | blob: Blob; 13 | }; 14 | 15 | export const transcode = ({ 16 | blob, 17 | encoder = encodeToWaw, 18 | sampleRate, 19 | }: TranscodeParams): Promise => 20 | encoder( 21 | createFileFromBlobs({ 22 | blobsArray: [blob], 23 | fileName: `audio_recording_${new Date().toISOString()}.${getExtensionFromMimeType( 24 | blob.type, 25 | )}`, 26 | mimeType: blob.type, 27 | }), 28 | sampleRate, 29 | ); 30 | -------------------------------------------------------------------------------- /src/components/Message/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useActionHandler'; 2 | export * from './useDeleteHandler'; 3 | export * from './useEditHandler'; 4 | export * from './useFlagHandler'; 5 | export * from './useMentionsHandler'; 6 | export * from './useMarkUnreadHandler'; 7 | export * from './useMuteHandler'; 8 | export * from './useOpenThreadHandler'; 9 | export * from './usePinHandler'; 10 | export * from './useReactionHandler'; 11 | export * from './useRetryHandler'; 12 | export * from './useUserHandler'; 13 | export * from './useUserRole'; 14 | export * from './useReactionsFetcher'; 15 | export * from './useMessageTextStreaming'; 16 | -------------------------------------------------------------------------------- /src/components/Message/hooks/useOpenThreadHandler.ts: -------------------------------------------------------------------------------- 1 | import { useChannelActionContext } from '../../../context/ChannelActionContext'; 2 | 3 | import type { LocalMessage } from 'stream-chat'; 4 | import type { ReactEventHandler } from '../types'; 5 | 6 | export const useOpenThreadHandler = ( 7 | message?: LocalMessage, 8 | customOpenThread?: (message: LocalMessage, event: React.BaseSyntheticEvent) => void, 9 | ): ReactEventHandler => { 10 | const { openThread: channelOpenThread } = 11 | useChannelActionContext('useOpenThreadHandler'); 12 | 13 | const openThread = customOpenThread || channelOpenThread; 14 | 15 | return (event) => { 16 | if (!openThread || !message) { 17 | console.warn( 18 | 'Open thread handler was called but it is missing one of its parameters', 19 | ); 20 | return; 21 | } 22 | 23 | openThread(message, event); 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/Message/hooks/useRetryHandler.ts: -------------------------------------------------------------------------------- 1 | import type { RetrySendMessage } from '../../../context/ChannelActionContext'; 2 | import { useChannelActionContext } from '../../../context/ChannelActionContext'; 3 | 4 | export const useRetryHandler = ( 5 | customRetrySendMessage?: RetrySendMessage, 6 | ): RetrySendMessage => { 7 | const { retrySendMessage: contextRetrySendMessage } = 8 | useChannelActionContext('useRetryHandler'); 9 | 10 | const retrySendMessage = customRetrySendMessage || contextRetrySendMessage; 11 | 12 | return async (message) => { 13 | if (message) { 14 | await retrySendMessage(message); 15 | } 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/Message/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FixedHeightMessage'; 2 | export * from './hooks'; 3 | export * from './icons'; 4 | export * from './Message'; 5 | export * from './MessageDeleted'; 6 | export * from './MessageOptions'; 7 | export * from './MessageRepliesCountButton'; 8 | export * from './MessageSimple'; 9 | export * from './MessageStatus'; 10 | export * from './MessageText'; 11 | export * from './MessageTimestamp'; 12 | export * from './QuotedMessage'; 13 | export * from './renderText'; 14 | export * from './types'; 15 | export * from './utils'; 16 | export * from './StreamedMessageText'; 17 | export type { TimestampProps } from './Timestamp'; 18 | -------------------------------------------------------------------------------- /src/components/Message/renderText/componentRenderers/Anchor.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import type { ComponentProps } from 'react'; 3 | import React from 'react'; 4 | 5 | export const Anchor = ({ children, href }: ComponentProps<'a'>) => { 6 | const isEmail = href?.startsWith('mailto:'); 7 | const isUrl = href?.startsWith('http'); 8 | 9 | if (!href || (!isEmail && !isUrl)) return <>{children}; 10 | 11 | return ( 12 | 18 | {children} 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/Message/renderText/componentRenderers/Emoji.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { PropsWithChildrenOnly } from '../../../../types/types'; 3 | 4 | export const Emoji = ({ children }: PropsWithChildrenOnly) => ( 5 | 6 | {children} 7 | 8 | ); 9 | -------------------------------------------------------------------------------- /src/components/Message/renderText/componentRenderers/Mention.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import React from 'react'; 3 | 4 | import type { UserResponse } from 'stream-chat'; 5 | 6 | export type MentionProps = PropsWithChildren<{ 7 | node: { 8 | mentionedUser: UserResponse; 9 | }; 10 | }>; 11 | 12 | export const Mention = ({ children, node: { mentionedUser } }: MentionProps) => ( 13 | 14 | {children} 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/components/Message/renderText/componentRenderers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Anchor'; 2 | export * from './Emoji'; 3 | export * from './Mention'; 4 | -------------------------------------------------------------------------------- /src/components/Message/renderText/index.ts: -------------------------------------------------------------------------------- 1 | export type { MentionProps } from './componentRenderers'; 2 | export { escapeRegExp, matchMarkdownLinks, messageCodeBlocks } from './regex'; 3 | export * from './rehypePlugins'; 4 | export * from './remarkPlugins'; 5 | export * from './renderText'; 6 | -------------------------------------------------------------------------------- /src/components/Message/renderText/regex.ts: -------------------------------------------------------------------------------- 1 | export function escapeRegExp(text: string) { 2 | return text.replace(/[-[\]{}()*+?.,/\\^$|#]/g, '\\$&'); 3 | } 4 | 5 | export const detectHttp = /(http(s?):\/\/)?(www\.)?/; 6 | 7 | export const messageCodeBlocks = (message: string) => { 8 | const codeRegex = /```[a-z]*\n[\s\S]*?\n```|`[a-z]*[\s\S]*?`/gm; 9 | const matches = message.match(codeRegex); 10 | return matches || []; 11 | }; 12 | 13 | export const matchMarkdownLinks = (message: string) => { 14 | const regexMdLinks = /\[([^[]+)\](\(.*\))/gm; 15 | const matches = message.match(regexMdLinks); 16 | const singleMatch = /\[([^[]+)\]\((.*)\)/; 17 | 18 | const links = matches 19 | ? matches.map((match) => { 20 | const i = singleMatch.exec(match); 21 | return i && [i[1], i[2]]; 22 | }) 23 | : []; 24 | 25 | return links.flat(); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/Message/renderText/rehypePlugins/emojiMarkdownPlugin.ts: -------------------------------------------------------------------------------- 1 | import type { ReplaceFunction } from 'hast-util-find-and-replace'; 2 | import { findAndReplace } from 'hast-util-find-and-replace'; 3 | import { u } from 'unist-builder'; 4 | import emojiRegex from 'emoji-regex'; 5 | 6 | import type { Nodes } from 'hast-util-find-and-replace/lib'; 7 | 8 | export const emojiMarkdownPlugin = () => { 9 | const replace: ReplaceFunction = (match) => 10 | u('element', { properties: {}, tagName: 'emoji' }, [u('text', match)]); 11 | 12 | const transform = (node: Nodes) => findAndReplace(node, [emojiRegex(), replace]); 13 | 14 | return transform; 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/Message/renderText/rehypePlugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './emojiMarkdownPlugin'; 2 | export * from './mentionsMarkdownPlugin'; 3 | -------------------------------------------------------------------------------- /src/components/Message/renderText/remarkPlugins/htmlToTextPlugin.ts: -------------------------------------------------------------------------------- 1 | import type { Visitor } from 'unist-util-visit'; 2 | import { visit } from 'unist-util-visit'; 3 | 4 | import type { Nodes } from 'hast-util-find-and-replace/lib'; 5 | 6 | const visitor: Visitor = (node) => { 7 | if (node.type !== 'html') return; 8 | 9 | node.type = 'text'; 10 | }; 11 | const transform = (tree: Nodes) => { 12 | visit(tree, visitor); 13 | }; 14 | 15 | export const htmlToTextPlugin = () => transform; 16 | -------------------------------------------------------------------------------- /src/components/Message/renderText/remarkPlugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './htmlToTextPlugin'; 2 | export * from './keepLineBreaksPlugin'; 3 | -------------------------------------------------------------------------------- /src/components/Message/renderText/types.ts: -------------------------------------------------------------------------------- 1 | import type { Content, Root } from 'hast'; 2 | 3 | export type HNode = Content | Root; 4 | -------------------------------------------------------------------------------- /src/components/MessageActions/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useMessageActionsBoxPopper'; 2 | -------------------------------------------------------------------------------- /src/components/MessageActions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MessageActions'; 2 | export * from './MessageActionsBox'; 3 | export * from './CustomMessageActionsList'; 4 | -------------------------------------------------------------------------------- /src/components/MessageBounce/MessageBounceModal.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentType, PropsWithChildren } from 'react'; 2 | import React from 'react'; 3 | import type { ModalProps } from '../Modal'; 4 | import { Modal } from '../Modal'; 5 | import { MessageBounceProvider } from '../../context'; 6 | import type { MessageBouncePromptProps } from './MessageBouncePrompt'; 7 | 8 | export type MessageBounceModalProps = PropsWithChildren< 9 | ModalProps & { 10 | MessageBouncePrompt: ComponentType; 11 | } 12 | >; 13 | 14 | export function MessageBounceModal({ 15 | MessageBouncePrompt, 16 | ...modalProps 17 | }: MessageBounceModalProps) { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/MessageBounce/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MessageBounceModal'; 2 | export * from './MessageBouncePrompt'; 3 | -------------------------------------------------------------------------------- /src/components/MessageInput/AttachmentPreviewList/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AttachmentPreviewList'; 2 | export type { FileAttachmentPreviewProps } from './FileAttachmentPreview'; 3 | export type { ImageAttachmentPreviewProps } from './ImageAttachmentPreview'; 4 | export type { UploadAttachmentPreviewProps as AttachmentPreviewProps } from './types'; 5 | export type { UnsupportedAttachmentPreviewProps } from './UnsupportedAttachmentPreview'; 6 | export type { VoiceRecordingPreviewProps } from './VoiceRecordingPreview'; 7 | -------------------------------------------------------------------------------- /src/components/MessageInput/AttachmentPreviewList/types.ts: -------------------------------------------------------------------------------- 1 | import type { LocalUploadAttachment } from 'stream-chat'; 2 | 3 | export type UploadAttachmentPreviewProps = { 4 | attachment: A; 5 | handleRetry: ( 6 | attachment: LocalUploadAttachment, 7 | ) => void | Promise; 8 | removeAttachments: (ids: string[]) => void; 9 | }; 10 | -------------------------------------------------------------------------------- /src/components/MessageInput/CooldownTimer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTimer } from './hooks/useTimer'; 3 | 4 | export type CooldownTimerProps = { 5 | cooldownInterval: number; 6 | setCooldownRemaining: React.Dispatch>; 7 | }; 8 | export const CooldownTimer = ({ cooldownInterval }: CooldownTimerProps) => { 9 | const secondsLeft = useTimer({ startFrom: cooldownInterval }); 10 | 11 | return ( 12 |
13 | {secondsLeft} 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/MessageInput/SendButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SendIcon } from './icons'; 3 | import { useMessageComposerHasSendableData } from './hooks'; 4 | import type { UpdatedMessage } from 'stream-chat'; 5 | 6 | export type SendButtonProps = { 7 | sendMessage: ( 8 | event: React.BaseSyntheticEvent, 9 | customMessageData?: Omit, 10 | ) => void; 11 | } & React.ComponentProps<'button'>; 12 | export const SendButton = ({ sendMessage, ...rest }: SendButtonProps) => { 13 | const hasSendableData = useMessageComposerHasSendableData(); 14 | return ( 15 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/MessageInput/StopAIGenerationButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslationContext } from '../../context'; 3 | 4 | export type StopAIGenerationButtonProps = React.ComponentProps<'button'>; 5 | 6 | export const StopAIGenerationButton = ({ 7 | onClick, 8 | ...restProps 9 | }: StopAIGenerationButtonProps) => { 10 | const { t } = useTranslationContext(); 11 | return ( 12 |
19 | ); 20 | -------------------------------------------------------------------------------- /src/components/Modal/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const CloseIconRound = () => ( 4 | 12 | 13 | 14 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /src/components/Modal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Modal'; 2 | -------------------------------------------------------------------------------- /src/components/Poll/Poll.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PollContent as DefaultPollContent } from './PollContent'; 3 | import { QuotedPoll as DefaultQuotedPoll } from './QuotedPoll'; 4 | import { PollProvider, useComponentContext } from '../../context'; 5 | import type { Poll as PollClass } from 'stream-chat'; 6 | 7 | export const Poll = ({ isQuoted, poll }: { poll: PollClass; isQuoted?: boolean }) => { 8 | const { PollContent = DefaultPollContent, QuotedPoll = DefaultQuotedPoll } = 9 | useComponentContext(); 10 | return poll ? ( 11 | {isQuoted ? : } 12 | ) : null; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/Poll/PollActions/PollAction.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import React from 'react'; 3 | import { Modal } from '../../Modal'; 4 | 5 | export type PollActionProps = { 6 | buttonText: string; 7 | closeModal: () => void; 8 | modalIsOpen: boolean; 9 | openModal: () => void; 10 | modalClassName?: string; 11 | }; 12 | 13 | export const PollAction = ({ 14 | buttonText, 15 | children, 16 | closeModal, 17 | modalClassName, 18 | modalIsOpen, 19 | openModal, 20 | }: PropsWithChildren) => ( 21 | <> 22 | 25 | 26 | {children} 27 | 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /src/components/Poll/PollActions/PollResults/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PollResults'; 2 | -------------------------------------------------------------------------------- /src/components/Poll/PollActions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AddCommentForm'; 2 | export * from './EndPollDialog'; 3 | export * from './PollActions'; 4 | export * from './PollAnswerList'; 5 | export * from './PollOptionsFullList'; 6 | export * from './PollResults'; 7 | export * from './SuggestPollOptionForm'; 8 | -------------------------------------------------------------------------------- /src/components/Poll/PollCreationDialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PollCreationDialog'; 2 | -------------------------------------------------------------------------------- /src/components/Poll/PollCreationDialog/types.ts: -------------------------------------------------------------------------------- 1 | type Id = string; 2 | 3 | export type PollOptionFormData = { 4 | id: Id; 5 | text: string; 6 | }; 7 | 8 | export type OptionErrors = Record; 9 | -------------------------------------------------------------------------------- /src/components/Poll/constants.ts: -------------------------------------------------------------------------------- 1 | export const MAX_POLL_OPTIONS = 100 as const; 2 | 3 | export const MAX_OPTIONS_DISPLAYED = 10 as const; 4 | -------------------------------------------------------------------------------- /src/components/Poll/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './usePollAnswerPagination'; 2 | export * from './usePollOptionVotesPagination'; 3 | -------------------------------------------------------------------------------- /src/components/Poll/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Poll'; 2 | export * from './PollActions'; 3 | export * from './PollContent'; 4 | export * from './PollCreationDialog'; 5 | export * from './PollHeader'; 6 | export * from './PollOptionList'; 7 | export * from './PollOptionSelector'; 8 | export * from './PollVote'; 9 | export * from './QuotedPoll'; 10 | export * from './hooks'; 11 | -------------------------------------------------------------------------------- /src/components/Portal/Portal.ts: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren, ReactPortal } from 'react'; 2 | import { useLayoutEffect, useState } from 'react'; 3 | import { createPortal } from 'react-dom'; 4 | 5 | export type PortalProps = { 6 | getPortalDestination: () => Element | null; 7 | isOpen?: boolean; 8 | }; 9 | 10 | export const Portal = ({ 11 | children, 12 | getPortalDestination, 13 | isOpen, 14 | }: PropsWithChildren): ReactPortal | null => { 15 | const [portalDestination, setPortalDestination] = useState(null); 16 | 17 | useLayoutEffect(() => { 18 | const destination = getPortalDestination(); 19 | if (!destination || !isOpen) return; 20 | setPortalDestination(destination); 21 | }, [getPortalDestination, isOpen]); 22 | 23 | if (!portalDestination) return null; 24 | 25 | return createPortal(children, portalDestination); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/ReactFileUtilities/FileIcon/index.ts: -------------------------------------------------------------------------------- 1 | export { FileIcon } from './FileIcon'; 2 | -------------------------------------------------------------------------------- /src/components/ReactFileUtilities/LoadingIndicator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export type LoadingIndicatorProps = { 4 | backgroundColor?: string; 5 | color?: string; 6 | size?: number; 7 | width?: number; 8 | }; 9 | 10 | export const LoadingIndicator = ({ 11 | backgroundColor, 12 | color, 13 | size = 20, 14 | width = 2, 15 | }: LoadingIndicatorProps) => ( 16 |
27 | ); 28 | -------------------------------------------------------------------------------- /src/components/ReactFileUtilities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FileIcon'; 2 | export * from './LoadingIndicator'; 3 | export * from './UploadButton'; 4 | export * from './types'; 5 | export * from './utils'; 6 | -------------------------------------------------------------------------------- /src/components/ReactFileUtilities/types.ts: -------------------------------------------------------------------------------- 1 | export type RecordedMediaType = 'audio' | 'video'; 2 | 3 | export type FileLike = Blob | File; 4 | -------------------------------------------------------------------------------- /src/components/Reactions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ReactionSelector'; 2 | export * from './ReactionsList'; 3 | export * from './ReactionsListModal'; 4 | export * from './SimpleReactionsList'; 5 | export * from './SpriteImage'; 6 | export * from './StreamEmoji'; 7 | export * from './reactionOptions'; 8 | -------------------------------------------------------------------------------- /src/components/Reactions/types.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentType } from 'react'; 2 | import type { ReactionResponse } from 'stream-chat'; 3 | 4 | export interface ReactionSummary { 5 | EmojiComponent: ComponentType | null; 6 | firstReactionAt: Date | null; 7 | isOwnReaction: boolean; 8 | lastReactionAt: Date | null; 9 | latestReactedUserNames: string[]; 10 | reactionCount: number; 11 | reactionType: string; 12 | unlistedReactedUserCount: number; 13 | } 14 | 15 | export type ReactionsComparator = (a: ReactionSummary, b: ReactionSummary) => number; 16 | 17 | export type ReactionDetailsComparator = ( 18 | a: ReactionResponse, 19 | b: ReactionResponse, 20 | ) => number; 21 | 22 | export type ReactionType = ReactionResponse['type']; 23 | -------------------------------------------------------------------------------- /src/components/Reactions/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import type { ForwardedRef, MutableRefObject } from 'react'; 2 | 3 | export const isMutableRef = ( 4 | ref: ForwardedRef | null, 5 | ): ref is MutableRefObject => { 6 | if (ref) { 7 | return (ref as MutableRefObject).current !== undefined; 8 | } 9 | return false; 10 | }; 11 | 12 | export const getImageDimensions = (source: string) => 13 | new Promise<[number, number]>((resolve, reject) => { 14 | const image = new Image(); 15 | 16 | image.addEventListener( 17 | 'load', 18 | () => { 19 | resolve([image.width, image.height]); 20 | }, 21 | { once: true }, 22 | ); 23 | 24 | image.addEventListener('error', () => reject(`Couldn't load image from ${source}`), { 25 | once: true, 26 | }); 27 | 28 | image.src = source; 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/SafeAnchor/index.ts: -------------------------------------------------------------------------------- 1 | // export { default as SafeAnchor } from './SafeAnchor'; 2 | export * from './SafeAnchor'; 3 | -------------------------------------------------------------------------------- /src/components/TextareaComposer/SuggestionList/CommandItem.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import React from 'react'; 3 | import type { CommandResponse } from 'stream-chat'; 4 | 5 | export type CommandItemProps = { 6 | entity: CommandResponse; 7 | }; 8 | 9 | export const CommandItem = (props: PropsWithChildren) => { 10 | const { entity } = props; 11 | 12 | return ( 13 |
14 | 15 | {entity.name} {entity.args} 16 | 17 |
18 | {entity.description} 19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/TextareaComposer/SuggestionList/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CommandItem'; 2 | export * from './EmoticonItem'; 3 | export * from './SuggestionList'; 4 | export * from './SuggestionListItem'; 5 | export * from './UserItem'; 6 | -------------------------------------------------------------------------------- /src/components/TextareaComposer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SuggestionList'; 2 | export * from './TextareaComposer'; 3 | -------------------------------------------------------------------------------- /src/components/Thread/ThreadHead.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import type { MessageProps } from '../Message'; 4 | import { Message } from '../Message'; 5 | import { ThreadStart as DefaultThreadStart } from './ThreadStart'; 6 | 7 | import { useComponentContext } from '../../context'; 8 | 9 | export const ThreadHead = (props: MessageProps) => { 10 | const { ThreadStart = DefaultThreadStart } = useComponentContext('ThreadHead'); 11 | return ( 12 |
13 | 14 | 15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/Thread/ThreadStart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useChannelStateContext } from '../../context/ChannelStateContext'; 4 | import { useTranslationContext } from '../../context/TranslationContext'; 5 | 6 | export const ThreadStart = () => { 7 | const { thread } = useChannelStateContext('ThreadStart'); 8 | const { t } = useTranslationContext('ThreadStart'); 9 | 10 | if (!thread?.reply_count) return null; 11 | 12 | return ( 13 |
14 | {t('replyCount', { count: thread.reply_count })} 15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/Thread/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useTranslationContext } from '../../context/TranslationContext'; 4 | 5 | export const CloseIcon = ({ title }: { title?: string }) => { 6 | const { t } = useTranslationContext('CloseIcon'); 7 | 8 | return ( 9 | 15 | {title ?? t('Close')} 16 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/Thread/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Thread'; 2 | export * from './ThreadHeader'; 3 | export { ThreadStart } from './ThreadStart'; 4 | -------------------------------------------------------------------------------- /src/components/Threads/ThreadContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext } from 'react'; 2 | 3 | import { Channel } from '../../components'; 4 | 5 | import type { PropsWithChildren } from 'react'; 6 | import type { Thread } from 'stream-chat'; 7 | 8 | export type ThreadContextValue = Thread | undefined; 9 | 10 | export const ThreadContext = createContext(undefined); 11 | 12 | export const useThreadContext = () => useContext(ThreadContext); 13 | 14 | export const ThreadProvider = ({ 15 | children, 16 | thread, 17 | }: PropsWithChildren<{ thread?: Thread }>) => ( 18 | 19 | {children} 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /src/components/Threads/ThreadList/ThreadListEmptyPlaceholder.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from '../icons'; 3 | 4 | export const ThreadListEmptyPlaceholder = () => ( 5 |
6 | 7 | {/* TODO: translate */} 8 | No threads here yet... 9 |
10 | ); 11 | -------------------------------------------------------------------------------- /src/components/Threads/ThreadList/ThreadListLoadingIndicator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import type { ThreadManagerState } from 'stream-chat'; 4 | 5 | import { LoadingIndicator as DefaultLoadingIndicator } from '../../Loading'; 6 | import { useChatContext, useComponentContext } from '../../../context'; 7 | import { useStateStore } from '../../../store'; 8 | 9 | const selector = (nextValue: ThreadManagerState) => ({ 10 | isLoadingNext: nextValue.pagination.isLoadingNext, 11 | }); 12 | 13 | export const ThreadListLoadingIndicator = () => { 14 | const { LoadingIndicator = DefaultLoadingIndicator } = useComponentContext(); 15 | const { client } = useChatContext(); 16 | const { isLoadingNext } = useStateStore(client.threads.state, selector); 17 | 18 | if (!isLoadingNext) return null; 19 | 20 | return ( 21 |
22 | 23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/Threads/ThreadList/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ThreadList'; 2 | export * from './ThreadListItem'; 3 | export * from './ThreadListItemUI'; 4 | -------------------------------------------------------------------------------- /src/components/Threads/UnreadCountBadge.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import React from 'react'; 3 | import type { PropsWithChildren } from 'react'; 4 | 5 | export const UnreadCountBadge = ({ 6 | children, 7 | count, 8 | position, 9 | }: PropsWithChildren<{ 10 | count: number; 11 | position?: 'top-right' | 'bottom-right' | 'bottom-left' | 'top-left'; 12 | }>) => ( 13 |
14 | {children} 15 | {count > 0 && ( 16 |
22 | {count} 23 |
24 | )} 25 |
26 | ); 27 | -------------------------------------------------------------------------------- /src/components/Threads/hooks/useThreadManagerState.ts: -------------------------------------------------------------------------------- 1 | import type { ThreadManagerState } from 'stream-chat'; 2 | 3 | import { useChatContext } from '../../../context'; 4 | import { useStateStore } from '../../../store'; 5 | 6 | export const useThreadManagerState = ( 7 | selector: (nextValue: ThreadManagerState) => T, 8 | ) => { 9 | const { client } = useChatContext(); 10 | 11 | return useStateStore(client.threads.state, selector); 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/Threads/hooks/useThreadState.ts: -------------------------------------------------------------------------------- 1 | import type { ThreadState } from 'stream-chat'; 2 | import { useThreadListItemContext } from '../ThreadList'; 3 | import { useThreadContext } from '../ThreadContext'; 4 | import { useStateStore } from '../../../store/'; 5 | 6 | /** 7 | * @description returns thread state, prioritizes `ThreadListItemContext` falls back to `ThreadContext` if not former is not present 8 | */ 9 | export const useThreadState = ( 10 | selector: (nextValue: ThreadState) => T, 11 | ) => { 12 | const listItemThread = useThreadListItemContext(); 13 | const thread = useThreadContext(); 14 | 15 | return useStateStore(listItemThread?.state ?? thread?.state, selector); 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/Threads/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ThreadContext'; 2 | export * from './ThreadList'; 3 | -------------------------------------------------------------------------------- /src/components/Tooltip/__tests__/Tooltip.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Tooltip } from '../Tooltip'; 4 | import { render } from '@testing-library/react'; 5 | 6 | describe('Tooltip', () => { 7 | it('should render as expected', () => { 8 | const { container } = render(); 9 | expect(container).toMatchInlineSnapshot(` 10 |
11 |
14 |
15 | `); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/Tooltip/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useEnterLeaveHandlers'; 2 | -------------------------------------------------------------------------------- /src/components/Tooltip/hooks/useEnterLeaveHandlers.ts: -------------------------------------------------------------------------------- 1 | import type React from 'react'; 2 | import { useCallback, useState } from 'react'; 3 | 4 | export const useEnterLeaveHandlers = ({ 5 | onMouseEnter, 6 | onMouseLeave, 7 | }: Partial>> = {}) => { 8 | const [tooltipVisible, setTooltipVisible] = useState(false); 9 | 10 | const handleEnter: React.MouseEventHandler = useCallback( 11 | (e) => { 12 | setTooltipVisible(true); 13 | onMouseEnter?.(e); 14 | }, 15 | [onMouseEnter], 16 | ); 17 | 18 | const handleLeave: React.MouseEventHandler = useCallback( 19 | (e) => { 20 | setTooltipVisible(false); 21 | onMouseLeave?.(e); 22 | }, 23 | [onMouseLeave], 24 | ); 25 | 26 | return { handleEnter, handleLeave, tooltipVisible }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/Tooltip/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Tooltip'; 2 | -------------------------------------------------------------------------------- /src/components/TypingIndicator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TypingIndicator'; 2 | -------------------------------------------------------------------------------- /src/components/UtilityComponents/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import type { PropsWithChildren, ReactNode } from 'react'; 3 | 4 | type ErrorBoundaryProps = PropsWithChildren<{ fallback?: ReactNode }>; 5 | 6 | export class ErrorBoundary extends Component { 7 | constructor(props: ErrorBoundaryProps) { 8 | super(props); 9 | this.state = { hasError: false }; 10 | } 11 | 12 | static getDerivedStateFromError() { 13 | return { hasError: true }; 14 | } 15 | 16 | componentDidCatch(error: unknown, information: unknown) { 17 | console.error(error, information); 18 | } 19 | 20 | render() { 21 | if (this.state.hasError) { 22 | return this.props.fallback; 23 | } 24 | 25 | return this.props.children; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/UtilityComponents/NullComponent.tsx: -------------------------------------------------------------------------------- 1 | export const NullComponent = () => null; 2 | -------------------------------------------------------------------------------- /src/components/UtilityComponents/index.ts: -------------------------------------------------------------------------------- 1 | export * from './NullComponent'; 2 | export * from './ErrorBoundary'; 3 | -------------------------------------------------------------------------------- /src/components/UtilityComponents/useStableId.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid'; 2 | import { useMemo } from 'react'; 3 | 4 | /** 5 | * The ID is generated using the `nanoid` library and is memoized to ensure 6 | * that it remains the same across renders unless the key changes. 7 | */ 8 | export const useStableId = (key?: string) => { 9 | // eslint-disable-next-line react-hooks/exhaustive-deps 10 | const id = useMemo(() => nanoid(), [key]); 11 | 12 | return id; 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/Window/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Window'; 2 | -------------------------------------------------------------------------------- /src/constants/limits.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_INITIAL_CHANNEL_PAGE_SIZE = 25; 2 | export const DEFAULT_NEXT_CHANNEL_PAGE_SIZE = 100; 3 | export const DEFAULT_JUMP_TO_PAGE_SIZE = 100; 4 | export const DEFAULT_THREAD_PAGE_SIZE = 50; 5 | export const DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD = 250; 6 | export const DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 100 * 1024 * 1024; // 100 MB 7 | export const DEFAULT_HIGHLIGHT_DURATION = 500; 8 | -------------------------------------------------------------------------------- /src/constants/messageTypes.ts: -------------------------------------------------------------------------------- 1 | export const CUSTOM_MESSAGE_TYPE = { 2 | date: 'message.date', 3 | intro: 'channel.intro', 4 | } as const; 5 | -------------------------------------------------------------------------------- /src/context/AttachmentSelectorContext.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | import React, { createContext, useContext } from 'react'; 3 | 4 | export type AttachmentSelectorContextValue = { 5 | fileInput: HTMLInputElement | null; 6 | }; 7 | 8 | const AttachmentSelectorContext = createContext({ 9 | fileInput: null, 10 | }); 11 | 12 | export const AttachmentSelectorContextProvider = ({ 13 | children, 14 | value, 15 | }: PropsWithChildren<{ value: AttachmentSelectorContextValue }>) => ( 16 | 17 | {children} 18 | 19 | ); 20 | 21 | export const useAttachmentSelectorContext = () => useContext(AttachmentSelectorContext); 22 | -------------------------------------------------------------------------------- /src/context/PollContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import type { PropsWithChildren } from 'react'; 3 | import type { Poll } from 'stream-chat'; 4 | 5 | export type PollContextValue = { 6 | poll: Poll; 7 | }; 8 | 9 | export const PollContext = React.createContext(undefined); 10 | 11 | export const PollProvider = ({ 12 | children, 13 | poll, 14 | }: PropsWithChildren<{ 15 | poll: Poll; 16 | }>) => 17 | poll ? ( 18 | 19 | {children} 20 | 21 | ) : null; 22 | 23 | export const usePollContext = () => { 24 | const contextValue = useContext(PollContext); 25 | return contextValue as unknown as PollContextValue; 26 | }; 27 | -------------------------------------------------------------------------------- /src/context/WithComponents.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import type { PropsWithChildren } from 'react'; 3 | 4 | import { ComponentContext } from './ComponentContext'; 5 | import type { ComponentContextValue } from './ComponentContext'; 6 | 7 | export function WithComponents({ 8 | children, 9 | overrides, 10 | }: PropsWithChildren<{ overrides: Partial }>) { 11 | const parentOverrides = useContext(ComponentContext); 12 | const actualOverrides: ComponentContextValue = { ...parentOverrides, ...overrides }; 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/context/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ChannelActionContext'; 2 | export * from './ChannelListContext'; 3 | export * from './ChannelStateContext'; 4 | export * from './ChatContext'; 5 | export * from './ComponentContext'; 6 | export * from './DialogManagerContext'; 7 | export * from './MessageContext'; 8 | export * from './MessageBounceContext'; 9 | export * from './MessageInputContext'; 10 | export * from './MessageListContext'; 11 | export * from './PollContext'; 12 | export * from './TranslationContext'; 13 | export * from './TypingContext'; 14 | export * from './WithComponents'; 15 | -------------------------------------------------------------------------------- /src/context/utils/getDisplayName.ts: -------------------------------------------------------------------------------- 1 | import type { UnknownType } from '../../types/types'; 2 | 3 | export const getDisplayName =

( 4 | Component: React.ComponentType

, 5 | ) => Component.displayName || Component.name || 'Component'; 6 | -------------------------------------------------------------------------------- /src/experimental/MessageActions/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useBaseMessageActionSetFilter'; 2 | export * from './useSplitMessageActionSet'; 3 | -------------------------------------------------------------------------------- /src/experimental/MessageActions/hooks/useSplitMessageActionSet.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | 3 | import type { MessageActionSetItem } from '../MessageActions'; 4 | 5 | export const useSplitMessageActionSet = (messageActionSet: MessageActionSetItem[]) => 6 | useMemo(() => { 7 | const quickActionSet: MessageActionSetItem[] = []; 8 | const dropdownActionSet: MessageActionSetItem[] = []; 9 | 10 | for (const action of messageActionSet) { 11 | if (action.placement === 'quick') quickActionSet.push(action); 12 | if (action.placement === 'dropdown') dropdownActionSet.push(action); 13 | } 14 | 15 | return { dropdownActionSet, quickActionSet }; 16 | }, [messageActionSet]); 17 | -------------------------------------------------------------------------------- /src/experimental/MessageActions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MessageActions'; 2 | export * from './defaults'; 3 | export * from './hooks'; 4 | -------------------------------------------------------------------------------- /src/experimental/Search/SearchBar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SearchBar'; 2 | -------------------------------------------------------------------------------- /src/experimental/Search/SearchResults/SearchResultsPresearch.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useTranslationContext } from '../../../context'; 4 | 5 | import type { SearchSource } from 'stream-chat'; 6 | 7 | export type SearchResultsPresearchProps = { 8 | activeSources: SearchSource[]; 9 | }; 10 | 11 | export const SearchResultsPresearch = () => { 12 | const { t } = useTranslationContext(); 13 | return ( 14 |

15 | {t('Start typing to search')} 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/experimental/Search/SearchResults/SearchSourceResultsEmpty.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslationContext } from '../../../context'; 3 | 4 | export const SearchSourceResultsEmpty = () => { 5 | const { t } = useTranslationContext(); 6 | return ( 7 |
8 | {t('No results found')} 9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/experimental/Search/SearchResults/SearchSourceResultsHeader.tsx: -------------------------------------------------------------------------------- 1 | export const SearchSourceResultsHeader = () => null; 2 | -------------------------------------------------------------------------------- /src/experimental/Search/SearchResults/SearchSourceResultsLoadingIndicator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTranslationContext } from '../../../context'; 3 | import { useSearchSourceResultsContext } from '../SearchSourceResultsContext'; 4 | 5 | export const SearchSourceResultsLoadingIndicator = () => { 6 | const { t } = useTranslationContext(); 7 | const { searchSource } = useSearchSourceResultsContext(); 8 | return ( 9 |
13 | {t(`Searching for ${searchSource.type}...`)} 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/experimental/Search/SearchResults/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SearchResultItem'; 2 | export * from './SearchResults'; 3 | export * from './SearchResultsHeader'; 4 | export * from './SearchResultsPresearch'; 5 | export * from './SearchSourceResultsEmpty'; 6 | export * from './SearchSourceResultsLoadingIndicator'; 7 | export * from './SearchSourceResultList'; 8 | export * from './SearchSourceResultListFooter'; 9 | export * from './SearchSourceResults'; 10 | -------------------------------------------------------------------------------- /src/experimental/Search/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useSearchFocusedMessage'; 2 | export * from './useSearchQueriesInProgress'; 3 | -------------------------------------------------------------------------------- /src/experimental/Search/hooks/useSearchFocusedMessage.ts: -------------------------------------------------------------------------------- 1 | import type { InternalSearchControllerState } from 'stream-chat'; 2 | import { useChatContext } from '../../../context'; 3 | import { useStateStore } from '../../../store'; 4 | 5 | const searchControllerStateSelector = (nextValue: InternalSearchControllerState) => ({ 6 | focusedMessage: nextValue.focusedMessage, 7 | }); 8 | 9 | export const useSearchFocusedMessage = () => { 10 | const { searchController } = useChatContext('Channel'); 11 | const { focusedMessage } = useStateStore( 12 | searchController._internalState, 13 | searchControllerStateSelector, 14 | ); 15 | 16 | return focusedMessage; 17 | }; 18 | -------------------------------------------------------------------------------- /src/experimental/Search/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Search'; 2 | export * from './SearchBar'; 3 | export * from './SearchContext'; 4 | export * from './SearchResults'; 5 | export * from './SearchSourceResultsContext'; 6 | -------------------------------------------------------------------------------- /src/experimental/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MessageActions'; 2 | export * from './Search'; 3 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | export * from './translations'; 2 | export * from './Streami18n'; 3 | export { 4 | defaultDateTimeParser, 5 | defaultTranslatorFunction, 6 | isDate, 7 | isDayOrMoment, 8 | isLanguageSupported, 9 | isNumberOrString, 10 | } from './utils'; 11 | export * from './types'; 12 | -------------------------------------------------------------------------------- /src/i18n/translations.ts: -------------------------------------------------------------------------------- 1 | import deTranslations from './de.json'; 2 | import enTranslations from './en.json'; 3 | import esTranslations from './es.json'; 4 | import frTranslations from './fr.json'; 5 | import hiTranslations from './hi.json'; 6 | import itTranslations from './it.json'; 7 | import jaTranslations from './ja.json'; 8 | import koTranslations from './ko.json'; 9 | import nlTranslations from './nl.json'; 10 | import ptTranslations from './pt.json'; 11 | import ruTranslations from './ru.json'; 12 | import trTranslations from './tr.json'; 13 | 14 | export { 15 | deTranslations, 16 | enTranslations, 17 | esTranslations, 18 | frTranslations, 19 | hiTranslations, 20 | itTranslations, 21 | jaTranslations, 22 | koTranslations, 23 | nlTranslations, 24 | ptTranslations, 25 | ruTranslations, 26 | trTranslations, 27 | }; 28 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './context'; 3 | export * from './i18n'; 4 | export * from './store'; 5 | export * from './types'; 6 | export * from './utils'; 7 | -------------------------------------------------------------------------------- /src/mock-builders/__test__/translator.test.js: -------------------------------------------------------------------------------- 1 | import { mockTranslatorFunction } from '../translator'; 2 | 3 | describe('mockTranslatorFunction', () => { 4 | it('returns string if no map passed in as second argument', () => { 5 | const result = mockTranslatorFunction(''); 6 | expect(result).toStrictEqual(''); 7 | }); 8 | it('inserts a single param value', () => { 9 | const result = mockTranslatorFunction('{{ testKey }}', { testKey: 'test' }); 10 | expect(result).toStrictEqual('test'); 11 | }); 12 | it('inserts multiple param values', () => { 13 | const result = mockTranslatorFunction( 14 | '{{ testKey1 }}, {{ testKey2 }}, and {{ testKey3 }}', 15 | { 16 | testKey1: 'test1', 17 | testKey2: 'test2', 18 | testKey3: 'test3', 19 | }, 20 | ); 21 | expect(result).toStrictEqual('test1, test2, and test3'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/mock-builders/api/getOrCreateChannel.js: -------------------------------------------------------------------------------- 1 | import { mockedApiResponse } from './utils.js'; 2 | 3 | /** 4 | * Returns the api response for queryChannel api. 5 | * 6 | * api - /channels/{type}/{id}/query 7 | * 8 | * @param {*} channel 9 | */ 10 | export const getOrCreateChannelApi = ( 11 | channel = { 12 | channel: {}, 13 | members: [], 14 | messages: [], 15 | pinnedMessages: [], 16 | read: [], 17 | }, 18 | ) => { 19 | const result = { 20 | channel: channel.channel, 21 | duration: 0.01, 22 | members: channel.members, 23 | messages: channel.messages, 24 | pinnedMessages: channel.pinnedMessages, 25 | read: channel.read, 26 | }; 27 | if (channel.draft) { 28 | result.draft = channel.draft; 29 | } 30 | 31 | return mockedApiResponse(result, 'post'); 32 | }; 33 | -------------------------------------------------------------------------------- /src/mock-builders/api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Hook to mock the calls made through axios module. 3 | * You should provide the responses of Apis in order that they will be called. 4 | * You should use api functions from current directory to build these responses. 5 | * e.g., queryChannelsApi, sendMessageApi 6 | * 7 | * @param {StreamClient} client 8 | * @param {*} apiResponses 9 | */ 10 | export const useMockedApis = (client, apiResponses) => { 11 | apiResponses.forEach(({ response, type }) => { 12 | jest 13 | .spyOn(client.axiosInstance, type) 14 | .mockImplementation() 15 | .mockResolvedValue(response); 16 | }); 17 | }; 18 | 19 | export * from './queryChannels'; 20 | export * from './queryMembers'; 21 | export * from './queryUsers'; 22 | export * from './getOrCreateChannel'; 23 | export * from './markRead'; 24 | export * from './threadReplies'; 25 | export * from './sendMessage'; 26 | export * from './error'; 27 | -------------------------------------------------------------------------------- /src/mock-builders/api/markRead.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the api response for markRead api 3 | * 4 | * api - /read 5 | * 6 | * @param {*} channel Initialized channel object. 7 | */ 8 | export const markReadApi = (channel) => ({ 9 | duration: 0.01, 10 | event: { 11 | channel_id: channel.id, 12 | channel_type: channel.type, 13 | cid: channel.cid, 14 | created_at: new Date().toISOString(), 15 | last_read_message_id: channel.state.messages.slice(-1)[0], 16 | type: 'message.read', 17 | user: channel.getClient().user, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /src/mock-builders/api/queryChannels.js: -------------------------------------------------------------------------------- 1 | import { mockedApiResponse } from './utils.js'; 2 | 3 | /** 4 | * Returns the api response for queryChannels api 5 | * 6 | * api - /channels 7 | * 8 | * @param {*} channels Array of channel objects. 9 | */ 10 | export const queryChannelsApi = (channels = []) => { 11 | const result = { 12 | channels, 13 | duration: 0.01, 14 | }; 15 | 16 | return mockedApiResponse(result, 'post'); 17 | }; 18 | -------------------------------------------------------------------------------- /src/mock-builders/api/queryMembers.js: -------------------------------------------------------------------------------- 1 | import { mockedApiResponse } from './utils.js'; 2 | 3 | /** 4 | * Returns the api response for queryMembers api 5 | * 6 | * api - /query_members 7 | * 8 | * @param {*} members Array of User objects. 9 | */ 10 | export const queryMembersApi = (members = []) => { 11 | const result = { 12 | members, 13 | }; 14 | 15 | return mockedApiResponse(result, 'get'); 16 | }; 17 | -------------------------------------------------------------------------------- /src/mock-builders/api/queryUsers.js: -------------------------------------------------------------------------------- 1 | import { mockedApiResponse } from './utils.js'; 2 | 3 | /** 4 | * Returns the api response for queryUsers api 5 | * 6 | * api - /users 7 | * 8 | * @param {*} users Array of User objects. 9 | */ 10 | export const queryUsersApi = (users = []) => { 11 | const result = { 12 | users, 13 | }; 14 | 15 | return mockedApiResponse(result, 'get'); 16 | }; 17 | -------------------------------------------------------------------------------- /src/mock-builders/api/sendMessage.js: -------------------------------------------------------------------------------- 1 | import { mockedApiResponse } from './utils.js'; 2 | 3 | /** 4 | * Returns the api response for sendMessage api. 5 | * 6 | * api - /channels/{type}/{id}/message 7 | * 8 | * @param {*} message 9 | */ 10 | export const sendMessageApi = (message = {}) => { 11 | const result = { 12 | message, 13 | }; 14 | 15 | return mockedApiResponse(result, 'post'); 16 | }; 17 | -------------------------------------------------------------------------------- /src/mock-builders/api/threadReplies.js: -------------------------------------------------------------------------------- 1 | import { mockedApiResponse } from './utils.js'; 2 | 3 | /** 4 | * Returns the api response for thread replies api 5 | * 6 | * api - /messages/${parent_id}/replies 7 | * 8 | * @param {*} replies Array of message objects. 9 | */ 10 | export const threadRepliesApi = (replies = []) => { 11 | const result = { 12 | messages: replies, 13 | }; 14 | 15 | return mockedApiResponse(result, 'get'); 16 | }; 17 | -------------------------------------------------------------------------------- /src/mock-builders/api/utils.js: -------------------------------------------------------------------------------- 1 | export const mockedApiResponse = (response, type = 'get', status = 200) => ({ 2 | response: { 3 | data: response, 4 | status, 5 | }, 6 | type, 7 | }); 8 | -------------------------------------------------------------------------------- /src/mock-builders/browser/AnalyserNode.js: -------------------------------------------------------------------------------- 1 | export class AnalyserNodeMock { 2 | disconnect = jest.fn(); 3 | getByteFrequencyData = jest.fn(); 4 | } 5 | -------------------------------------------------------------------------------- /src/mock-builders/browser/AudioContext.js: -------------------------------------------------------------------------------- 1 | class Connectable { 2 | connect = jest.fn(); 3 | disconnect = jest.fn(); 4 | } 5 | 6 | export class AudioContextMock { 7 | // eslint-disable-next-line @typescript-eslint/no-empty-function 8 | constructor() {} 9 | 10 | createAnalyser = jest.fn(() => new Connectable()); 11 | createMediaStreamSource = jest.fn(() => new Connectable()); 12 | close = jest.fn(); 13 | } 14 | -------------------------------------------------------------------------------- /src/mock-builders/browser/EventEmitter.js: -------------------------------------------------------------------------------- 1 | export class EventEmitterMock { 2 | addEventListener = jest.fn(); 3 | removeEventListener = jest.fn(); 4 | } 5 | -------------------------------------------------------------------------------- /src/mock-builders/browser/HTMLMediaElement.js: -------------------------------------------------------------------------------- 1 | jest.spyOn(window.HTMLMediaElement.prototype, 'pause').mockImplementation(); 2 | -------------------------------------------------------------------------------- /src/mock-builders/browser/MediaRecorder.js: -------------------------------------------------------------------------------- 1 | import { EventEmitterMock } from './EventEmitter'; 2 | 3 | export class MediaRecorderMock extends EventEmitterMock { 4 | constructor() { 5 | super(); 6 | this.start = jest.fn(); 7 | this.pause = jest.fn(); 8 | this.resume = jest.fn(); 9 | this.stop = jest.fn(); 10 | } 11 | 12 | static isTypeSupported = jest.fn().mockReturnValue(true); 13 | } 14 | -------------------------------------------------------------------------------- /src/mock-builders/browser/ResizeObserver.js: -------------------------------------------------------------------------------- 1 | export class ResizeObserverMock { 2 | static observers = []; 3 | 4 | active = false; 5 | cb; 6 | 7 | constructor(cb) { 8 | this.cb = cb; 9 | ResizeObserverMock.observers.push(this); 10 | } 11 | 12 | observe = jest.fn(() => { 13 | this.active = true; 14 | }); 15 | disconnect = jest.fn(() => { 16 | this.active = false; 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/mock-builders/browser/events/dataavailable.js: -------------------------------------------------------------------------------- 1 | export const generateDataavailableEvent = ( 2 | { dataOverrides, mediaRecorder } = { 3 | dataOverrides: {}, 4 | mediaRecorder: new window.MediaRecorder(), 5 | }, 6 | ) => ({ 7 | bubbles: false, 8 | cancelable: false, 9 | cancelBubble: false, 10 | composed: false, 11 | currentTarget: mediaRecorder, 12 | data: new Blob([0x48], { type: 'audio/webm' }), 13 | defaultPrevented: false, 14 | eventPhase: 0, 15 | isTrusted: true, 16 | returnValue: true, 17 | srcElement: mediaRecorder, 18 | target: mediaRecorder, 19 | timecode: 1713214079256.997, 20 | timeStamp: 11853.20000000298, 21 | type: 'dataavailable', 22 | ...dataOverrides, 23 | }); 24 | -------------------------------------------------------------------------------- /src/mock-builders/browser/index.js: -------------------------------------------------------------------------------- 1 | export * from './AnalyserNode'; 2 | export * from './AudioContext'; 3 | export * from './EventEmitter'; 4 | export * from './MediaRecorder'; 5 | export * from './ResizeObserver'; 6 | -------------------------------------------------------------------------------- /src/mock-builders/event/channelDeleted.js: -------------------------------------------------------------------------------- 1 | export default (client, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | cid: channel.cid, 5 | type: 'channel.deleted', 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/mock-builders/event/channelHidden.js: -------------------------------------------------------------------------------- 1 | export default (client, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | cid: channel.cid, 5 | type: 'channel.hidden', 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/mock-builders/event/channelTruncated.js: -------------------------------------------------------------------------------- 1 | export default (client, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | cid: channel.cid, 5 | type: 'channel.truncated', 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/mock-builders/event/channelUpdated.js: -------------------------------------------------------------------------------- 1 | export default (client, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | cid: channel.cid, 5 | type: 'channel.updated', 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/mock-builders/event/channelVisible.js: -------------------------------------------------------------------------------- 1 | export default (client, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | channel_id: channel.id, 5 | channel_type: channel.type, 6 | cid: channel.cid, 7 | type: 'channel.visible', 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /src/mock-builders/event/connectionChanged.js: -------------------------------------------------------------------------------- 1 | export default (client, online) => { 2 | client.dispatchEvent({ 3 | online, 4 | type: 'connection.changed', 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /src/mock-builders/event/connectionRecovered.js: -------------------------------------------------------------------------------- 1 | export default (client) => { 2 | client.dispatchEvent({ 3 | type: 'connection.recovered', 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /src/mock-builders/event/draftDeleted.ts: -------------------------------------------------------------------------------- 1 | import type { DraftResponse, StreamChat } from 'stream-chat'; 2 | 3 | export const dispatchDraftDeleted = ({ 4 | client, 5 | draft, 6 | }: { 7 | client: StreamChat; 8 | draft: DraftResponse; 9 | }) => { 10 | client.dispatchEvent({ 11 | cid: draft.channel_cid, 12 | created_at: new Date().toISOString(), 13 | draft, 14 | type: 'draft.deleted', 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/mock-builders/event/draftUpdated.ts: -------------------------------------------------------------------------------- 1 | import type { DraftResponse, StreamChat } from 'stream-chat'; 2 | 3 | export const dispatchDraftUpdated = ({ 4 | client, 5 | draft, 6 | }: { 7 | client: StreamChat; 8 | draft: DraftResponse; 9 | }) => { 10 | client.dispatchEvent({ 11 | cid: draft.channel_cid, 12 | created_at: new Date().toISOString(), 13 | draft, 14 | type: 'draft.updated', 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/mock-builders/event/messageDeleted.js: -------------------------------------------------------------------------------- 1 | export default (client, message, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | cid: channel.cid, 5 | message, 6 | type: 'message.deleted', 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /src/mock-builders/event/messageNew.js: -------------------------------------------------------------------------------- 1 | export default (client, newMessage, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | channel_id: channel.id, 5 | channel_type: channel.type, 6 | cid: channel.cid, 7 | message: newMessage, 8 | type: 'message.new', 9 | user: newMessage.user, 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/mock-builders/event/messageRead.js: -------------------------------------------------------------------------------- 1 | export default (client, user, channel = {}, last_read_message_id) => { 2 | const event = { 3 | channel, 4 | cid: channel.cid, 5 | created_at: new Date().toISOString(), 6 | last_read_message_id: last_read_message_id || 'last_read_message_id', 7 | type: 'message.read', 8 | user, 9 | }; 10 | client.dispatchEvent(event); 11 | 12 | return event; 13 | }; 14 | -------------------------------------------------------------------------------- /src/mock-builders/event/messageUndeleted.js: -------------------------------------------------------------------------------- 1 | export default (client, message, channel = {}) => { 2 | const [channel_id, channel_type] = channel.cid.split(':'); 3 | client.dispatchEvent({ 4 | channel_id, 5 | channel_type, 6 | cid: channel.cid, 7 | message, 8 | type: 'message.undeleted', 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /src/mock-builders/event/messageUpdated.js: -------------------------------------------------------------------------------- 1 | export default (client, newMessage, channel = {}, user) => { 2 | const [channel_id, channel_type] = channel.cid.split(':'); 3 | client.dispatchEvent({ 4 | channel, 5 | channel_id, 6 | channel_type, 7 | cid: channel.cid, 8 | message: newMessage, 9 | type: 'message.updated', 10 | user: user || newMessage.user, 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /src/mock-builders/event/notificationAddedToChannel.js: -------------------------------------------------------------------------------- 1 | export default (client, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | cid: channel.cid, 5 | type: 'notification.added_to_channel', 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/mock-builders/event/notificationMarkRead.js: -------------------------------------------------------------------------------- 1 | import { generateUser } from '../generator'; 2 | 3 | export default ({ channel, client, payload, user }) => 4 | client.dispatchEvent({ 5 | channel: channel?.data, 6 | channel_id: channel?.id, 7 | channel_type: channel?.type, 8 | cid: channel?.cid, 9 | created_at: new Date().toISOString(), 10 | last_read_message_id: 'user_id-rfh6ieeQ8XCqabLN-GCHo', 11 | total_unread_count: 3, 12 | type: 'notification.mark_read', 13 | unread_channels: 1, 14 | unread_count: 3, 15 | user: user || generateUser(), 16 | ...payload, 17 | }); 18 | -------------------------------------------------------------------------------- /src/mock-builders/event/notificationMessageNew.js: -------------------------------------------------------------------------------- 1 | export default (client, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | cid: channel.cid, 5 | type: 'notification.message_new', 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/mock-builders/event/notificationMutesUpdated.js: -------------------------------------------------------------------------------- 1 | export default (client, mutes = []) => { 2 | client.dispatchEvent({ 3 | created_at: '2020-05-26T07:11:57.968294216Z', 4 | me: { 5 | ...client.user, 6 | channel_mutes: [], 7 | mutes, 8 | }, 9 | type: 'notification.mutes_updated', 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/mock-builders/event/notificationRemovedFromChannel.js: -------------------------------------------------------------------------------- 1 | export default (client, channel = {}) => { 2 | client.dispatchEvent({ 3 | channel, 4 | cid: channel.cid, 5 | type: 'notification.removed_from_channel', 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/mock-builders/event/userUpdated.js: -------------------------------------------------------------------------------- 1 | export default (client, user) => { 2 | client.dispatchEvent({ 3 | created_at: new Date().toISOString(), 4 | type: 'user.updated', 5 | user, 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/mock-builders/generator/index.js: -------------------------------------------------------------------------------- 1 | export * from './attachment'; 2 | export * from './channel'; 3 | export * from './member'; 4 | export * from './message'; 5 | export * from './poll'; 6 | export * from './reaction'; 7 | export * from './user'; 8 | -------------------------------------------------------------------------------- /src/mock-builders/generator/member.js: -------------------------------------------------------------------------------- 1 | export const generateMember = (options = {}) => ({ 2 | invited: false, 3 | is_moderator: false, 4 | role: 'member', 5 | user: options.user, 6 | user_id: options.user.id, 7 | ...options, 8 | }); 9 | -------------------------------------------------------------------------------- /src/mock-builders/generator/message.js: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid'; 2 | 3 | export const generateMessage = (options) => ({ 4 | __html: '

regular

', 5 | attachments: [], 6 | created_at: new Date(), 7 | html: '

regular

', 8 | id: nanoid(), 9 | mentioned_users: [], 10 | pinned_at: null, 11 | status: 'received', 12 | text: nanoid(), 13 | type: 'regular', 14 | updated_at: new Date(), 15 | user: null, 16 | ...options, 17 | }); 18 | -------------------------------------------------------------------------------- /src/mock-builders/generator/messageDraft.ts: -------------------------------------------------------------------------------- 1 | import { generateMessage } from './message'; 2 | import type { DraftResponse } from 'stream-chat'; 3 | 4 | export const generateMessageDraft = ({ 5 | channel_cid, 6 | ...customMsgDraft 7 | }: Partial) => 8 | ({ 9 | channel_cid, 10 | created_at: new Date().toISOString(), 11 | message: generateMessage(), 12 | ...customMsgDraft, 13 | }) as DraftResponse; 14 | -------------------------------------------------------------------------------- /src/mock-builders/generator/user.js: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid'; 2 | 3 | export const generateUser = (options = {}) => ({ 4 | banned: false, 5 | created_at: '2020-04-27T13:39:49.331742Z', 6 | id: nanoid(), 7 | image: nanoid(), 8 | name: nanoid(), 9 | online: false, 10 | role: 'user', 11 | updated_at: '2020-04-27T13:39:49.332087Z', 12 | ...options, 13 | }); 14 | -------------------------------------------------------------------------------- /src/mock-builders/translator.js: -------------------------------------------------------------------------------- 1 | export const mockTranslatorFunction = (value, params = {}) => 2 | Object.keys(params).reduce((acc, key) => { 3 | const regexp = new RegExp(`\\{\\{\\s${key}\\s\\}\\}`, 'g'); 4 | return acc.replace(regexp, params[key]); 5 | }, value); 6 | 7 | export const mockTranslationContext = { 8 | // Mock translation function that always falls back to the key. 9 | // It also handles nested keys, e.g. 'aria/Send' will fall back to 'Send'. 10 | t: (key) => key.split('/').pop(), 11 | }; 12 | -------------------------------------------------------------------------------- /src/plugins/Emojis/icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const EmojiPickerIcon = () => ( 4 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/plugins/Emojis/index.ts: -------------------------------------------------------------------------------- 1 | export * from './EmojiPicker'; 2 | export * from './middleware'; 3 | export { EmojiPickerIcon } from './icons'; 4 | -------------------------------------------------------------------------------- /src/plugins/Emojis/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from './textComposerEmojiMiddleware'; 2 | -------------------------------------------------------------------------------- /src/store/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useStateStore'; 2 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks'; 2 | -------------------------------------------------------------------------------- /src/types/defaultDataInterfaces.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-object-type */ 2 | export interface DefaultChannelData { 3 | image?: string; 4 | name?: string; 5 | subtitle?: string; 6 | } 7 | 8 | export interface DefaultAttachmentData {} 9 | 10 | export interface DefaultCommandData {} 11 | 12 | export interface DefaultEventData {} 13 | 14 | export interface DefaultMemberData {} 15 | 16 | export interface DefaultMessageData {} 17 | 18 | export interface DefaultPollOptionData {} 19 | 20 | export interface DefaultPollData {} 21 | 22 | export interface DefaultReactionData {} 23 | 24 | export interface DefaultUserData {} 25 | 26 | export interface DefaultThreadData {} 27 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type { ChannelUnreadUiState } from './types'; 2 | export type * from './defaultDataInterfaces'; 3 | -------------------------------------------------------------------------------- /src/utils/browsers.ts: -------------------------------------------------------------------------------- 1 | export const isSafari = () => { 2 | if (typeof navigator === 'undefined') return false; 3 | return /^((?!chrome|android).)*safari/i.test(navigator.userAgent || ''); 4 | }; 5 | -------------------------------------------------------------------------------- /src/utils/deprecationWarning.ts: -------------------------------------------------------------------------------- 1 | export const deprecationAndReplacementWarning =

[][]>( 2 | pairs: P, 3 | component: string, 4 | ) => { 5 | pairs.forEach((data) => { 6 | const [[oldName, oldValue], [newName, newValue]] = [ 7 | Object.entries(data[0])[0], 8 | Object.entries(data[1])[0], 9 | ]; 10 | 11 | if ( 12 | (typeof oldValue !== 'undefined' && typeof newValue === 'undefined') || 13 | (typeof oldValue !== 'undefined' && typeof newValue !== 'undefined') 14 | ) { 15 | console.warn( 16 | `[Deprecation notice (${component})]: prefer using prop ${newName} instead of ${oldName}`, 17 | ); 18 | } 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getChannel'; 2 | export * from './getWholeChar'; 3 | -------------------------------------------------------------------------------- /src/utils/mergeDeep.ts: -------------------------------------------------------------------------------- 1 | import mergeWith from 'lodash.mergewith'; 2 | 3 | const overrideEverything = (_: unknown, source: unknown) => source; 4 | 5 | export const mergeDeep = (target: TObject, source: TSource) => 6 | mergeWith(target, source, overrideEverything); 7 | 8 | const overrideUndefinedOnly = (object: unknown, source: unknown) => object ?? source; 9 | 10 | export const mergeDeepUndefined = (target: TObject, source: TSource) => 11 | mergeWith(target, source, overrideUndefinedOnly); 12 | --------------------------------------------------------------------------------