Sheet

You use the Sheet component to avoid cluttering up the main content. With a bottom sheet, for example, the sheet starts off collapsed and the user expands it by dragging. In our example application, when the user opens an email we display the sender's information in a Sheet.

import {Sheet} from 'navigation-react-native';

<Sheet hideable={true}>
  <ScrollView />
</Sheet>

Note

On iOS, dragging the sheet isn't completely smooth. Removing the slight stutter will have to wait until React Native introduces synchronous view resizing.

The resting states of a Sheet are called detents, for example, 'collapsed' or 'expanded'. By default the Sheet is uncontrolled, where only the user can change the detent (by dragging). But it can also be controlled so you can programmatically change the detent. In our email example, when the user presses the sender's icon button we expand the Sheet to reveal the sender's details.

const [detent, setDetent] = useState('hidden');

<Button onPress={() => setDetent('expanded')} />
<Sheet detent={detent} onChangeDetent={setDetent} hideable={true}>
  <Button onPress={() => setDetent('hidden')} />
</Sheet>

There are props that allow you to configure the various detent heights. The peekHeight prop sets the height of the 'collapsed' detent. The halfExpandedRatio prop is for the 'halfExpanded' detent. And the height of the 'expanded' sheet is determined by the expandedHeight or expandedOffset props.

Interacting with the Presenting Scene

You can also have a non-modal Sheet. The Sheet is 100% native so, on Android, the non-modal version must be a child of a CoordinatorLayout. In our email example, we make the sheet non-modal so the user can compose a reply while viewing the sender's details. We turn on nested scrolling so that the sheet's content scrolls seamlessly on Android when the user expands it.

<CoordinatorLayout>
  <Sheet modal={false}>
    <ScrollView nestedScrollEnabled={true} />
  </Sheet>
</CoordinatorLayout>

Making a Full-Screen Sheet

You can make a full-screen Sheet by setting bottom to false. Because the Sheet is 100% native, a full-screen sheet can't be dismissed by dragging on iOS. Instead, you have to programatically close it by setting the detent to 'hidden'. In our email example, we add a 'Create Contact' button to the contacts list. When pressed, we display a sheet to collect the contact's name and number.

<Sheet bottom={false} detent={detent} onChangeDetent={setDetent}>
  <Contact />
</Sheet>

Navigating in a Sheet

A sheet can have its own stack of scenes independent of the main stack. Inside the Sheet, you render a NavigationStack with its own StateNavigator. We need two scenes in the full-screen sheet in our email example. The first collects the new contact's name and number and the second collects their address. We create a StateNavigator with a State for each scene.

const contactNavigator = new StateNavigator([
  {key: 'contact'},
  {key: 'address', trackCrumbTrail: true}
]);

Then we render the NavigationStack inside the Sheet and assign it the StateNavigator we just created.

<Sheet>
  <NavigationHandler stateNavigator={contactNavigator}>
    <NavigationStack>
      <Scene stateKey="contact"><Contact /></Scene>
      <Scene stateKey="address"><Address /></Scene>
    </NavigationStack>
  </NavigationHandler>
</Sheet>