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

feat: refactor input group #767

Merged
merged 25 commits into from Oct 17, 2019
Merged

feat: refactor input group #767

merged 25 commits into from Oct 17, 2019

Conversation

hertweckhr1
Copy link
Contributor

@hertweckhr1 hertweckhr1 commented Oct 14, 2019

Description

Refactored Input Group to be a bit more clean and act as wrapper to form input and input group add on.

  • consumers will now put their own form input and input group addon

Added Storybook examples.

Deleted callbacks.

Will create follow-up issue to create component for up/down number form input.

fixes #518

@hertweckhr1 hertweckhr1 requested a review from a team October 14, 2019 20:03
@netlify
Copy link

netlify bot commented Oct 14, 2019

Deploy preview for fundamental-react ready!

Built with commit 0a8b751

https://deploy-preview-767--fundamental-react.netlify.com

src/_playground/Routes.js Outdated Show resolved Hide resolved
@greg-a-smith
Copy link
Contributor

@hertweckhr1 The PR title (and subsequent merge commit message) should still be prefixed with either fix: or feat: (I think fix: in this case). The BREAKING CHANGE: is added when merging in the description (not title) field as the first line. Having the breaking change text in the PR title and merge commit title won't do anything with standard version.

actions: actions,
addon: addon,
compact: compact,
className: (child.type === FormInput) ? inputClasses : '',
Copy link
Contributor

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.

<div
{...props}
className={inputGroupClasses}
compact={compact}>
Copy link
Contributor

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/InputGroup.js Outdated Show resolved Hide resolved
<Icon
disableStyles={disableStyles}
glyph={glyph}
role='presentation' />
Copy link
Contributor

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).


InputGroupAddon.propTypes = {
actions: PropTypes.bool,
addon: PropTypes.oneOf(INPUT_GROUP_ADDON_TYPES),
Copy link
Contributor

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.

@hertweckhr1 hertweckhr1 changed the title BREAKING CANGE: refactor input group fix: refactor input group Oct 14, 2019
</FormItem>
</FormGroup>
<br />
<FormGroup>
<FormLabel>Right Aligned Text Addon</FormLabel>
<FormItem>
<InputGroup
addon='€'
addonPos='after'
Copy link
Contributor

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! 🎉

render() {
const {
let {
Copy link
Contributor

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed -- should be const.

'fd-input-group__addon',
[{ 'fd-input-group__addon--button': !!actions || inputType === 'number' }]
);

const inputClasses = classnames(
Copy link
Contributor

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.

const inputGroupClasses = classnames(
className,
'fd-input-group',
[{ 'fd-input-group--compact': compact }]
Copy link
Contributor

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).

inputClasses,
'fd-input--no-number-spinner'
);
children = React.Children.map(children, (child) => {
Copy link
Contributor

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:

  1. make a new variable for this to designate it's a newly created map (something like childrenWithProps?)
  2. just do the React.children.map() inline inside the <div> (doesn't necessarily have to be a variable up here)

}
}

InputGroup.Addon = InputGroupAddon;
InputGroup.Input = FormInput;

InputGroup.displayName = 'InputGroup';

InputGroup.propTypes = {
actions: PropTypes.bool,
Copy link
Contributor

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.

Copy link
Contributor

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?" 😛

Copy link
Contributor

Choose a reason for hiding this comment

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

🔥

Copy link
Contributor Author

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 🏅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🔥

);
children = React.Children.map(children, (child) => {
return React.cloneElement(child, {
actions: actions,
Copy link
Contributor

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'
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor

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.

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', () => {
Copy link
Contributor

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?

Copy link
Contributor

@jbadan jbadan left a 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 🎉

src/InputGroup/InputGroup.Component.js Outdated Show resolved Hide resolved
@jbadan
Copy link
Contributor

jbadan commented Oct 15, 2019

One more: the styling seems off - looks like we're missing a class on the buttons fd-input-group__button

Screen Shot 2019-10-15 at 9 57 24 AM

Copy link
Contributor

@greg-a-smith greg-a-smith left a 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.

inputProps={{ 'aria-label': 'Search' }}
inputValue={this.state.query}
onChange={this.onChangeHandler} />
<InputGroup type='icon'>
Copy link
Contributor

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.

<Icon glyph='search' />
</InputGroup.Addon>
<FormInput
onChange={this.onChangeHandler} placeholder='Search'
Copy link
Contributor

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.

render() {
const {
let {
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed -- should be const.

'fd-input-group__addon',
[{ 'fd-input-group__addon--button': !!actions || inputType === 'number' }]
'fd-input-group',
{ 'fd-input-group--compact': compact }
Copy link
Contributor

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,
Copy link
Contributor

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\').'
Copy link
Contributor

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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.

addonClassNames,
'fd-input-group__addon',
{ 'fd-input-group__addon--button': isButton },
{ 'fd-input-group__addon--button--compact': compact && isButton }
Copy link
Contributor

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.

InputGroupAddon.displayName = 'InputGroup.Addon';

InputGroupAddon.propTypes = {
addonClassNames: PropTypes.string,
Copy link
Contributor

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,
Copy link
Contributor

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.

Copy link
Contributor

@greg-a-smith greg-a-smith left a 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',
Copy link
Contributor

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.

<div
{...props}
className={inputGroupClasses}>
{React.Children.map(children, (child) => (
Copy link
Contributor

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 => {

Copy link
Contributor Author

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?

Copy link
Contributor

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.

https://reactjs.org/docs/react-api.html#reactchildrenmap

Copy link
Contributor

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.

{React.Children.map(children, (child) => (
React.cloneElement(child, {
compact,
className: (child.type.displayname === InputGroup.Addon) ? '' : inputClasses
Copy link
Contributor

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

Copy link
Contributor Author

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 👍

};

InputGroupAddon.propDescriptions = {
isButton: 'Set to **true** if add on is button.'
Copy link
Contributor

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 = {
Copy link
Contributor

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>
Copy link
Contributor

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:

  1. When the displayName comparison is fixed in InputGroup, these will start working better.
  2. Examples that are using buttons need to have the isButton prop set on InputGroup.Addon.
  3. The compact prop can be removed from the Button components.
  4. I see a few of the examples using knobs for the compact prop on InputGroup -- I think all of these should have that.

Copy link
Contributor Author

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.

Copy link
Contributor

@greg-a-smith greg-a-smith left a 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.

@hertweckhr1
Copy link
Contributor Author

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 isButton. Since compact is being passed down from inputgroup, isn't this shown in the inputgroup snapshot tests that already include snapshots for compact?

Copy link
Contributor

@greg-a-smith greg-a-smith left a 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.

Copy link
Contributor

@jbadan jbadan left a 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'
Copy link
Contributor

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.


describe('Default Rendering', () => {
let element = setup({
children: [<FormInput />, <InputGroup.Addon>@</InputGroup.Addon>]
Copy link
Contributor

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()

Copy link
Contributor

@jbadan jbadan left a comment

Choose a reason for hiding this comment

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

🚢 ⭐️ 🚀

@hertweckhr1 hertweckhr1 changed the title fix: refactor input group feat: refactor input group Oct 17, 2019
@hertweckhr1 hertweckhr1 merged commit cc17bfd into master Oct 17, 2019
@hertweckhr1 hertweckhr1 deleted the fix/refactorInputGroup branch October 17, 2019 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refactor Input Group
4 participants