Skip to content
Open
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
96 changes: 94 additions & 2 deletions docs/6.x/docs/guides/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ So you can use Reanimated's `useSharedValue` and `useAnimatedStyle` to animate t

```tsx
import { useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
import { Card, Text } from 'react-native-paper';

const MyComponent = () => {
const opacity = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));

return <Card style={animatedStyle}>Button</Card>;
return <Card content={<Text>Animated Card</Text>} style={animatedStyle} />;
};
```

Expand All @@ -47,7 +48,7 @@ const MyComponent = () => {
The `elevation` prop no longer accepts a React Native `Animated.Value` in the following components:

- `Banner`
- `Card`
- `Card` (`variant="elevated"` only)
- `Searchbar`
- `Snackbar`
- `Surface`
Expand Down Expand Up @@ -115,6 +116,97 @@ The `style` props for `Appbar` and `Appbar.Header` no longer accept `Animated.Va

The `style.elevation` property is no longer supported. Use the `elevated` prop to control Appbar elevation.

### Card

Paper 6 replaces the Card's `mode` and arbitrary-children interfaces with Material 3 variants and explicit slots. These interfaces were removed; they are not deprecated APIs. Migrate each Card directly to the new contract.

#### Variants and default

Replace `mode` with `variant`:

| Paper 5 | Paper 6 |
| --- | --- |
| `mode="contained"` | `variant="filled"` |
| `mode="elevated"` | `variant="elevated"` |
| `mode="outlined"` | `variant="outlined"` |

The default also changed. A Paper 5 Card without `mode` was elevated; a Paper 6 Card without `variant` is filled and has no resting shadow. Add `variant="elevated"` if you need to preserve the old default emphasis. The `elevation` prop is accepted only with `variant="elevated"`.

#### Replace nested composition with slots

Arbitrary Card children were removed. Paper 6 renders the explicit regions in the deterministic order `media`, header, `content`, and `actions`, regardless of the order in which props are written. Arrays, fragments, conditional values, and custom wrappers can be passed inside a slot without changing region placement.

For the common header form, move `Card.Title` values to `title`, `subtitle`, `leading`, and `trailing`. Move the remaining regions to their corresponding slots:

```tsx
// Before (v5)
<Card mode="contained" onPress={openDetails}>
<Card.Cover source={{ uri: coverUri }} />
<Card.Title title="Weekend trip" subtitle="2 days" />
<Card.Content>
<Text variant="bodyMedium">View the itinerary.</Text>
</Card.Content>
</Card>

// After (v6)
<Card
accessibilityLabel="Open weekend trip details"
onPress={openDetails}
media={<Card.Cover source={{ uri: coverUri }} />}
title="Weekend trip"
subtitle="2 days"
content={
<Card.Content>
<Text variant="bodyMedium">View the itinerary.</Text>
</Card.Content>
}
/>
```

Use `header` when the complete header is custom. It is mutually exclusive with `title`, `subtitle`, `leading`, and `trailing`:

```tsx
<Card
variant="outlined"
header={<TripHeader trip={trip} />}
content={
<Card.Content>
<Text>{trip.summary}</Text>
</Card.Content>
}
/>
```

`Card.Content`, `Card.Cover`, `Card.Title`, and `Card.Actions` remain available as layout helpers inside the new slots. They are not arbitrary Card children.

#### Choose one interaction model

Give the Card an interaction handler when the whole Card represents one action. It becomes one accessibility target with button semantics by default, so do not place independent controls in its `actions` slot.

When buttons or other controls perform independent actions, keep the Card itself neutral and put those controls in `actions`:

```tsx
<Card
variant="elevated"
title="Draft itinerary"
content={
<Card.Content>
<Text>Review before saving.</Text>
</Card.Content>
}
actions={
<Card.Actions>
<Button onPress={discard}>Discard</Button>
<Button mode="contained" onPress={save}>Save</Button>
</Card.Actions>
}
/>
```

Paper 6 warns in development if whole-Card interaction handlers and a populated `actions` slot are combined. A neutral Card remains a grouping container unless you provide accessibility semantics explicitly. The `dragged` prop controls the Material dragged presentation only; drag gestures and lifecycle remain application responsibilities.

Card refs and test IDs now target documented nodes: `ref` targets the outer shell, `touchableRef` targets the actionable interaction node, and `testID` targets the interaction node for actionable Cards or the slot-content node for neutral Cards. `${testID}-container` and `${testID}-visual` identify the outer shell and clipped visual region.

### Surface

- The `elevation` prop no longer accepts a React Native `Animated.Value`. Any `elevation` changes are animated automatically.
Expand Down
12 changes: 6 additions & 6 deletions docs/6.x/docs/guides/theming-with-react-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,27 @@ For React Native Paper theme to work, we need to use `PaperProvider` also at the
```js
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { TouchableOpacity } from 'react-native';
import { Card, Text, List, PaperProvider } from 'react-native-paper';

const Stack = createNativeStackNavigator();

const HomeScreen = ({ navigation }) => (
<TouchableOpacity
<Card
accessibilityLabel={`Open ${title}`}
onPress={() =>
navigation?.push('Details', {
title,
content,
})
}
>
<Card>
title={title}
content={
<Card.Content>
<Text variant="titleLarge">{title}</Text>
<Text variant="bodyMedium">{content}</Text>
</Card.Content>
</Card>
</TouchableOpacity>
}
/>
);

const DetailsScreen = (props) => {
Expand Down
6 changes: 5 additions & 1 deletion docs/component-docs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@ const pages = {
SegmentedButtons: 'SegmentedButtons/SegmentedButtons',
},
Snackbar: 'Snackbar',
Surface: 'Surface',
Surface: {
source: 'Surface',
component: 'StaticSurface',
title: 'Surface',
},
Switch: {
Switch: 'Switch/Switch',
},
Expand Down
25 changes: 25 additions & 0 deletions docs/plugins/component-docs/__tests__/parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, it } from '@jest/globals';
import path from 'node:path';

import { createComponentParser } from '../parser';

describe('component docs parser', () => {
it('documents every branch of the Card public prop unions', () => {
const repositoryRoot = process.cwd();
const parse = createComponentParser(
path.join(repositoryRoot, 'tsconfig.source.json')
);
const { props: documentedProps } = parse(
path.join(repositoryRoot, 'src', 'components'),
{ source: 'Card/Card' }
);
const props = new Map(documentedProps.map((prop) => [prop.name, prop]));

expect(props.get('variant')?.type).toContain('"filled"');
expect(props.get('variant')?.type).toContain('"elevated"');
expect(props.get('variant')?.type).toContain('"outlined"');
expect(props.get('elevation')?.type).toContain('Elevation');
expect(props.get('header')?.type).toBe('React.ReactNode');
expect(props.get('title')?.type).toBe('React.ReactNode');
});
});
16 changes: 1 addition & 15 deletions docs/plugins/component-docs/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,22 +206,8 @@ const getProps = (
? [item.type.getText(item.getSourceFile())]
: []
);
const hasUnionDeclarations = declarations.some((item) => {
let parent: ts.Node | undefined = item.parent;

while (parent && !ts.isTypeAliasDeclaration(parent)) {
if (ts.isUnionTypeNode(parent)) {
return true;
}

parent = parent.parent;
}

return false;
});
const type =
(hasUnionDeclarations && new Set(declarationTypes).size > 1) ||
!declarations.includes(property)
new Set(declarationTypes).size > 1 || !declarations.includes(property)
? checker.typeToString(
checker.getTypeOfSymbolAtLocation(symbol, property),
property,
Expand Down
29 changes: 18 additions & 11 deletions docs/src/components/ThemeColorsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ const getTableCell = (keys: string[], modes: DataObject): ReactNode[] => {
const isDataObject = (value: DataObject[string]): value is DataObject =>
typeof value === 'object';

type TableProps = {
firstColumnLabel: string;
themeColorsData: DataObject;
uniqueKeys: string[];
};

const FlatTable = ({
firstColumnLabel,
themeColorsData,
uniqueKeys,
}: {
themeColorsData: DataObject;
uniqueKeys: string[];
}): ReactNode => {
}: TableProps): ReactNode => {
const rows = Object.keys(themeColorsData).map((mode) => {
const value = themeColorsData[mode];

Expand All @@ -50,7 +54,7 @@ const FlatTable = ({
<table>
<thead>
<tr>
<th>mode</th>
<th>{firstColumnLabel}</th>
{getTableHeader(uniqueKeys)}
</tr>
</thead>
Expand All @@ -61,12 +65,10 @@ const FlatTable = ({
};

const TabbedTable = ({
firstColumnLabel,
themeColorsData,
uniqueKeys,
}: {
themeColorsData: DataObject;
uniqueKeys: string[];
}): ReactNode => {
}: TableProps): ReactNode => {
const tabTableContent = Object.entries(themeColorsData).map(
([key, modes]) => {
if (!isDataObject(modes)) {
Expand All @@ -88,7 +90,7 @@ const TabbedTable = ({
<table>
<thead>
<tr>
<th>mode</th>
<th>{firstColumnLabel}</th>
{getTableHeader(uniqueKeys)}
</tr>
</thead>
Expand Down Expand Up @@ -120,12 +122,17 @@ const ThemeColorsTable = ({
const uniqueKeys = getUniqueNestedKeys(themeColorsData);
const nestingLevel = getMaxNestedLevel(themeColorsData);
const isFlatTable = nestingLevel === 1;
const firstColumnLabel = componentName === 'Card' ? 'variant' : 'mode';

const Table = isFlatTable ? FlatTable : TabbedTable;

return (
<>
<Table themeColorsData={themeColorsData} uniqueKeys={uniqueKeys} />
<Table
firstColumnLabel={firstColumnLabel}
themeColorsData={themeColorsData}
uniqueKeys={uniqueKeys}
/>
<Admonition type="tip">
<p>
If a dedicated prop for a specific color is not available or the{' '}
Expand Down
2 changes: 1 addition & 1 deletion docs/src/data/screenshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export const screenshots = {
'contained-tonal': 'screenshots/button-5.png',
},
Card: {
filled: 'screenshots/card-3.png',
elevated: 'screenshots/card-1.png',
outlined: 'screenshots/card-2.png',
contained: 'screenshots/card-3.png',
},
'Card.Actions': 'screenshots/card-actions.png',
'Card.Content': 'screenshots/card-content-example.png',
Expand Down
8 changes: 4 additions & 4 deletions docs/src/data/themeColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ export const themeColors = {
},
},
Card: {
contained: {
backgroundColor: 'theme.colors.surfaceVariant',
filled: {
backgroundColor: 'theme.colors.surfaceContainerHighest',
},
elevated: {
backgroundColor: 'theme.colors.elevation.level1',
backgroundColor: 'theme.colors.surfaceContainerLow',
},
outlined: {
backgroundColor: 'theme.colors.surface',
borderColor: 'theme.colors.outline',
borderColor: 'theme.colors.outlineVariant',
},
},
Dialog: {
Expand Down
Loading
Loading