colorfy-software / react-native-modalfy

🥞 Modal citizen of React Native.
https://colorfy-software.gitbook.io/react-native-modalfy
MIT License
1.06k stars 41 forks source link

ScrollView within modal breaks positioning #24

Closed jack828 closed 3 years ago

jack828 commented 3 years ago

Hi, first off, thanks for a bloody amazing library! It's totally changed the way I handle modals in my applications from a stupid mess of local state and weird edge-case handling into a beautiful abstraction.

So in my modal provider I have this:

import React from 'react'
import { ModalProvider, createModalStack } from 'react-native-modalfy'

import * as modals from './'

const defaultOptions = {
  backdropOpacity: 0.4,
  position: 'center'
}

const stack = createModalStack(modals, defaultOptions)

export default ({ children }) => (
  <ModalProvider stack={stack}>{children}</ModalProvider>
)

I had recently re-found the position option after the initial implementation, as I was adding some annoying height styles to my modal views to get it in the position I wanted.

But some of my modals don't position correctly:

import React from 'react'
import { StyleSheet, View, ScrollView } from 'react-native'
import {
  Headline,
  Paragraph,
  Button,
  TouchableRipple
} from 'react-native-paper'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'

const LocationResetModal = ({
  theme: { colors, ...theme },
  modal: { closeModal },
  resetLocation
}) => {
  const handleSubmitLocation = () => {
    closeModal()
    resetLocation()
  }

  return (
    <View
      style={[
        styles.contentContainer,
        {
          backgroundColor: colors.secondary,
          borderRadius: theme.roundness
        }
      ]}
    >
      <View style={styles.header}>
        <Headline>Are you sure?</Headline>
        <TouchableRipple onPress={() => closeModal()}>
          <Icon size={24} name="close" />
        </TouchableRipple>
      </View>

      <ScrollView style={styles.container}>
        <Paragraph>
          If you change your location, you will lose all items in your basket!
        </Paragraph>
      </ScrollView>

      <View style={styles.buttonContainer}>
        <Button
          style={[styles.button, styles.marginRight]}
          mode="contained"
          compact
          onPress={() => closeModal()}
        >
          Go back
        </Button>
        <Button
          style={styles.button}
          mode="outlined"
          compact
          onPress={() => handleSubmitLocation()}
        >
          I’m sure
        </Button>
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  contentContainer: {
    padding: 24,
    margin: 24
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between'
  },
  container: {},
  buttonContainer: {
    paddingTop: 12,
    flexDirection: 'row'
  },
  button: {
    flex: 1
  },
  marginRight: {
    marginRight: 12
  }
})

export default LocationResetModal

This modal will always render at the top of the screen, regardless of config option.

If I replace the ScrollView around the central content elements with a View, it works fine! (it's a scroll view because I copy-pasted from a terms & conditions modal...)

Let me know if you need anything else. I'll be available for the next week, on mobile only though.

CharlesMangwa commented 3 years ago

Hey @jack828! First of all: thanks for the kind words, much appreciated! Glad to read that Modalfy is helping you that much!

Regarding your issue: if you could provide a reproducible app with Expo's Snack, that'd be really helpful.

Fwi, we do have a use case where we have a ScrollView inside a modal with position: 'center' working; but the main wrapper is a View. I think you could go for such an approach (aka having a dummy View around your ScrollView), a little like what you seem to already have. The idea would be to always have the View as your outer component with the rest inside of it.

To give you an example, here is a stripped-down version the Modal component we use to wrap all the other modals we write. It renders a close button at the top right hand plus the modal component itself inside the ScrollView.

import React from 'react'
import {
  View,
  Image,
  Pressable,
  ScrollView,
  StyleSheet,
  Dimensions,
} from 'react-native'

const { width } = Dimension.get('window')

const Modal = ({
  style,
  children,
  contentContainerStyle,
  modal: { closeModal },
}) => (
  <View style={styles.wrapper}>
    <Pressable onPress={() => closeModal()} style={styles.closeButton}>
      <Image source={require('./assets/closeButton.png)} size={{ width: 30 } />
    </Pressable>
    <ScrollView
      contentContainerStyle={[styles.container, contentContainerStyle]}
      style={style}>
      {children}
    </ScrollView>
  </View>
)

const styles = StyleSheet.create({
  wrapper: {
    width: width * 0.9,
    borderRadius: 30,
    marginBottom: 7,
  },
  container: {
    paddingHorizontal: 30,
    paddingBottom: 20,
  },
  closeButton: {
    alignSelf: 'flex-end',
    paddingTop: 15,
    paddingRight: 20,
  },
})

export default Modal
CharlesMangwa commented 3 years ago

Closing for now, feel free to reopen with a reproducible app!

jack828 commented 3 years ago

Thank you for the advice!

I managed to get it working by adding an additional <View> around the ScrollView, setting a maxHeight for the outer-most modal container, and a maxHeight on the ScrollView's View container.

JoeDareZone commented 3 weeks ago

So I had this same issue - I didn't want to use a maxHeight so explored a different option. For some reason it was crucial to set the width of the outermost container, or ScrollView and Flatlist don't centre vertically!