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
feat: refactor input group #767
Conversation
Deploy preview for fundamental-react ready! Built with commit 0a8b751 |
@hertweckhr1 The PR title (and subsequent merge commit message) should still be prefixed with either |
src/InputGroup/InputGroup.js
Outdated
actions: actions, | ||
addon: addon, | ||
compact: compact, | ||
className: (child.type === FormInput) ? inputClasses : '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't match on FormInput
. I would match on InputGroupAddon
and then put the inputClasses
on anything that didn't match that. Keep in mind, the "input" here also needs to support a FormTextarea
.
src/InputGroup/InputGroup.js
Outdated
<div | ||
{...props} | ||
className={inputGroupClasses} | ||
compact={compact}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Placing compact
on a <div>
is not only not going to work, but will throw a console warning as an unexpected DOM attribute.
src/InputGroup/_InputGroupAddon.js
Outdated
<Icon | ||
disableStyles={disableStyles} | ||
glyph={glyph} | ||
role='presentation' /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems wrong. I think the consumer should own what Icon
or other children are within the InputGroupAddon
. The only "extra" thing we should need is knowing when the child is a button (or children are buttons).
src/InputGroup/_InputGroupAddon.js
Outdated
|
||
InputGroupAddon.propTypes = { | ||
actions: PropTypes.bool, | ||
addon: PropTypes.oneOf(INPUT_GROUP_ADDON_TYPES), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be able to just get away with a boolean isButton
prop. There's really nothing special we would do with any other children.
</FormItem> | ||
</FormGroup> | ||
<br /> | ||
<FormGroup> | ||
<FormLabel>Right Aligned Text Addon</FormLabel> | ||
<FormItem> | ||
<InputGroup | ||
addon='€' | ||
addonPos='after' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hate weird abbreviations anyway! 🎉
src/InputGroup/InputGroup.js
Outdated
render() { | ||
const { | ||
let { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change this back so we're not modifying children
props below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed -- should be const
.
src/InputGroup/InputGroup.js
Outdated
'fd-input-group__addon', | ||
[{ 'fd-input-group__addon--button': !!actions || inputType === 'number' }] | ||
); | ||
|
||
const inputClasses = classnames( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this probably doesn't need to call classnames
anymore if it's one static string.
src/InputGroup/InputGroup.js
Outdated
const inputGroupClasses = classnames( | ||
className, | ||
'fd-input-group', | ||
[{ 'fd-input-group--compact': compact }] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you shouldn't need the redundant []
(just the object should work).
src/InputGroup/InputGroup.js
Outdated
inputClasses, | ||
'fd-input--no-number-spinner' | ||
); | ||
children = React.Children.map(children, (child) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modifying (resetting) props variables in React components always makes me nervous, even just so we know exactly what we're dealing with with no side effects. Can we either:
- make a new variable for this to designate it's a newly created map (something like
childrenWithProps
?) - just do the
React.children.map()
inline inside the<div>
(doesn't necessarily have to be a variable up here)
src/InputGroup/InputGroup.js
Outdated
} | ||
} | ||
|
||
InputGroup.Addon = InputGroupAddon; | ||
InputGroup.Input = FormInput; | ||
|
||
InputGroup.displayName = 'InputGroup'; | ||
|
||
InputGroup.propTypes = { | ||
actions: PropTypes.bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SAP/fundamental-react-team Would anyone be in favor of renaming this while we have a breaking change on our hands?
IMO this would be clearer as hasActions
; right now it sounds like it should take an array of actions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OR do we need actions
at all? Is this always just related to "is this thing a button?" 😛
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no actions for the winnnnnn 🏅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
src/InputGroup/InputGroup.js
Outdated
); | ||
children = React.Children.map(children, (child) => { | ||
return React.cloneElement(child, { | ||
actions: actions, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you don't need to list these twice if the key and variable names match:
{
actions,
addon,
compact
}
it('should allow data attribute to be passed to span element', () => { | ||
const element = setup({ | ||
children: [<InputGroup.Input />, <InputGroup.Addon>@</InputGroup.Addon>], | ||
'data-sample': 'Sample' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way this is setup, shouldn't this data-sample
be being passed to the input group, not the addon?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah ok - the more I look at this - the test is right - just the description should be updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this got lost in the shuffle - can you update the "it" block - it's not passing to the span
anymore.
src/InputGroup/InputGroup.test.js
Outdated
test('should allow props to be spread to the InputGroup component for type number\'s up button element', () => { | ||
const element = mount(<InputGroup inputType='number' numberUpButtonProps={{ 'data-sample': 'Sample' }} />); | ||
describe('Rendering with Props', () => { | ||
it('should not throw error if child.type is undefined', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add an additional test for adding the fd-input-group__input
logic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some small changes - InputGroup.Input, eslint-disable and the comment cleanup! Otherwise it's looking great 🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't review must in the tests or the stories since some of my other comments may change how those work anyway.
src/_playground/Routes.js
Outdated
inputProps={{ 'aria-label': 'Search' }} | ||
inputValue={this.state.query} | ||
onChange={this.onChangeHandler} /> | ||
<InputGroup type='icon'> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InputGroup
doesn't have a type
prop.
src/_playground/Routes.js
Outdated
<Icon glyph='search' /> | ||
</InputGroup.Addon> | ||
<FormInput | ||
onChange={this.onChangeHandler} placeholder='Search' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor nit: when stacking props on multiple lines, there should only be one per line -- placeholder
should be on its own line.
src/InputGroup/InputGroup.js
Outdated
render() { | ||
const { | ||
let { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed -- should be const
.
src/InputGroup/InputGroup.js
Outdated
'fd-input-group__addon', | ||
[{ 'fd-input-group__addon--button': !!actions || inputType === 'number' }] | ||
'fd-input-group', | ||
{ 'fd-input-group--compact': compact } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class fd-input-group--compact
doesn't exist. Child components have the "compact" variations.
addonClassNames, | ||
addonPos, | ||
addon, | ||
let { | ||
children, | ||
className, | ||
compact, | ||
disableStyles, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should prune customStyles
here as well so it doesn't get spread to the top-level <div>
.
up: 'Value for aria-label on the up <button> element.' | ||
}, | ||
numberDownButtonProps: 'Additional props to be spread to the down `<button>` element (for inputType=\'number\').', | ||
numberUpButtonProps: 'Additional props to be spread to the up `<button>` element (for inputType=\'number\').' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of 🔥 and I love it! 👍
actions: PropTypes.bool, | ||
addon: PropTypes.string, | ||
addonClassNames: PropTypes.string, | ||
addonPos: PropTypes.oneOf(INPUT_GROUP_ADDON_POSITIONS), | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
compact: PropTypes.bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a choice to make regarding compact
and I think there are merits for each approach. If we take a compact
prop here, it is simply to pass along to its children (and subsequently through InputGroupAddon
to its children) the prop so the proper classes get added to the proper elements. The other option would be to not take the prop here and just have the consumer pass their compact
prop to their respective inputs and buttons manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've kept the prop for now to get it simple for users based on team feedback, but can be up for further debate.
src/InputGroup/_InputGroupAddon.js
Outdated
addonClassNames, | ||
'fd-input-group__addon', | ||
{ 'fd-input-group__addon--button': isButton }, | ||
{ 'fd-input-group__addon--button--compact': compact && isButton } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class fd-input-group__addon--button--compact
doesn't exist so this can be removed. See comment in InputGroup.js
about how to handle compact
prop.
src/InputGroup/_InputGroupAddon.js
Outdated
InputGroupAddon.displayName = 'InputGroup.Addon'; | ||
|
||
InputGroupAddon.propTypes = { | ||
addonClassNames: PropTypes.string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There no need for addonClassNames
-- these are for this component so we can just use className
.
addonClassNames: PropTypes.string, | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
compact: PropTypes.bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no defined CSS classes for a "compact" addon. This should be passed to its children (if we keep the prop). See comment in InputGroup.js
about how to handle compact
prop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking much better! Just a few more things to clean up. I haven't reviewed much of the tests yet, but will do so after the next round of edits.
}) => { | ||
|
||
const addonClasses = classnames( | ||
'fd-input-group__addon', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prop className
should also be included in here.
src/InputGroup/InputGroup.js
Outdated
<div | ||
{...props} | ||
className={inputGroupClasses}> | ||
{React.Children.map(children, (child) => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be safe, we should use the toArray
syntax (like is being used in InputGroupAddon
).
{React.Children.toArray(children).map(child => {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good - what the extra benefit is with using React.Children.toArray(children).map
vs. React.Children.map
in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually don't know we need to do this. React.Children.map
already protects against null
or undefined
, and so this feels like redundancy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@markpalfreeman There was actually a case that someone found in TabGroup
where the React.Children.map
didn't protect against null
or undefined
children, which is where this toArray
pattern was introduced. Whether it's actually needed here could be up for debate, but I think this is a safe pattern.
src/InputGroup/InputGroup.js
Outdated
{React.Children.map(children, (child) => ( | ||
React.cloneElement(child, { | ||
compact, | ||
className: (child.type.displayname === InputGroup.Addon) ? '' : inputClasses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this comparison will ever match -- InputGroup.Addon
is a function. I think it should be this:
className: (child.type.displayname === InputGroupAddon.displayName) ? '' : inputClasses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
className: (child.type.displayname === InputGroupAddon.displayName) ? '' : inputClasses
does not work but className: (child.type.displayName === 'InputGroup.Addon') ? '' : inputClasses
does 👍
src/InputGroup/_InputGroupAddon.js
Outdated
}; | ||
|
||
InputGroupAddon.propDescriptions = { | ||
isButton: 'Set to **true** if add on is button.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit: put an "a" before "button".
isButton: PropTypes.bool | ||
}; | ||
|
||
InputGroupAddon.propDescriptions = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the compact
prop description should be overwritten here since we are accepting compact
at the InputGroup
level and the consumer really shouldn't be passing it here (although nothing bad would happen if they did).
compact: '_INTERNAL USE ONLY._',
compact | ||
glyph='navigation-down-arrow' | ||
option='light' /> | ||
</InputGroup.Addon> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple things I noticed:
- When the
displayName
comparison is fixed inInputGroup
, these will start working better. - Examples that are using buttons need to have the
isButton
prop set onInputGroup.Addon
. - The
compact
prop can be removed from theButton
components. - I see a few of the examples using knobs for the
compact
prop onInputGroup
-- I think all of these should have that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
compact has been added to all example for stories, except for TextArea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very close. Just one last request. I think we should add additional snapshot testing for InputGroupAddon
. Something that exercises both the compact
and isButton
props.
@greg-a-smith -> can do so for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great!! 🚢
Just remember this is a breaking change, but since this is a 0th version library, we don't actually mark it with "BREAKING CHANGE:" (we don't want a 1.0.0 yet). Instead, change the prefix from fix:
to feat:
and make sure that same change appears in the commit message title when merging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple cleanup items in the tests and we're good to 🚀
it('should allow data attribute to be passed to span element', () => { | ||
const element = setup({ | ||
children: [<InputGroup.Input />, <InputGroup.Addon>@</InputGroup.Addon>], | ||
'data-sample': 'Sample' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this got lost in the shuffle - can you update the "it" block - it's not passing to the span
anymore.
src/InputGroup/InputGroup.test.js
Outdated
|
||
describe('Default Rendering', () => { | ||
let element = setup({ | ||
children: [<FormInput />, <InputGroup.Addon>@</InputGroup.Addon>] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary? Your setup on line 143 already has children in it. I would either edit the setup to not include any default children or edit your other tests to remove this and just call let element = setup()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚢 ⭐️ 🚀
Description
Refactored Input Group to be a bit more clean and act as wrapper to form input and input group add on.
Added Storybook examples.
Deleted callbacks.
Will create follow-up issue to create component for up/down number form input.
fixes #518