Skip to content
Draft
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
86 changes: 86 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,89 @@ const theme = {
style={{ fontSize: 16, color: '#1C1B1F' }}
/>
```

### Appbar

#### Child order

Children render in the order they are written. `Appbar.BackAction` is no longer moved to the front for you.

```tsx
// Before (v5)
<Appbar>
<Appbar.Content title="Title" />
<Appbar.BackAction onPress={goBack} />
</Appbar>

// After (v6)
<Appbar>
<Appbar.BackAction onPress={goBack} />
<Appbar.Content title="Title" />
</Appbar>
```

#### Injected props

`Appbar` no longer injects `color`, `mode` and `theme` into its children. Those values come from context instead, so wrapping `Appbar.Content` or `Appbar.Action` in your own component keeps working.

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

```tsx
// Before (v5)
<Appbar theme={theme}>
<Appbar.Content title="Title" />
</Appbar>

// After (v6)
<Appbar theme={theme}>
<Appbar.Content title="Title" theme={theme} />
</Appbar>
```

#### `isLeading`

`isLeading` on `Appbar.Action` sets the icon color, and in `medium` and `large` it places the action on the leading side of the controls row. It no longer changes the order in `small` and `center-aligned`.

### 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>
```
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
167 changes: 52 additions & 115 deletions src/components/Appbar/Appbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@ import { Animated, StyleSheet, View } from 'react-native';
import type { ColorValue, StyleProp, ViewProps, ViewStyle } from 'react-native';

import AppbarContent from './AppbarContent';
import {
getAppbarBackgroundColor,
modeAppbarHeight,
renderAppbarContent,
filterAppbarActions,
} from './utils';
import { AppbarContext } from './AppbarContext';
import { getAppbarBackgroundColor, modeAppbarHeight } from './utils';
import type { AppbarModes, AppbarChildProps } from './utils';
import { useInternalTheme } from '../../core/theming';
import type { Elevation, ThemeProp } from '../../types';
Expand Down Expand Up @@ -175,45 +171,60 @@ const Appbar = ({

const isDark = typeof dark === 'boolean' ? dark : false;

const isCenterAlignedMode = isMode('center-aligned');

let shouldCenterContent = false;
let shouldAddLeftSpacing = false;
let shouldAddRightSpacing = false;
if (isCenterAlignedMode) {
let hasAppbarContent = false;
let leftItemsCount = 0;
let rightItemsCount = 0;

React.Children.forEach(children, (child) => {
if (React.isValidElement<AppbarChildProps>(child)) {
const isLeading = child.props.isLeading === true;

if (child.type === AppbarContent) {
hasAppbarContent = true;
} else if (isLeading || !hasAppbarContent) {
leftItemsCount++;
} else {
rightItemsCount++;
}
}
});

shouldCenterContent =
hasAppbarContent && leftItemsCount < 2 && rightItemsCount < 3;
shouldAddLeftSpacing = shouldCenterContent && leftItemsCount === 0;
shouldAddRightSpacing = shouldCenterContent && rightItemsCount === 0;
}

const spacingStyle = styles.v3Spacing;

const insets = {
paddingBottom: safeAreaInsets?.bottom,
paddingTop: safeAreaInsets?.top,
paddingLeft: safeAreaInsets?.left,
paddingRight: safeAreaInsets?.right,
};

const appbarContextValue = React.useMemo(
() => ({ isDark, mode }),
[isDark, mode]
);

let content: React.ReactNode = children;

if (isMode('medium') || isMode('large')) {
// Medium/large top app bars use a two-row layout: a controls row with the
// leading and trailing actions above a full-width title row. React Native
// flexbox has no `order`, so the title has to be separated from the actions
// structurally. We partition the children by element identity and the
// `isLeading` prop for layout only — nothing is injected into them; shared
// values flow through `AppbarContext`.
const items = React.Children.toArray(children).filter(
(child): child is React.ReactElement<AppbarChildProps> =>
React.isValidElement(child)
);
const isAppbarContent = (child: React.ReactElement<AppbarChildProps>) => {
const { type } = child;
// React.memo(AppbarContent) wraps the component in an object whose
// `.type` holds the original component — unwrap it so memoized
// Content still lands in the title row.
const innerType =
typeof type === 'object' && type !== null && 'type' in type
? (type as { type: unknown }).type
: type;
return innerType === AppbarContent;
};
const titleItems = items.filter(isAppbarContent);
const actionItems = items.filter((child) => !isAppbarContent(child));
const leadingActions = actionItems.filter((child) => child.props.isLeading);
const trailingActions = actionItems.filter(
(child) => !child.props.isLeading
);

content = (
<View style={styles.columnContainer}>
<View style={styles.controlsRow}>
{leadingActions}
<View style={styles.rightActionControls}>{trailingActions}</View>
</View>
{titleItems}
</View>
);
}

return (
<Surface
style={[
Expand All @@ -229,77 +240,9 @@ const Appbar = ({
container
{...rest}
>
{shouldAddLeftSpacing ? <View style={spacingStyle} /> : null}
{(isMode('small') || isMode('center-aligned')) && (
<>
{/* Render only the back action at first place */}
{renderAppbarContent({
children,
isDark,
theme,
renderOnly: ['Appbar.BackAction'],
shouldCenterContent: isCenterAlignedMode || shouldCenterContent,
})}
{/* Render the rest of the content except the back action */}
{renderAppbarContent({
// Filter appbar actions - first leading icons, then trailing icons
children: [
...filterAppbarActions(children, true),
...filterAppbarActions(children),
],
isDark,
theme,
renderExcept: ['Appbar.BackAction'],
shouldCenterContent: isCenterAlignedMode || shouldCenterContent,
})}
</>
)}
{(isMode('medium') || isMode('large')) && (
<View
style={[
styles.columnContainer,
isMode('center-aligned') && styles.centerAlignedContainer,
]}
>
{/* Appbar top row with controls */}
<View style={styles.controlsRow}>
{/* Left side of row container, can contain AppbarBackAction or AppbarAction if it's leading icon */}
{renderAppbarContent({
children,
isDark,
renderOnly: ['Appbar.BackAction'],
mode,
})}
{renderAppbarContent({
children: filterAppbarActions(children, true),
isDark,
renderOnly: ['Appbar.Action'],
mode,
})}
{/* Right side of row container, can contain other AppbarAction if they are not leading icons */}
<View style={styles.rightActionControls}>
{renderAppbarContent({
children: filterAppbarActions(children),
isDark,
renderExcept: [
'Appbar',
'Appbar.BackAction',
'Appbar.Content',
'Appbar.Header',
],
mode,
})}
</View>
</View>
{renderAppbarContent({
children,
isDark,
renderOnly: ['Appbar.Content'],
mode,
})}
</View>
)}
{shouldAddRightSpacing ? <View style={spacingStyle} /> : null}
<AppbarContext.Provider value={appbarContextValue}>
{content}
</AppbarContext.Provider>
</Surface>
);
};
Expand All @@ -310,9 +253,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
paddingHorizontal: 4,
},
v3Spacing: {
width: 52,
},
controlsRow: {
flex: 1,
flexDirection: 'row',
Expand All @@ -329,9 +269,6 @@ const styles = StyleSheet.create({
flex: 1,
paddingTop: 8,
},
centerAlignedContainer: {
paddingTop: 0,
},
});

export default Appbar;
Expand Down
13 changes: 9 additions & 4 deletions src/components/Appbar/AppbarAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import type {
ViewStyle,
} from 'react-native';

import { useAppbarContext } from './AppbarContext';
import { useInternalTheme } from '../../core/theming';
import { white } from '../../theme/colors';
import type { ThemeProp } from '../../types';
import type { IconSource } from '../Icon';
import IconButton from '../IconButton/IconButton';
Expand Down Expand Up @@ -40,7 +42,7 @@ export type Props = React.ComponentPropsWithoutRef<typeof IconButton> & {
/**
* @supported Available in v5.x with theme version 3
*
* Whether it's the leading button. Note: If `Appbar.BackAction` is present, it will be rendered before any `isLeading` icons.
* Whether it's the leading button. Sets the icon color, and in `medium` and `large` modes places the action on the leading side of the controls row. In `small` and `center-aligned` modes children render in the order they are written.
*/
isLeading?: boolean;
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
Expand Down Expand Up @@ -87,12 +89,15 @@ const AppbarAction = ({
}: Props) => {
const theme = useInternalTheme(themeOverrides);
const { colors } = theme;
const { isDark = false } = useAppbarContext() ?? {};

const actionIconColor = iconColor
? iconColor
: isLeading
? colors.onSurface
: colors.onSurfaceVariant;
: isDark
? white
: isLeading
? colors.onSurface
: colors.onSurfaceVariant;

return (
<IconButton
Expand Down
Loading