mrzachnugent / react-native-reusables

Universal shadcn/ui for React Native featuring a focused collection of components - Crafted with NativeWind v4 and accessibility in mind.
https://rnr-docs.vercel.app
MIT License
3.2k stars 132 forks source link

Select dropdown does not work inside page which is set to `presentation: "modal"` #136

Closed 5starkarma closed 4 months ago

5starkarma commented 5 months ago

Describe the bug As title states. The dropdown does not open if inside modal.

Expected behavior Expect the Select to open.

Screenshots Simulator Screenshot - iPhone 15 Pro - 2024-04-25 at 14 39 24

Platform (please complete the following information):

mrzachnugent commented 5 months ago

Hey @5starkarma,

Thanks for bringing it up. I will look more into this over the weekend. It looks like you would need to add a custom portal host to bottom of your modal screen. Example:

<PortalHost name='invite-employee-portal' />

Then, you would need to add a portalHostName prop to your SelectContent component. Pass the name of your custom portal host (ex: "invite-employee-portal") to the SelectPrimitive.Portal through its hostName prop. Something like:

const SelectContent = React.forwardRef<
  React.ElementRef<typeof SelectPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> & { portalHost?: string }
>(({ className, children, position = 'popper', portalHost, ...props }, ref) => {
  const { open } = SelectPrimitive.useRootContext();

  return (
    <SelectPrimitive.Portal hostName={portalHost} >
      <SelectPrimitive.Overlay style={Platform.OS !== 'web' ? StyleSheet.absoluteFill : undefined}>
        <Animated.View entering={FadeIn} exiting={FadeOut}>
          <SelectPrimitive.Content
            ref={ref}
            className={cn(
              'relative z-50 max-h-96 min-w-[8rem] rounded-md border border-border bg-popover shadow-md shadow-foreground/10 py-2 px-1 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
              position === 'popper' &&
                'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
              open
                ? 'web:zoom-in-95 web:animate-in web:fade-in-0'
                : 'web:zoom-out-95 web:animate-out web:fade-out-0',
              className
            )}
            position={position}
            {...props}
          >
            <SelectScrollUpButton />
            <SelectPrimitive.Viewport
              className={cn(
                'p-1',
                position === 'popper' &&
                  'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
              )}
            >
              {children}
            </SelectPrimitive.Viewport>
            <SelectScrollDownButton />
          </SelectPrimitive.Content>
        </Animated.View>
      </SelectPrimitive.Overlay>
    </SelectPrimitive.Portal>
  );
});
SelectContent.displayName = SelectPrimitive.Content.displayName;

When you use that select, it should look something like:

<SelectContent portalHost='invite-employee-portal' >
mrzachnugent commented 5 months ago

@5starkarma Until I update the documentation, you can use the following commits as guidelines to fixing this issue.

  1. Update the portal primitive: https://github.com/mrzachnugent/react-native-reusables/commit/7312bd89b4821ec2ba235156200bdd6484384a3b. This adds a hook called useModalPortalRoot which will be needed in the component with the presentation: "modal" screen options.
  2. Add the portalHost props to the components that use a portal: https://github.com/mrzachnugent/react-native-reusables/commit/a055dbe5c921297ea217263a2cef0615e7155a2f.
  3. Use the modal.tsx screen as a model for your implementation https://github.com/mrzachnugent/react-native-reusables/commit/31acef5540d1e1914386d9e905e29d53d06cba95.
    • Use the following hook: const { sideOffset, ...rootProps } = useModalPortalRoot();
    • Wrap your entire JSX with a View, pass it the rootProps, and add a custom portal host as its last child
      <View {...rootProps}>
      {/* your existing JSX */}
      <PortalHost name='invite-employee-portal' />
      </View>
    • Add the following props to your SelectContent component: sideOffset={sideOffset} and portalHost='invite-employee-portal'
5starkarma commented 4 months ago

@mrzachnugent Thank you for the prompt response. Before I got the response I switched it to a normal stack page but I will keep this in mind for the future. Thanks again!

whatislifelol123 commented 4 months ago

Should this work with a Select in a Dialog? I think I've added the extra port here (have on in my root _layout file too) and it does enable the select - which is progress - but seems the nested portals are the wrong z index or something related? I did try change them to no avail

Screenshot 2024-05-09 at 10 50 45 PM

For example

  const { sideOffset, ...rootProps } = useModalPortalRoot();
  const contentInsets = {
    top: insets.top,
    bottom: insets.bottom + Math.abs(sideOffset),
    left: 16,
    right: 16,
  };

  return (
    <Dialog open={adding} onOpenChange={() => setAdding(false)}>
      <DialogContent className="sm:max-w-[425px] native:w-[385px] z-50 bg-lime-900">
        <FormProvider {...methods}>
          <TextController name="firstName" label="First Name" />
          <TextController name="lastName" label="Last Name" />
          <MultilineTextController
            name="metadata"
            label="Metadata"
            placeholder="random"
            numberOfLines={4}
          />
          <Select>
            <SelectTrigger>
              <SelectValue
                className="text-foreground text-sm native:text-lg"
                placeholder="Select a role"
              />
            </SelectTrigger>
            <View {...rootProps}>
              <SelectContent
                insets={contentInsets}
                sideOffset={sideOffset}
                className="w-full z-40 bg-cyan-500"
                portalHost="modal-example"
              >
                <SelectGroup>
                  <SelectLabel>Roles</SelectLabel>
                  <SelectItem label="Staff" value="staff">
                    Staff
                  </SelectItem>
                  <SelectItem label="Manager" value="manager">
                    Manager
                  </SelectItem>
                  <SelectItem label="Admin" value="admin">
                    Admin
                  </SelectItem>
                </SelectGroup>
              </SelectContent>
              <PortalHost name="modal-example" />
            </View>
          </Select>
        </FormProvider>
        <DialogFooter>
          <Button onPress={methods.handleSubmit(onSubmit)}>
            <Text>Save</Text>
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );

If i move the custom portal outside of Dialog, its also (more) wrong:

Screenshot 2024-05-09 at 10 58 17 PM
admapop commented 4 months ago

I am facing the same issue even with the new components and examples.

image
hfb0 commented 4 months ago

Should this work with a Select in a Dialog? I think I've added the extra port here (have on in my root _layout file too) and it does enable the select - which is progress - but seems the nested portals are the wrong z index or something related? I did try change them to no avail

I had the same problem in the web version, I solved it in a palliative way by adding the container attribute in SelectContent.

image

To use it, I created a div inside the dialog and assigned the div's ref to the container property.

const divRef = useRef<HTMLDivElement>(null);
...
 return (
  <Dialog>
    <DialogContent>
      <div ref={divRef}>
         <Select>
           <SelectContent container={divRef.current}>
           </SelectContent>
        </Select>
      </div>
    </DialogContent>
  </Dialog>
)

One important thing is to make sure the ref is not null. To do this, the div has to be added to the DOM first.

{isDivLoaded && (
  <SelectContent container={divRef.current}>
  </SelectContent>
 )}