Material-ui: [Autocomplete] Multiselection with Chips

Created on 11 Aug 2016  ·  49Comments  ·  Source: mui-org/material-ui

Description

It would be cool to extend the functionality of the autocomplete component to enable users to select multiple values. An example would be Contact Chips on [1].

Essentially it would look something like:

<AutoComplete dataSource={filteredOptionsNotContainingSelectedOptions}>
  {selectedOptions.map(option => <Chip key={option.id}>{option.name}</Chip>)}
</AutoComplete>

Instead of a single value the AutoComplete would return an object like {1: {...option1}, 2: {...option2}} or something similar. Any thoughts/ideas?

Images & references

[1] https://material.angularjs.org/latest/demo/chips

Autocomplete enhancement

Most helpful comment

I agree with Sharlaan.
For what it's worth you can achieve Multi Select Autocomplete with chips using the react-select library.
https://github.com/JedWatson/react-select

It's easy to use it in a material ui way.

Create your component as:

import React, {Component, PropTypes} from 'react';
import Select from 'react-select';
import classNames from 'classnames';

class MultiAutoCompleteFilter extends Component {
    constructor(props) {
        super(props);
        this.onChange = this.onChange.bind(this);
    }

    onChange(value) {
        if (this.props.onChange) {
            this.props.onChange(value);
        }
    }

    render() {
        const {name, placeholder, model, style, className, onClick} = this.props;

        return (<Select
            name={name}
            onChange={this.onChange}
            onClick={onClick}
            options={model.results}
            value={model.current}
            backspaceRemoves={false}
            multi
            className={classNames('multi-autocomplete', className)}
            placeholder={placeholder}
            style={{width: 540, ...style}}
            menuContainerStyle={style}
        />);
    }
}

MultiAutoCompleteFilter.propTypes = {
    onChange: PropTypes.func,
    name: PropTypes.string,
    placeholder: PropTypes.string,
    model: PropTypes.shape({}),
    style: PropTypes.shape({}),
    className: PropTypes.string,
    onClick: PropTypes.func,
};

export default MultiAutoCompleteFilter;

Import the css from react-select and add this css file for making it more material ui styled:

@import "../node_modules/react-select/scss/default.scss";
.multi-autocomplete {
    font-size: 1rem;
    margin-top: 18px;
    &.is-focused:not(.is-open)>.Select-control { border-color: rgb(224, 224, 224); }

    .Select-control { cursor: pointer;border: none;border-bottom: 1px solid rgb(224, 224, 224);border-radius: 0; }
    .Select--single>.Select-control .Select-value,
    .Select-placeholder { padding: 0; }
    .Select-arrow { border-color: rgb(224, 224, 224) transparent transparent; }
    .is-open .Select-arrow, .Select-arrow-zone:hover>.Select-arrow { border-top-color: rgb(224, 224, 224); }

    .Select-input { margin: 0; }

    .Select-value {
        color: #000;border: none;border-radius: 16px;margin: 1px;padding: 6px 4px 3px 6px;background-color: rgb(224, 224, 224);
        &:hover { background-color: #d6d6d6; }
    }
    .Select-value-icon {
        float: right;margin: 1px 1px 0 0;border: none;border-radius: 50%;background-color: #a6a6a6;color: rgb(224, 224, 224);padding: 0 4px;font-size: 1.6em;line-height: 0.95em;width: 20px;height: 20px;transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
        &:hover { background-color: #7f7f7f;color: rgb(224, 224, 224); }
    }

    .Select-menu-outer {
        border: none;
        box-shadow: rgba(0, 0, 0, 0.117647) 0 1px 6px, rgba(0, 0, 0, 0.117647) 0 1px 4px;
        z-index: 2100;
        background-color: #fff;
        transition: transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
        transform: scaleY(1);
        transform-origin: left top 0;
        .Select-option {
            background-color: #fff;transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
            &:hover { background-color: rgba(0, 0, 0, 0.0980392); }
            &.is-focused { background-color: rgba(0, 0, 0, 0.0980392); }
            &.is-selected { background-color: rgba(0, 0, 0, 0.0980392); }
        }
    }
}

You can see I use a .scss file but you can also use a css file.

Finally you can use it like:

class Filters extends React.Component {
    constructor(props) {
        super(props);
        this.setStatus = this.setStatus.bind(this);
    }
    setStatus(value) {
        this.props.setStatus(value.map(o => o.value));
    }
    render() {
        const {
            status
        } = this.props;

        return (<MultiAutoCompleteFilter model={status} placeholder="Status" onChange={this.setStatus} />);
    }
}

status is an object:

status: {
            results: [
                {label: 'prod', value: 'prod'},
                {label: 'debug', value: 'debug'},
                {label: 'on', value: 'on'},
                {label: 'maintenance', value: 'maintenance'},
                {label: 'off', value: 'off'},
                {label: 'on_hold', value: 'on_hold'},
            ],
            current: state.models.prototype.filters.status,
        },

You can map it with a reducer for more convenience:

import {actionTypes} from '../actions';

const initialState = {
    status: [],
};

export default (state = initialState, {type, payload}) => {
    switch (type) {
    case actionTypes.filters.status.SET:
        return {
            ...state,
            status: payload,
        };
    default:
        return state;
    }
};

Hope it helps.
Code is already in the wild :tada:

All 49 comments

i have some spare time at the moment... trying to implement "multiple" prop on the Autocomplete component, following my analysis @ #1956
I started yesterday directly from Autocomplete.jsx.

It currently returns an array of objects [{text: "selectedOption1", value: "id1"}, {text: "selectedOption2", value: "id2"}, ... ] but i'm having trouble with state.focusTextField preventing a lot of behaviors i wish for the extended component...

if someone could tell me where to post my WIP to help me with this, things might go faster.

You could fork the repo, create a new branch and push to it and reference it here so anybody could have a look at.
Acording to #1956 your are planning to implement a multi selection list with checkboxes for each item. My proposal goes into another direction by using the chip component.

Yes me too i expect to use Chips to display selected options, but main difference is Chips won't be stacked directly inside the main input of the Autocomplete, but instead the dev will have to reuse the returned array of results to display them as Chips in an external component (a multiline textarea for example)

Why? Currently i'm using a fork of react-select-popover: the chips stacking up in the input makes the surrounding UI stretch/wrap... This does not give the feel of a controlled, clean Material UX :s

EDIT: here for an initial fork

Looks like we have something very close made by @leMaik https://github.com/TeamWertarbyte/material-ui-chip-input ✨ .

@oliviertassinari Do you think it makes sense to integrate the component into material-ui?

@davincho Integrating this component was also suggested here by a user.
@oliviertassinari If you'd like to integrate it, I'll make the component ready for a PR.

AutoComplete is getting a rewrite and simplification for the next branch. I think if it were to be included in the library, it would be best to build it on that branch.

@mbrookes Is right, we need to rewrite the AutoComplete component for the _next_ branch: #4783.

@davincho @leMaik I don't really mind about integrating _material-ui-chip-input_ or not inside Material-UI. Either way is great from my point of view. As long as, _material-ui-chip-input_ is discoverable enough. Building a community around the low-level Material-UI components is a good things to aim at.

What I really care about, is the simple fact that _material-ui-chip-input_ project was made possible by the composition approach.

@davincho @oliviertassinari I'd keep the seperate project then. Having small projects that depend on mui and build a community around material-ui also seems more useful to me than to make material-ui bigger and bigger by adding more and more components. It is already a good base for creating new components by composition.
Regarding discoverability: I have no ideas on how to improve that, but for me, the 'related projects' page wouldn't be the first page to look for more _components_.

Edit: Sorry for hijacking this thread. :grimacing:
@mbrookes I'll start building for _next_ in a new branch when the new AutoComplete is ready.

@leMaik Great :+1:.

the 'related projects' page wouldn't be the first page to look for more components.

True, that's the minimum we can do.
A more effective approach would be to add a demo in the docs section. Kind of what we really do for the _react-swipeable-views_ component.

I'd actually prefer that this be included with material-ui rather than be another dependency I have to add to my project. I think we should try to be on par with the Angular Material library.

There's images of this sort of thing in the material.io chip component docs, so all the more reason to include it here in my opinion.

Let's make it as easy as possible for developers to build whatever it is they want to build with as little work and as few dependencies as possible.

@MarkMurphy What's the problem with dependencies? You could either have one very big, everything-dependency (and I don't think that Material-UI should aim to be this library) or you could "outsource" things into seperate projects. I don't really see a difference here, as long as both, the everything-dependency and the small dependencies are well documented.

Also, just look at how many issues and PRs I got for the _chip input_. That is a single, small component. It's much easier to handle that on a per-component basis than it is for _one_ project that does everything.

Disclaimer: I implemented material-ui-chip-input, and I like libraries that do _one_ thing and do it well.

Regarding the "as little work as possible" part: npm i --save material-ui-chip-input isn't _that_ much work to do. :wink:

imho, i think Murphy refers to the hassle of searching into multiple different documentations in the case of a project with tons of tierce components/dependencies.
Ideally would be easier for the developper to search all infos he need in material-ui.com documentation.
Correct me if i'm wrong.

But in practice, i think it's better to start those new components in a per-component basis like leMaik explained, then MaterialUI authors can consider making a PR to discuss integration and/or include the component in their lib.
"Stabilize first, integrate last"

Like leMaik, i also implemented my own component merging DropdownMenu, SelectField, and Autocomplete with chips. It's still missing integration of react-virtualized for heavy datalists.

Now it's up to MaterialUI authors to contat me or leMaik if they want to integrate our components into materialui@next

@Sharlaan I'd prefer to merge the _documentation_ instead of the code. Wouldn't it be cool to keep the code separate but have a single source of documentation for Material-UI and 3rd-party components? That could be the best of both worlds: Less searching for documentation and components while still maintaining every component on its own.

@Sharlaan Yes, valid point.

@leMaik Nothing wrong with dependencies but the more you have the harder it is to maintain when you need to update them and later on reason about when you ask yourself why you installed them in the first place.

Second, when I found material-ui, and read through material.io I was disappointed that the component you built (but existed in the Angular version) wasn't available. After some googling around I found this issue and then your component (nicely done 👍 ).

You're right, it's not a big deal but it's more than just npm i --save material-ui-chip-input.
People have to first discover that your component exists outside of material-ui (just like I did) or assume that it just doesn't exist. That takes valuable time and as with any separate project people will want to evaluate that your component is up to date with current versions of material-ui, how it looks, and assess the quality of your workmanship before deciding to use the component.

If it was bundled with material-ui none of those things are an issue anymore.

The size of material-ui doesn't bother me at all. Even if I'm only using a single component, that's all that gets bundled come build and deploy time.

Also, being in the same repo means existing contributors have access. That's another plus in my opinion.

I would say that if it's included or shown in material.io documentation, it should be included in material-ui

@MarkMurphy The point is: When it's inside of Material-UI, I actually don't have access to the component anymore. I'd have to PR every change and filter out the issues of Material-UI, making maintenance a little harder.
The Material-UI contributors (and all other people) already _have_ access to the 3rd-party components, so that wouldn't change by merging. It's all open source and anyone is free to send PRs.

I agree that it should be easier to find components, see my previous comment.

True it's annoying to PR every change and waiting for author thoughts/approval ....

Then how about applying to MaterialUI maintainer team XD ?

How do other projects handle this, is there a way to do both? I think Ruby on Rails is kind of a combination of both. But obviously that's a bit different

@Sharlaan I still don't see a point in merging every component inside this project. The bigger it gets, the harder it is to maintain.

@MarkMurphy

I would say that if it's included or shown in material.io documentation, it should be included in material-ui

I would say that if it's included or shown in material.io documentation, it should be implemented in Material-UI or at least be linked in the documentation of Material-UI.

@leMaik In my opinion it would be even harder to maintain having what I consider core components distributed elsewhere.

@MarkMurphy For me (a maintainer), it would be harder to maintain if it wasn't my project. Above, you were on the user's side, and I totally agree that it's harder to use many dependencies.

I don't really regard the chip input as a core component. The chip and the text field are core components, that can be used to build other stuff (e.g. a chip input).

Edit: I think that we should wait for the Material-UI maintainers to say what they think. :+1:

er.... little correction leMaik: this topic is about Autocomplete with multiselect feature (chips or not is secondary ?)

I agree this topic (Autocomplete with multiselect) should be a core feature., and thus should be integrated into material-ui.

But as a maintainer, it's much easier and faster to fork it (than PR'ing every change), and then simply ask MaterialUI to add a link in their main site.

For instance, MarkMurphy if you are here, i bet you are probably searching for an autocomplete component with multiselect ?
Following your reasoning you could wait long time before finding one proposed from MaterialUI....
In the meantime, you can try right now 2 components linked in this topic. That's more dependencies and more documentation search true... but better than implement it yourself, isnot it ?

@Sharlaan Yep, but then @oliviertassinari said that there was material-ui-chip input and @MarkMurphy wanted it to be included in Material-UI itself (if I got it right).

The thing is (imho): "Autocomplete with multiselect" _is_ a chip input. Or how would you implement it?

I'm considering Angular Material the gold standard by which to compare.

@Sharlaan I'm actually looking for an autocomplete multiselect contact chip component like the one shown in material.io docs:

| -- | -- |
| -- | -- |
| alt chips contact 1 | alt chips contact 2 |

I agree with Sharlaan.
For what it's worth you can achieve Multi Select Autocomplete with chips using the react-select library.
https://github.com/JedWatson/react-select

It's easy to use it in a material ui way.

Create your component as:

import React, {Component, PropTypes} from 'react';
import Select from 'react-select';
import classNames from 'classnames';

class MultiAutoCompleteFilter extends Component {
    constructor(props) {
        super(props);
        this.onChange = this.onChange.bind(this);
    }

    onChange(value) {
        if (this.props.onChange) {
            this.props.onChange(value);
        }
    }

    render() {
        const {name, placeholder, model, style, className, onClick} = this.props;

        return (<Select
            name={name}
            onChange={this.onChange}
            onClick={onClick}
            options={model.results}
            value={model.current}
            backspaceRemoves={false}
            multi
            className={classNames('multi-autocomplete', className)}
            placeholder={placeholder}
            style={{width: 540, ...style}}
            menuContainerStyle={style}
        />);
    }
}

MultiAutoCompleteFilter.propTypes = {
    onChange: PropTypes.func,
    name: PropTypes.string,
    placeholder: PropTypes.string,
    model: PropTypes.shape({}),
    style: PropTypes.shape({}),
    className: PropTypes.string,
    onClick: PropTypes.func,
};

export default MultiAutoCompleteFilter;

Import the css from react-select and add this css file for making it more material ui styled:

@import "../node_modules/react-select/scss/default.scss";
.multi-autocomplete {
    font-size: 1rem;
    margin-top: 18px;
    &.is-focused:not(.is-open)>.Select-control { border-color: rgb(224, 224, 224); }

    .Select-control { cursor: pointer;border: none;border-bottom: 1px solid rgb(224, 224, 224);border-radius: 0; }
    .Select--single>.Select-control .Select-value,
    .Select-placeholder { padding: 0; }
    .Select-arrow { border-color: rgb(224, 224, 224) transparent transparent; }
    .is-open .Select-arrow, .Select-arrow-zone:hover>.Select-arrow { border-top-color: rgb(224, 224, 224); }

    .Select-input { margin: 0; }

    .Select-value {
        color: #000;border: none;border-radius: 16px;margin: 1px;padding: 6px 4px 3px 6px;background-color: rgb(224, 224, 224);
        &:hover { background-color: #d6d6d6; }
    }
    .Select-value-icon {
        float: right;margin: 1px 1px 0 0;border: none;border-radius: 50%;background-color: #a6a6a6;color: rgb(224, 224, 224);padding: 0 4px;font-size: 1.6em;line-height: 0.95em;width: 20px;height: 20px;transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
        &:hover { background-color: #7f7f7f;color: rgb(224, 224, 224); }
    }

    .Select-menu-outer {
        border: none;
        box-shadow: rgba(0, 0, 0, 0.117647) 0 1px 6px, rgba(0, 0, 0, 0.117647) 0 1px 4px;
        z-index: 2100;
        background-color: #fff;
        transition: transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
        transform: scaleY(1);
        transform-origin: left top 0;
        .Select-option {
            background-color: #fff;transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
            &:hover { background-color: rgba(0, 0, 0, 0.0980392); }
            &.is-focused { background-color: rgba(0, 0, 0, 0.0980392); }
            &.is-selected { background-color: rgba(0, 0, 0, 0.0980392); }
        }
    }
}

You can see I use a .scss file but you can also use a css file.

Finally you can use it like:

class Filters extends React.Component {
    constructor(props) {
        super(props);
        this.setStatus = this.setStatus.bind(this);
    }
    setStatus(value) {
        this.props.setStatus(value.map(o => o.value));
    }
    render() {
        const {
            status
        } = this.props;

        return (<MultiAutoCompleteFilter model={status} placeholder="Status" onChange={this.setStatus} />);
    }
}

status is an object:

status: {
            results: [
                {label: 'prod', value: 'prod'},
                {label: 'debug', value: 'debug'},
                {label: 'on', value: 'on'},
                {label: 'maintenance', value: 'maintenance'},
                {label: 'off', value: 'off'},
                {label: 'on_hold', value: 'on_hold'},
            ],
            current: state.models.prototype.filters.status,
        },

You can map it with a reducer for more convenience:

import {actionTypes} from '../actions';

const initialState = {
    status: [],
};

export default (state = initialState, {type, payload}) => {
    switch (type) {
    case actionTypes.filters.status.SET:
        return {
            ...state,
            status: payload,
        };
    default:
        return state;
    }
};

Hope it helps.
Code is already in the wild :tada:

Excellent Cisco, i'll add it as comparison component alongside my superSelectField and leMaik's ChipInput.
Hopefully i might see how to improve this into the best Autocomplete ever :p

@Murphy you have now 3 possible implementations for what you need, happy ? :)

@Cisco: why don't you use class methods, replacing this.method = this.method.bind(this); with method = (param) => { ... }, getting rid of the constructor ? Just a suggestion.

@Sharlaan using this.method = this.method.bind(this); allows avoiding creating a closure every time you rerender your component. It is bad for performance, see https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Material ui should seriously take care of this component!
It is needed!
Date, Time and DateTime components should be a priority too. I ended up creating my own DateTime component too...

i know the rerender issue, but i were refering to class properties (check method4). It's currently Stage-2.

Regarding DatePicker, how about this one ? which feature(s) is it missing ? (i never used yet)
... but i feel going off-topic there, maybe should open another issue ?

I did not know this syntax, thanks for the discovery @Sharlaan.
Yes it is off topic we should not talk about Date, Time and DateTime anymore.
Date is missing primary color override, setting Date directly by typing it (easy to modify already set date).
Time is missing a lot features: same as date and the base TextInput in way to large by default, it makes no sense. Not open it in a dialog box should be integrated asap.
A correct DateTime component does not exist.

@GuillaumeCisco You seem to have created a great chip input component and a DateTime component and think that Material UI should take care of them? :+1: Why don't you submit a PR?

Btw, material-ui@next switched from inlined styles to JSS.

Hey @leMaik, I did not created a chip input component in the material ui way, just used another library : react-select.
Regarding DateTime, I created one for my own needs using DatePicker and TimePicker from material ui directly. I use redux so it is very opinionated and not compatible with material ui. Also I did not found in the material ui doc from google how they implemented a correct DateTime Picker, it seems always a combination of DatePicker and TimePicker.

And absolutely yes! Material UI should take care of them! It is really important, the need is here!

For my own needs, I needed a Clearable DateTime Picker, so I ended up creating several files.
For people interested:

Clearable component (HOC)

import React, {PropTypes} from 'react';
import IconButton from 'material-ui/IconButton';
import Clear from 'material-ui/svg-icons/content/clear';

const Clearable = (ComposedComponent, clearStyle) => {
    class Component extends React.Component {

        constructor(props) {
            super(props);
            this.clear = this.clear.bind(this);
            this.clearStyle = {
                display: 'inline-block',
                verticalAlign: 'middle',
                marginLeft: '4px',
                padding: '0',
                width: '24px',
                height: '24px',
                ...clearStyle,
            };
        }

        clear(event) {
            event.preventDefault();
            this.component.clear();
        }

        render() {
            const {value, style} = this.props;

            return (
                <div style={{position: 'relative', ...style}}>
                    <ComposedComponent
                        ref={(c) => {
                            this.component = c;
                        }} {...this.props}
                        style={{width: 'calc(100% - 28px)'}}
                    />
                    {value &&
                    <IconButton
                        onClick={this.clear}
                        style={this.clearStyle}
                    >
                        <Clear />
                    </IconButton>
                    }
                </div>
            );
        }
    }

    Component.propTypes = {
        value: PropTypes.oneOfType([
            PropTypes.string,
            PropTypes.number,
            PropTypes.shape({}),
        ]),
        style: PropTypes.shape({}),
    };

    return Component;
};

export default Clearable;

Dumb Date component

import React, {PropTypes} from 'react';
import DatePicker from 'material-ui/DatePicker';

const Date = (props) => {
    const {value, onChange, label, style} = props;

    return (
        <DatePicker
            autoOk
            container="inline"
            hintText={label}
            floatingLabelText={label}
            onChange={onChange}
            value={value || null}
            style={{width: 100, display: 'inline-block', ...style}}
            textFieldStyle={{width: 100}}
        />);
};

Date.propTypes = {
    value: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.shape({}),
    ]),
    label: PropTypes.string,
    onChange: PropTypes.func,
    style: PropTypes.shape({}),
};


export default Date;

dumb Time component

import React, {PropTypes} from 'react';
import TimePicker from 'material-ui/TimePicker';
import {isValid, parse, setHours, setMinutes} from 'date-fns';

const Time = (props) => {
    const {value, label, onChange} = props;

    return (
        <TimePicker
            autoOk
            format="24hr"
            hintText={label}
            floatingLabelText={label}
            onChange={onChange}
            value={value === '' || !value ?
                null :
                (!isValid(parse(value)) ?
                    setHours(setMinutes(new Date(), value.split(':')[1]), value.split(':')[0]) :
                    parse(value))
            }
            style={{width: 50, display: 'inline-block'}}
            textFieldStyle={{width: 50}}
        />
    );
};

Time.propTypes = {
    value: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.shape({}),
    ]),
    label: PropTypes.string,
    onChange: PropTypes.func,
};

export default Time;

Clearable Date Picker:

import React, {PropTypes} from 'react';
import Clearable from './Clearable';
import Date from '../../presentation/filter/date';

class ClearableDatePickerFilter extends React.Component {
    constructor(props) {
        super(props);
        this.clear = this.clear.bind(this);
    }
    clear() {
        this.props.onChange(null, null);
    }
    render() {
        return <Date {...this.props} />;
    }
}

ClearableDatePickerFilter.propTypes = {
    onChange: PropTypes.func,
};

export default Clearable(ClearableDatePickerFilter);

Clearable Time Picker:

import React, {PropTypes} from 'react';
import Clearable from './Clearable';
import Time from '../../presentation/filter/time';

class ClearableTimePickerFilter extends React.Component {
    constructor(props) {
        super(props);
        this.clear = this.clear.bind(this);
    }
    clear() {
        this.props.onChange(null, null);
    }
    render() {
        return <Time {...this.props} />;
    }
}

ClearableTimePickerFilter.propTypes = {
    onChange: PropTypes.func,
};

export default Clearable(ClearableTimePickerFilter);

Clearable DateTime Picker:

import React, {PropTypes} from 'react';
import {setHours, setMinutes, getMinutes, getHours} from 'date-fns';

import Clearable from './Clearable';
import Date from '../../presentation/filter/date';
import Time from '../../presentation/filter/time';

const containerStyle = {
    display: 'inline-block',
    verticalAlign: 'middle',
};

class ClearableDateTime extends React.Component {
    constructor(props) {
        super(props);
        this.clear = this.clear.bind(this);
        this.onDateChange = this.onDateChange.bind(this);
    }
    onDateChange(evt, value) {
        // we need to keep the time
        const v = setHours(setMinutes(value, getMinutes(this.props.value) || 0), getHours(this.props.value) || 0);
        return this.props.onChange(evt, v);
    }
    clear() {
        this.props.onChange(null, null);
    }
    render() {
        return (<div style={{...containerStyle, ...this.props.style}}>
            <Date {...this.props} onChange={this.onDateChange} style={{}} />
            <Time {...this.props} style={{}} />
        </div>);
    }
}

ClearableDateTime.propTypes = {
    value: PropTypes.oneOfType([
        PropTypes.instanceOf(Date),
        PropTypes.shape({}),
    ]),
    onChange: PropTypes.func,
    style: PropTypes.shape({}),
};

export default Clearable(ClearableDateTime, {verticalAlign: -20});

You can even create a Clearable Select with this method, it easier in an UX point of view than putting a null item in the top of the list:

Dumb Select

import React, {PropTypes} from 'react';
import MenuItem from 'material-ui/MenuItem';
import SelectField from 'material-ui/SelectField';
import LoaderSmall from '../loaders/small';

const Select = (props) => {
    const {model, onChange, label, style} = props;

    return (
        <div style={{display: 'inline-block', ...style}}>
            {(model.loading || model.next) && <LoaderSmall />}
            {!(model.loading || model.next) &&
            <SelectField
                hintText={label}
                floatingLabelText={label}
                value={model.current}
                onChange={onChange}
                style={{width: '100%', minWidth: 256}}
            >
                {model.results.map((o, i) =>
                    <MenuItem key={i} value={o.value} primaryText={o.label} />)
                }
            </SelectField>
            }
        </div>);
};

Select.propTypes = {
    model: PropTypes.oneOfType([
        PropTypes.shape({}),
        PropTypes.arrayOf(PropTypes.oneOfType([
            PropTypes.string,
            PropTypes.number,
        ])),
    ]),
    label: PropTypes.string,
    style: PropTypes.shape({}),
    onChange: PropTypes.func,
};


export default Select;

import React, {PropTypes} from 'react';
import Clearable from './Clearable';
import Select from '../../presentation/filter/select';

class ClearableSelectFilter extends React.Component {
    constructor(props) {
        super(props);
        this.clear = this.clear.bind(this);
    }
    clear() {
        this.setState({
            value: null,
        }, () => {
            this.props.onChange(null, null);
        });
    }
    render() {
        return <Select {...this.props} />;
    }
}

ClearableSelectFilter.propTypes = {
    onChange: PropTypes.func,
};

export default Clearable(ClearableSelectFilter);

Please be aware that this code satisfy my needs. It needs more test, refactoring etc.
It just an idea, if it helps ;)
It could be inserted in the doc as an example.

And absolutely yes! Material UI should take care of them! It is really important, the need is here!

@GuillaumeCisco Well, Material-UI sure would take care of those components _if_ somebody would just send a PR to add them! :smile:

@leMaik i'm wondering how to add
status: { results: [ {label: 'prod', value: 'prod'}, {label: 'debug', value: 'debug'}, {label: 'on', value: 'on'}, {label: 'maintenance', value: 'maintenance'}, {label: 'off', value: 'off'}, {label: 'on_hold', value: 'on_hold'}, ], current: state.models.prototype.filters.status, },
to ChipInput's dataSource ? does it even support arrayOf(object) ?

@Sharlaan It is possible, as shown here. Feel free to open an issue over there if you have more questions regarding the chip input component and let's get back to topic. :wink:

@GuillaumeCisco I'm really trying hard (breaking my head over this for several days now) to get what you've suggested to work with https://github.com/reactGo/reactGo
I just cant get it to work =[ (That boiler plate uses PostCSS webpack loaders)

@slavab89 what are you trying to achieve?
I don't really understand, need more context.

@GuillaumeCisco Sorry for the late response.
I'm trying to integrate https://github.com/JedWatson/react-select with that boilerplate (like in the example that you've provided)
The thing is that react-select does not support PostCSS (as far as i can tell) cause they dont support classnames. So with material-ui alone it should be quite simple because currently we use inline-css. But what will happen once we move to JSS?

In any case, the only way i was able to make it work was to manually copy the whole CSS file of react-select to my project and then put your css over it. That works but its kinda hacky. Any other suggestion?

@slavab89 Please take this discussion of the Material-UI repo. Thanks!

For whoever reaches here, i've recently been trying to create this exact behavior and so far i've managed to do that with the great library https://github.com/paypal/downshift
You can refer to the relevant issue https://github.com/paypal/downshift/issues/163 there and look at the last comments where i show a basic example of achieving this.

I will update the code in the example shown there and hopefully it will be added to the examples of the project.
https://codesandbox.io/s/o4yp9vmm8z

The components in the example are simple HTML ones but they can easily be swapped with Chip and such. I'll share here an example with Material-UI once its done for anyone that would be interested.

@slavab89 I am looking for something like your solution. How far with the implementation are you :-)? If you aren't started working on it yet i could give it a try :-)

@thupi Go ahead 😄 . It will probably be a while before i'll be able to do it with material-ui v1.
You can refer to the example that i've linked for the basic functionality. I'll probably open a PR there by the end of the week to put it as part of the examples.

I'm closing the issue as it's basically what the react-select demo provides. I wish the demo was simpler. Let's hope someone from the community will be build something even better.

capture d ecran 2018-02-21 a 23 16 05

Well done @oliviertassinari !
It is great the philosophy I provided earlier has been used and optimized with the last versions of the tools!

Next step should be to rewrite it in material-ui directly in order not to be dependent of react-select.
I think we are close.

@oliviertassinari the demo is is awfully long it should be simplify or just make a components that takes an array of suggestions

@daiky00 Yes, you are right. You can track #9997 for this problem.

Was this page helpful?
0 / 5 - 0 ratings