# Tray

An elevated container pinned to the bottom of the screen.

## Import

```tsx
import { Tray } from '@coinbase/cds-mobile/overlays/tray/Tray'
```

## Examples

### Basic usage with Callback

The recommended way to use a `Tray` is by passing a callback as children, which receives a `handleClose` function:

```tsx
function BasicTray() {
  const [visible, setVisible] = useState(false);
  const handleOpen = () => setVisible(true);
  const handleClose = () => setVisible(false);

  return (
    <VStack gap={2}>
      <Button onPress={handleOpen}>Open Tray</Button>
      {visible && (
        <Tray title="Example Title" onCloseComplete={handleClose}>
          {({ handleClose }) => (
            <VStack gap={2} padding={3}>
              <Text>This is the content of the tray.</Text>
              <Button onPress={handleClose}>Close</Button>
            </VStack>
          )}
        </Tray>
      )}
    </VStack>
  );
}
```

### Using Ref to Control the Tray

You can also control the Tray using a ref, which provides `open()` and `close()` methods:

```tsx
function TrayWithRef() {
  const [visible, setVisible] = useState(false);
  const trayRef = useRef<DrawerRefBaseProps>(null);

  const handleOpen = () => setVisible(true);
  const handleClose = () => setVisible(false);

  return (
    <VStack gap={2}>
      <Button onPress={handleOpen}>Open Tray</Button>
      {visible && (
        <Tray ref={trayRef} title="Ref Controlled Tray" onCloseComplete={handleClose}>
          <VStack gap={2} padding={3}>
            <Text>Control this tray using the ref.</Text>
            <Button onPress={() => trayRef.current?.close()}>Close</Button>
          </VStack>
        </Tray>
      )}
    </VStack>
  );
}
```

### Scrollable Content

To enable scrolling in a Tray, use `ScrollView` and set `disablePanGesture`:

```tsx
function ScrollableTray() {
  const [visible, setVisible] = useState(false);
  const handleOpen = () => setVisible(true);
  const handleClose = () => setVisible(false);

  return (
    <VStack gap={2}>
      <Button onPress={handleOpen}>Open Scrollable Tray</Button>
      {visible && (
        <Tray title="Scrollable Content" onCloseComplete={handleClose} disablePanGesture>
          {({ handleClose }) => (
            <VStack gap={2}>
              <ScrollView style={{ maxHeight: 200 }}>
                <Pressable>
                  <VStack padding={3} gap={2}>
                    <Text>
                      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
                      incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
                      nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
                    </Text>
                    <Text>
                      Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
                      eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt
                      in culpa qui officia deserunt mollit anim id est laborum.
                    </Text>
                  </VStack>
                </Pressable>
              </ScrollView>
              <HStack padding={3} justifyContent="flex-end">
                <Button onPress={handleClose}>Close</Button>
              </HStack>
            </VStack>
          )}
        </Tray>
      )}
    </VStack>
  );
}
```

### With Selection Menu

When using a Tray for selection, wrap the options in a `Menu` component:

```tsx
function TrayWithMenu() {
  const [visible, setVisible] = useState(false);
  const [selectedValue, setSelectedValue] = useState<string>();
  const trayRef = useRef<DrawerRefBaseProps>(null);

  const handleOpen = () => setVisible(true);
  const handleClose = () => setVisible(false);

  const options = ['Option 1', 'Option 2', 'Option 3'];

  const handleSelect = (value: string) => {
    setSelectedValue(value);
    trayRef.current?.close();
  };

  return (
    <VStack gap={2}>
      <Button onPress={handleOpen}>Open Menu Tray</Button>
      {visible && (
        <Tray ref={trayRef} title="Select an Option" onCloseComplete={handleClose}>
          <Menu value={selectedValue} onChange={handleSelect}>
            {options.map((option) => (
              <SelectOption
                key={option}
                title={option}
                value={option}
                onPress={() => handleSelect(option)}
              />
            ))}
          </Menu>
        </Tray>
      )}
    </VStack>
  );
}
```

### Multiple Overlay Flow

When transitioning between overlays, ensure proper dismounting using `onCloseComplete`:

```tsx
function TrayToModalFlow() {
  const [isTrayVisible, setIsTrayVisible] = useState(false);
  const [isModalVisible, setIsModalVisible] = useState(false);

  const openTray = () => setIsTrayVisible(true);
  const closeTray = () => setIsTrayVisible(false);
  const openModal = () => setIsModalVisible(true);
  const closeModal = () => setIsModalVisible(false);

  const handleTrayClose = useCallback(() => {
    closeTray();
    openModal();
  }, []);

  return (
    <VStack gap={2}>
      <Button onPress={openTray}>Start Flow</Button>
      {isTrayVisible && (
        <Tray title="First Step" onCloseComplete={handleTrayClose}>
          {({ handleClose }) => (
            <VStack gap={2} padding={3}>
              <Text>Click below to continue to the modal</Text>
              <Button onPress={handleClose}>Continue to Modal</Button>
            </VStack>
          )}
        </Tray>
      )}
      <Modal visible={isModalVisible} onRequestClose={closeModal}>
        <ModalHeader title="Second Step" />
        <ModalBody>
          <VStack gap={2} padding={3}>
            <Text>This is the second step in the flow.</Text>
            <Button onPress={closeModal}>Finish</Button>
          </VStack>
        </ModalBody>
      </Modal>
    </VStack>
  );
}
```

Note: The Tray component is built on top of the Drawer component and provides a standardized way to present bottom sheets in your mobile application. Key points:

- Use `onCloseComplete` for cleanup when the tray is dismissed
- Children can be either a React node or a render function that receives a `handleClose` function
- The `ref` provides `open()` and `close()` methods for controlling the tray
- Use `disablePanGesture` when implementing scrollable content
- When transitioning between overlays, ensure proper dismounting using lifecycle methods

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `onCloseComplete` | `() => void` | Yes | `-` | Action that will happen when drawer is dismissed |
| `animated` | `boolean` | No | `-` | - |
| `animationType` | `none \| slide \| fade` | No | `-` | The animationType prop controls how the modal animates.  - slide slides in from the bottom - fade fades into view - none appears without an animation |
| `disableCapturePanGestureToDismiss` | `boolean` | No | `false` | Prevents the Drawer from capturing pan gestures on children. Set to true when using a ScrollView as a child |
| `footer` | `null \| string \| number \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | No | `-` | ReactNode to render as the Tray footer |
| `handleBarAccessibilityLabel` | `string` | No | `-` | Accessibility label for handlebar |
| `hardwareAccelerated` | `boolean` | No | `-` | Controls whether to force hardware acceleration for the underlying window. |
| `header` | `null \| string \| number \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | No | `-` | ReactNode to render as the Tray header |
| `hideHandleBar` | `boolean` | No | `false` | The HandleBar by default only is used for a bottom pinned drawer. This removes it. |
| `key` | `Key \| null` | No | `-` | - |
| `onBlur` | `(() => void)` | No | `-` | Callback fired when the overlay is pressed, or swipe to close |
| `onDismiss` | `(() => void)` | No | `-` | The onDismiss prop allows passing a function that will be called once the modal has been dismissed. |
| `onOrientationChange` | `((event: NativeSyntheticEvent<any>) => void)` | No | `-` | The onOrientationChange callback is called when the orientation changes while the modal is being displayed. The orientation provided is only portrait or landscape. This callback is also called on initial render, regardless of the current orientation. |
| `onPointerCancel` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerCancelCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerDown` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerDownCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerEnter` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerEnterCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerLeave` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerLeaveCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerMove` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerMoveCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerUp` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onPointerUpCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
| `onShow` | `((event: NativeSyntheticEvent<any>) => void)` | No | `-` | The onShow prop allows passing a function that will be called once the modal has been shown. |
| `onVisibilityChange` | `((context: visible \| hidden) => void)` | No | `-` | Optional callback that, if provided, will be triggered when the Tray is toggled open/ closed If used for analytics, context (visible \| hidden) can be bundled with the event info to track whether the multiselect was toggled into or out of view |
| `presentationStyle` | `fullScreen \| pageSheet \| formSheet \| overFullScreen` | No | `-` | The presentationStyle determines the style of modal to show |
| `preventDismissGestures` | `boolean` | No | `false` | Prevents a user from dismissing the drawer by pressing the overlay or swiping |
| `preventHardwareBackBehaviorAndroid` | `boolean` | No | `false` | Prevents a user from dismissing the drawer by pressing hardware back button on Android |
| `ref` | `((instance: DrawerRefBaseProps \| null) => void) \| RefObject<DrawerRefBaseProps> \| null` | No | `-` | - |
| `statusBarTranslucent` | `boolean` | No | `-` | Determines whether your modal should go under the system statusbar. |
| `stickyFooter` | `ReactNode \| DrawerRenderChildren` | No | `-` | StickyFooter to be rendered at bottom of Drawer |
| `supportedOrientations` | `(portrait \| portrait-upside-down \| landscape \| landscape-left \| landscape-right)[]` | No | `-` | The supportedOrientations prop allows the modal to be rotated to any of the specified orientations. On iOS, the modal is still restricted by whats specified in your apps Info.plists UISupportedInterfaceOrientations field. |
| `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID Used to locate this view in end-to-end tests. |
| `title` | `null \| string \| number \| false \| true \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal` | No | `-` | Text or ReactNode for optional Tray title |
| `transparent` | `boolean` | No | `-` | The transparent prop determines whether your modal will fill the entire view. Setting this to true will render the modal over a transparent background. |
| `verticalDrawerPercentageOfView` | `number` | No | `-` | Allow user of component to define maximum percentage of screen that can be taken up by the Drawer |
| `visible` | `boolean` | No | `-` | The visible prop determines whether your modal is visible. |


