Search code examples
cssreactjsdrop-down-menufluent-uifluentui-react

Keep the list open when selecting an item


I would like to create a CSS element such that:

  1. There is a button.
  2. Clicking on the button opens a list (e.g., a dropdown list) of items.
  3. We can click on the items to select and the parent component is systematically informed.
  4. We click on the area outside the list (e.g., clicking on the button again) to close the list.

I have made a normal dropdown list with the following code by Dropdown (one version and another preview version) of Fluent UI (codesandbox: https://codesandbox.io/s/inspiring-lovelace-ivln6v?file=/src/App.js). It does not fulfil the 4th condition above: selecting an item will systematically close the dropdown, whereas I would expect the list is still open and clicking on the area outside the list (e.g., clicking on the button again) closes the list.

Note that this is the default behavior of controlled multi-select Dropdown, where we have to click on the area outside the dropdown (e.g., clicking on the button again) to close the dropdown. So it's not an unreasonable need.

Does anyone know how to make such a CSS element (probably by adjusting existing controls)? I'm open to list controls such as Dropdown, Select and Combobox of libraries such as Fabric UI, Fluent UI, Material UI.

import React from "react";
import { Dropdown } from "@fluentui/react/lib/Dropdown";
import { initializeIcons } from "@fluentui/react/lib/Icons";

initializeIcons();

const numberOptions = [8, 9, 10].map((i) => ({
  key: i,
  text: "Number: " + i.toString()
}));

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 8 };
  }
  onnumberChange = (_ev, option) => {
    if (typeof option.key === "number") {
      this.setState({ number: option.key });
    }
  };

  render() {
    return (
      <div>
        {`Number: ${this.state.number}`}
        <br />
        <br />
        <Dropdown
          options={numberOptions}
          defaultSelectedKey={this.state.number}
          onChange={this.onnumberChange}
        />
      </div>
    );
  }
}

Solution

  • A workaround is to use open, onOpenChange, onOpenSelect of the preview version of Dropdown.

    CodeSandbox: https://codesandbox.io/s/inspiring-almeida-3lgtcy?file=/src/App.js

    import React from "react";
    import { Dropdown, Option } from "@fluentui/react-components/unstable";
    import { FluentProvider, webLightTheme } from "@fluentui/react-components";
    import { initializeIcons } from "@fluentui/react/lib/Icons";
    
    initializeIcons();
    
    const numberOptions = ["8", "9", "10"];
    
    export default class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = { number: "9", op: false };
      }
    
      render() {
        return (
          <FluentProvider theme={webLightTheme}>
            <div>
              {`Number: ${this.state.number}`}
              <br />
              <br />
              <Dropdown
                open={this.state.op}
                onOpenChange={(e, data) =>
                  this.setState(
                    { op: data.open },
                    console.log("onOpenChange", this.state.op ? "closed" : "open")
                  )
                }
                onOptionSelect={(e, data) =>
                  this.setState({ op: true, number: data.optionText })
                }
                defaultSelectedOptions={["9"]}
              >
                {numberOptions.map((option) => (
                  <Option key={option}>{option}</Option>
                ))}
              </Dropdown>
            </div>
          </FluentProvider>
        );
      }
    }