Skip to content
This repository has been archived by the owner on Jan 26, 2022. It is now read-only.

Add ButtonGroup component #33

Merged
merged 11 commits into from
Mar 8, 2018
Merged

Add ButtonGroup component #33

merged 11 commits into from
Mar 8, 2018

Conversation

andrewjpiro
Copy link
Contributor

@andrewjpiro andrewjpiro commented Mar 1, 2018

This adds a component for grouping Buttons together. It simply adds the
correct borderRadius to any of it's children that are buttons and passes
through the rest of the props. Along with this, the group can be
disabled rather than having to disable all buttons individually.

On Deploy

Choose "Squash and merge" instead of "Create a merge commit". Then, in the title field write:
"feat: Add ButtonGroup component".

This is so the commit messages don't end up cluttering the release notes.

This adds a component for grouping Buttons together. It simply adds the
correct borderRadius to any of it's children that are buttons and passes
through the rest of the props. Along with this, the group can be
disabled rather than having to disable all buttons individually.
>
{React.Children.map(children, child => {
if (child.type !== Button) {
return 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.

I did this because, in my use case, I wanted to put a hidden input inside of the ButtonGroup without it becoming a Button. I figured this was fine and it would be better not to screw with children that aren't buttons but maybe I'm wrong on that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was initially against this approach but after thinking about it for a little bit I think this is probably the right way to go. It'll prevent the ButtonGroup component from breaking if it's passed components that aren't Buttons 👍

/** Callback when mouse enters component. */
onMouseEnter: PropTypes.func,
/** Callback when mouse leaves component. */
onMouseLeave: PropTypes.func,
Copy link

Choose a reason for hiding this comment

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

children needs to go here too, this broke CI

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. This seems a bit odd to me because children is a prop on every component. There's discussion here on whether this should be enforced jsx-eslint/eslint-plugin-react#7 but I ended up adding it because it's not too difficult.

className: '',
disabled: false,
onMouseEnter: () => {},
onMouseLeave: () => {},
Copy link

Choose a reason for hiding this comment

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

children needs a default prop too

<GroupedButton
disabled={disabled}
{...child.props}
/>
Copy link

Choose a reason for hiding this comment

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

could this fit in 80 chars? <GroupedButton disabled={disabled} {...child.props} />

CI complains here.

@n-fisher
Copy link

n-fisher commented Mar 1, 2018

dev_block 🌮 merge conflicts but CR 👍 progress looks good

Andrew Pirondini added 2 commits March 1, 2018 12:45
There are still CI failures. Hopefully this actually fixes them.
@codecov
Copy link

codecov bot commented Mar 1, 2018

Codecov Report

Merging #33 into master will increase coverage by 24.23%.
The diff coverage is 100%.

Impacted file tree graph

@@             Coverage Diff             @@
##           master      #33       +/-   ##
===========================================
+ Coverage   19.71%   43.94%   +24.23%     
===========================================
  Files          14       16        +2     
  Lines         142      157       +15     
  Branches       11       14        +3     
===========================================
+ Hits           28       69       +41     
+ Misses        114       88       -26
Impacted Files Coverage Δ
src/components/ButtonGroup/ButtonGroup.jsx 100% <100%> (ø)
src/components/SearchInput/SearchInput.jsx 100% <0%> (ø) ⬆️
src/components/Avatar/Avatar.jsx 100% <0%> (ø)
src/components/Button/Button.jsx 100% <0%> (+100%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ed707e6...9f8493c. Read the comment docs.

This adds new tests for the ButtonGroup with snapshots. It also pushes
the latest version of the toolbox distribution since that got borked
during merging.
@n-fisher
Copy link

n-fisher commented Mar 1, 2018

good to un block?

@andrewjpiro
Copy link
Contributor Author

Yep. un_dev_block 💥

@n-fisher
Copy link

n-fisher commented Mar 1, 2018

CR 👍


ButtonGroup.propTypes = {
/** Buttons to be grouped together. */
children: PropTypes.arrayOf(PropTypes.node),
Copy link
Contributor

Choose a reason for hiding this comment

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

I find that PropTypes read much nicer when you destructure the types in the import:

import { arrayOf, node } from 'prop-types';

...

ButtonGroup.propTypes = {
  children: arrayOf(node),
  ...
}

<ButtonGroupContainer
className={className}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
Copy link
Contributor

@colebemis colebemis Mar 2, 2018

Choose a reason for hiding this comment

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

Do we need to explicitly pass onMouseEnter and onMouseLeave to the ButtonGroupContainer? I think a better approach might be to collect the unused props and spread them onto the container. Like this:

const ButtonGroup = ({ children, disabled, ...props }) => (
  <Div display="inline-block" {...props}>
    ...
  </Div>
);

Notice that I used Div instead of ButtonGroupContainer. Since ButtonGroupContainer only contains the display: inline-block rule, this is a good place to use glamorous' Div component and simply pass a display prop instead of defining a new component. From the glamorous docs:

Having to name all of that stuff could be tedious, so having these pre-built components is handy. The other handy bit here is that the props are the styles of these components.

I like to use the built-in components for layout purposes like setting display or position.

>
{React.Children.map(children, child => {
if (child.type !== Button) {
return child;
Copy link
Contributor

Choose a reason for hiding this comment

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

I was initially against this approach but after thinking about it for a little bit I think this is probably the right way to go. It'll prevent the ButtonGroup component from breaking if it's passed components that aren't Buttons 👍

display: 'inline-block',
});

const GroupedButton = glamorous(Button)({
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 ButtonGroupItem would probably be a better name here.


'&:first-of-type': {
borderTopLeftRadius: borderRadius,
borderBottomLeftRadius: borderRadius,
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 probably handle border-width as well. We can do this by setting the border-right-width to 0 for all ButtonGroupItems except the last one (which would have a border-right-width of 1.

)
.toJSON();
expect(tree).toMatchSnapshot();
});
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 probably add a test for when ButtonGroup wraps a component other than Button.

};

ButtonGroup.defaultProps = {
children: [],
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to have an empty ButtonGroup? I'm cool with making children a required prop.

@@ -7,6 +7,7 @@ import './plugins';
export { default as glamorous } from 'glamorous';

export { default as Button } from './components/Button/Button';
export { default as ButtonGroup } from './components/ButtonGroup/ButtonGroup';
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice 👍 I always forget to export components from Toolbox.

@colebemis
Copy link
Contributor

Nice work! Thanks for bearing with me as Toolbox changes :)

I know the commit message convention can be annoying (I'm not a huge fan either) but I promise that the benefits of formalized commit messages outweigh the annoyances. You can read more about that here: https://github.com/iFixit/toolbox#why

If you have any other feedback about how we can improve Toolbox, I'd love to hear it.

deploy_block 👍 on responding to my comments.

@sterlinghirsh
Copy link
Member

CR ⚡ I've read to here but it's still blocked on some changes

@addison-grant
Copy link
Member

Tested out the different props/methods on the example code for the ButtonGroup and its four children Buttons. The only one that didn't seem to have any effect was disabled on the ButtonGroup. screen shot 2018-03-02 at 11 19 43 am

dev_block ☀️

Andrew Pirondini added 3 commits March 5, 2018 18:50
The issue was that the disabled prop was being overridden by the more
generic {...child.props}. To fix this I just had to reorder the props.

Along with this I made several style changes suggested in CR.
This fixes the issue of having both the borderLeft of one button and the
borderRight of the other button creating a border with twice the normal
width. Instead, we remove the borderRight for all but the last button
and use only the borderLefts to separate buttons.
This adds the test case for when a ButtonGroup has other children
besides Button components. It should, in this case, not alter the other
children as the updated shapshots show.
@andrewjpiro
Copy link
Contributor Author

andrewjpiro commented Mar 6, 2018

Addressed all the comments with the last few commits.
Also checked the deploy preview and the disabled prop now works.

un_deploy_block 💥 un_dev_block 💥


const { borderRadius } = constants;
const { Div } = glamorous;

Copy link
Contributor

Choose a reason for hiding this comment

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

You could include this in the import above. It would just be:
import glamorous, { Div } from 'glamorous';

const ButtonGroup = ({ children, disabled, ...props }) => (
<Div display="inline-block" {...props}>
{React.Children.map(children, child => {
if (child.type !== Button) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a little bit confused why this is necessary. Is there really a good reason to ever wrap something that isn't a Button in a ButtonGroup?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my use case I have a hidden input inside the ButtonGroup so there's one.

</ButtonGroup>,
)
.toJSON();

Copy link
Contributor

Choose a reason for hiding this comment

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

This is super minor, but I'd probably remove this extra line. Just to be consistent with how it is above.

return child;
}
return <ButtonGroupItem {...child.props} disabled={disabled} />;
})}
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't this mean that you can only ever disable the entire button group? What if we want to disable just a single button in the group?

I think you could do something like:
return <ButtonGroupItem {...child.props} disabled={disabled || child.props.disabled} />;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch I'll fix that right away.

@sterlinghirsh
Copy link
Member

I've read up to here now, but there are still some outstanding questions

Andrew Pirondini added 2 commits March 6, 2018 12:52
The fact that the ButtonGroup's disabled overrode the childrens meant
that you couldn't individually disable the children. This updates it so
you still can and adds a test case to verify it works.
This removes the unnecessary additional line for destructuring glamorous
to get Div.
@andrewjpiro
Copy link
Contributor Author

Okay I think I've addressed everything. Another round!

@lexahall
Copy link
Contributor

lexahall commented Mar 6, 2018

Thanks for making those changes!
CR :octocat:

@sterlinghirsh
Copy link
Member

CR ⚡

@addison-grant
Copy link
Member

QA ☀️

  1. Made sure the ButtonGroup component's disabled attribute trickled down to the children Buttons on the test page.
  2. Made sure the children Buttons could have their optional props/methods set as before while part of a group.

@andrewjpiro andrewjpiro merged commit 060ab34 into master Mar 8, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants