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

Custom Dropdown/Select #4311

Closed
CaitlinSweeney opened this issue Jul 1, 2021 · 42 comments
Closed

Custom Dropdown/Select #4311

CaitlinSweeney opened this issue Jul 1, 2021 · 42 comments
Labels
needs triage Issues and pull requests that need triage attention Type: Feature 🚀 Issue or PR that adds a feature or improvement

Comments

@CaitlinSweeney
Copy link

CaitlinSweeney commented Jul 1, 2021

Description

Create a custom, keyboard compliant dropdown/select component with search capabilities.

Problem Statement/Justification

  • Most designs and apps need a more dynamic select component, such as react-select.
  • I needed to create a custom Select component, while using Chakra-UI's infra and system utils, hooks. Referencing the Menu component, I added optional search to filter out the listed options, the DescendantsContext for keyboard focus management, along with custom SelectOptions for rending labels and optional icons, etc.

Proposed Solution or API

I found the react-select format much more versatile in terms of controlling and rendering data.

Select will render and array of OptionTypes.

export type OptionType = {
  icon?: string;
  label: string;
  value: string; 
}

Example usage:

function SelectUsage() {
  const [state, setState] = React.useState(null)
  const options = [
    { label: 'Green', value: 'green' },
    { label: 'Green-Yellow', value: 'greenyellow' },
    { label: 'Red', value: 'red' },
    { label: 'Violet', value: 'violet' },
    { label: 'Forest', value: 'forest' },
    { label: 'Tangerine', value: 'tangerine' },
    { label: 'Blush', value: 'blush' },
    { label: 'Purple', value: 'purple' },
  ]
  return (
    <>
      <Select
        isSearchable
        defaultValue={options[0].value}
        value={state}
        options={options}
        onChange={(value) => setState(value)}
      />
    </>
  )
}
  • container SelectDescendantsProvider, SelectProvider & StylesProvider
  • Select consumes four internal components, each with its relative context.
<SelectContainer />
  <SelectInputValue />
<SelectList />
   <SelectOption />

Alternatives

  • Structure: maybe moving the components out of the context, leaving the developers more control of the general structure of the elements? I found this be more difficult in terms of controlling data.
<Select>
  <SelectContainer>
    <SelectInputValue /> // handles current state, and search state
  </SelectContainer>
  <SelectList>
    <SelectOption label="Green" value="green" />
    <SelectOption label="Red" value="red" />
    <SelectOption label="Yellow" value="yellow" />
    <SelectOption label="Blue" value="blue" />
    <SelectOption label="Black" value="black" />
  </SelectList>
</Select>

Additional Information

  • current component development
Screen.Recording.2021-06-30.at.6.12.44.PM.mov
@CaitlinSweeney CaitlinSweeney added the needs triage Issues and pull requests that need triage attention label Jul 1, 2021
@anubra266
Copy link
Collaborator

You could check out choc autocomplete.

@csandman
Copy link

I made a chakra wrapper for react-select recently, if anyone else is looking for something like this! https://github.com/csandman/chakra-react-select

@acarolinafbarros
Copy link

Hi :)
It's possible to provide the full code (styling)?

@csandman
Copy link

@acarolinafbarros Are you asking for the full code for my wrapper?

@acarolinafbarros
Copy link

@acarolinafbarros Are you asking for the full code for my wrapper?

yes

@csandman
Copy link

csandman commented Oct 18, 2021

@acarolinafbarros So I originally made this package as a public Gist that people could just copy into their projects and tweak as needed. You can find that here:

Since making it into a package I have added a few new features and converted it to TypeScript. You can find the main component with my customizations here:

As far as customization goes, it is currently pretty tricky to customize my package further than it already is but it is on my roadmap to make it as customizable as the original react-select is. It's just a big task, and I haven't had the time to flesh it out yet. So if you really need to make some major changes to it, I'd recommend either forking my package, or copying the code into your project and modifying it as needed!

EDIT: There is now a full style customization system that functions similarly to the original package's styles prop, so ignore that last part

@ghost
Copy link

ghost commented Oct 19, 2021

Thanks @csandman 🙇‍♀️

@edgars-sirokovs
Copy link

Is there a plan to add this to Chakra UI React components, any roadmap? That would be just awesome!

@anubra266 anubra266 added the Type: Feature 🚀 Issue or PR that adds a feature or improvement label Nov 10, 2021
@csandman
Copy link

There have been some major improvements in version 2 and version 3 of my chakra-react-select package, featuring a fully customizable style system, and the native typescript support added in react-select@5. So if my component didn't feel customizable enough at first, or you had issues implementing it in a TypeScript project, I highly recommend giving it a second chance.

@lcswillems
Copy link

lcswillems commented Jan 26, 2022

@csandman Thanks a lot for this package!! Would it be possible to allow to use React nodes as labels? I opened an issue: csandman/chakra-react-select#41

@CaitlinSweeney
Copy link
Author

@csandman this package is awesome! Thank you so much for creating it and for the great docs. I created a chakra-ui Select last year, inspired by react-select and the chakra-ui Menu, but just not as solid as the react-select framework.

@csandman
Copy link

@CaitlinSweeney Thanks for the feedback, I'm glad you like it!

@fransiscushermanto
Copy link

fransiscushermanto commented Mar 11, 2022

@csandman can you please export out SelectType interface in your library?

@csandman
Copy link

Sure I can do that some time tomorrow. However I'm probably going to rename it SelectComponent

@fransiscushermanto
Copy link

Sure I can do that sometime tomorrow. However, I'm probably going to rename it SelectComponent

Thanks! I'm looking forward to it.

@fransiscushermanto
Copy link

fransiscushermanto commented Mar 11, 2022

@csandman there is one issue when using your package, It seems your package default theme is overriding my Chakra default theme is there any solution and cause ordering issues?

@csandman
Copy link

csandman commented Mar 11, 2022

@csandman there is one issue when using your package, It seems your package default theme is overriding my Chakra default theme is there any solution and cause ordering issues?

Could you elaborate on that? Like it's overriding the theme from other unrelated components? Or changes to you theme aren't propegating to the select?

Can you give me an example of a specific component/style?

@csandman
Copy link

@csandman can you please export out SelectType interface in your library?

@fransiscushermanto The component types are now being exported in 3.1.1. And like I mentioned before, they are now named SelectComponent instead of SelectType.

@stale
Copy link

stale bot commented Apr 16, 2022

Hi!
This issue has been automatically marked as stale because lack of recent activity. It will be closed if no further activity occurs within 5 days. Thank you for your contributions.

@stale stale bot added the stale label Apr 16, 2022
@lcswillems
Copy link

Doesn't make sense to close it. Problem is still here

@stale stale bot removed the stale label Apr 16, 2022
@julia-allyce
Copy link

Hello - I'd be interested in creating a simpler implementation of this request by using the existing Menu components. But this component would implement WAI listbox properties, so it could be used as a form element. Is that something that would be a good addition to the chakra library?

@stale
Copy link

stale bot commented Jun 12, 2022

Hi!
This issue has been automatically marked as stale because lack of recent activity. It will be closed if no further activity occurs within 5 days. Thank you for your contributions.

@stale stale bot added the stale label Jun 12, 2022
@lcswillems
Copy link

Don't close

@stale stale bot removed the stale label Jun 12, 2022
@CaitlinSweeney
Copy link
Author

CaitlinSweeney commented Jun 13, 2022

Hello - I'd be interested in creating a simpler implementation of this request by using the existing Menu components. But this component would implement WAI listbox properties, so it could be used as a form element. Is that something that would be a good addition to the chakra library?

That is doable. I created a Select components referencing the chakra-ui Menu components and inspiration from react-select architecture. Some features, specifically search, were not behaving as I would have liked (A11y and keyboard related). Unfortunately I did not have time to spend debugging.

I ended up using chakra-react-select, it's clean and I like the additional features. Personally, I would prefer not to re-invent the wheel, but if I ever get around to it debugging my custom select, I'll post it here.

@anubra266
Copy link
Collaborator

@CaitlinSweeney The Just concluded Chakrathon was based on Custom Select, so it's available already, they're just working on implementing it into the Chakra Codebase.

@csandman
Copy link

@anubra266 was the whole Chakrathon based on a custom select, or was that just what the winners made?

@anubra266
Copy link
Collaborator

@csandman the whole chakrathon was based on it.

@csandman
Copy link

@anubra266 oh I see it now. so it was based on making a single select component, no multi-select capabilities (unless the team decided to add them).

@anubra266
Copy link
Collaborator

@csandman I don't expect that multi select will take too much effort once single select is settled.

@csandman
Copy link

@csandman I don't expect that multi select will take too much effort once single select is settled.

I'd tend to disagree, making selected options in a multi-select component composable in a similar way to how the rest of the chakra components are is a pretty big step from making a single-select component. I'd be happy to be proven wrong, but it's a whole different challenge.

@anubra266
Copy link
Collaborator

@csandman, chakra ui has a descendants package. Like that of React spectrum, so composability won't be an issue.

@csandman
Copy link

I more meant composability for the end user, as in how you'd store them and display them as their own list of a built in chakra component. The list of options to select is relatively straightforward (similar to the menu), but I'm finding it very difficult to imagine how you could use the list of selected options to display those components in a way that aligns with any of the patterns other Chakra components use.

@anubra266
Copy link
Collaborator

I'm not sure I understand what you mean here @csadman, perhaps you could explain in terms of react-select

@csandman
Copy link

csandman commented Jun 15, 2022

@anubra266 sure, react-select follows a completely different paradigm for how it renders the sub-components that make up the overall component than most of Chakra UI's components. Instead of manually composing those sub-components, you pass in essentially an example component and react-select handles rendering it, as many times as it needs to be:

// The container for the selected option components
const MultiValueContainer = (props: MultiValueGenericProps<ColourOption>) => {
  return (
    <Tooltip content={'Customise your multi-value container!'}>
      <components.MultiValueContainer {...props} />
    </Tooltip>
  );
};

export default () => (
  <Select
    isMulti
    components={{ MultiValueContainer }}
  />
);

Where as, if we take the Chakra UI Menu component for example, each of the MenuItem components is manually added by the user:

<Menu>
  <MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
    Actions
  </MenuButton>
  <MenuList>
    {/* Or map a list of options */}
    <MenuItem>Download</MenuItem>
    <MenuItem>Create a Copy</MenuItem>
    <MenuItem>Mark as Draft</MenuItem>
    <MenuItem>Delete</MenuItem>
    <MenuItem>Attend a Workshop</MenuItem>
  </MenuList>
</Menu>

The big benefit of this pattern is that it allows you to customize each of the sub-components on their own, as you're manually placing them in the DOM.

For the most part, this pattern works fine for a single select, as it essentially follows the same pattern as the Menu. But as soon as you start talking about a multi select, this pattern breaks when you get to the selected options. Here are the only ways I think you might be able to handle it (using the winning project from Chakrathon as an example):

  1. Rely on the user to manage the state of the selected options and map them to their respective components.
const [selectedOptions, setSelectedOptions] = useState([]);

return (
  <Select clearable isDisabled={isDisabled || isLoading} onChange={setSelectedOptions}>
    <SelectedOptions>
      {selectedOptions.map((option) => (
        <SelectedOption option={option} />
      ))}
    </SelectedOptions>
    <SelectSelector />
    <SelectMenu>
      <SelectOption isDisabled>Option 1</SelectOption>
      <SelectOption value="option-value-1">Option 2</SelectOption>
      <SelectOption value="option-value-3">Option 3</SelectOption>
    </SelectMenu>
  </Select>
);

This goes against Chakra's patterns as none of the other Chakra components require you to manage state outside of the rendered components in order to render them properly.

  1. Pass an example component (similar to react-select) for Chakra to render multiple times for each option selected.
<Select clearable isDisabled={isDisabled || isLoading}>
  <SelectedOptions option={<SelectedOption fontSize="lg" />} />
  <SelectSelector />
  <SelectMenu>
    <SelectOption isDisabled>Option 1</SelectOption>
    <SelectOption value="option-value-1">Option 2</SelectOption>
    <SelectOption value="option-value-3">Option 3</SelectOption>
  </SelectMenu>
</Select>

This, like I mention above, does not follow a pattern that any of the other Chakra components use, and makes it a little more complicated to customize each SelectedOption for each time it's rendered. There are definitely cases where they use this component={<Component />} prop pattern, but its only ever used in instances where there is a single instance of the component being used, such as an Icon for an IconButton.

  1. Using render props to get the selected options from the components context.
<Select placeholder="Select option" value={value} onChange={onChange}>
  {({ selectedOptions }) => (
    <>
      <SelectedOptions>
        {selectedOptions.map((option) => (
          <SelectedOption option={option}>{option.label}</SelectedOption>
        ))}
      </SelectedOptions>
      <SelectButton>{option?.label ?? "Placeholder"}</SelectButton>
      <SelectMenu>
        <SelectOption value="option-value-1">Option 1</SelectOption>
        <SelectOption value="option-value-2">Option 2</SelectOption>
      </SelectMenu>
    </>
  )}
</Select>

This is just something no other Chakra components do, and render props as a pattern have generally fallen out of favor with the introduction of hooks. I doubt they'll go this route, for either their new single select or multi select components.


So these are the reasons I think it'll get complicated to implement a multi select without breaking away from some of the patterns which make up Chakra's design system. I've put a lot of thought in to how you might accomplish this in a way that strictly follows the component composition pattern that other Chakra components use, but I've had no luck.

But hey, I could always be missing another implementation pattern, and I wouldn't be upset if they come up with a way to make it possible. This is just my thought process after thinking about it for some time. Honestly, I'm glad to be able to put it into words haha.

@anubra266
Copy link
Collaborator

Oh I see @csandman you're talking of tags. The way I was thinking of it, is selected items, have a checkmark or whatever way the developer chooses.
But talking of tags, A tag component is a completely different component on it's own. React Aria is very much composition oriented like chakra, you can check it out. The select, passes the state to a tag component, which then does it's thing with that state. Note that the tag component has it's own levels of composition too, thereby keeping the composition apis intacts.
Chakra UI is going to be using zagjs components. Zagjs actually has a tag components already. So I guess that'll settle that part.

@csandman
Copy link

@anubra266 Sure, so if they decide not to include any implementation for displaying the list of selected options other than marking them in the list itself, then my point is moot. But that get's tricky if people want to do anything like react-select where the options are displayed inside the select.

Can you link me to an example of what you're talking about with React Aria? I've seen the package before but not the multi-select.

@anubra266
Copy link
Collaborator

@csandman they don't have an official implementation in the docs, but a maintainer kinda showed an implementation, can't find it now.

@stale
Copy link

stale bot commented Jul 31, 2022

Hi!
This issue has been automatically marked as stale because lack of recent activity. It will be closed if no further activity occurs within 5 days. Thank you for your contributions.

@stale stale bot added the stale label Jul 31, 2022
@stellarhoof
Copy link
Contributor

Not stale

@stale stale bot removed the stale label Aug 5, 2022
@segunadebayo
Copy link
Member

This has been created in Zag.js and will land in Chakra UI pretty soon:
https://zagjs.com/components/react/select

@schettn
Copy link

schettn commented Nov 30, 2023

Hey @segunadebayo. Any ETA on this?

@codler
Copy link

codler commented Jun 23, 2024

@segunadebayo any ETA?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs triage Issues and pull requests that need triage attention Type: Feature 🚀 Issue or PR that adds a feature or improvement
Projects
None yet
Development

Successfully merging a pull request may close this issue.