Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
00e72de
refactor(list): replace React.Children injection in List.Accordion wi…
matkoson Jun 18, 2026
0e91ea5
refactor(dialog): remove child composition injection
matkoson Jun 23, 2026
41ecff8
refactor(card): remove child composition injection
matkoson Jun 23, 2026
2d97fbc
refactor(toggle-button): style row buttons via context
matkoson Jun 23, 2026
012f27f
test: align composition tests with upstream utilities
matkoson Jun 29, 2026
426827f
fix: preserve dialog icon title spacing
matkoson Jun 29, 2026
d2f5f61
refactor(toggle-button): align segmented row to MD3 spec
matkoson Jun 30, 2026
414bc13
test(toggle-button): assert MD3 secondaryContainer selected fill in row
matkoson Jun 30, 2026
17199df
refactor(appbar): provide shared values via context
matkoson Jun 30, 2026
b3d0b92
revert(toggle-button): drop MD3 redesign, keep legacy visual identity
matkoson Jun 30, 2026
0d08f46
fix(toggle-button, dialog): address review feedback on composition re…
matkoson Jul 2, 2026
38fb0b4
fix(card, dialog, appbar): resolve remaining Copilot review findings
matkoson Jul 6, 2026
a33bb5b
Merge branch 'main' into refactor/react-children-takeover
adam-sajko Aug 28, 2026
526de9e
revert: drop Appbar from this PR
adam-sajko Aug 28, 2026
6e81c50
fix: preserve child keys in Card and Dialog action rows
adam-sajko Aug 25, 2026
3d070b5
test: stabilise the Tooltip pressOut timing
adam-sajko Aug 25, 2026
6b22e0b
docs: document Card, Dialog, List and ToggleButton.Row breaking changes
adam-sajko Aug 28, 2026
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: 96 additions & 0 deletions docs/6.x/docs/guides/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,99 @@ const theme = {
style={{ fontSize: 16, color: '#1C1B1F' }}
/>
```

### Card

#### `Card.Actions`

`Card.Actions` no longer assigns `mode` to its buttons, previously `outlined` for the first one and `contained` for the rest, and no longer injects `compact`. Set both on the buttons.

```tsx
// Before (v5)
<Card.Actions>
<Button>Cancel</Button>
<Button>Ok</Button>
</Card.Actions>

// After (v6)
<Card.Actions>
<Button mode="outlined">Cancel</Button>
<Button mode="contained">Ok</Button>
</Card.Actions>
```

#### `Card.Content`

`Card.Content` uses the same vertical padding everywhere. Before it changed depending on the sections next to it.

### Dialog

#### `Dialog.Actions`

`Dialog.Actions` no longer injects `compact` and `uppercase` into the action buttons. Set them yourself if you want the old look.

```tsx
// Before (v5)
<Dialog.Actions>
<Button onPress={hide}>Done</Button>
</Dialog.Actions>

// After (v6)
<Dialog.Actions>
<Button compact uppercase onPress={hide}>
Done
</Button>
</Dialog.Actions>
```

### List

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No ### ToggleButton section, but ToggleButton.Row changes visibly - divider ownership moves to the row and alignSelf: 'flex-start' is new. Card, Dialog and List all got one.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch, done


#### `List.Accordion`

When `List.Accordion` has a `left` element, it used to indent every child that rendered no `left` or `right` of its own, whatever the child was. The indent now comes from context and only `List.Item` reads it, so a custom child keeps its own padding. Indent it yourself if you need the old alignment.

```tsx
// Before (v5)
<List.Accordion title="Group" left={props => <List.Icon {...props} icon="folder" />}>
<View>
<Text>Custom row</Text>
</View>
</List.Accordion>

// After (v6)
<List.Accordion title="Group" left={props => <List.Icon {...props} icon="folder" />}>
<View style={{ paddingLeft: 40 }}>
<Text>Custom row</Text>
</View>
</List.Accordion>
```

`theme` set on `List.Accordion` no longer reaches its children either. Pass it to the child that needs the override.

### ToggleButton

#### `ToggleButton.Row`

`ToggleButton.Row` no longer clones its children to give them a position in the row. The segmented look comes from context, so a `ToggleButton` wrapped in a component of your own now picks it up, while a child that is not a `ToggleButton` gets no treatment at all.

The dividers moved to the row with it. Buttons used to draw their own borders, the row now paints an `outline` background and the hairline gaps between buttons show through. A border set on a single `ToggleButton` no longer builds the segmented outline.

The row also sits on `alignSelf: 'flex-start'`, so it wraps its buttons instead of stretching to the parent. Set it back if you relied on the full width.

```tsx
// Before (v5)
<ToggleButton.Row value={value} onValueChange={setValue}>
<ToggleButton icon="format-align-left" value="left" />
<ToggleButton icon="format-align-right" value="right" />
</ToggleButton.Row>

// After (v6)
<ToggleButton.Row
value={value}
onValueChange={setValue}
style={{ alignSelf: 'stretch' }}
>
<ToggleButton icon="format-align-left" value="left" />
<ToggleButton icon="format-align-right" value="right" />
</ToggleButton.Row>
```
12 changes: 8 additions & 4 deletions example/src/Examples/CardExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ const CardExample = () => {
<Card style={styles.card} mode={selectedMode}>
<Card.Cover source={require('../../assets/images/forest.jpg')} />
<Card.Actions>
<Button onPress={() => {}}>Share</Button>
<Button onPress={() => {}}>Explore</Button>
<Button mode="outlined" onPress={() => {}}>
Share
</Button>
<Button mode="contained" onPress={() => {}}>
Explore
</Button>
</Card.Actions>
</Card>
<Card style={styles.card} mode={selectedMode}>
Expand Down Expand Up @@ -104,10 +108,10 @@ const CardExample = () => {
/>
<Card.Title title="Custom Button styles" />
<Card.Actions>
<Button style={styles.button} onPress={() => {}}>
<Button mode="outlined" style={styles.button} onPress={() => {}}>
Share
</Button>
<Button style={styles.button} onPress={() => {}}>
<Button mode="contained" style={styles.button} onPress={() => {}}>
Explore
</Button>
</Card.Actions>
Expand Down
16 changes: 12 additions & 4 deletions example/src/Examples/TeamDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,12 @@ const News = () => {
</Text>
</Card.Content>
<Card.Actions>
<Button onPress={() => {}}>Share</Button>
<Button onPress={() => {}}>Read more</Button>
<Button mode="outlined" onPress={() => {}}>
Share
</Button>
<Button mode="contained" onPress={() => {}}>
Read more
</Button>
</Card.Actions>
</Card>
<Card style={styles.card} mode="contained">
Expand All @@ -110,8 +114,12 @@ const News = () => {
</Text>
</Card.Content>
<Card.Actions>
<Button onPress={() => {}}>Share</Button>
<Button onPress={() => {}}>Read more</Button>
<Button mode="outlined" onPress={() => {}}>
Share
</Button>
<Button mode="contained" onPress={() => {}}>
Read more
</Button>
</Card.Actions>
</Card>
</View>
Expand Down
32 changes: 10 additions & 22 deletions src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export type Props = $Omit<React.ComponentProps<typeof Surface>, 'mode'> & {

/**
* A card is a sheet of material that serves as an entry point to more detailed information.
* Card clips its inner content to the card shape and renders children directly;
* section spacing is owned by the section components themselves.
*
* ## Usage
* ```js
Expand All @@ -112,8 +114,8 @@ export type Props = $Omit<React.ComponentProps<typeof Surface>, 'mode'> & {
* </Card.Content>
* <Card.Cover source={{ uri: 'https://picsum.photos/700' }} />
* <Card.Actions>
* <Button>Cancel</Button>
* <Button>Ok</Button>
* <Button mode="outlined">Cancel</Button>
* <Button mode="contained">Ok</Button>
* </Card.Actions>
* </Card>
* );
Expand Down Expand Up @@ -185,15 +187,6 @@ const Card = ({
runElevationAnimation('out');
});

const total = React.Children.count(children);
const siblings = React.Children.map(children, (child) =>
React.isValidElement(child) && child.type
? typeof child.type !== 'string' && 'displayName' in child.type
? child.type.displayName
: null
: null
);

const { backgroundColor, borderColor: themedBorderColor } = getCardColors({
theme,
mode: cardMode,
Expand All @@ -215,17 +208,11 @@ const Card = ({
};

const content = (
<View style={[styles.innerContainer, contentStyle]} testID={testID}>
{React.Children.map(children, (child, index) =>
React.isValidElement(child)
? React.cloneElement(child as React.ReactElement<any>, {
index,
total,
siblings,
borderRadiusStyles,
})
: child
)}
<View
style={[styles.innerContainer, borderRadiusCombinedStyles, contentStyle]}
testID={testID}
>
{children}
</View>
);

Expand Down Expand Up @@ -291,6 +278,7 @@ Card.Title = CardTitle;
const styles = StyleSheet.create({
innerContainer: {
flexShrink: 1,
overflow: 'hidden',
},
outline: {
borderWidth: 1,
Expand Down
39 changes: 17 additions & 22 deletions src/components/Card/CardActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import type { StyleProp, ViewProps, ViewStyle } from 'react-native';

import type { CardActionChildProps } from './utils';
import { useInternalTheme } from '../../core/theming';
import type { ThemeProp } from '../../types';

Expand All @@ -17,6 +16,8 @@ export type Props = ViewProps & {

/**
* A component to show a list of actions inside a Card.
* Actions are rendered directly, so set button `mode`, `compact`, and custom
* spacing props explicitly on each action when needed.
*
* ## Usage
* ```js
Expand All @@ -26,8 +27,8 @@ export type Props = ViewProps & {
* const MyComponent = () => (
* <Card>
* <Card.Actions>
* <Button>Cancel</Button>
* <Button>Ok</Button>
* <Button mode="outlined">Cancel</Button>
* <Button mode="contained">Ok</Button>
* </Card.Actions>
* </Card>
* );
Expand All @@ -43,26 +44,20 @@ const CardActions = ({ theme, style, children, ...rest }: Props) => {
{ justifyContent: 'flex-end' } satisfies ViewStyle,
style,
];
const items = React.Children.toArray(children);

return (
<View {...rest} style={containerStyle}>
{React.Children.map(children, (child, index) => {
if (!React.isValidElement<CardActionChildProps>(child)) {
return child;
}

const compact = child.props.compact;
const mode =
child.props.mode ?? (index === 0 ? 'outlined' : 'contained');
const childStyle = [styles.button, child.props.style];

return React.cloneElement(child, {
...child.props,
compact,
mode,
style: childStyle,
});
})}
{items.map((child, index) => (
<React.Fragment
key={
React.isValidElement(child) && child.key != null ? child.key : index
}
>
{index > 0 && <View style={styles.spacer} />}
{child}
</React.Fragment>
))}
</View>
);
};
Expand All @@ -75,8 +70,8 @@ const styles = StyleSheet.create({
alignItems: 'center',
padding: 8,
},
button: {
marginLeft: 8,
spacer: {
width: 8,
},
});

Expand Down
61 changes: 5 additions & 56 deletions src/components/Card/CardContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,13 @@ export type Props = ViewProps & {
* Items inside the `Card.Content`.
*/
children: React.ReactNode;
/**
* @internal
*/
index?: number;
/**
* @internal
*/
total?: number;
/**
* @internal
*/
siblings?: Array<string | null>;
style?: StyleProp<ViewStyle>;
};

/**
* A component to show content inside a Card.
* Content uses uniform vertical padding and does not depend on neighboring
* card sections.
*
* ## Usage
* ```js
Expand All @@ -42,59 +32,18 @@ export type Props = ViewProps & {
* export default MyComponent;
* ```
*/
const CardContent = ({ index, total, siblings, style, ...rest }: Props) => {
const cover = 'withInternalTheme(CardCover)';
const title = 'withInternalTheme(CardTitle)';

let contentStyle, prev, next;

if (typeof index === 'number' && siblings) {
prev = siblings[index - 1];
next = siblings[index + 1];
}

if (
(prev === cover && next === cover) ||
(prev === title && next === title) ||
total === 1
) {
contentStyle = styles.only;
} else if (index === 0) {
if (next === cover || next === title) {
contentStyle = styles.only;
} else {
contentStyle = styles.first;
}
} else if (typeof total === 'number' && index === total - 1) {
if (prev === cover || prev === title) {
contentStyle = styles.only;
} else {
contentStyle = styles.last;
}
} else if (prev === cover || prev === title) {
contentStyle = styles.first;
} else if (next === cover || next === title) {
contentStyle = styles.last;
}

return <View {...rest} style={[styles.container, contentStyle, style]} />;
};
const CardContent = ({ style, ...rest }: Props) => (
<View {...rest} style={[styles.container, style]} />
);

CardContent.displayName = 'Card.Content';

const styles = StyleSheet.create({
container: {
paddingHorizontal: 16,
},
first: {
paddingTop: 16,
},
last: {
paddingBottom: 16,
},
only: {
paddingVertical: 16,
},
});

export default CardContent;
Loading