Search code examples
javascriptreactjstypescriptmaterial-uiautocomplete

Render buttons under MUI Autocomplete


I am trying to put buttons ("Accept" and "Cancel") under MUI Autocomplete element and am trying to implement the following

  1. Autocomplete popover height is limited (say to 3 elements).

This is done by passing sx to ListboxProps

  1. The buttons are always visible, that is, when the popover is open, the buttons are shown under the popover.

I was trying various options to put the buttons within Autocomplete (in Paper, Popper, etc.), but with little luck.

Here is the simplified code:

import "./styles.css";
import { Autocomplete } from "@mui/lab";
import TextField from "@mui/material/TextField";
import Paper from "@mui/material/Paper";
import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip";

export default function App() {
  return (
    <div className="App">
      <Paper sx={{ padding: 1 }}>
        <Autocomplete
          freeSolo
          options={["one", "two", "three", "four"]}
          renderInput={(params) => <TextField {...params} />}
          ListboxProps={{ sx: { height: 100 } }}
        />
        <Box sx={{ display: "flex", direction: "row" }}>
          <Chip label="Accept" sx={{ margin: 1 }} />
          <Chip label="Cancel" sx={{ margin: 1 }} />
        </Box>
      </Paper>
    </div>
  );
}

Edit polished-hooks-m6tpco

Any help is greatly appreciated!


Solution

  • The PaperComponent prop controls the Paper element that surrounds the Autocomplete's list box. You can put additional content around that list box by customizing that Paper component. The example below uses your original code for displaying the buttons when the list box is closed and then CustomPaper takes care of displaying them when the list box is open. To manage the rendering of the buttons in one place, the buttons are passed as a prop to the custom Paper. In order for the buttons in CustomPaper to be clickable, you need to call event.preventDefault() in the onMouseDown event; this prevents the blur event of the text input from occurring which would then trigger the closing of the list box. This same approach is used within Autocomplete here and here.

    import Autocomplete from "@mui/material/Autocomplete";
    import TextField from "@mui/material/TextField";
    import Paper from "@mui/material/Paper";
    import Box from "@mui/material/Box";
    import Chip from "@mui/material/Chip";
    
    function CustomPaper({ children, buttons, ...other }) {
      return (
        <Paper {...other}>
          {children}
          {buttons}
        </Paper>
      );
    }
    
    export default function App() {
      const buttons = (
        <Box sx={{ display: "flex", direction: "row" }}>
          <Chip
            label="Accept"
            sx={{ margin: 1 }}
            onMouseDown={(event) => {
              // Prevent input blur which triggers closing the Popper
              event.preventDefault();
            }}
            onClick={() => console.log("Accept clicked")}
          />
          <Chip
            label="Cancel"
            sx={{ margin: 1 }}
            onMouseDown={(event) => {
              // Prevent input blur which triggers closing the Popper
              event.preventDefault();
            }}
            onClick={() => console.log("Cancel clicked")}
          />
        </Box>
      );
      return (
        <div className="App">
          <Paper sx={{ padding: 1 }}>
            <Autocomplete
              freeSolo
              options={["one", "two", "three", "four"]}
              renderInput={(params) => <TextField {...params} />}
              ListboxProps={{ sx: { maxHeight: 100 } }}
              PaperComponent={CustomPaper}
              componentsProps={{ paper: { buttons: buttons } }}
            />
            {buttons}
          </Paper>
        </div>
      );
    }
    

    Edit Custom Paper component

    Related answers: