JedWatson / react-select

The Select Component for React.js
https://react-select.com/
MIT License
27.63k stars 4.13k forks source link

Auto width / set width to longest option width #4201

Closed garyee closed 3 years ago

garyee commented 4 years ago

There are two Stackoverflow questions 1. 2. and an issue #323 asking addressing the issue. But there is no real solution.

The problem: When using a standard html select element the width of the element get's automatically set to the width of the longest/biggest option. This is not the default behaviour of react-select there is not even a prop to make it behave this way. In my opinion, a component built to replace/augment the html select should still implement the behaviour of the html element everybody is used to, which makes this more of a bug than a feature. At least there should be some kind of documentation about this (and why it is not technically feasible or whatever the reason).

The issue #323 does kind of address this but then it is closed without an explanation.

There is another "problem" which is that when inserting a react-select component without setting any width to it, it will not even get the width of the placeholder. Which is also discussed in #323 but this can be solved by modifying the styles of the placeholder,singelValue,menu see #323 for hints on how to.

ebonow commented 4 years ago

Greetings @garyee

Have you tried removing the width of the menu?

const styles = {
  menu: ({ width, ...css }) => ({ ...css })
}

<Select styles={styles} .../>

https://codesandbox.io/s/react-select-fitcontent-width-27ed6?file=/example.js

garyee commented 4 years ago

Thank you for your quick answer! But it did not solve the problem. Additional to the snippet you posted in your answer there is:

control:' (css) => ({
    ...css,
    width: "300px"
  }),'

This sets a width to the control, which is what I do not want to do as I expect it to have the width of the longest option automatically.

ebonow commented 4 years ago

Apologies. I did not click the example links and assumed you were talking about the Menu being as large as the longest item and not the Select itself.

This presents itself as two questions: 1. How do I make the select inline?

I have a working example for this here. Note that this example goes a bit further to strip away more things to fit a use case someone else had, but the point remains the same. Set the container to inline-block and remove some css properties from SingleValue

styles={{
    singleValue: ({ maxWidth, position, top, transform, ...otherStyles }) => ({ ...otherStyles }),
}}

2. How do I make the select width equal to the largest option? Far more complicated...

Control and Menu are siblings so it would be much easier if any of the following were true...

One late consideration I came across is that the total width of the select. Select Width = Longest possible Option + SingleValue padding (8) + DropdownIndicator width (36)

That all out of the way, the menu width needs to be calculated. I updated the example with the following logic:

  1. Add state variable for menuWidth

  2. Add styling

    • Control: Set width to menuWidth or auto (coalescing to auto removes transition) and hide with opacity until we have menuWidth
    • Menu: Remove width from menu so it fits content and hide with opacity until we have menuWidth
    • Option: Add paddingRight to account for Dropdown Indicator and padding between it and selected text
  3. Add props for onFocus (to handle getting sizing) and openMenuOnFocus (to "programmatically" trigger menu open)

  4. onMount, focus on Select to trigger menu open

  5. onFocus function checks that there is no menuWidth set yet and if so

    • wait 0 seconds to allow selectRef.current to update
    • calculate the menuWidth
    • set the menuWidth to state
    • blur the select (to close it)

The implementation feels clumsy as it relies on setTimeout, but it works. I blame this mostly on the inability to dynamically open/close/render the menu without needing to add menuIsOpen state management everywhere.

As for adding this as a feature I would argue that adding a prop specifically for this feature seems like an edge case, but would instead address the issues that prevents this from being a more straightforward affair (render menu even when menu is closed, or make it easier to open/close menu without completely controlling state). This would allow someone to set the select container style to grid, flex, or even table without adding any complex state changes or DOM width calculations.

Hopefully, this better answers what you were asking and hope it helps

mde-pach commented 4 years ago

@ebonow you provide a workaround to reproduce a standard behavior of the html element this component replace. So I agree with @garyee when saying this behavior should be a standard one in this component as the html provided select element

ebonow commented 3 years ago

@mde-pach I understand your perspective, but it is fair to say that this component is a bit more complex and dynamic than a standard select input.

Please consider the following use-cases:

This is all independent of the code complexity required do the width calculations as props, state, and window size changes of each type of available Select. While I understand that it would be nice to have for some, it would likely result in being more problematic for others as listed above if made to be default behavior which would be matching the behavior of a select as proposed.

What could arguably be done to better enable this behavior is to allow the menu to be rendered but hidden so the Menu ref would be easier to calculate its width, or simply roll your own controlled isMenuOpen which instead hide/shows content. Then you can add the resizeObserver to the element and watch for width changes and apply some calculation to the width of your Control (taking all of the factors mentioned above into account).

There are likely others who may be interested in this functionality and I would encourage you or anyone else to put together a codesandbox and share it in the Show and Tell discussion room This is an open source project and thoroughly encourage the community to contribute and share if you or anyone else feels inclined to build such functionality. This however is something that likely does not fit in the roadmap, and instead as a custom developer built component that can be shared with the community. If you would like to continue to discuss the merits of this as a feature, please feel free to start a discussion, but I will be closing this as an issue in an effort to get through known bugs and critical issues.

ebonow commented 3 years ago

@mde-pach @garyee

Stopped by to leave a working codesandbox example. Hope this is helpful. https://codesandbox.io/s/react-select-longest-option-width-geunu?file=/src/App.js

nhuanhoangduc commented 3 years ago

Set menu: { minWidth: '100%', left: 0, right: unset } Hope it works for you.

aidan-keay commented 3 years ago

No need to use refs. Using width: "max-content" and min-width: "100%" set the menu width equal to the longest option for me.

menu: (base) => ({
      ...base,
      width: "max-content",
      minWidth: "100%"
 }),
nhuanhoangduc commented 3 years ago

No need to use refs. Using width: "max-content" and min-width: "100%" set the menu width equal to the longest option for me.

Full solution:

menu: ({ width, ...css }) => ({
      ...css,
      width: "max-content",
      minWidth: "100%"
 }),

Same as my solution

aidan-keay commented 3 years ago

Same as my solution

Your solution didn't work for me unfortunately. It was still the same width as the selector. I used unset as a string from your example, should it be something else?

buskerone commented 3 years ago

Same as my solution

Your solution didn't work for me unfortunately. It was still the same width as the selector. I used unset as a string from your example, should it be something else?

I think this is only working for the options, but not for the entire container.

tstirrat15 commented 2 years ago

Does setting display: 'inline-block' on the container have the desired effect for you guys? It seems to work for me.

nilupul14 commented 2 years ago

@mde-pach @garyee

Stopped by to leave a working codesandbox example. Hope this is helpful. https://codesandbox.io/s/react-select-longest-option-width-geunu?file=/src/App.js

This is not working in react-select 3.0.1v higher.

image

nilupul14 commented 2 years ago

This CSS style works for me. No need to use refs and it's worked for the latest version(of 5.2.1v) as well.

const stylesWidth = { control: css => ({ ...css, width: 150, }), menu: ({ width, ...css }) => ({ ...css, width: '400px', minWidth: '20%', }), option: css => ({ ...css, width: 370 }), };

sample project example: :100: https://codesandbox.io/s/react-select-options-width-resize-forked-h0sfg?file=/example.js

robotkoruslan commented 2 years ago
menu: provided => ({
  ...provided,
  width: 'max-content'
})
AndrejGajdos commented 1 year ago

Would be great to implement this feature. I am using menuPortal and none of suggestions works

romanh24 commented 1 year ago

I have this issue in "react-select": "^5.7.4",

bhavinzwt commented 1 year ago

I have issue in "react-select": "^5.7.3",

bhavinzwt commented 1 year ago

@mde-pach @garyee Stopped by to leave a working codesandbox example. Hope this is helpful. https://codesandbox.io/s/react-select-longest-option-width-geunu?file=/src/App.js

This is not working in react-select 3.0.1v higher.

image

I have got same issue in V5.7.3. Have you got any solution?