Skip to content
This repository was archived by the owner on Dec 23, 2022. It is now read-only.

Add support for start and end adornments #255

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
body {
font-family: 'Roboto', sans-serif;
}
</style>
</style>
177 changes: 157 additions & 20 deletions src/ChipInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import PropTypes from 'prop-types'
import Input from '@material-ui/core/Input'
import FilledInput from '@material-ui/core/FilledInput/FilledInput'
import OutlinedInput from '@material-ui/core/OutlinedInput'
import InputAdornment from '@material-ui/core/InputAdornment'
import InputLabel from '@material-ui/core/InputLabel'
import Chip from '@material-ui/core/Chip'
import withStyles from '@material-ui/core/styles/withStyles'
Expand Down Expand Up @@ -35,25 +36,59 @@ const styles = (theme) => {
boxSizing: 'border-box'
},
'&$outlined': {
paddingTop: 14
paddingTop: 8
},
'&$filled': {
paddingTop: 28
paddingTop: 22,
'&$hasChips': {
paddingTop: 16
}
},

'&:not($adornmentRoot)': {
flexGrow: 1,
display: 'flex',
alignContent: 'center',
flexWrap: 'wrap',
'& > *': {
'&:not($chip)': {
flex: '1 0'
}
},
'& > div + input': {
paddingBottom: '7px',
minWidth: 88
}
}
},
input: {
display: 'inline-block',
appearance: 'none', // Remove border in Safari, doesn't seem to break anything in other browsers
WebkitTapHighlightColor: 'rgba(0,0,0,0)', // Remove mobile color flashing (deprecated style).
float: 'left',
'&:not($standard)': {
paddingTop: 0
minWidth: 'min-content',
paddingTop: 0,
'&$outlined': {
paddingTop: 6,
paddingBottom: 15,
'&$hasChips': {
paddingTop: 0,
paddingBottom: 7
}
},
'&$filled': {
paddingBottom: 15,
'&$hasChips': {
paddingBottom: 10
}
}
},
hasChips: {},
chipContainer: {
cursor: 'text',
marginBottom: -2,
minHeight: 40,
display: 'flex',
'&$labeled&$standard': {
marginTop: 18
}
Expand All @@ -65,14 +100,30 @@ const styles = (theme) => {
label: {
top: 4,
'&$outlined&:not($labelShrink)': {
top: -4
top: -6
},
'&$filled&:not($labelShrink)': {
top: 0
},
'&$adornedStart': {
left: 24
}
},
labelShrink: {
top: 0
top: 0,
'&$filled': {
marginTop: -8,
'&$adornedStart': {
marginLeft: -8
}
},
'&$adornedStart': {
left: 0,
'&$outlined': {
marginLeft: -8,
marginTop: -4
}
}
},
helperText: {
marginBottom: -20
Expand Down Expand Up @@ -137,6 +188,35 @@ const styles = (theme) => {
chip: {
margin: '0 8px 8px 0',
float: 'left'
},
adornedStart: {},
adornedEnd: {},
adornmentRoot: {
'&$standard': {
marginTop: '8px'
},
'&$outlined': {
paddingTop: '14px',
paddingBottom: 0
},
'&$filled': {
paddingTop: '22px'
},
'& > *:first-child': {
marginTop: '-3.5px'
}
},
inputAdornedStart: {
'&:not($standard)': {
paddingLeft: 34,
marginLeft: -38
}
},
inputAdornedEnd: {
'&:not($standard)': {
paddingRight: 30,
marginRight: -38
}
}
}
}
Expand Down Expand Up @@ -412,12 +492,14 @@ class ChipInput extends React.Component {
chipRenderer = defaultChipRenderer,
classes,
className,
color,
clearInputValueOnChange,
defaultValue,
dataSource,
dataSourceConfig,
disabled,
disableUnderline,
endAdornment,
error,
filter,
FormHelperTextProps,
Expand All @@ -426,6 +508,7 @@ class ChipInput extends React.Component {
helperText,
id,
InputProps = {},
inputAdornmentRenderer = defaultInputAdornmentRenderer,
inputRef,
InputLabelProps = {},
inputValue,
Expand All @@ -444,15 +527,17 @@ class ChipInput extends React.Component {
placeholder,
required,
rootRef,
startAdornment,
value,
variant,
...other
} = this.props

const chips = value || this.state.chips
const actualInputValue = inputValue != null ? inputValue : this.state.inputValue
const actualInputValue = inputValue || this.state.inputValue

const hasInput = (this.props.value || actualInputValue).length > 0 || actualInputValue.length > 0
const hasInput = (value || chips).length > 0 || actualInputValue.length > 0
const hasChips = chips.length > 0
const shrinkFloatingLabel = InputLabelProps.shrink != null
? InputLabelProps.shrink
: (label != null && (hasInput || this.state.isFocused))
Expand All @@ -468,7 +553,8 @@ class ChipInput extends React.Component {
isFocused: this.state.focusedChip === i,
handleClick: () => this.setState({ focusedChip: i }),
handleDelete: () => this.handleDeleteChip(value, i),
className: classes.chip
className: classes.chip,
color: color
},
i
)
Expand All @@ -485,15 +571,27 @@ class ChipInput extends React.Component {
0
}

if (variant !== 'standard') {
InputMore.startAdornment = (
<React.Fragment>{chipComponents}</React.Fragment>
)
} else {
if (variant === 'standard') {
InputProps.disableUnderline = true
}
InputMore.startAdornment = (
<React.Fragment>{chipComponents}</React.Fragment>
)
InputMore.endAdornment = null

const InputComponent = variantComponent[variant]
const hasAdornment = !!InputProps.startAdornment || startAdornment || InputProps.endAdornment || endAdornment
const adornmentClasses = {
root: cx(
classes.inputRoot,
classes[variant],
{
[classes.adornmentRoot]: hasAdornment
}
)
}
const renderedStartAdornment = (InputProps.startAdornment || startAdornment) ? inputAdornmentRenderer(InputProps.startAdornment || startAdornment, adornmentClasses) : null
const renderedEndAdornment = (InputProps.endAdornment || endAdornment) ? inputAdornmentRenderer(InputProps.endAdornment || endAdornment, adornmentClasses) : null

return (
<FormControl
Expand All @@ -510,7 +608,14 @@ class ChipInput extends React.Component {
{label && (
<InputLabel
htmlFor={id}
classes={{root: cx(classes[variant], classes.label), shrink: classes.labelShrink}}
classes={{
root: cx(
classes[variant],
classes.label,
{
[classes.adornedStart]: renderedStartAdornment
}),
shrink: classes.labelShrink}}
shrink={shrinkFloatingLabel}
focused={this.state.isFocused}
variant={variant}
Expand All @@ -530,15 +635,25 @@ class ChipInput extends React.Component {
[classes.underline]: !disableUnderline && variant === 'standard',
[classes.disabled]: disabled,
[classes.labeled]: label != null,
[classes.error]: error
[classes.error]: error,
[classes.adornedStart]: renderedStartAdornment,
[classes.adornedEnd]: renderedEndAdornment
})}
>
{variant === 'standard' && chipComponents}
{renderedStartAdornment}
<InputComponent
ref={this.input}
classes={{
input: cx(classes.input, classes[variant]),
root: cx(classes.inputRoot, classes[variant])
input: cx(classes.input, classes[variant],
{
[classes.hasChips]: hasChips
}),
root: cx(classes.inputRoot, classes[variant],
{
[classes.inputAdornedStart]: renderedStartAdornment,
[classes.inputAdornedEnd]: renderedEndAdornment,
[classes.hasChips]: hasChips
})
}}
id={id}
value={actualInputValue}
Expand All @@ -555,6 +670,7 @@ class ChipInput extends React.Component {
{...InputProps}
{...InputMore}
/>
{renderedEndAdornment}
</div>
{helperText && (
<FormHelperText
Expand All @@ -580,6 +696,8 @@ ChipInput.propTypes = {
chipRenderer: PropTypes.func,
/** Whether the input value should be cleared if the `value` prop is changed. */
clearInputValueOnChange: PropTypes.bool,
/** The color to render the chip based on the color palette **/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other new props, i.e. startAdornment, endAdornment and adornmentRenderer should also be added and documented here.

color: PropTypes.oneOf(['default', 'primary', 'secondary']),
/** Data source for auto complete. This should be an array of strings or objects. */
dataSource: PropTypes.array,
/** Config for objects list dataSource, e.g. `{ text: 'text', value: 'value' }`. If not specified, the `dataSource` must be a flat array of strings or a custom `chipRenderer` must be set to handle the objects. */
Expand All @@ -593,6 +711,8 @@ ChipInput.propTypes = {
disabled: PropTypes.bool,
/** Disable the input underline. Only valid for 'standard' variant */
disableUnderline: PropTypes.bool,
/** End `InputAdornment` for this component. */
endAdornment: PropTypes.node,
/** Props to pass through to the `FormHelperText` component. */
FormHelperTextProps: PropTypes.object,
/** If true, the chip input will fill the available width. */
Expand All @@ -601,6 +721,8 @@ ChipInput.propTypes = {
fullWidthInput: PropTypes.bool,
/** Helper text that is displayed below the input. */
helperText: PropTypes.node,
/** A function of the type `({ inputAdornment, additionalClasses }) => node` that returns an Input Adornment to render within the input. This can be used to overwrite the default element used to wrap the input adornment or to overwrite how the styles are applied to the input adornment. */
inputAdornmentRenderer: PropTypes.func,
/** Props to pass through to the `InputLabel`. */
InputLabelProps: PropTypes.object,
/** Props to pass through to the `Input`. */
Expand All @@ -625,6 +747,8 @@ ChipInput.propTypes = {
onUpdateInput: PropTypes.func,
/** A placeholder that is displayed if the input has no values. */
placeholder: PropTypes.string,
/** Start `InputAdornment` for this component. */
startAdornment: PropTypes.node,
/** The chips to display (enables controlled mode if set). */
value: PropTypes.array,
/** The variant of the Input component */
Expand All @@ -635,20 +759,33 @@ ChipInput.defaultProps = {
allowDuplicates: false,
blurBehavior: 'clear',
clearInputValueOnChange: false,
color: 'default',
disableUnderline: false,
newChipKeyCodes: [13],
variant: 'standard'
}

export default withStyles(styles)(ChipInput)

export const defaultChipRenderer = ({ value, text, isFocused, isDisabled, handleClick, handleDelete, className }, key) => (
export const defaultChipRenderer = ({ value, text, isFocused, isDisabled, handleClick, handleDelete, className, color }, key) => (
<Chip
key={key}
className={className}
style={{ pointerEvents: isDisabled ? 'none' : undefined, backgroundColor: isFocused ? blue[300] : undefined }}
onClick={handleClick}
onDelete={handleDelete}
label={text}
color={color || 'default'}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value for color could be specified in the component's defaultProps so that it appears in the documentation, too.

/>
)

export const defaultInputAdornmentRenderer = (inputAdornment, additionalClasses) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not using this function would probably break a lot of things. Is a prop for customizing the renderer actually needed ? 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The biggest item for this would be to resolve the classes for styling. It was loosely based on what was happening from any of the inputs in Material-ui compared to BaseInput.

When you say not using this function, can you clarify what issue you are seeing? It is a default renderer function and if someone were to override it, I would be expecting them to resolve the styling issues in their manual implementation anyhow. I was opting for customization capability more than anything.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I could easily just hide this from being a prop if that is what you are going after. Let me know if you want me to do that instead

const classes = inputAdornment.props.classes || {}
const rootClass = cx(additionalClasses.root, classes.root)
const filled = cx(additionalClasses.filled, classes.filled)
const positionStart = cx(additionalClasses.positionStart, classes.positionStart)
const positionEnd = cx(additionalClasses.positionEnd, classes.positionEnd)
return (
<InputAdornment {...inputAdornment.props} classes={{root: rootClass, filled: filled, positionStart: positionStart, positionEnd: positionEnd}} />
)
}
1 change: 1 addition & 0 deletions src/ChipInput.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ describe('custom chips', () => {
text: 'a',
chip: 'a',
className: expect.any(String),
color: 'default',
isDisabled: false,
isFocused: false,
handleClick: expect.any(Function),
Expand Down
Loading