diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 9d2dc40ad1..720c66ca91 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -31,7 +31,7 @@ jobs: uses: ./.github/actions/install-and-build-sdk - name: Lint run: yarn lint - - name: Typecheck tests - run: yarn workspace stream-chat-react-native-core test:typecheck + - name: Typecheck packages and example apps + run: yarn typecheck - name: Test run: yarn test:coverage diff --git a/configs/typescript-config/base.json b/configs/typescript-config/base.json new file mode 100644 index 0000000000..dc63c95f3e --- /dev/null +++ b/configs/typescript-config/base.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Stream Base", + "compilerOptions": { + "target": "ES2022", + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true + } +} diff --git a/configs/typescript-config/library.json b/configs/typescript-config/library.json new file mode 100644 index 0000000000..19286c2b4f --- /dev/null +++ b/configs/typescript-config/library.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Stream Library (React Native)", + "extends": "./base.json", + "compilerOptions": { + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "esnext", + "moduleResolution": "bundler", + "jsx": "react-native", + "allowJs": true, + "checkJs": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noEmitOnError": false + } +} diff --git a/configs/typescript-config/package.json b/configs/typescript-config/package.json new file mode 100644 index 0000000000..5ad832cc6b --- /dev/null +++ b/configs/typescript-config/package.json @@ -0,0 +1,16 @@ +{ + "name": "@stream-io/typescript-config", + "version": "0.0.0", + "description": "Shared TypeScript config presets for the Stream Chat React Native SDK monorepo.", + "license": "SEE LICENSE IN LICENSE", + "private": true, + "files": [ + "base.json", + "library.json" + ], + "exports": { + "./base.json": "./base.json", + "./library.json": "./library.json", + "./package.json": "./package.json" + } +} diff --git a/examples/ExpoMessaging/app/channel/[cid]/index.tsx b/examples/ExpoMessaging/app/channel/[cid]/index.tsx index 80598ee8d0..baee48930f 100644 --- a/examples/ExpoMessaging/app/channel/[cid]/index.tsx +++ b/examples/ExpoMessaging/app/channel/[cid]/index.tsx @@ -51,8 +51,8 @@ export default function ChannelScreen() { payload, ) => { const { message, defaultHandler, emitter } = payload; - const { shared_location } = message ?? {}; - if (emitter === 'messageContent' && shared_location) { + const shared_location = message?.shared_location; + if (emitter === 'messageContent' && message && shared_location) { // Create url params from shared_location const params = Object.entries(shared_location) .map(([key, value]) => `${key}=${value}`) diff --git a/examples/ExpoMessaging/app/map/[id].tsx b/examples/ExpoMessaging/app/map/[id].tsx index 89a5bad284..59083e80de 100644 --- a/examples/ExpoMessaging/app/map/[id].tsx +++ b/examples/ExpoMessaging/app/map/[id].tsx @@ -13,11 +13,13 @@ import MapView, { MapMarker, Marker } from 'react-native-maps'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Stack, useLocalSearchParams } from 'expo-router'; -import { SharedLocationResponse, StreamChat } from 'stream-chat'; +import { Channel, SharedLocationResponse, StreamChat } from 'stream-chat'; import { useChatContext, useHandleLiveLocationEvents, useTheme } from 'stream-chat-expo'; import { AppContext } from '../../context/AppContext'; +import type { AppTheme } from '@/types/theme'; + export type SharedLiveLocationParamsStringType = SharedLocationResponse & { latitude: string; longitude: string; @@ -40,12 +42,19 @@ const MapScreenFooter = ({ theme: { colors: { accent_blue, accent_red, grey }, }, - } = useTheme(); - const liveLocationActive = isLiveLocationStopped ? false : new Date(end_at) > new Date(); + } = useTheme() as unknown as { theme: AppTheme }; const endedAtDate = end_at ? new Date(end_at) : null; + const liveLocationActive = isLiveLocationStopped + ? false + : endedAtDate + ? endedAtDate > new Date() + : false; const formattedEndedAt = endedAtDate ? endedAtDate.toLocaleString() : ''; const stopSharingLiveLocation = useCallback(async () => { + if (!channel || !locationResponse) { + return; + } await channel.stopLiveLocationSharing(locationResponse); }, [channel, locationResponse]); @@ -53,7 +62,7 @@ const MapScreenFooter = ({ return null; } - const isCurrentUser = user_id === client.user.id; + const isCurrentUser = user_id === client.user?.id; if (!isCurrentUser) { return ( @@ -107,7 +116,7 @@ export default function MapScreen() { theme: { colors: { accent_blue }, }, - } = useTheme(); + } = useTheme() as unknown as { theme: AppTheme }; const { width, height } = useWindowDimensions(); const aspect_ratio = width / height; @@ -131,7 +140,10 @@ export default function MapScreen() { }, []); const { isLiveLocationStopped, locationResponse } = useHandleLiveLocationEvents({ - channel, + // This screen is only reached by navigating from an open channel (which sets `channel` + // in AppContext), so a channel is always present here even though the context type + // allows `undefined`. + channel: channel as Channel, messageId: shared_location.message_id, onLocationUpdate, }); @@ -152,13 +164,15 @@ export default function MapScreen() { const region = useMemo(() => { const latitudeDelta = 0.1; const longitudeDelta = latitudeDelta * aspect_ratio; + // Fall back to the initial coordinates parsed from the route params when no live + // location update has arrived yet, so the values are always concrete numbers. return { - latitude: locationResponse?.latitude, - longitude: locationResponse?.longitude, + latitude: locationResponse?.latitude ?? parseFloat(shared_location.latitude), + longitude: locationResponse?.longitude ?? parseFloat(shared_location.longitude), latitudeDelta, longitudeDelta, }; - }, [aspect_ratio, locationResponse]); + }, [aspect_ratio, locationResponse, shared_location.latitude, shared_location.longitude]); return ( @@ -181,7 +195,7 @@ export default function MapScreen() { @@ -193,7 +207,7 @@ export default function MapScreen() { client={client} shared_location={shared_location} locationResponse={locationResponse} - isLiveLocationStopped={isLiveLocationStopped} + isLiveLocationStopped={isLiveLocationStopped ?? undefined} /> ); diff --git a/examples/ExpoMessaging/components/ChatWrapper.tsx b/examples/ExpoMessaging/components/ChatWrapper.tsx index 8eedbcaed1..3a8da325a8 100644 --- a/examples/ExpoMessaging/components/ChatWrapper.tsx +++ b/examples/ExpoMessaging/components/ChatWrapper.tsx @@ -42,8 +42,11 @@ export const ChatWrapper = ({ children }: PropsWithChildren) => { streami18n.registerTranslation('en', { ...enTranslations, + // Custom translation key used by the live-location feature. It is not part of the + // SDK's `enTranslations` (a closed key set), but i18next resolves arbitrary keys at + // runtime, so we cast to the expected parameter type to register it. 'timestamp/Location end at': '{{ milliseconds | durationFormatter(withSuffix: false) }}', - }); + } as typeof enTranslations); const theme = useStreamChatTheme(); const componentOverrides = useExpoMessagingComponentOverrides(); diff --git a/examples/ExpoMessaging/components/LocationSharing/CreateLocationModal.tsx b/examples/ExpoMessaging/components/LocationSharing/CreateLocationModal.tsx index 6574b7bb95..6459939bcd 100644 --- a/examples/ExpoMessaging/components/LocationSharing/CreateLocationModal.tsx +++ b/examples/ExpoMessaging/components/LocationSharing/CreateLocationModal.tsx @@ -22,6 +22,8 @@ import { useTranslationContext, } from 'stream-chat-expo'; +import type { AppTheme } from '@/types/theme'; + type LiveLocationCreateModalProps = { visible: boolean; onRequestClose: () => void; @@ -41,7 +43,7 @@ export const LiveLocationCreateModal = ({ theme: { colors: { accent_blue, grey, grey_whisper }, }, - } = useTheme(); + } = useTheme() as unknown as { theme: AppTheme }; const { t } = useTranslationContext(); const mapRef = useRef(null); const markerRef = useRef(null); @@ -112,10 +114,13 @@ export const LiveLocationCreateModal = ({ const options: AlertButton[] = endedAtDurations.map((offsetMs) => ({ text: t('timestamp/Location end at', { milliseconds: offsetMs }), onPress: async () => { + if (!location) { + return; + } await messageComposer.locationComposer.setData({ durationMs: offsetMs, - latitude: location?.latitude, - longitude: location?.longitude, + latitude: location.latitude, + longitude: location.longitude, }); await messageComposer.sendLocation(); onRequestClose(); diff --git a/examples/ExpoMessaging/components/LocationSharing/MessageLocation.tsx b/examples/ExpoMessaging/components/LocationSharing/MessageLocation.tsx index 87392f9dab..1ba7e8561b 100644 --- a/examples/ExpoMessaging/components/LocationSharing/MessageLocation.tsx +++ b/examples/ExpoMessaging/components/LocationSharing/MessageLocation.tsx @@ -19,6 +19,8 @@ import { useTheme, } from 'stream-chat-expo'; +import type { AppTheme } from '@/types/theme'; + const MessageLocationFooter = ({ client, shared_location, @@ -32,9 +34,9 @@ const MessageLocationFooter = ({ theme: { colors: { grey }, }, - } = useTheme(); - const liveLocationActive = new Date(end_at) > new Date(); + } = useTheme() as unknown as { theme: AppTheme }; const endedAtDate = end_at ? new Date(end_at) : null; + const liveLocationActive = endedAtDate ? endedAtDate > new Date() : false; const formattedEndedAt = endedAtDate ? endedAtDate.toLocaleString() : ''; const stopSharingLiveLocation = useCallback(async () => { @@ -44,7 +46,7 @@ const MessageLocationFooter = ({ if (!end_at) { return null; } - const isCurrentUser = user_id === client.user.id; + const isCurrentUser = user_id === client.user?.id; if (!isCurrentUser) { return ( @@ -72,7 +74,10 @@ const MessageLocationFooter = ({ export const MessageLocation = ({ message }: MessageLocationProps) => { const { client } = useChatContext(); const { shared_location } = message; - const { latitude, longitude } = shared_location || {}; + // Coordinates are only rendered when `shared_location` exists (the component returns + // `null` below otherwise). Default to 0 so the values are concrete numbers for the + // `Region`/`LatLng` shapes the hooks below build. + const { latitude = 0, longitude = 0 } = shared_location || {}; const mapRef = useRef(null); const markerRef = useRef(null); @@ -83,7 +88,7 @@ export const MessageLocation = ({ message }: MessageLocationProps) => { theme: { colors: { accent_blue }, }, - } = useTheme(); + } = useTheme() as unknown as { theme: AppTheme }; const region = useMemo(() => { const latitudeDelta = 0.1; @@ -132,7 +137,7 @@ export const MessageLocation = ({ message }: MessageLocationProps) => { diff --git a/examples/ExpoMessaging/context/AppContext.tsx b/examples/ExpoMessaging/context/AppContext.tsx index f69d4a2171..70970bdc5f 100644 --- a/examples/ExpoMessaging/context/AppContext.tsx +++ b/examples/ExpoMessaging/context/AppContext.tsx @@ -12,8 +12,8 @@ export type AppContextType = { export const AppContext = createContext({ channel: undefined, - setChannel: undefined, - setThread: undefined, + setChannel: () => {}, + setThread: () => {}, thread: undefined, }); diff --git a/examples/ExpoMessaging/hooks/useStreamChatTheme.ts b/examples/ExpoMessaging/hooks/useStreamChatTheme.ts index 80c9e604b8..3a6330274e 100644 --- a/examples/ExpoMessaging/hooks/useStreamChatTheme.ts +++ b/examples/ExpoMessaging/hooks/useStreamChatTheme.ts @@ -5,7 +5,7 @@ import type { DeepPartial, Theme } from 'stream-chat-expo'; export const useStreamChatTheme = () => { const colorScheme = useColorScheme(); - const getChatStyle = (): DeepPartial => ({ + const getChatStyle = (): DeepPartial & { colors: Record } => ({ avatar: { image: { height: 32, diff --git a/examples/ExpoMessaging/icons/ShareLocationIcon.tsx b/examples/ExpoMessaging/icons/ShareLocationIcon.tsx index 27078b6e1a..0205a05e96 100644 --- a/examples/ExpoMessaging/icons/ShareLocationIcon.tsx +++ b/examples/ExpoMessaging/icons/ShareLocationIcon.tsx @@ -2,13 +2,15 @@ import Svg, { Path } from 'react-native-svg'; import { useTheme } from 'stream-chat-expo'; +import type { AppTheme } from '@/types/theme'; + // Icon for "Share Location" button, next to input box. export const ShareLocationIcon = () => { const { theme: { colors: { grey }, }, - } = useTheme(); + } = useTheme() as unknown as { theme: AppTheme }; return ( ` (see `useStreamChatTheme`), + * so `theme.colors` does exist at runtime. `AppTheme` augments the SDK type to reflect + * that runtime shape so components can read `theme.colors.X` without migrating the colors. + */ +export type AppTheme = Theme & { colors: Record }; diff --git a/examples/SampleApp/package.json b/examples/SampleApp/package.json index 903b94629f..5703d15b93 100644 --- a/examples/SampleApp/package.json +++ b/examples/SampleApp/package.json @@ -85,10 +85,11 @@ "@react-native/metro-config": "0.85.3", "@react-native/typescript-config": "0.85.3", "@rnx-kit/metro-config": "^2.1.0", + "@stream-io/typescript-config": "workspace:^", "@types/lodash.mergewith": "^4.6.9", "@types/react": "^19.2.0", "babel-plugin-react-compiler": "^1.0.0", - "typescript": "5.9.3" + "typescript": "6.0.3" }, "engines": { "node": ">=20.19.4" diff --git a/examples/SampleApp/src/components/DraftsList.tsx b/examples/SampleApp/src/components/DraftsList.tsx index 63ca055ef7..956fb7e023 100644 --- a/examples/SampleApp/src/components/DraftsList.tsx +++ b/examples/SampleApp/src/components/DraftsList.tsx @@ -1,9 +1,15 @@ import { useCallback, useEffect, useMemo } from 'react'; import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native'; -import { useIsFocused, useNavigation } from '@react-navigation/native'; +import { NavigationProp, useIsFocused, useNavigation } from '@react-navigation/native'; import dayjs from 'dayjs'; -import { ChannelResponse, DraftMessage, DraftResponse, MessageResponseBase } from 'stream-chat'; +import { + ChannelResponse, + DraftMessage, + DraftResponse, + LocalMessage, + MessageResponseBase, +} from 'stream-chat'; import { useChatContext, useStateStore, @@ -14,6 +20,7 @@ import { import { DraftsIcon } from '../icons/DraftIcon'; import { useLegacyColors } from '../theme/useLegacyColors'; +import type { StackNavigatorParamList } from '../types'; import { DraftManagerState, DraftsManager } from '../utils/DraftsManager'; export type DraftItemProps = { @@ -28,7 +35,7 @@ export type DraftItemProps = { export const DraftItem = ({ type, channel, date, message, thread }: DraftItemProps) => { useTheme(); const { grey } = useLegacyColors(); - const navigation = useNavigation(); + const navigation = useNavigation>(); const { client } = useChatContext(); const messagePreviewText = useMessagePreviewText({ message }); const channelName = channel?.name ? channel.name : 'Channel'; @@ -40,7 +47,12 @@ export const DraftItem = ({ type, channel, date, message, thread }: DraftItemPro if (type === 'thread' && thread?.id) { navigation.navigate('ThreadScreen', { - thread, + // `thread` is a draft's `parent_message` (`MessageResponseBase`, with + // string timestamps). ThreadScreen consumes it as a thread root typed + // as `LocalMessage | ThreadType`; the shapes only differ in date field + // types, so a targeted cast preserves the existing behavior. See the + // `DraftItemProps.thread` TODO about tightening this type upstream. + thread: thread as unknown as LocalMessage, channel: resultChannel, }); } else if (type === 'channel') { diff --git a/examples/SampleApp/src/components/LocationSharing/MessageLocation.tsx b/examples/SampleApp/src/components/LocationSharing/MessageLocation.tsx index f2dd4f7285..a0ee374870 100644 --- a/examples/SampleApp/src/components/LocationSharing/MessageLocation.tsx +++ b/examples/SampleApp/src/components/LocationSharing/MessageLocation.tsx @@ -136,7 +136,7 @@ const MessageLocationComponent = ({ ) : ( - + )} diff --git a/examples/SampleApp/src/components/MessageInfoBottomSheet.tsx b/examples/SampleApp/src/components/MessageInfoBottomSheet.tsx index 0335ab5e8b..b4f29d7127 100644 --- a/examples/SampleApp/src/components/MessageInfoBottomSheet.tsx +++ b/examples/SampleApp/src/components/MessageInfoBottomSheet.tsx @@ -12,6 +12,8 @@ import { UserAvatar, } from 'stream-chat-react-native'; +import type { StreamThemeWithColors } from '../theme/AppTheme'; + const renderUserItem = ({ item }: { item: UserResponse }) => ( @@ -34,7 +36,7 @@ export const MessageInfoBottomSheet = ({ }) => { const { theme: { colors }, - } = useTheme(); + } = useTheme() as unknown as { theme: StreamThemeWithColors }; const { client } = useChatContext(); const deliveredStatus = useMessageDeliveredData({ message }); const readStatus = useMessageReadData({ message }); diff --git a/examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx b/examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx index 21d9bb75ec..ff03b2263e 100644 --- a/examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx +++ b/examples/SampleApp/src/components/MessageSearch/MessageSearchList.tsx @@ -61,130 +61,132 @@ export type MessageSearchListProps = { refreshList?: () => void; showResultCount?: boolean; }; -export const MessageSearchList: React.FC = React.forwardRef( - (props, scrollRef: React.Ref | null>) => { - const { - EmptySearchIndicator, - loading, - loadMore, - messages, - refreshing, - refreshList, - showResultCount = false, - } = props; - const { - theme: { semantics }, - } = useTheme(); - const { black, grey, white_snow } = useLegacyColors(); - const { vw } = useViewport(); - const navigation = - useNavigation>(); +export const MessageSearchList = React.forwardRef< + FlatList | null, + MessageSearchListProps +>((props, scrollRef) => { + const { + EmptySearchIndicator, + loading, + loadMore, + messages, + refreshing, + refreshList, + showResultCount = false, + } = props; + const { + theme: { semantics }, + } = useTheme(); + const { black, grey, white_snow } = useLegacyColors(); + const { vw } = useViewport(); + const navigation = useNavigation>(); - if (!messages && !refreshing) { - return null; - } + if (!messages && !refreshing) { + return null; + } - return ( - <> - {messages && showResultCount && ( - + {messages && showResultCount && ( + + + {`${ + messages.length >= DEFAULT_PAGINATION_LIMIT + ? DEFAULT_PAGINATION_LIMIT + : messages.length + }${messages.length >= DEFAULT_PAGINATION_LIMIT ? '+ ' : ' '} result${ + messages.length === 1 ? '' : 's' + }`} + + + )} + !parent_id) : []} + keyboardDismissMode='on-drag' + ListEmptyComponent={ + loading && !refreshing && (!messages || messages.length === 0) ? ( + + + + ) : ( + EmptySearchIndicator + ) + } + onEndReached={loadMore} + onRefresh={refreshList} + ref={scrollRef} + refreshing={refreshing} + renderItem={({ item }) => ( + { + navigation.navigate('ChannelScreen', { + channelId: item.channel?.id, + messageId: item.id, + }); }} + style={[styles.itemContainer, { borderBottomColor: semantics.borderCoreDefault }]} + testID='channel-preview-button' > - - {`${ - messages.length >= DEFAULT_PAGINATION_LIMIT - ? DEFAULT_PAGINATION_LIMIT - : messages.length - }${messages.length >= DEFAULT_PAGINATION_LIMIT ? '+ ' : ' '} result${ - messages.length === 1 ? '' : 's' - }`} - - - )} - !parent_id) : []} - keyboardDismissMode='on-drag' - ListEmptyComponent={ - loading && !refreshing && (!messages || messages.length === 0) ? ( - - - - ) : ( - EmptySearchIndicator - ) - } - onEndReached={loadMore} - onRefresh={refreshList} - ref={scrollRef} - refreshing={refreshing} - renderItem={({ item }) => ( - { - navigation.navigate('ChannelScreen', { - channelId: item.channel?.id, - messageId: item.id, - }); - }} - style={[styles.itemContainer, { borderBottomColor: semantics.borderCoreDefault }]} - testID='channel-preview-button' - > - {item.user ? : null} + {item.user ? : null} - - - - {`${item.user?.name} `} - {!!item.channel?.name && ( - - in - {` ${item.channel?.name}`} - - )} - - - - - {item.text} - - - {dayjs(item.created_at).calendar(undefined, { - lastDay: 'DD/MM', // The day before ( Yesterday at 2:30 AM ) - lastWeek: 'DD/MM', // Last week ( Last Monday at 2:30 AM ) - sameDay: 'h:mm A', // The same day ( Today at 2:30 AM ) - sameElse: 'DD/MM/YYYY', // Everything else ( 17/10/2011 ) - })} - - + + + + {`${item.user?.name} `} + {!!item.channel?.name && ( + + in + {` ${item.channel?.name}`} + + )} + - - )} - style={styles.flex} - /> - - ); - }, -); + + + {item.text} + + + {dayjs(item.created_at).calendar(undefined, { + lastDay: 'DD/MM', // The day before ( Yesterday at 2:30 AM ) + lastWeek: 'DD/MM', // Last week ( Last Monday at 2:30 AM ) + sameDay: 'h:mm A', // The same day ( Today at 2:30 AM ) + sameElse: 'DD/MM/YYYY', // Everything else ( 17/10/2011 ) + })} + + + + + )} + style={styles.flex} + /> + + ); +}); + +MessageSearchList.displayName = 'MessageSearchList'; diff --git a/examples/SampleApp/src/components/Reminders/ReminderItem.tsx b/examples/SampleApp/src/components/Reminders/ReminderItem.tsx index 0a899b3365..9211ed6378 100644 --- a/examples/SampleApp/src/components/Reminders/ReminderItem.tsx +++ b/examples/SampleApp/src/components/Reminders/ReminderItem.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react'; import { Alert, AlertButton, Pressable, StyleSheet, Text, View } from 'react-native'; -import { useNavigation } from '@react-navigation/native'; +import { NavigationProp, useNavigation } from '@react-navigation/native'; import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable'; import { ReminderResponse } from 'stream-chat'; @@ -18,12 +18,13 @@ import { import { ReminderBanner } from './ReminderBanner'; import { useLegacyColors } from '../../theme/useLegacyColors'; +import type { StackNavigatorParamList } from '../../types'; export const ReminderItem = ( item: ReminderResponse & { onDeleteHandler?: (id: string) => void }, ) => { const { channel, message } = item; - const navigation = useNavigation(); + const navigation = useNavigation>(); const { client } = useChatContext(); const { t } = useTranslationContext(); const channelName = channel?.name ? channel.name : 'Channel'; diff --git a/examples/SampleApp/src/components/ToastComponent/Toast.tsx b/examples/SampleApp/src/components/ToastComponent/Toast.tsx index 0371b7f3fb..57d3518ccb 100644 --- a/examples/SampleApp/src/components/ToastComponent/Toast.tsx +++ b/examples/SampleApp/src/components/ToastComponent/Toast.tsx @@ -2,20 +2,25 @@ import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-nati import Animated, { Easing, SlideInDown, SlideOutDown } from 'react-native-reanimated'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; -import type { Notification, NotificationState } from 'stream-chat'; +import type { Notification } from 'stream-chat'; import { useInAppNotificationsState, useTheme } from 'stream-chat-react-native'; import { useLegacyColors } from '../../theme/useLegacyColors'; const { width } = Dimensions.get('window'); -const severityIconMap: Record = { +type KnownSeverity = 'error' | 'success' | 'warning' | 'info'; + +const severityIconMap: Record = { error: '❌', success: '✅', warning: '⚠️', info: 'ℹ️', }; +const getSeverityIcon = (severity: Notification['severity']) => + severity && severity in severityIconMap ? severityIconMap[severity as KnownSeverity] : 'ℹ️'; + export const Toast = () => { const { closeInAppNotification, notifications } = useInAppNotificationsState(); @@ -25,7 +30,7 @@ export const Toast = () => { // When offline, we upload pending attachments by cleaning up the previous upload, this results in a cancelled/aborted error from the server, so we filter out those notifications. const filteredNotifications = notifications.filter( - (notification: NotificationState) => notification.metadata?.reason !== 'canceled', + (notification: Notification) => notification.metadata?.reason !== 'canceled', ); return ( @@ -39,7 +44,7 @@ export const Toast = () => { > - {severityIconMap[notification.severity]} + {getSeverityIcon(notification.severity)} diff --git a/examples/SampleApp/src/components/UnreadCountBadge.tsx b/examples/SampleApp/src/components/UnreadCountBadge.tsx index 6092ae3b55..e6925609bf 100644 --- a/examples/SampleApp/src/components/UnreadCountBadge.tsx +++ b/examples/SampleApp/src/components/UnreadCountBadge.tsx @@ -16,7 +16,7 @@ export const ThreadsUnreadCountBadge: React.FC = () => { return null; } - return ; + return ; }; export const ChannelsUnreadCountBadge: React.FC = () => { @@ -52,5 +52,5 @@ export const ChannelsUnreadCountBadge: React.FC = () => { return null; } - return ; + return ; }; diff --git a/examples/SampleApp/src/hooks/useStreamChatTheme.ts b/examples/SampleApp/src/hooks/useStreamChatTheme.ts index 1f077d5621..0016c73343 100644 --- a/examples/SampleApp/src/hooks/useStreamChatTheme.ts +++ b/examples/SampleApp/src/hooks/useStreamChatTheme.ts @@ -3,7 +3,9 @@ import { ColorSchemeName, useColorScheme } from 'react-native'; import type { DeepPartial, Theme } from 'stream-chat-react-native'; -const getChatStyle = (colorScheme: ColorSchemeName): DeepPartial => ({ +const getChatStyle = ( + colorScheme: ColorSchemeName, +): DeepPartial & { colors: Record } => ({ colors: colorScheme === 'dark' ? { diff --git a/examples/SampleApp/src/icons/MenuPointHorizontal.tsx b/examples/SampleApp/src/icons/MenuPointHorizontal.tsx index e76818cc7d..ffd6f14885 100644 --- a/examples/SampleApp/src/icons/MenuPointHorizontal.tsx +++ b/examples/SampleApp/src/icons/MenuPointHorizontal.tsx @@ -2,7 +2,7 @@ import React from 'react'; import Svg, { Path } from 'react-native-svg'; -import { IconProps } from './utils/base'; +import { IconProps } from '../utils/base'; export const MenuPointHorizontal = (props: IconProps) => ( diff --git a/examples/SampleApp/src/icons/ShareLocationIcon.tsx b/examples/SampleApp/src/icons/ShareLocationIcon.tsx index 51c190c53d..a0bd708027 100644 --- a/examples/SampleApp/src/icons/ShareLocationIcon.tsx +++ b/examples/SampleApp/src/icons/ShareLocationIcon.tsx @@ -1,16 +1,9 @@ -import { ColorValue } from 'react-native'; import Svg, { Path } from 'react-native-svg'; +import type { IconProps } from 'stream-chat-react-native'; + // Icon for "Share Location" button, next to input box. -export const ShareLocationIcon = ({ - width, - height, - stroke, -}: { - stroke: ColorValue; - width: number; - height: number; -}) => { +export const ShareLocationIcon = ({ width = 24, height = 24, stroke }: IconProps) => { return ( = ({ } }); + // These are display-only messages built from search results (`MessageResponse`), + // which the image gallery consumes purely to render attachments. The gallery + // store is typed for `LocalMessage` (with `Date` timestamps), so we cast the + // search-shaped objects to satisfy that API without converting fields the + // gallery never reads. const messagesWithImages = messages .map((message) => ({ ...message, groupStyles: [], readBy: false })) .filter((message) => { @@ -117,7 +123,7 @@ export const ChannelImagesScreen: React.FC = ({ ); } return false; - }); + }) as unknown as LocalMessage[]; useEffect(() => { imageGalleryStateStore.openImageGallery({ messages: messagesWithImages }); diff --git a/examples/SampleApp/src/screens/ChannelListScreen.tsx b/examples/SampleApp/src/screens/ChannelListScreen.tsx index 1e9b09d995..0cc490bee9 100644 --- a/examples/SampleApp/src/screens/ChannelListScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelListScreen.tsx @@ -10,13 +10,14 @@ import { View, } from 'react-native'; -import { useNavigation, useScrollToTop } from '@react-navigation/native'; -import { Channel } from 'stream-chat'; +import { NavigationProp, useNavigation, useScrollToTop } from '@react-navigation/native'; +import { Channel, MessageResponse } from 'stream-chat'; import { ChannelList, useTheme, useStableCallback, ChannelActionItem, + GetChannelActionItems, } from 'stream-chat-react-native'; import { ChatScreenHeader } from '../components/ChatScreenHeader'; @@ -29,6 +30,7 @@ import { ChannelInfo } from '../icons/ChannelInfo.tsx'; import { CircleClose } from '../icons/CircleClose.tsx'; import { Search } from '../icons/Search'; import { useLegacyColors } from '../theme/useLegacyColors'; +import type { StackNavigatorParamList } from '../types'; const styles = StyleSheet.create({ channelListContainer: { @@ -76,7 +78,7 @@ const baseOptions = { export const ChannelListScreen: React.FC = () => { const { chatClient } = useAppContext(); - const navigation = useNavigation(); + const navigation = useNavigation>(); useTheme(); const { black, grey, grey_gainsboro, grey_whisper, white, white_snow } = useLegacyColors(); const { setChannel } = useStreamChatContext(); @@ -142,28 +144,30 @@ export const ChannelListScreen: React.FC = () => { [], ); - const getChannelActionItems = useStableCallback(({ context: { channel }, defaultItems }) => { - const viewInfo = () => { - if (!channel) { - return; - } - if (navigation) { - navigation.navigate('ChannelDetailsScreen', { - channel, - }); - } - }; - - const viewInfoItem: ChannelActionItem = { - action: viewInfo, - Icon: ChannelInfo, - id: 'info', - label: 'View Info', - placement: 'sheet', - type: 'standard', - }; - return [viewInfoItem, ...defaultItems]; - }); + const getChannelActionItems = useStableCallback( + ({ context: { channel }, defaultItems }: Parameters[0]) => { + const viewInfo = () => { + if (!channel) { + return; + } + if (navigation) { + navigation.navigate('ChannelDetailsScreen', { + channel, + }); + } + }; + + const viewInfoItem: ChannelActionItem = { + action: viewInfo, + Icon: ChannelInfo, + id: 'info', + label: 'View Info', + placement: 'sheet', + type: 'standard', + }; + return [viewInfoItem, ...defaultItems]; + }, + ); if (!chatClient) { return null; @@ -230,7 +234,11 @@ export const ChannelListScreen: React.FC = () => { loading={loading} loadMore={loadMore} messages={messages} - ref={scrollRef} + // The search list and the channel list never mount at the same time, + // so this single ref holder is reused for both. It is declared as a + // `FlatList` ref for the `ChannelList`/`useScrollToTop` usage, + // hence the cast for the message-typed search list here. + ref={scrollRef as unknown as RefObject | null>} refreshing={refreshing} refreshList={refreshList} showResultCount diff --git a/examples/SampleApp/src/screens/ChannelScreen.tsx b/examples/SampleApp/src/screens/ChannelScreen.tsx index 01d4a7fa86..c9a29c0b90 100644 --- a/examples/SampleApp/src/screens/ChannelScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelScreen.tsx @@ -31,6 +31,7 @@ import { ScreenHeader } from '../components/ScreenHeader'; import { useAppContext } from '../context/AppContext'; import { useStreamChatContext } from '../context/StreamChatContext.tsx'; import { useChannelMembersStatus } from '../hooks/useChannelMembersStatus'; +import type { StreamThemeWithColors } from '../theme/AppTheme'; import type { StackNavigatorParamList } from '../types'; import { channelMessageActions } from '../utils/messageActions.tsx'; @@ -54,7 +55,7 @@ export type ChannelHeaderProps = { const ChannelHeader: React.FC = ({ channel }) => { const { closePicker } = useAttachmentPickerContext(); const membersStatus = useChannelMembersStatus(channel); - const displayName = useChannelPreviewDisplayName(channel, 30); + const displayName = useChannelPreviewDisplayName(channel); const { isOnline } = useChatContext(); const { chatClient } = useAppContext(); const navigation = useNavigation(); @@ -121,7 +122,7 @@ export const ChannelScreen: React.FC = ({ navigation, route } = useAppContext(); const { theme: { semantics, colors }, - } = useTheme(); + } = useTheme() as unknown as { theme: StreamThemeWithColors }; const { t } = useTranslationContext(); const { setThread } = useStreamChatContext(); const [modalVisible, setModalVisible] = useState(false); diff --git a/examples/SampleApp/src/screens/LoadingScreen.tsx b/examples/SampleApp/src/screens/LoadingScreen.tsx index ccc15a42b0..2445f98abf 100644 --- a/examples/SampleApp/src/screens/LoadingScreen.tsx +++ b/examples/SampleApp/src/screens/LoadingScreen.tsx @@ -3,6 +3,8 @@ import { ActivityIndicator, StyleSheet, useColorScheme, View } from 'react-nativ import { useTheme } from 'stream-chat-react-native'; +import type { StreamThemeWithColors } from '../theme/AppTheme'; + const styles = StyleSheet.create({ container: { alignItems: 'center', @@ -13,7 +15,7 @@ const styles = StyleSheet.create({ export const LoadingScreen: React.FC = () => { const colorScheme = useColorScheme(); - const { theme } = useTheme(); + const { theme } = useTheme() as unknown as { theme: StreamThemeWithColors }; return ( { ); const { isLiveLocationStopped, locationResponse } = useHandleLiveLocationEvents({ - channel, + // `channel` is guaranteed to be set in context before navigating to this + // screen (the map is opened from a message's shared location), and the hook + // dereferences it directly, so a non-null assertion matches the runtime + // contract here. + channel: channel!, messageId: shared_location.message_id, onLocationUpdate, }); @@ -198,7 +202,7 @@ export const MapScreen = ({ route }: MapScreenProps) => { ) : ( - + )} = const [focusOnMessageInput, setFocusOnMessageInput] = useState(false); const [focusOnSearchInput, setFocusOnSearchInput] = useState(true); - // As we don't use the state value, we can omit it here and separate it with a comma within the array. - const [, setMessageInputText] = useState(''); useEffect(() => { const initialUser = route.params?.initialUser; @@ -374,7 +372,6 @@ export const NewDirectMessagingScreen: React.FC = channel={currentChannel.current} enforceUniqueReaction keyboardVerticalOffset={0} - onChangeText={setMessageInputText} overrideOwnCapabilities={{ sendMessage: true }} setInputRef={(ref) => (messageInputRef.current = ref)} > diff --git a/examples/SampleApp/src/screens/ThreadListScreen.tsx b/examples/SampleApp/src/screens/ThreadListScreen.tsx index ff45321138..52d1b7a5eb 100644 --- a/examples/SampleApp/src/screens/ThreadListScreen.tsx +++ b/examples/SampleApp/src/screens/ThreadListScreen.tsx @@ -1,14 +1,14 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import { useNavigation, useIsFocused } from '@react-navigation/native'; +import { NavigationProp, useNavigation, useIsFocused } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useTheme, ThreadList } from 'stream-chat-react-native'; import { ChatScreenHeader } from '../components/ChatScreenHeader'; import { useLegacyColors } from '../theme/useLegacyColors'; -import type { BottomTabNavigatorParamList } from '../types'; +import type { BottomTabNavigatorParamList, StackNavigatorParamList } from '../types'; const styles = StyleSheet.create({ container: { @@ -32,7 +32,7 @@ export type ThreadsScreenProps = { export const ThreadListScreen: React.FC = () => { useTheme(); const { white_snow } = useLegacyColors(); - const navigation = useNavigation(); + const navigation = useNavigation>(); const isFocused = useIsFocused(); return ( diff --git a/examples/SampleApp/src/theme/AppTheme.ts b/examples/SampleApp/src/theme/AppTheme.ts new file mode 100644 index 0000000000..b5ebf7b1a3 --- /dev/null +++ b/examples/SampleApp/src/theme/AppTheme.ts @@ -0,0 +1,13 @@ +import type { Colors, Theme } from 'stream-chat-react-native'; + +/** + * The SampleApp injects a custom `colors` palette at runtime via + * `` (see `useStreamChatTheme`). + * The SDK's `Theme` type no longer exposes a top-level `colors` map (it moved + * to the semantics/primitives token model), so this alias re-augments the theme + * with that runtime palette for the call sites that still read `theme.colors`. + * + * The injected palette is the legacy `Colors` object, so we type it as such to + * stay accurate for consumers that expect the legacy color keys. + */ +export type StreamThemeWithColors = Theme & { colors: typeof Colors }; diff --git a/examples/SampleApp/tsconfig.json b/examples/SampleApp/tsconfig.json index edad44a02d..5a1531949c 100644 --- a/examples/SampleApp/tsconfig.json +++ b/examples/SampleApp/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@react-native/typescript-config", + "extends": ["@react-native/typescript-config", "@stream-io/typescript-config/base.json"], "compilerOptions": { "types": [] } diff --git a/examples/TypeScriptMessaging/package.json b/examples/TypeScriptMessaging/package.json index ba24fef7a6..1db422822e 100644 --- a/examples/TypeScriptMessaging/package.json +++ b/examples/TypeScriptMessaging/package.json @@ -47,7 +47,8 @@ "@react-native/metro-config": "0.80.2", "@react-native/typescript-config": "0.80.2", "@rnx-kit/metro-config": "^2.1.0", + "@stream-io/typescript-config": "workspace:^", "@types/react": "^19.1.0", - "typescript": "5.9.3" + "typescript": "6.0.3" } } diff --git a/examples/TypeScriptMessaging/tsconfig.json b/examples/TypeScriptMessaging/tsconfig.json index edad44a02d..5a1531949c 100644 --- a/examples/TypeScriptMessaging/tsconfig.json +++ b/examples/TypeScriptMessaging/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@react-native/typescript-config", + "extends": ["@react-native/typescript-config", "@stream-io/typescript-config/base.json"], "compilerOptions": { "types": [] } diff --git a/examples/TypeScriptMessaging/useStreamChatTheme.ts b/examples/TypeScriptMessaging/useStreamChatTheme.ts index aded706c14..5cb76ac032 100644 --- a/examples/TypeScriptMessaging/useStreamChatTheme.ts +++ b/examples/TypeScriptMessaging/useStreamChatTheme.ts @@ -3,7 +3,9 @@ import { useColorScheme } from 'react-native'; import type { DeepPartial, Theme } from 'stream-chat-react-native'; -const getChatStyle = (colorScheme: string | null | undefined): DeepPartial => ({ +const getChatStyle = ( + colorScheme: string | null | undefined, +): DeepPartial & { colors: Record } => ({ avatar: { image: { height: 32, diff --git a/package.json b/package.json index a3e8cef113..f00166e80f 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "root", "private": true, "workspaces": [ + "configs/typescript-config", "package", "package/native-package", "package/expo-package", @@ -57,7 +58,7 @@ "husky": "^9.1.7", "prettier": "^3.8.3", "semantic-release": "^25.0.3", - "typescript": "5.9.3", + "typescript": "6.0.3", "typescript-eslint": "^8.59.2", "uglify-js": "^3.19.2" }, @@ -71,7 +72,7 @@ "lint-fix": "prettier --write . && eslint . --fix --max-warnings 0", "prettier": "prettier --list-different .", "prettier-fix": "prettier --write .", - "typecheck": "yarn workspaces foreach -A --parallel --include 'stream-chat-react-native-core' --include 'sampleapp' --include 'typescriptmessaging' --include 'ExpoMessaging' run typecheck", + "typecheck": "yarn workspaces foreach -A --parallel --include 'stream-chat-react-native-core' --include 'stream-chat-react-native' --include 'stream-chat-expo' --include 'sampleapp' --include 'typescriptmessaging' --include 'ExpoMessaging' run typecheck", "build": "yarn workspace stream-chat-react-native-core build", "test:coverage": "yarn workspace stream-chat-react-native-core test:coverage", "test:unit": "yarn workspace stream-chat-react-native-core test:unit", diff --git a/package/expo-package/package.json b/package/expo-package/package.json index 97a4ce8624..4350be9c8d 100644 --- a/package/expo-package/package.json +++ b/package/expo-package/package.json @@ -80,11 +80,14 @@ } }, "devDependencies": { + "@stream-io/typescript-config": "workspace:^", "expo": "^56.0.0", "expo-audio": "~56.0.0", - "expo-image-manipulator": "~56.0.0" + "expo-image-manipulator": "~56.0.0", + "typescript": "6.0.3" }, "scripts": { + "typecheck": "tsc --noEmit -p tsconfig.json", "prepack": "bash ../scripts/sync-shared-native.sh expo-package && cp ../../README.md .", "postpack": "rm README.md && bash ../scripts/clean-shared-native-copies.sh expo-package" }, diff --git a/package/expo-package/src/optionalDependencies/shareImage.ts b/package/expo-package/src/optionalDependencies/shareImage.ts index 1eac482a5f..e3c6abab4d 100644 --- a/package/expo-package/src/optionalDependencies/shareImage.ts +++ b/package/expo-package/src/optionalDependencies/shareImage.ts @@ -19,6 +19,7 @@ export const shareImage = Sharing return true; } catch (error) { console.warn('Sharing failed or cancelled...'); + return false; } } : null; diff --git a/package/expo-package/tsconfig.json b/package/expo-package/tsconfig.json new file mode 100644 index 0000000000..f8b97a6352 --- /dev/null +++ b/package/expo-package/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@stream-io/typescript-config/library.json", + "compilerOptions": { + // The optional-dependency shims dynamically `require()` peer deps that are + // not installed here (so their values are untyped) and guard them behind + // runtime checks TS can't narrow across closures. The strict-`any`/null/ + // unused checks (meant for the first-party core source) are therefore + // relaxed for these thin wrapper packages; `noImplicitReturns` is kept. + "noImplicitAny": false, + "strictNullChecks": false, + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": ["./src/**/*"], + "exclude": ["node_modules", "**/__tests__"] +} diff --git a/package/native-package/package.json b/package/native-package/package.json index 9e086ba19a..5dc1da8e59 100644 --- a/package/native-package/package.json +++ b/package/native-package/package.json @@ -78,11 +78,14 @@ } }, "scripts": { + "typecheck": "tsc --noEmit -p tsconfig.json", "prepack": "bash ../scripts/sync-shared-native.sh native-package && cp ../../README.md .", "postpack": "rm README.md && bash ../scripts/clean-shared-native-copies.sh native-package" }, "devDependencies": { - "react-native": "0.85.3" + "@stream-io/typescript-config": "workspace:^", + "react-native": "0.85.3", + "typescript": "6.0.3" }, "codegenConfig": { "name": "StreamChatReactNativeSpec", diff --git a/package/native-package/src/optionalDependencies/Audio.ts b/package/native-package/src/optionalDependencies/Audio.ts index 5c76ddcba4..f4b2009317 100644 --- a/package/native-package/src/optionalDependencies/Audio.ts +++ b/package/native-package/src/optionalDependencies/Audio.ts @@ -150,7 +150,7 @@ class _Audio { await verifyAndroidPermissions(); } catch (err) { console.warn('Audio Recording Permissions error', err); - return; + return { accessGranted: false, recording: null }; } } try { diff --git a/package/native-package/src/optionalDependencies/AudioVideo.ts b/package/native-package/src/optionalDependencies/AudioVideo.ts index 3c4367cec2..f05dfa97a6 100644 --- a/package/native-package/src/optionalDependencies/AudioVideo.ts +++ b/package/native-package/src/optionalDependencies/AudioVideo.ts @@ -16,6 +16,8 @@ let AudioVideoComponent: audioOnly?: boolean; ignoreSilentSwitch?: 'ignore' | 'obey'; repeat?: boolean; + resizeMode?: string; + rate?: number; }> | undefined; try { diff --git a/package/native-package/src/optionalDependencies/shareImage.ts b/package/native-package/src/optionalDependencies/shareImage.ts index 6186f498ca..19bb8fa043 100644 --- a/package/native-package/src/optionalDependencies/shareImage.ts +++ b/package/native-package/src/optionalDependencies/shareImage.ts @@ -40,6 +40,7 @@ export const shareImage = RNShare return true; } catch (error) { console.warn('Sharing failed...', error); + return false; } } : null; diff --git a/package/native-package/tsconfig.json b/package/native-package/tsconfig.json new file mode 100644 index 0000000000..f8b97a6352 --- /dev/null +++ b/package/native-package/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@stream-io/typescript-config/library.json", + "compilerOptions": { + // The optional-dependency shims dynamically `require()` peer deps that are + // not installed here (so their values are untyped) and guard them behind + // runtime checks TS can't narrow across closures. The strict-`any`/null/ + // unused checks (meant for the first-party core source) are therefore + // relaxed for these thin wrapper packages; `noImplicitReturns` is kept. + "noImplicitAny": false, + "strictNullChecks": false, + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": ["./src/**/*"], + "exclude": ["node_modules", "**/__tests__"] +} diff --git a/package/package.json b/package/package.json index 2376804f6d..e922344eae 100644 --- a/package/package.json +++ b/package/package.json @@ -116,6 +116,7 @@ "@react-native/babel-preset": "0.85.3", "@react-native/jest-preset": "0.85.3", "@shopify/flash-list": "^2.3.1", + "@stream-io/typescript-config": "workspace:^", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "13.2.0", "@total-typescript/shoehorn": "^0.1.2", @@ -148,7 +149,7 @@ "react-native-worklets": "^0.9.1", "react-test-renderer": "19.2.3", "rimraf": "^6.0.1", - "typescript": "5.9.3", + "typescript": "6.0.3", "uuid": "^11.1.0" }, "resolutions": { diff --git a/package/src/__tests__/jest-globals.d.ts b/package/src/__tests__/jest-globals.d.ts new file mode 100644 index 0000000000..3ba8401b0e --- /dev/null +++ b/package/src/__tests__/jest-globals.d.ts @@ -0,0 +1,8 @@ +// Provides Jest's ambient globals (describe/it/expect/jest, …) to the test +// type-check program. TypeScript 6.0 no longer auto-includes `@types/jest` +// from `node_modules/@types` the way 5.x did, so we reference it explicitly. +// +// This file lives under `__tests__/` so it is excluded from the published +// build (`tsconfig.json` excludes `**/__tests__`) — keeping `@types/jest` out +// of `lib/` — while still being picked up by `tsconfig.test.json`. +/// diff --git a/package/src/index.ts b/package/src/index.ts index 8a8eacfb28..6c8f8c1f45 100644 --- a/package/src/index.ts +++ b/package/src/index.ts @@ -4,7 +4,13 @@ import './polyfills'; export * from './components'; export * from './hooks'; -export { registerNativeHandlers, SoundReturnType, PlaybackStatus, RecordingStatus } from './native'; +export { + PickImageOptions, + PlaybackStatus, + RecordingStatus, + registerNativeHandlers, + SoundReturnType, +} from './native'; export * from './contexts'; export * from './icons'; diff --git a/package/tsconfig.json b/package/tsconfig.json index b1fb8c3d18..b9851bafa7 100644 --- a/package/tsconfig.json +++ b/package/tsconfig.json @@ -1,29 +1,7 @@ { + "extends": "@stream-io/typescript-config/library.json", "compilerOptions": { - "allowUnreachableCode": false, - "allowUnusedLabels": false, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "jsx": "react-native", - "lib": ["esnext", "DOM", "DOM.Iterable"], - "module": "esnext", - "moduleResolution": "bundler", - "noFallthroughCasesInSwitch": true, - "noImplicitReturns": true, - "noImplicitUseStrict": false, - "noStrictGenericChecks": false, - "noUnusedLocals": true, - "noUnusedParameters": true, - "strictNullChecks": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true, - "target": "esnext", - "allowSyntheticDefaultImports": true, - "noEmitOnError": false, - "noImplicitAny": true, - "allowJs": true, - "checkJs": false + "rootDir": "./src" }, "include": ["./src/**/*"], "exclude": [ @@ -35,6 +13,8 @@ "babel.config.js", "metro.config.js", "jest.config.js", - "**/__tests__" + "**/__tests__", + "**/*.test.ts", + "**/*.test.tsx" ] } diff --git a/yarn.lock b/yarn.lock index d1eaf455e5..013467a6b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6548,6 +6548,12 @@ __metadata: languageName: node linkType: hard +"@stream-io/typescript-config@workspace:^, @stream-io/typescript-config@workspace:configs/typescript-config": + version: 0.0.0-use.local + resolution: "@stream-io/typescript-config@workspace:configs/typescript-config" + languageName: unknown + linkType: soft + "@swc/core-darwin-arm64@npm:1.15.33": version: 1.15.33 resolution: "@swc/core-darwin-arm64@npm:1.15.33" @@ -7409,6 +7415,7 @@ __metadata: "@rnx-kit/metro-config": "npm:^2.1.2" "@rnx-kit/metro-resolver-symlinks": "npm:^0.2.6" "@shopify/flash-list": "npm:^2.3.1" + "@stream-io/typescript-config": "workspace:^" "@types/react": "npm:~19.2.10" expo: "npm:^56.0.0" expo-asset: "npm:~56.0.0" @@ -7445,7 +7452,7 @@ __metadata: stream-chat: "npm:^9.47.0" stream-chat-expo: "workspace:^" stream-chat-react-native-core: "workspace:^" - typescript: "npm:~5.9.2" + typescript: "npm:6.0.3" languageName: unknown linkType: soft @@ -18733,7 +18740,7 @@ __metadata: husky: "npm:^9.1.7" prettier: "npm:^3.8.3" semantic-release: "npm:^25.0.3" - typescript: "npm:5.9.3" + typescript: "npm:6.0.3" typescript-eslint: "npm:^8.59.2" uglify-js: "npm:^3.19.2" dependenciesMeta: @@ -18861,6 +18868,7 @@ __metadata: "@react-navigation/native-stack": "npm:^7.16.0" "@rnx-kit/metro-config": "npm:^2.1.0" "@shopify/flash-list": "npm:^2.3.1" + "@stream-io/typescript-config": "workspace:^" "@types/lodash.mergewith": "npm:^4.6.9" "@types/react": "npm:^19.2.0" babel-plugin-react-compiler: "npm:^1.0.0" @@ -18887,7 +18895,7 @@ __metadata: stream-chat: "npm:^9.47.0" stream-chat-react-native: "workspace:^" stream-chat-react-native-core: "workspace:^" - typescript: "npm:5.9.3" + typescript: "npm:6.0.3" languageName: unknown linkType: soft @@ -19511,11 +19519,13 @@ __metadata: version: 0.0.0-use.local resolution: "stream-chat-expo@workspace:package/expo-package" dependencies: + "@stream-io/typescript-config": "workspace:^" expo: "npm:^56.0.0" expo-audio: "npm:~56.0.0" expo-image-manipulator: "npm:~56.0.0" mime: "npm:^4.0.7" stream-chat-react-native-core: "workspace:^" + typescript: "npm:6.0.3" peerDependencies: expo: ">=52.0.0" expo-audio: "*" @@ -19568,6 +19578,7 @@ __metadata: "@react-native/babel-preset": "npm:0.85.3" "@react-native/jest-preset": "npm:0.85.3" "@shopify/flash-list": "npm:^2.3.1" + "@stream-io/typescript-config": "workspace:^" "@testing-library/jest-native": "npm:^5.4.3" "@testing-library/react-native": "npm:13.2.0" "@total-typescript/shoehorn": "npm:^0.1.2" @@ -19612,7 +19623,7 @@ __metadata: react-test-renderer: "npm:19.2.3" rimraf: "npm:^6.0.1" stream-chat: "npm:^9.47.0" - typescript: "npm:5.9.3" + typescript: "npm:6.0.3" use-sync-external-store: "npm:^1.5.0" uuid: "npm:^11.1.0" peerDependencies: @@ -19643,10 +19654,12 @@ __metadata: version: 0.0.0-use.local resolution: "stream-chat-react-native@workspace:package/native-package" dependencies: + "@stream-io/typescript-config": "workspace:^" es6-symbol: "npm:^3.1.3" mime: "npm:^4.0.7" react-native: "npm:0.85.3" stream-chat-react-native-core: "workspace:^" + typescript: "npm:6.0.3" peerDependencies: "@react-native-camera-roll/camera-roll": ">=7.9.0" "@react-native-clipboard/clipboard": ">=1.16.0" @@ -20481,23 +20494,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.9.3, typescript@npm:~5.9.2": - version: 5.9.3 - resolution: "typescript@npm:5.9.3" +"typescript@npm:6.0.3": + version: 6.0.3 + resolution: "typescript@npm:6.0.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + checksum: 10c0/4a25ff5045b984370f48f196b3a0120779b1b343d40b9a68d114ea5e5fff099809b2bb777576991a63a5cd59cf7bffd96ff6fe10afcefbcb8bd6fb96ad4b6606 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.9.3#optional!builtin, typescript@patch:typescript@npm%3A~5.9.2#optional!builtin": - version: 5.9.3 - resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" +"typescript@patch:typescript@npm%3A6.0.3#optional!builtin": + version: 6.0.3 + resolution: "typescript@patch:typescript@npm%3A6.0.3#optional!builtin::version=6.0.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + checksum: 10c0/2f25c74e65663c248fa1ade2b8459d9ce5372ff9dad07067310f132966ebec1d93f6c42f0baf77a6b6a7a91460463f708e6887013aaade22111037457c6b25df languageName: node linkType: hard @@ -20521,6 +20534,7 @@ __metadata: "@react-navigation/native": "npm:^7.1.10" "@react-navigation/stack": "npm:^7.3.3" "@rnx-kit/metro-config": "npm:^2.1.0" + "@stream-io/typescript-config": "workspace:^" "@types/react": "npm:^19.1.0" react: "npm:19.1.0" react-native: "npm:0.80.2" @@ -20539,7 +20553,7 @@ __metadata: stream-chat: "npm:^9.47.0" stream-chat-react-native: "workspace:^" stream-chat-react-native-core: "workspace:^" - typescript: "npm:5.9.3" + typescript: "npm:6.0.3" languageName: unknown linkType: soft