Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ jobs:
- name: Check types
run: yarn typecheck

# React Native < 0.87 still uses its legacy types by default
- name: Check types against legacy React Native types
run: yarn tsc -p tsconfig.build.json --noEmit --customConditions react-native-paper-source,react-native-legacy-deep-imports

unit-tests:
name: Unit tests
strategy:
Expand Down
24 changes: 24 additions & 0 deletions docs/6.x/docs/guides/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ Some components now accept explicit `testID` props for their interactable elemen
- `Searchbar`: `searchTestID`, `clearTestID`, and `trailingTestID` for the search, clear, and trailing icon buttons.
- `Snackbar`: `iconTestID` for the icon button.

### Refs

From React Native 0.87, `View`, `Text`, and `TextInput` are function components in React Native's types, so `React.Ref<View>` no longer refers to an instance. Component refs, such as the ones on `Button`, `Card`, `Chip`, and `Surface`, are now typed with `React.ComponentRef`. This works on older React Native versions too.

Type your refs the same way:

```diff
- const ref = React.useRef<View>(null);
+ const ref = React.useRef<React.ComponentRef<typeof View>>(null);

<Button ref={ref}>Press me</Button>;
```

For `TextInput` and `Searchbar`, keep using `TextInputHandles`. It now keeps its methods, such as `focus` and `clear`, on React Native 0.87 and later.

## Components

### Appbar
Expand Down Expand Up @@ -504,6 +519,15 @@ We have also added a `direction` prop to the `Card` component, which lets you ar
</Card>
```

#### `Card.Cover`

The `style` prop of `Card.Cover` is now typed as a `View` style. Image style properties such as `resizeMode` or `tintColor` were never applied, so pass them as `Image` props instead:

```diff
- <Card.Cover source={cover} style={{ resizeMode: 'contain' }} />
+ <Card.Cover source={cover} resizeMode="contain" />
```

#### `Card.Actions`

`Card.Actions` no longer injects `mode` on the buttons. To keep the previous behavior, you need to set `mode="outlined"` on the first button and `mode="contained"` on the rest:
Expand Down
13 changes: 7 additions & 6 deletions docs/rspress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,16 @@ export default withCallstackPreset(
react: resolve('react'),
'react-dom': resolve('react-dom'),
'react-native$': resolve('react-native-web'),
'react-native-web/dist/exports/StyleSheet/compiler/createReactDOMStyle':
'react-native-web/dist/cjs/exports/StyleSheet/compiler/createReactDOMStyle.js':
path.resolve(
resolve('react-native-web'),
'dist/exports/StyleSheet/compiler/createReactDOMStyle.js'
'dist/cjs/exports/StyleSheet/compiler/createReactDOMStyle.js'
),
'react-native-web/dist/cjs/exports/StyleSheet/preprocess.js':
path.resolve(
resolve('react-native-web'),
'dist/cjs/exports/StyleSheet/preprocess.js'
),
'react-native-web/dist/exports/StyleSheet/preprocess': path.resolve(
resolve('react-native-web'),
'dist/exports/StyleSheet/preprocess.js'
),
'react-native-paper': paper,
},
},
Expand Down
2 changes: 2 additions & 0 deletions docs/theme/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import './requestAnimationFramePolyfill';

import { Fragment, type ComponentProps } from 'react';

import { HomeBanner } from '@callstack/rspress-theme';
Expand Down
7 changes: 7 additions & 0 deletions docs/theme/requestAnimationFramePolyfill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// react-native-worklets 0.13 calls `requestAnimationFrame` at module scope,
// which doesn't exist during SSG in Node. Remove once the upstream fallback
// (software-mansion/react-native-reanimated#10667) ships in a stable release.
if (typeof globalThis.requestAnimationFrame === 'undefined') {
globalThis.requestAnimationFrame = (callback: FrameRequestCallback) =>
setTimeout(() => callback(Date.now()), 16);
}
50 changes: 25 additions & 25 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,36 @@
"ios": "EXPO_NO_TYPESCRIPT_SETUP=1 expo run:ios"
},
"dependencies": {
"@expo/metro-config": "~56.0.0",
"@expo/metro-config": "~58.0.21",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-native-masked-view/masked-view": "0.3.2",
"@react-navigation/bottom-tabs": "^8.0.0-alpha.33",
"@react-navigation/drawer": "^8.0.0-alpha.34",
"@react-navigation/elements": "3.0.0-alpha.31",
"@react-navigation/native": "^8.0.0-alpha.28",
"@react-navigation/native-stack": "^8.0.0-alpha.34",
"expo": "~56.0.0",
"expo-constants": "~56.0.18",
"expo-crypto": "~56.0.3",
"expo-dev-client": "~56.0.13",
"expo-font": "~56.0.5",
"expo-keep-awake": "~56.0.3",
"expo-splash-screen": "~56.0.9",
"expo-status-bar": "~56.0.4",
"expo-updates": "~56.0.14",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.85.3",
"react-native-gesture-handler": "~2.31.1",
"react-native-reanimated": "4.3.1",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.1",
"react-native-web": "^0.21.0",
"react-native-worklets": "0.8.3"
"@react-navigation/bottom-tabs": "^8.0.0-alpha.57",
"@react-navigation/drawer": "^8.0.0-alpha.58",
"@react-navigation/elements": "3.0.0-alpha.55",
"@react-navigation/native": "^8.0.0-alpha.51",
"@react-navigation/native-stack": "^8.0.0-alpha.59",
"expo": "~58.0.23",
"expo-constants": "~58.0.15",
"expo-crypto": "~58.0.7",
"expo-dev-client": "~58.0.18",
"expo-font": "~58.0.11",
"expo-keep-awake": "~58.0.3",
"expo-splash-screen": "~58.0.13",
"expo-status-bar": "~58.0.4",
"expo-updates": "~58.0.21",
"react": "19.3.0",
"react-dom": "19.3.0",
"react-native": "0.88.1",
"react-native-gesture-handler": "~3.3.0",
"react-native-reanimated": "4.7.1",
"react-native-safe-area-context": "~5.11.1",
"react-native-screens": "~4.30.1",
"react-native-web": "~0.21.4",
"react-native-worklets": "0.13.0"
},
"devDependencies": {
"@babel/core": "^7.29.0",
"babel-preset-expo": "~56.0.0",
"babel-preset-expo": "~58.0.24",
"react-native-builder-bob": "^0.42.1",
"react-native-monorepo-config": "^0.4.0"
},
Expand Down
7 changes: 7 additions & 0 deletions example/src/Examples/BottomNavigationBarExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,17 @@ const BottomNavigationBarExample = createBottomTabNavigator({
<BottomNavigation.Bar
navigationState={state}
onTabPress={({ route, preventDefault }) => {
const isRepeatedPress = state.routes[state.index].key === route.key;
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
data: {
behavior: {
scrollToTop: isRepeatedPress,
popToTop: isRepeatedPress,
},
},
});

if (event.defaultPrevented) {
Expand Down
20 changes: 11 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,15 @@
"@eslint/js": "9.39.4",
"@jest/globals": "^29.7.0",
"@react-native-vector-icons/material-design-icons": "^12.0.0",
"@react-native/babel-preset": "^0.85.3",
"@react-native/jest-preset": "^0.85.3",
"@react-navigation/native": "^8.0.0-alpha.28",
"@react-native/assets-registry": "^0.88.1",
"@react-native/babel-preset": "^0.88.1",
"@react-native/jest-preset": "^0.88.1",
"@react-navigation/native": "^8.0.0-alpha.51",
"@release-it/conventional-changelog": "^1.1.0",
"@testing-library/react-native": "^14.0.0",
"@types/color": "^3.0.0",
"@types/node": "^24.0.0",
"@types/react": "^19.2.7",
"@types/react": "^19.3.0",
"all-contributors-cli": "^6.24.0",
"babel-jest": "^29.6.3",
"babel-test": "^0.1.1",
Expand All @@ -82,12 +83,12 @@
"jest-file-snapshot": "^0.3.2",
"lefthook": "^2.1.9",
"prettier": "^3.8.4",
"react": "19.2.3",
"react-native": "0.85.3",
"react": "19.3.0",
"react-native": "0.88.1",
"react-native-builder-bob": "^0.42.1",
"react-native-reanimated": "4.3.1",
"react-native-safe-area-context": "5.7.0",
"react-native-worklets": "0.8.3",
"react-native-reanimated": "4.7.1",
"react-native-safe-area-context": "5.11.1",
"react-native-worklets": "0.13.0",
"release-it": "^13.4.0",
"test-renderer": "1.2.0",
"typescript": "5.8.3",
Expand All @@ -105,6 +106,7 @@
},
"jest": {
"preset": "@react-native/jest-preset",
"resolver": "react-native-reanimated/jest/resolver",
"setupFiles": [
"<rootDir>/jest/testSetup.js"
],
Expand Down
2 changes: 1 addition & 1 deletion src/components/Appbar/AppbarAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export type Props = React.PropsWithoutRef<IconButtonProps> & {
*/
isLeading?: boolean;
style?: StyleProp<AnimatedStyle<ViewStyle>>;
ref?: React.Ref<View>;
ref?: React.Ref<React.ComponentRef<typeof View>>;
/**
* @optional
*/
Expand Down
2 changes: 1 addition & 1 deletion src/components/Appbar/AppbarBackAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type Props = Omit<React.PropsWithoutRef<AppbarActionProps>, 'icon'> & {
*/
onPress?: (e: GestureResponderEvent) => void;
style?: StyleProp<AnimatedStyle<ViewStyle>>;
ref?: React.Ref<View>;
ref?: React.Ref<React.ComponentRef<typeof View>>;
};

/**
Expand Down
2 changes: 1 addition & 1 deletion src/components/Appbar/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const getAppbarBackgroundColor = (
theme: InternalTheme,
elevated: boolean,
customBackground?: ColorValue
) => {
): ColorValue => {
const { colors } = theme;
if (customBackground) {
return customBackground;
Expand Down
2 changes: 1 addition & 1 deletion src/components/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export type Props = Omit<ViewProps, 'style'> & {
*/
maxFontSizeMultiplier?: number;
style?: StyleProp<SurfaceStyle>;
ref?: React.RefObject<View>;
ref?: React.Ref<React.ComponentRef<typeof View>>;
/**
* @optional
*/
Expand Down
6 changes: 3 additions & 3 deletions src/components/BottomNavigation/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const getActiveTintColor = ({
}: {
activeColor: ColorValue | undefined;
theme: InternalTheme;
}) => {
}): ColorValue => {
if (activeColor != null) {
return activeColor;
}
Expand All @@ -22,7 +22,7 @@ export const getInactiveTintColor = ({
}: {
inactiveColor: ColorValue | undefined;
theme: InternalTheme;
}) => {
}): ColorValue => {
if (inactiveColor != null) {
return inactiveColor;
}
Expand All @@ -40,7 +40,7 @@ export const getLabelColor = ({
hasColor: boolean;
focused: boolean;
theme: InternalTheme;
}) => {
}): ColorValue => {
const { colors } = theme;
if (hasColor) {
return tintColor;
Expand Down
4 changes: 2 additions & 2 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ export type Props = Omit<ViewProps, 'style'> & {
/**
* Reference for the touchable
*/
touchableRef?: React.RefObject<View>;
ref?: React.Ref<View>;
touchableRef?: React.Ref<React.ComponentRef<typeof View>>;
ref?: React.Ref<React.ComponentRef<typeof View>>;
/**
* testID to be used on tests.
*/
Expand Down
17 changes: 12 additions & 5 deletions src/components/Button/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,14 @@ export const getButtonColors = ({
customTextColor?: ColorValue;
disabled?: boolean;
dark?: boolean;
}) => {
}): {
backgroundColor: ColorValue;
borderColor: ColorValue;
textColor: ColorValue;
textOpacity: number;
borderWidth: number;
backgroundOpacity: number;
} => {
const isMode = (modeToCompare: ButtonMode) => {
return mode === modeToCompare;
};
Expand Down Expand Up @@ -191,8 +198,8 @@ export const getButtonColors = ({
};
};

type ViewStyleBorderRadiusStyles = Partial<
Pick<
type ViewStyleBorderRadiusStyles = {
-readonly [K in keyof Pick<
ViewStyle,
| 'borderBottomEndRadius'
| 'borderBottomLeftRadius'
Expand All @@ -203,8 +210,8 @@ type ViewStyleBorderRadiusStyles = Partial<
| 'borderTopRightRadius'
| 'borderTopStartRadius'
| 'borderRadius'
>
>;
>]?: ViewStyle[K];
};
export const getButtonTouchableRippleStyle = (
style?: ViewStyle,
borderWidth: number = 0
Expand Down
8 changes: 5 additions & 3 deletions src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export type Props = Omit<ViewProps, 'style'> & {
/**
* Reference to the card container.
*/
ref?: React.Ref<View>;
ref?: React.Ref<React.ComponentRef<typeof View>>;
};

const DEFAULT_CARD_GAP = 16;
Expand Down Expand Up @@ -198,8 +198,10 @@ const Card = ({
mode: cardMode,
});

// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const flattenedStyles = (StyleSheet.flatten(style) || {}) as ViewStyle;
// TODO: Remove together with StyleSheet.flatten in #5114.
const flattenedStyles =
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
StyleSheet.flatten(style as StyleProp<ViewStyle>) || {};

const { borderColor = themedBorderColor } = flattenedStyles;

Expand Down
4 changes: 2 additions & 2 deletions src/components/Card/CardCover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { grey200 } from '../../theme/colors';
import type { ThemeProp } from '../../theme/types';
import { splitStyles } from '../../utils/splitStyles';

export type Props = ImageProps & {
export type Props = Omit<ImageProps, 'style'> & {
Comment thread
lukewalczak marked this conversation as resolved.
Resolved
style?: StyleProp<ViewStyle>;
/**
* @optional
Expand Down Expand Up @@ -40,7 +40,7 @@ const CardCover = ({ style, theme: themeOverrides, ...rest }: Props) => {
const theme = useInternalTheme(themeOverrides);
const cardContext = React.useContext(CardContext);

const flattenedStyles = StyleSheet.flatten<ViewStyle>(style) || {};
const flattenedStyles = StyleSheet.flatten(style) || {};
const [, borderRadiusStyles] = splitStyles(
flattenedStyles,
(style) => style.startsWith('border') && style.endsWith('Radius')
Expand Down
7 changes: 5 additions & 2 deletions src/components/Card/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { StyleProp, ViewStyle } from 'react-native';
import type { ColorValue, StyleProp, ViewStyle } from 'react-native';

import type { InternalTheme } from '../../theme/types';

Expand Down Expand Up @@ -61,7 +61,10 @@ export const getCardColors = ({
}: {
theme: InternalTheme;
mode: CardMode;
}) => {
}): {
backgroundColor: ColorValue | undefined;
borderColor: ColorValue;
} => {
const isMode = (modeToCompare: CardMode) => {
return mode === modeToCompare;
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/Chip/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export type Props = Omit<ViewProps, 'style'> & {
/**
* Reference to the chip container.
*/
ref?: React.Ref<View>;
ref?: React.Ref<React.ComponentRef<typeof View>>;
};

/**
Expand Down
Loading
Loading