react-native-reusables icon indicating copy to clipboard operation
react-native-reusables copied to clipboard

Select is positioned incorrectly if is nested inside a portal

Open Alvi24 opened this issue 9 months ago • 6 comments

I have a drawer component that has a Portal components and it wraps the select component the first time i open the select, it is positioned at the bottom of the screen if i reopen it then it is correctly placed

Alvi24 avatar Feb 21 '25 18:02 Alvi24

Hey @Alvi24 , please provide a minimal reproduction repo

mrzachnugent avatar Feb 21 '25 18:02 mrzachnugent

Hi @mrzachnugent: Here is a overall structure of my component:

<Drawer>
<Portal>
<View className="absolute h-screen bottom-0 translate-y-[60%]>
<Select/>
</View>
</Portal>
</Drawer>

1st time i open select component: Image

2nd time: Image

Alvi24 avatar Feb 22 '25 09:02 Alvi24

Unfortunately, I need a minimal reproduction repo to help.

mrzachnugent avatar Feb 22 '25 18:02 mrzachnugent

I have experienced same issue in gorhom/bottom-sheet-modal component. Basically on first render of <SelectContent /> component it's a lot lower as it should be. @mrzachnugent

 // _layout.tsx
function RootLayoutNav() {
    return (
            <ThemeProvider value={DefaultTheme}>
                <GestureHandlerRootView>
                    <BottomSheetModalProvider>
                        <Slot />
                    </BottomSheetModalProvider>
                    <PortalHost />
                </GestureHandlerRootView>
            </ThemeProvider>
    );
}

// task-filter-sheet.tsx

interface Disposable {
    present: () => void;
}

const TaskFilterSheet = forwardRef<Disposable>((_, ref) => {
    const { t } = useTranslation();
    const bottomSheetRef = useRef<BottomSheetModal>(null);
    const snapPoints = useMemo(() => [600], []);

    const handleClose = () => {
        if (!bottomSheetRef || !bottomSheetRef.current) return;
        bottomSheetRef.current.dismiss();
    };

    useImperativeHandle(ref, () => ({
        present: () => {
            if (!bottomSheetRef.current) return;
            bottomSheetRef.current.present();
        },
    }));

    return (
        <BottomSheetModal
            name="task-filter"
            ref={bottomSheetRef}
            style={{ flex: 1 }}
            snapPoints={snapPoints}
            index={1}
            backgroundComponent={(props) => (
                <View className="bg-white rounded-2xl" {...props} />
            )}
            backdropComponent={(props) => (
                <View
                    className="bg-black text-brand-primary opacity-10"
                    {...props}
                />
            )}
            handleComponent={() => null}
            handleIndicatorStyle={{ backgroundColor: '#AC145A' }}
        >
            <BottomSheetView className="flex-1 gap-8 px-8 pt-5 pb-10">
                <View className="flex-row items-center justify-between">
                    <H3 className="font-normal">{t('filter')}</H3>
                </View>
                <View className="gap-6">
                    <SortByFilter />
                </View>
            </BottomSheetView>
        </BottomSheetModal>
    );
});

// sort-by-filter.tsx

const SortByFilter = () => {
    const { t } = useTranslation();
    const sortBy = useTaskFilterStore((state) => state.sortBy);
    const setSortBy = useTaskFilterStore((state) => state.setSortBy);

    return (
        <Select
            onValueChange={(opt) =>
                setSortBy((opt?.value as TaskFilterState['sortBy']) ?? null)
            }
            value={sortBy ? { value: sortBy, label: sortBy } : undefined}
        >
            <SelectTrigger
                className={cn(
                    'border border-paragraph rounded-[4] h-14 flex-row items-center justify-between px-4',
                )}
            >
                <Text
                    className={cn(
                        'absolute -top-[10] left-3 text-xs bg-white text-paragraph px-1',
                    )}
                >
                    {t('sort')}
                </Text>
                {sortBy ? (
                    <Text className="text-brand-secondary">
                        {t(sortByLabels[sortBy])}
                    </Text>
                ) : (
                    <Text className="text-paragraph">{t('sort_task_by')}</Text>
                )}
            </SelectTrigger>
            <SelectContent insets={{ top: 10, right: 10 }} align="end">
                <SelectGroup>
                    {SORT_OPTIONS.map((value) => (
                        <SelectItem key={value} label={value} value={value}>
                            <Text>{t(sortByLabels[value])}</Text>
                        </SelectItem>
                    ))}
                </SelectGroup>
            </SelectContent>
        </Select>
    );
};

kradchenko avatar Mar 14 '25 15:03 kradchenko

I have experienced same issue in gorhom/bottom-sheet-modal component. Basically on first render of component it's a lot lower as it should be. @mrzachnugent

 // _layout.tsx
function RootLayoutNav() {
    return (
            <ThemeProvider value={DefaultTheme}>
                <GestureHandlerRootView>
                    <BottomSheetModalProvider>
                        <Slot />
                    </BottomSheetModalProvider>
                    <PortalHost />
                </GestureHandlerRootView>
            </ThemeProvider>
    );
}

// task-filter-sheet.tsx

interface Disposable {
    present: () => void;
}

const TaskFilterSheet = forwardRef<Disposable>((_, ref) => {
    const { t } = useTranslation();
    const bottomSheetRef = useRef<BottomSheetModal>(null);
    const snapPoints = useMemo(() => [600], []);

    const handleClose = () => {
        if (!bottomSheetRef || !bottomSheetRef.current) return;
        bottomSheetRef.current.dismiss();
    };

    useImperativeHandle(ref, () => ({
        present: () => {
            if (!bottomSheetRef.current) return;
            bottomSheetRef.current.present();
        },
    }));

    return (
        <BottomSheetModal
            name="task-filter"
            ref={bottomSheetRef}
            style={{ flex: 1 }}
            snapPoints={snapPoints}
            index={1}
            backgroundComponent={(props) => (
                <View className="bg-white rounded-2xl" {...props} />
            )}
            backdropComponent={(props) => (
                <View
                    className="bg-black text-brand-primary opacity-10"
                    {...props}
                />
            )}
            handleComponent={() => null}
            handleIndicatorStyle={{ backgroundColor: '#AC145A' }}
        >
            <BottomSheetView className="flex-1 gap-8 px-8 pt-5 pb-10">
                <View className="flex-row items-center justify-between">
                    <H3 className="font-normal">{t('filter')}</H3>
                </View>
                <View className="gap-6">
                    <SortByFilter />
                </View>
            </BottomSheetView>
        </BottomSheetModal>
    );
});

// sort-by-filter.tsx

const SortByFilter = () => {
    const { t } = useTranslation();
    const sortBy = useTaskFilterStore((state) => state.sortBy);
    const setSortBy = useTaskFilterStore((state) => state.setSortBy);

    return (
        <Select
            onValueChange={(opt) =>
                setSortBy((opt?.value as TaskFilterState['sortBy']) ?? null)
            }
            value={sortBy ? { value: sortBy, label: sortBy } : undefined}
        >
            <SelectTrigger
                className={cn(
                    'border border-paragraph rounded-[4] h-14 flex-row items-center justify-between px-4',
                )}
            >
                <Text
                    className={cn(
                        'absolute -top-[10] left-3 text-xs bg-white text-paragraph px-1',
                    )}
                >
                    {t('sort')}
                </Text>
                {sortBy ? (
                    <Text className="text-brand-secondary">
                        {t(sortByLabels[sortBy])}
                    </Text>
                ) : (
                    <Text className="text-paragraph">{t('sort_task_by')}</Text>
                )}
            </SelectTrigger>
            <SelectContent insets={{ top: 10, right: 10 }} align="end">
                <SelectGroup>
                    {SORT_OPTIONS.map((value) => (
                        <SelectItem key={value} label={value} value={value}>
                            <Text>{t(sortByLabels[value])}</Text>
                        </SelectItem>
                    ))}
                </SelectGroup>
            </SelectContent>
        </Select>
    );
};

Me too

zanottipaolo avatar Mar 15 '25 00:03 zanottipaolo

Please provide a minimal reproduction repo.

mrzachnugent avatar Mar 15 '25 00:03 mrzachnugent

Closed due to missing reproduction repo. Should be fixed with latest update: https://reactnativereusables.com/docs/changelog#august-2025-fullwindowoverlay-ios

mrzachnugent avatar Aug 22 '25 18:08 mrzachnugent