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

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

Open 5starkarma opened this issue 1 year ago • 2 comments
trafficstars

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):

  • Type: Simulator iPhone 15 Pro
  • OS: iOS 17.4

5starkarma avatar Apr 25 '24 21:04 5starkarma

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 avatar Apr 26 '24 21:04 mrzachnugent

@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'

mrzachnugent avatar Apr 28 '24 17:04 mrzachnugent

@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!

5starkarma avatar May 07 '24 23:05 5starkarma

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

whatislifelol123 avatar May 10 '24 03:05 whatislifelol123

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

admapop avatar May 27 '24 10:05 admapop

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>
 )}

hfb0 avatar May 27 '24 18:05 hfb0