ui icon indicating copy to clipboard operation
ui copied to clipboard

MUI Autocomplete dropdown doesn't respond on Shadcn Dialog

Open AsitDixit opened this issue 1 year ago • 3 comments

I have placed a MUI Autocomplete text input field on Dialog, the drop down scroll of MUI Autocomplete doesn't work on dialog. But in other places the dropdown works very well.

See the video and code below 👇👇

https://github.com/shadcn-ui/ui/assets/93305670/4a51c9e5-e63e-4058-b30d-83950ccc7d73

import React, { useState } from "react";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import FreeSoloCreateOption from "@/components/FreeSoloCreateOption";

export default function MyComp() {
  const [Time, setTime] = useState<string | undefined>("");
  const [Meredium, setMeredium] = useState<string | undefined>("");
  return (
    <div>
          <FreeSoloCreateOption setTime={setTime} setMeredium={setMeredium} />
      <Dialog>
        <DialogTrigger className="bg-green-500 mt-2">Open Dialog</DialogTrigger>
        <DialogContent>
          <FreeSoloCreateOption setTime={setTime} setMeredium={setMeredium} />
        </DialogContent>
      </Dialog>
    </div>
  );
}
import * as React from "react";
import TextField from "@mui/material/TextField";
import Autocomplete, { createFilterOptions } from "@mui/material/Autocomplete";
import { Button } from "@/components/ui/button";
import { ThemeProvider, createTheme } from "@mui/material/styles";
const filter = createFilterOptions<TimeSlotOptionType>();

import { useTheme } from "next-themes";
import dynamic from "next/dynamic";
type FreeSoloCreateOptionPropsType = {
  setTime: React.Dispatch<React.SetStateAction<string | undefined>>;
  setMeredium: React.Dispatch<React.SetStateAction<string | undefined>>;
};

function FreeSoloCreateOption({
  setTime,
  setMeredium,
}: FreeSoloCreateOptionPropsType) {
  const { theme, setTheme } = useTheme();
  const muiTheme = createTheme({
    palette: {
      mode: theme == "dark" ? "dark" : "light",
      primary: {
        main: "#9333ea",
      },
      secondary: {
        main: "#9333ea",
      },
    },
  });

  const [value, setValue] = React.useState<TimeSlotOptionType | null>(null);
  const [textError, settextError] = React.useState(false);
  const [amOrPm, setAmOrPm] = React.useState("AM");
  React.useEffect(() => {
    setMeredium(amOrPm);
    setTime(value?.time);
  }, [amOrPm, setMeredium, setTime, value?.time]);

  return (
    <ThemeProvider theme={muiTheme}>
      <div className="flex flex-row space-x-2 items-end justify-between">
        <Autocomplete
          value={value}
          onChange={(event, newValue) => {
            console.log("the newvalue", newValue, value);

            if (typeof newValue === "string") {
              setValue({
                time: newValue,
              });
            } else if (newValue && newValue.inputValue) {
              // Create a new value from the user input
              console.log("here 2");
              setValue({
                time: newValue.inputValue,
              });
            } else {
              console.log("here 3");
              setValue(newValue);
            }
          }}
          filterOptions={(options, params) => {
            const filtered = filter(options, params);

            const { inputValue } = params;
            // Suggest the creation of a new value
            const isExisting = options.some(
              (option) => inputValue === option.time
            );
            if (inputValue !== "" && !isExisting) {
              var newInputValue = "THIS_NEED_TO_BE_SET";

              if (inputValue.length == 1 && parseInt(inputValue)) {
                newInputValue = `${inputValue}:00`;
              }

              if (inputValue.length == 2 && parseInt(inputValue)) {
                newInputValue = `${inputValue}:00`;
              }

              if (inputValue.length == 3 && parseInt(inputValue)) {
                if (inputValue[0] == "0") {
                  const hours = inputValue.substring(0, 2);
                  const minutes = inputValue.substring(2);
                  newInputValue = `${hours}:0${minutes}`;
                } else {
                  const hours = inputValue.substring(0, 1);
                  const minutes = inputValue.substring(1);
                  newInputValue = `0${hours}:${minutes}`;
                }
              }
              if (inputValue.length == 4 && parseInt(inputValue)) {
                const hours = inputValue.substring(0, 2);
                const minutes = inputValue.substring(2, 4);

                newInputValue = `${hours}:${minutes}`;
              }
              if (isValidTimeFormat(newInputValue)) {
                settextError(false);
                filtered.push({
                  inputValue: newInputValue,
                  time: `Add "${newInputValue}"`,
                });
              } else {
                settextError(true);
              }
            }

            return filtered;
          }}
          selectOnFocus
          clearOnBlur
          handleHomeEndKeys
          id="free-solo-with-text-demo"
          options={availableTimeSlot}
          getOptionLabel={(option) => {
            // Value selected with enter, right from the input
            if (typeof option === "string") {
              return option;
            }
            // Add "xxx" option created dynamically
            if (option.inputValue) {
              return option.inputValue;
            }
            // Regular option
            return option.time;
          }}
          renderOption={(props, option) => (
            <li {...props} key={option.time} className="text-sm mb-2">
              {option.time}
            </li>
          )}
          sx={{ width: 76 }}
          freeSolo
          renderInput={(params) => (
            <TextField
              {...params}
              placeholder="hh:mm"
              variant="standard"
              className="placeholder:text-sm"
              error={textError}
            />
          )}
        />

        <Button
          variant="outline"
          size={"sm"}
          onClick={() =>
            setAmOrPm((prevValue) => (prevValue === "AM" ? "PM" : "AM"))
          }
        >
          {amOrPm}
        </Button>
      </div>
    </ThemeProvider>
  );
}

export default dynamic(() => Promise.resolve(FreeSoloCreateOption), {
  ssr: false,
});

interface TimeSlotOptionType {
  inputValue?: string;
  time: string;
}

const availableTimeSlot: TimeSlotOptionType[] = [
  { time: "12:00" },
  { time: "12:15" },
  { time: "12:30" },
  { time: "12:45" },
  { time: "01:00" },
  { time: "01:15" },
  { time: "01:30" },
  { time: "01:45" },
  { time: "02:00" },
  { time: "02:15" },
  { time: "02:30" },
  { time: "02:45" },
  { time: "03:00" },
  { time: "03:15" },
  { time: "03:30" },
  { time: "03:45" },
  { time: "04:00" },
  { time: "04:15" },
  { time: "04:30" },
  { time: "04:45" },
  { time: "05:00" },
  { time: "05:15" },
  { time: "05:30" },
  { time: "05:45" },
  { time: "06:00" },
  { time: "06:15" },
  { time: "06:30" },
  { time: "06:45" },
  { time: "07:00" },
  { time: "07:15" },
  { time: "07:30" },
  { time: "07:45" },
  { time: "08:00" },
  { time: "08:15" },
  { time: "08:30" },
  { time: "08:45" },
  { time: "09:00" },
  { time: "09:15" },
  { time: "09:30" },
  { time: "09:45" },
  { time: "10:00" },
  { time: "10:15" },
  { time: "10:30" },
  { time: "10:45" },
  { time: "11:00" },
  { time: "11:15" },
  { time: "11:30" },
  { time: "11:45" },
];

function isValidTimeFormat(timeString: string) {
  // Regular expression to match the hh:mm 12-hour format without AM/PM check
  const regex = /^(0[1-9]|1[0-2]):[0-5][0-9]$/;

  // Test the input string against the regular expression
  return regex.test(timeString);
}

AsitDixit avatar Dec 23 '23 09:12 AsitDixit

Hi, I had a similar issue, try adding "disablePortal" as a property in the autocomplete

<Autocomplete
       disablePortal
/>

osorionicolas avatar Dec 24 '23 03:12 osorionicolas

Hi, I had a similar issue, try adding "disablePortal" as a property in the autocomplete

<Autocomplete
       disablePortal
/>

Wow, I worked, Can I know the mechanism behind it @osorionicolas

AsitDixit avatar Dec 24 '23 06:12 AsitDixit

I'm not sure 100% but it seems related with the use of portals. The other way should be to use portals in the shadcn Dialog but I didn't try it https://www.radix-ui.com/primitives/docs/components/dialog#api-reference

mui documentation:

Disable portal

To render the Popup where it's defined in the source, without using React portals, pass in the disablePortal prop.

osorionicolas avatar Dec 24 '23 11:12 osorionicolas

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

shadcn avatar Feb 28 '24 23:02 shadcn