lodev09 / react-native-true-sheet

The true native bottom sheet experience 💩
https://sheet.lodev09.com
MIT License
394 stars 12 forks source link

ScrollView not taking full height of sheet #83

Closed mvolonnino closed 3 weeks ago

mvolonnino commented 3 weeks ago

Just wanted to start off by saying awesome work on this - I love this iOS design and having the ability natively is so nasty so thank you for the work here!

Running into some issues though with ScrollView inside a TrueSheet.

I read the part from your docs as well to try and avoid utilizing flex=1 within the sheet but running into issues still with the scrollview not being the full height of the sheet.

Here is code:

import { useRef, useState } from 'react';
import { Link, router } from 'expo-router';
import { TrueSheet } from '@lodev09/react-native-true-sheet';

import LogoutButton from '@/features/auth/components/LogoutButton';
import {
  Button,
  View,
  H2,
  styled,
  Heading,
  Input,
  ScrollView,
  Pressable,
  XStack,
  Avatar,
  YStack,
  Text,
} from '@/components/core';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { deviceHeight } from '@/utils/dimensions';
import { ChevronRight, XCircle } from '@tamagui/lucide-icons';
import { roster } from '../team/home';
import Caption from '@/components/Caption';

const StyledTrueSheet = styled(TrueSheet, {
  name: 'StyledTrueSheet',
});

export default function DashboardScreen() {
  const insets = useSafeAreaInsets();

  const sheetRef = useRef<TrueSheet>(null);
  const [search, setSearch] = useState('');

  return (
    <View flex={1} padding="$4" gap="$4">
      <H2>Dashboard</H2>
      <Button onPress={() => router.push('/roster')}>NJD Roster</Button>
      <Button onPress={() => sheetRef.current?.present()} themeInverse>
        Show True Sheet
      </Button>
      <LogoutButton />

      <TrueSheet
        ref={sheetRef}
        contentContainerStyle={{ padding: 15, backgroundColor: 'red' }}
        blurTint="default"
      >
        <Heading>2024-2025 Roster</Heading>
        <Input
          size="small"
          placeholder="Search"
          onChangeText={setSearch}
          value={search}
          {...(search
            ? {
                RightAdornment: (
                  <View
                    animation="bouncy"
                    pressStyle={{ opacity: 0.5, scale: 0.8 }}
                    hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }}
                    onPress={() => setSearch('')}
                  >
                    <XCircle color="$placeholderColor" size="$1" />
                  </View>
                ),
              }
            : null)}
        />
        <ScrollView
          nestedScrollEnabled
          keyboardDismissMode="interactive"
          showsVerticalScrollIndicator={false}
          onLayout={e => {
            console.log('scrollview', e.nativeEvent.layout.height);
          }}
          backgroundColor="blue"
        >
          {roster
            .filter(player => player.name.toLowerCase().trim().includes(search.toLowerCase()))
            .map(player => (
              <Link key={player.id} href={`/team/player?id=${player.id}`} asChild>
                <Pressable onPress={() => {}}>
                  <XStack key={player.id} gap="$2" ai="center">
                    <Avatar size={40}>
                      <Caption>
                        {player.name
                          .split(' ')
                          .map(text => text[0])
                          .join('')}
                      </Caption>
                    </Avatar>
                    <XStack jc="space-between" ai="center" f={1}>
                      <YStack key={player.id}>
                        <Text fontWeight="bold">{player.name}</Text>
                        <Caption>{player.position}</Caption>
                      </YStack>
                      <ChevronRight color="$gray10" />
                    </XStack>
                  </XStack>
                </Pressable>
              </Link>
            ))}
        </ScrollView>
      </TrueSheet>
    </View>
  );
}

Resulting:

Simulator Screenshot - iPhone 15 Pro - 2024-08-17 at 08 34 08


The only way right now to get it desired and have the scrollview take the entire available space of the sheet, I need to hardcode heights which I don't think is a very robust solution as different sized screens (ipads, orientations, etc) are all not the same.

Im curious as to why the contentContainerStyle of the sheet itself is not the full height of the sheet - If i update that styling to:

<TrueSheet
  contentContainerStyle={{ padding: 15, backgroundColor: 'red', height: deviceHeight }}
  {...}
/>

Simulator Screenshot - iPhone 15 Pro - 2024-08-17 at 08 37 21

Then it appears to have worked as intended... but now the scrollview can not be fully scrolled and you need more defined heights set to allow to scroll and see the last element...

The only way I got that to work is by added another <View height={150} /> within the scrollview after the mapping of the elements.

Appears that with some math, and taking the header height (heading + input) and adding insets.top & insets.bottom, that appears to give the correct extra height needed to the ScrollView.

import { useRef, useState } from 'react';
import { Link, router } from 'expo-router';
import { TrueSheet } from '@lodev09/react-native-true-sheet';

import LogoutButton from '@/features/auth/components/LogoutButton';
import {
  Button,
  View,
  H2,
  styled,
  Heading,
  Input,
  ScrollView,
  Pressable,
  XStack,
  Avatar,
  YStack,
  Text,
} from '@/components/core';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { deviceHeight } from '@/utils/dimensions';
import { ChevronRight, XCircle } from '@tamagui/lucide-icons';
import { roster } from '../team/home';
import Caption from '@/components/Caption';

const StyledTrueSheet = styled(TrueSheet, {
  name: 'StyledTrueSheet',
});

export default function DashboardScreen() {
  const insets = useSafeAreaInsets();

  const sheetRef = useRef<TrueSheet>(null);
  const [search, setSearch] = useState('');

  return (
    <View flex={1} padding="$4" gap="$4">
      <H2>Dashboard</H2>
      <Button onPress={() => router.push('/roster')}>NJD Roster</Button>
      <Button onPress={() => sheetRef.current?.present()} themeInverse>
        Show True Sheet
      </Button>
      <LogoutButton />

      <TrueSheet
        ref={sheetRef}
        contentContainerStyle={{ padding: 15, backgroundColor: 'red', height: deviceHeight }}
        blurTint="default"
      >
        <Heading>2024-2025 Roster</Heading>
        <Input
          size="small"
          placeholder="Search"
          onChangeText={setSearch}
          value={search}
          {...(search
            ? {
                RightAdornment: (
                  <View
                    animation="bouncy"
                    pressStyle={{ opacity: 0.5, scale: 0.8 }}
                    hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }}
                    onPress={() => setSearch('')}
                  >
                    <XCircle color="$placeholderColor" size="$1" />
                  </View>
                ),
              }
            : null)}
        />
        <ScrollView
          nestedScrollEnabled
          keyboardDismissMode="interactive"
          showsVerticalScrollIndicator={false}
          onLayout={e => {
            console.log('scrollview', e.nativeEvent.layout.height);
          }}
          backgroundColor="blue"
        >
          {roster
            .filter(player => player.name.toLowerCase().trim().includes(search.toLowerCase()))
            .map(player => (
              <Link key={player.id} href={`/team/player?id=${player.id}`} asChild>
                <Pressable onPress={() => {}}>
                  <XStack key={player.id} gap="$2" ai="center">
                    <Avatar size={40}>
                      <Caption>
                        {player.name
                          .split(' ')
                          .map(text => text[0])
                          .join('')}
                      </Caption>
                    </Avatar>
                    <XStack jc="space-between" ai="center" f={1}>
                      <YStack key={player.id}>
                        <Text fontWeight="bold">{player.name}</Text>
                        <Caption>{player.position}</Caption>
                      </YStack>
                      <ChevronRight color="$gray10" />
                    </XStack>
                  </XStack>
                </Pressable>
              </Link>
            ))}

          <View height={79 + insets.bottom + insets.top} /> // ! addition here
        </ScrollView>
      </TrueSheet>
    </View>
  );
}

https://github.com/user-attachments/assets/3ad712bd-8c6d-4be8-9317-8045bb77078e

Any help on this would be greatly appreciated! Thanks again for the awesome work on this library!

lodev09 commented 3 weeks ago

Hey, thanks for your feedback. Looking at your code, it looks like you missed scrollRef prop. This is the special prop for IOS to work. https://sheet.lodev09.com/guides/scrolling

mvolonnino commented 3 weeks ago

@lodev09 - ah i did miss that, adding that to both the TrueSheet and the ScrollView yields the same results tho, any other ideas / anything worth looking into?

lodev09 commented 3 weeks ago

Checkout the example. It should have lots of scrollable demo that you can reference

mvolonnino commented 3 weeks ago

Im looking at the examples now actually! thank you for the reference. Let me dive a bit deeper & ill post my findings here.

Im starting to suspect it has something to do with the header component I'm adding above the scrollview. i remove that along with the ref you pointed out, it does seem to properly size the scrollview and scroll correctly.

lodev09 commented 3 weeks ago

Ah.. yeah, for the header, you'll have to "float" it and add padding on your scrollview since IOS needs to constraint the height. That's the only solution I found for it to auto resize :/

mvolonnino commented 3 weeks ago

Yup thats it. Okay makes sense and shouldn't be to hard to work around either! Thanks for the help, appreciate it.

One other question - is it possible to change the background dim color?

Would be nice to change in say dark mode, a lighter dim if the sheet is not fully expanded for better contrast against the sheet 👍

lodev09 commented 3 weeks ago

Yep, I got lots of request to do that but it isn't simple/straightforward to implement. I'm open to PRs, however.

Will just keep it as-is for now as this library is intended to be used for people who wants that "native" experience.

Closing this. Thank you!