Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SelectUnstyled] Expose headless API (useSelect) #21022

Closed
Floriferous opened this issue May 13, 2020 · 20 comments · Fixed by #30113
Closed

[SelectUnstyled] Expose headless API (useSelect) #21022

Floriferous opened this issue May 13, 2020 · 20 comments · Fixed by #30113
Labels
component: select This is the name of the generic UI component, not the React module! new feature New feature or request package: base-ui Specific to @mui/base

Comments

@Floriferous
Copy link
Contributor

I'd like to use an IconButton or Button that behaves like a Select, or even a MultiSelect. Meaning that when I click on the button, it opens a Menu, with selected MenuItems, and the same display (open/close) logic as Selects depending on if it's multiple or not.

There is a lot of helpful logic inside the SelectInput component that handles the state of the Menu and its MenuItems, but I can't extract that functionality and replace the entire Input with a button.

It'd be really helpful if all the Select logic was available through a hook, or if the Select input supported being overriden with a simple button that takes an onClick handler.

Here's a reproduction I was able to create quickly, but a few things are missing, and it's not the simplest code out there:
https://codesandbox.io/s/youthful-galois-s82t2

Maybe this should only be documented, instead of supported?

@eps1lon
Copy link
Member

eps1lon commented May 13, 2020

Could you explain why you'd want to do this? What is currently missing from Select that you're trying to implement?

@Floriferous
Copy link
Contributor Author

Well sometimes you want to use a Select as a form component, but you don't want the entire input displayed nor its label, etc.

It allows you to build very dense UIs where the only user interaction is the click of a button.

Maybe there is a simple way of doing this currently and I'm just overcomplicating things?

@eps1lon
Copy link
Member

eps1lon commented May 13, 2020

I don't think you want an actual Select? Not sure what there is left to display if you don't want to display the input or label? So far I don't see anything that's different to the current implementation.

@Floriferous
Copy link
Contributor Author

Floriferous commented May 13, 2020

Select manages the state of the MenuItems, depending on what you select.

I don't understand what is not clear to you, I adjusted my example to feature an IconButton. As you can see, when you click multiple options in this Select, it keeps them highlighted.

Is there another way to get this functionality?

https://codesandbox.io/s/rough-morning-o41e8

@eps1lon
Copy link
Member

eps1lon commented May 13, 2020

That's not really a select. A select needs a label and display the currently selected value.

I'm not sure what you're building or how you envision the UX for this component. Leaving this open for upvotes. If more people are looking for this we can think about building a new component. But it won't be built into the Select.

@eps1lon eps1lon added new feature New feature or request waiting for 👍 Waiting for upvotes labels May 13, 2020
@Floriferous
Copy link
Contributor Author

Alright, if you want we can call it something else, I'm not sure why you're insisting on defining what a select is, it doesn't change what I initially created this issue for.

So to be more accurate: I'm looking to build a component that displays a Menu when clicked on it, and that can manage the open state of the Menu, as well as the selected state of the MenuItems just like a SelectInput does, through the use of a value and an onChange prop.

@KaRkY
Copy link
Contributor

KaRkY commented May 14, 2020

@Floriferous
Copy link
Contributor Author

@KaRkY Yes that's the behavior I'm looking for, though I wish I didn't have to reimplement all of the logic for multiple selects, that comes with a SelectInput. I was looking for a simple way to make it use the exact same API as the Select component. So at minimum basically value, onChange and multiple.

@oliviertassinari
Copy link
Member

It'd be really helpful if all the Select logic was available through a hook

@Floriferous Would something similar solve your issue https://downshift.netlify.app/use-select?

@Floriferous
Copy link
Contributor Author

@oliviertassinari yep, that’s exactly it! Though I wish I didn’t have to bring in another library :)

Thanks for the link.

@oliviertassinari
Copy link
Member

oliviertassinari commented May 15, 2020

What feature do you miss from the Menu component? What feature does has the useSelect hook that we miss?

@Floriferous
Copy link
Contributor Author

To me, what would be helpful is the multiple select state management:

  • Handling of selected state on each of the MenuItems
  • Handling of the open state of the Menu
    • i.e. it should stay open after clicking on a MenuItem, and close when you click next to the Menu

Other

  • Handling of an "Empty" state for single selects

Don't get me wrong, you can just build this right now, but I feel like all of that code has already been written in SelectInput.

@Floriferous
Copy link
Contributor Author

To be a bit more generic, I'd like to use Select as is, with all of its props, but simply change what the idle component is. As mentioned in the title of this issue, I just want to replace the Input with a Button.

@oliviertassinari
Copy link
Member

oliviertassinari commented May 15, 2020

I assume you have tried the renderValue prop, what was missing from it?

@Floriferous
Copy link
Contributor Author

I have not tried using the Autocomplete component, only Select, so I don't know. I assume that would work.

@oliviertassinari
Copy link
Member

I'm not talking about the Autocomplete, but the renderValue prop of the Select.

@Floriferous
Copy link
Contributor Author

Ah sorry, you tricked me because you talked about renderInput in your initial comment ;)

renderValue will not allow me to replace the Input with a Button though, so I'm not sure how that would help me with what I'm trying to build.

I really just want the Input component completely gone, and only render a Button that opens the Menu on click.

@oliviertassinari oliviertassinari added the component: select This is the name of the generic UI component, not the React module! label Jul 20, 2020
@k290
Copy link

k290 commented May 27, 2021

It is worth mentioning that the examples above don't work. If you move the component anywhere on the screen except the top left then the menu doesn't anchor correctly. You can see the warning in the console telling you that it is caused by the Input being hidden.

This is reproduced below: https://codesandbox.io/s/long-flower-i45un?file=/src/Demo.js

@oliviertassinari oliviertassinari added package: base-ui Specific to @mui/base and removed waiting for 👍 Waiting for upvotes labels May 27, 2021
@oliviertassinari oliviertassinari changed the title [Select] Allow replacing Input with a button [Select] Expose headless API (useSelect) May 27, 2021
@oliviertassinari
Copy link
Member

oliviertassinari commented May 27, 2021

@k290 Thanks for the reproduction, I have updated it a bit to get the desired outcome:

source
import React, { forwardRef } from "react";
import { Select, MenuItem, IconButton } from "@material-ui/core";
import AlarmIcon from "@material-ui/icons/Alarm";

const ButtonInput = forwardRef((props, ref) => {
  const {
    inputComponent: InputComponent,
    inputProps: { open, onOpen, onClose },
    value,
    onChange
  } = props;

  return (
    <IconButton
      onClick={(event) => {
        if (open) {
          onClose();
        } else {
          onOpen();
        }
      }}
      ref={ref}
    >
      <AlarmIcon />
      <InputComponent
        {...props.inputProps}
        value={value}
        onChange={onChange}
        renderValue={() => null}
        IconComponent={() => null}
        SelectDisplayProps={{
          style: { visibility: "hidden", minWidth: 0, height: 0, padding: 0 }
        }}
      />
    </IconButton>
  );
});

/**
 * how you used the components
 */
export default function Demo() {
  const [age, setAge] = React.useState([]);
  const [open, setOpen] = React.useState(false);

  const handleChange = (event) => {
    setAge(event.target.value);
  };

  const handleClose = () => {
    setOpen(false);
  };

  const handleOpen = () => {
    setOpen(true);
  };

  return (
    <>
      <br />
      <br />
      <br />
      <br />
      <br />
      <br />
      <br />
      <br />
      <br />
      <Select
        labelId="demo-simple-select-label"
        id="demo-simple-select"
        value={age}
        onChange={handleChange}
        input={<ButtonInput />}
        open={open}
        onClose={handleClose}
        onOpen={handleOpen}
        multiple
      >
        <MenuItem value={10}>Ten</MenuItem>
        <MenuItem value={20}>Twenty</MenuItem>
        <MenuItem value={30}>Thirty</MenuItem>
      </Select>
    </>
  );
}

https://codesandbox.io/s/crazy-wu-2wp61?file=/src/Demo.js:0-1786

It sounds very much like we should expose a useSelect hook that developers could use to recreate their own Collapsible Dropdown Listbox.

@oliviertassinari
Copy link
Member

Linking https://github.com/mui-org/material-ui/issues/27170 and #6218 as it might be related.

@michaldudak michaldudak changed the title [Select] Expose headless API (useSelect) [SelectUnstyled] Expose headless API (useSelect) Dec 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: select This is the name of the generic UI component, not the React module! new feature New feature or request package: base-ui Specific to @mui/base
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

5 participants