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
65 changes: 65 additions & 0 deletions docs/6.x/docs/guides/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,71 @@ You can specify a `testID` explicitly and use that value to query the component.

## Components

### Divider

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.

This conflicts with main now - #5078 landed a ## General changes section at the same insertion point. Both sides only add text, so keeping both resolves it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rebased on main and resolved the conflicts:

  • docs/6.x/docs/guides/migration.md: kept both sections: this PR's new ### Divider entry plus refactor: rework Surface and elevation shadows #5078's ## General changes section, as suggested.
  • docs/6.x/docs/components/Divider.mdx: removed, following main's deletion. main migrated to auto-generated component docs (from JSDoc in source), and the accessibility note this PR added here already lives in Divider.tsx's JSDoc, so nothing was lost.
  • docs/src/data/componentDocs6x.json: removed, also following main's deletion (a generated data file for the old docs system, no longer used).


| v5 | v6 |
| --- | --- |
| `leftInset` | `startInset` |
| `bold` | removed, dividers are 1dp thick by default |
| - | `orientation="vertical"` |

#### Thickness

Dividers are 1dp thick now, which is what the Material Design 3 spec asks for. In v5 the default was `StyleSheet.hairlineWidth`, thinner than 1dp on most screens, and `bold` was the only way to get a full 1dp line. The `bold` prop is gone.

```diff
- <Divider bold />
+ <Divider />
```

If you want the hairline back, set it in `style`:

```diff
- <Divider />
+ <Divider style={{ height: StyleSheet.hairlineWidth }} />
```

#### Inset

`leftInset` set `marginLeft`, so in RTL the inset stayed on the left instead of moving to the leading edge. Use `startInset` instead. It insets the leading edge and follows the writing direction.

```diff
- <Divider leftInset />
+ <Divider startInset />
```

`horizontalInset` works the same as before.

#### Orientation

Dividers can be vertical now. A vertical divider is 1dp wide and stretches to the height of its parent, so the parent has to lay its children out in a row.

```tsx
<View style={{ flexDirection: 'row' }}>
<Text>Lemon</Text>
<Divider orientation="vertical" />
<Text>Mango</Text>
</View>
```

Insets follow the orientation. On a vertical divider, `startInset` insets the top edge, and `horizontalInset` insets the top and bottom edges.

#### Accessibility

Dividers are decorative, so screen readers skip them and they stay out of the focus order. If a divider means something on its own, opt back in:

```diff
- <Divider />
+ <Divider accessible aria-hidden={false} role="separator" />
```

This also affects tests: `aria-hidden` excludes the divider from `getByTestId` and similar queries by default. Pass `{ includeHiddenElements: true }` to the query, or opt the divider into the accessibility tree as shown above.

```diff
- getByTestId('divider')
+ getByTestId('divider', { includeHiddenElements: true })
```

### Appbar

The `style` props for `Appbar` and `Appbar.Header` no longer accept `Animated.Value` or `Animated.AnimatedInterpolation`. They only accept static styles.
Expand Down
67 changes: 53 additions & 14 deletions example/src/Examples/DividerExample.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,67 @@
import { FlatList } from 'react-native';
import * as React from 'react';
import { StyleSheet, View } from 'react-native';

import { Divider, List, useTheme } from 'react-native-paper';
import { Divider, List, Text } from 'react-native-paper';

import ScreenWrapper from '../ScreenWrapper';

const items = ['Apple', 'Banana', 'Coconut', 'Lemon', 'Mango', 'Peach'];
const items = ['Apple', 'Banana', 'Coconut'];

const DividerExample = () => {
const { colors } = useTheme();

return (
<ScreenWrapper withScrollView={false}>
<FlatList
style={{ backgroundColor: colors?.background }}
renderItem={({ item }) => <List.Item title={item} />}
keyExtractor={(item) => item}
ItemSeparatorComponent={Divider}
data={items}
alwaysBounceVertical={false}
/>
<ScreenWrapper>
<List.Section title="Full width">
{items.map((item) => (
<React.Fragment key={item}>
<List.Item title={item} />
<Divider />
</React.Fragment>
))}
</List.Section>
<List.Section title="Inset from the start">
{items.map((item) => (
<React.Fragment key={item}>
<List.Item title={item} />
<Divider startInset />
</React.Fragment>
))}
</List.Section>
<List.Section title="Inset from both sides">
{items.map((item) => (
<React.Fragment key={item}>
<List.Item title={item} />
<Divider horizontalInset />
</React.Fragment>
))}
</List.Section>
<List.Section title="Vertical">
<View style={styles.row}>
{items.map((item, index) => (
<React.Fragment key={item}>
{index > 0 && <Divider orientation="vertical" horizontalInset />}
<Text variant="bodyLarge" style={styles.column}>
{item}
</Text>
</React.Fragment>
))}
</View>
</List.Section>
</ScreenWrapper>
);
};

DividerExample.title = 'Divider';

const styles = StyleSheet.create({
row: {
flexDirection: 'row',
marginHorizontal: 16,
},
column: {
flex: 1,
paddingVertical: 24,
textAlign: 'center',
},
});

export default DividerExample;
2 changes: 1 addition & 1 deletion example/src/Examples/FABExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ const FABExample = () => {
)}
onPress={() => setShowFab((v) => !v)}
/>
<Divider bold style={{ backgroundColor: colors.outline }} />
<Divider style={{ backgroundColor: colors.outline }} />
</View>
<FlatList
data={rows}
Expand Down
65 changes: 43 additions & 22 deletions src/components/Divider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@ import type { StyleProp, ViewProps, ViewStyle } from 'react-native';
import { useInternalTheme } from '../core/theming';
import type { ThemeProp } from '../theme/types';

const THICKNESS = 1;
const INSET = 16;

export type Props = Omit<React.PropsWithoutRef<ViewProps>, 'children'> & {
/**
* @renamed Renamed from 'inset' to 'leftInset` in v5.x
* Whether divider has a left inset.
* Orientation of the divider. A vertical divider stretches to the height of
* its parent, so the parent has to lay its children out in a row.
*/
leftInset?: boolean;
orientation?: 'horizontal' | 'vertical';
/**
* @supported Available in v5.x with theme version 3
* Whether divider has a horizontal inset on both sides.
* Whether the divider is inset from the leading edge, which is the left edge
* in LTR and the right edge in RTL. On a vertical divider it's the top edge.
*/
horizontalInset?: boolean;
startInset?: boolean;
/**
* @supported Available in v5.x with theme version 3
* Whether divider should be bolded.
* Whether the divider is inset from both edges: left and right on a
* horizontal divider, top and bottom on a vertical one.
*/
bold?: boolean;
horizontalInset?: boolean;
style?: StyleProp<ViewStyle>;
/**
* @optional
Expand All @@ -31,6 +34,10 @@ export type Props = Omit<React.PropsWithoutRef<ViewProps>, 'children'> & {
/**
* A divider is a thin, lightweight separator that groups content in lists and page layouts.
*
* Dividers are decorative, so screen readers skip them. If a divider means
* something on its own, pass `accessible`, `aria-hidden={false}` and
* `role="separator"`.
*
* ## Usage
* ```js
* import * as React from 'react';
Expand All @@ -50,41 +57,55 @@ export type Props = Omit<React.PropsWithoutRef<ViewProps>, 'children'> & {
* ```
*/
const Divider = ({
leftInset,
orientation = 'horizontal',
startInset = false,
horizontalInset = false,
style,
theme: themeOverrides,
bold = false,
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);

const dividerColor = theme.colors.outlineVariant;
const isVertical = orientation === 'vertical';

return (
<View
aria-hidden

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.

Tests that look up a divider will stop finding it - aria-hidden keeps it out of getByTestId unless the query passes { includeHiddenElements: true }. Worth a line in the migration guide?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good catch, I've added a note in the migration guide. Also fixed latest conflicts.

{...rest}
style={[
{ height: StyleSheet.hairlineWidth, backgroundColor: dividerColor },
leftInset && styles.v3LeftInset,
horizontalInset && styles.horizontalInset,
bold && styles.bold,
isVertical ? styles.vertical : styles.horizontal,
{ backgroundColor: theme.colors.outlineVariant },
startInset &&
(isVertical ? styles.verticalStartInset : styles.startInset),
horizontalInset &&
(isVertical ? styles.verticalInset : styles.horizontalInset),
style,
]}
/>
);
};

const styles = StyleSheet.create({
v3LeftInset: {
marginLeft: 16,
horizontal: {
height: THICKNESS,
},
vertical: {
width: THICKNESS,
alignSelf: 'stretch',
},
startInset: {
marginStart: INSET,
},
horizontalInset: {
marginLeft: 16,
marginRight: 16,
marginStart: INSET,
marginEnd: INSET,
},
verticalStartInset: {
marginTop: INSET,
},
bold: {
height: 1,
verticalInset: {
marginTop: INSET,
marginBottom: INSET,
},
});

Expand Down
1 change: 0 additions & 1 deletion src/components/Drawer/DrawerSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ const DrawerSection = ({
{showDivider && (
<Divider
horizontalInset
bold
style={[styles.divider, styles.v3Divider]}
theme={theme}
/>
Expand Down
1 change: 0 additions & 1 deletion src/components/Searchbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,6 @@ const Searchbar = ({
right?.({ color: textColor, style: styles.rightStyle, testID })}
{!isBarMode && showDivider && (
<Divider
bold
style={[
styles.divider,
{
Expand Down
97 changes: 97 additions & 0 deletions src/components/__tests__/Divider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { expect, it } from '@jest/globals';

import { defaultThemes } from '../../core/theming';
import { render, screen } from '../../test-utils';
import Divider from '../Divider';

const hidden = { includeHiddenElements: true };

it('renders divider', async () => {
const tree = (await render(<Divider />)).toJSON();

expect(tree).toMatchSnapshot();
});

it('renders vertical divider', async () => {
const tree = (await render(<Divider orientation="vertical" />)).toJSON();

expect(tree).toMatchSnapshot();
});

it('renders 1dp thick horizontal line by default', async () => {
await render(<Divider testID="divider" />);

expect(screen.getByTestId('divider', hidden)).toHaveStyle({
height: 1,
backgroundColor: defaultThemes.light.colors.outlineVariant,
});
});

it('renders 1dp thick line stretched to the parent when vertical', async () => {
await render(<Divider orientation="vertical" testID="divider" />);

expect(screen.getByTestId('divider', hidden)).toHaveStyle({
width: 1,
alignSelf: 'stretch',
backgroundColor: defaultThemes.light.colors.outlineVariant,
});
expect(screen.getByTestId('divider', hidden)).not.toHaveStyle({ height: 1 });
});

it('insets the start edge in a writing direction aware way', async () => {
await render(<Divider startInset testID="divider" />);

const divider = screen.getByTestId('divider', hidden);

expect(divider).toHaveStyle({ marginStart: 16 });
expect(divider).not.toHaveStyle({ marginLeft: 16 });
});

it('insets both edges', async () => {
await render(<Divider horizontalInset testID="divider" />);

const divider = screen.getByTestId('divider', hidden);

expect(divider).toHaveStyle({ marginStart: 16, marginEnd: 16 });
expect(divider).not.toHaveStyle({ marginLeft: 16 });
expect(divider).not.toHaveStyle({ marginRight: 16 });
});

it('insets the leading end of a vertical divider', async () => {
await render(<Divider orientation="vertical" startInset testID="divider" />);

const divider = screen.getByTestId('divider', hidden);

expect(divider).toHaveStyle({ marginTop: 16 });
expect(divider).not.toHaveStyle({ marginStart: 16 });
});

it('insets both ends of a vertical divider', async () => {
await render(
<Divider orientation="vertical" horizontalInset testID="divider" />
);

expect(screen.getByTestId('divider', hidden)).toHaveStyle({
marginTop: 16,
marginBottom: 16,
});
});

it('applies custom styles over the defaults', async () => {
await render(<Divider style={{ height: 4 }} testID="divider" />);

expect(screen.getByTestId('divider', hidden)).toHaveStyle({ height: 4 });
});

it('stays out of the accessibility tree', async () => {
await render(<Divider testID="divider" />);

expect(screen.queryByTestId('divider')).toBeNull();
expect(screen.getByTestId('divider', hidden)).toHaveProp('aria-hidden', true);
});

it('can be exposed as a separator', async () => {
await render(<Divider accessible aria-hidden={false} role="separator" />);

expect(screen.getByRole('separator')).toBeOnTheScreen();
});
Loading