-
Notifications
You must be signed in to change notification settings - Fork 5
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
Spinner button #471
Spinner button #471
Conversation
import 'styles/adslotUi/SpinnerButton.scss'; | ||
import Button from './ButtonComponent'; | ||
|
||
const isBtnProp = (val, key) => Button.propTypes[key]; |
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.
isButtonProp = (value, key) => Button.propTypes[key]
@@ -0,0 +1 @@ | |||
module.exports = require('react-bootstrap/lib/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.
just do import Button from 'react-bootstrap/lib/Button';'
below and remove this file
loadingElement, | ||
loadingHoverElement = children, | ||
//...btnProps | ||
} = props; |
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.
Destructure props as arguments...
const SpinnerButton = ({
isLoading,
children,
...etc...
}) =>
Also, set default value of className (className = ''
) as
SpinnerButton.defaultProps = {
className: '',
};
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.
Actually, nevermind. I didn't see you were using props
elsewhere. Instead, just put props argument in parentheses... SpinnerButton = (props) => {
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 couldn't find out how to destructure argument and get the argument at the same time
const SpinnerButton = ({
isLoading,
children,
...etc...
}) => {
// I need the props object here
}
const size = t.oneOf(['lg', 'large'])(props.bsSize)? null: 'medium' | ||
loadingElement = React.cloneElement(loadingElement, { size }) | ||
*/ | ||
const btnProps = _.pick(props, isBtnProp); |
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.
buttonProps
{...btnProps} | ||
className={`spinner-button ${className}`} | ||
> | ||
<div className="spinner-button__wrapper" style={{ visibility: isLoading ? 'initial' : 'hidden' }}> |
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.
Change class name to spinner-button-wrapper
. And the next two.
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.
Also, don't use style attribute. Add a class that changes the style.
<div className="spinner-button__loading">{loadingElement}</div> | ||
<div className="spinner-button__reveal">{loadingHoverElement}</div> | ||
</div> | ||
<div style={{ visibility: isLoading ? 'hidden' : 'initial' }}>{children}</div> |
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.
Same here... use classes to change style.
<div className="spinner-button__loading">{loadingElement}</div> | ||
<div className="spinner-button__reveal">{loadingHoverElement}</div> | ||
</div> | ||
<div style={{ visibility: isLoading ? 'hidden' : 'initial' }}>{children}</div> |
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 just not render elements rather than hiding them?
isLoading ?
<div className="spinner-button__wrapper">
<div className="spinner-button__loading">{loadingElement}</div>
<div className="spinner-button__reveal">{loadingHoverElement}</div>
</div> :
<div>{children}</div>
(pseudocode)
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 need to preserve the original size of the element. If we hide/remove the text part of the button the size will shrink. The only alternatives are to measure the size and set it in style.
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.
umm, so let's suppose you're making a button that says [ O Exporting... ] when 'loading' and [ Export ] initially. Wouldn't it render like [ O Exporting... Export ] so effectively as [ _____________ Export ]
?
ExampleForm, | ||
ExampleSelect, | ||
ExampleSpinnerButton, |
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 need to do this, you can just import the spinner button directly to Main.jsx. These examples were put here to isolate larger, more complicated examples.
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 need a stateful component to hold isLoading state but our linter does not allow having two components in Main.jsx. Also the file already has a lot of things.
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.
nm, see what you're doing further down
@@ -0,0 +1,42 @@ | |||
.spinner-button { | |||
&__layer { |
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.
These double underscores are not a naming convention we follow.
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.
Also, layer, wrapper(maybe) and reveal aren't particularly helpful names. What are they trying to explain.
expect(onClick.calledOnce).to.equal(true); | ||
onClick = sinon.spy(); | ||
element = shallow( | ||
<SpinnerButton isLoading onCLick={onClick}>Test</SpinnerButton> |
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.
onClick rather than onCLick
} | ||
} | ||
// workaround for vertical centering | ||
& .spinner-component { |
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 think the & is needed here, the nesting should do this for us
|
||
return ( | ||
<Button | ||
disabled={isLoading} |
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.
isLoading || btnProps.disabled. This still needs to be disabled if it's passed as a prop to disable
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 next line covered that
{...btnProps}
loadingElement, | ||
loadingHoverElement = children, | ||
//...btnProps | ||
} = props; |
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 generally do destructuring in the arguments of the fn
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.
nm, i've seen yours and patricks comments
import { Spinner } from 'alexandria-adslot'; | ||
import _ from 'lodash'; | ||
import 'styles/adslotUi/SpinnerButton.scss'; | ||
import Button from './ButtonComponent'; |
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.
Require direct from bootstrap rather than having the one line export file.
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 thought having SpinnerButton dependent on AdslotUI's Button rather than Bootstrap's, that way if we diverge from Bootstrap we can simply change that one file and have consistency.
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 one day we will have wrappers for 3rd party components which would make it a single dependency (and provide way to inject/overwrite stuff globally), but we should do it consistently (and purposely).
|
||
SpinnerButton.defaultProps = { | ||
isLoading: false, | ||
loadingElement: <Spinner size="small" />, |
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 think we need to pass this as a prop; if we want this to do anything else it'll either be a different component or we update it then.
this.setState({ isLoading: true }, () => | ||
setTimeout(() => this.setState({ isLoading: false }), 5000) | ||
) | ||
, |
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.
Maybe change style to...
onClick: () => {
this.setState({ isLoading: true }, () =>
setTimeout(() => this.setState({ isLoading: false }), 5000)
);
},
Just for consistency's sake.
@omgaz Thoughts on this?
@@ -0,0 +1,42 @@ | |||
.spinner-button { | |||
&__layer { |
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 is not our convention. Please change to &-layer
. The same for the remaining styles.
import 'styles/_icheck-custom.scss'; | ||
import 'styles/_react-select-custom.scss'; | ||
import 'styles/_react-toggle-custom.scss'; | ||
import 'styles/_react-datepicker-custom.scss'; |
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 believe, SCSS files still need to be required so that webpack creates a separate css file.
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 tried dist in both cases and saw only one css ouput, had content of those files. Am I missing something?
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.
Awesome! Just trash your dist file and rebuild; double-check that it's still all good :)
import React from 'react'; | ||
import { SpinnerButton } from 'components/distributionEntry'; | ||
|
||
export default class extends React.Component { |
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.
Export at the end of the file, not here.
} | ||
} | ||
} | ||
} |
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.
New line at end of file. 😝
it('should render with defaults', () => { | ||
const element = shallow(<SpinnerButton>Test</SpinnerButton>); | ||
const hoverElement = element.find('.spinner-button__reveal'); | ||
expect(hoverElement.text()).to.equal('Test'); |
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.
Not that it makes a functional difference, but you can write it as .to.be('Test');
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.
save keystrokes too
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 note that it is not a function, there is to.be.a() but not to.be()
); | ||
|
||
const btnElement = element.find('Button'); | ||
expect(btnElement.prop('bsStyle')).is.equal('primary'); |
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.
Try to write the expect in a way that reads like an English sentence:
e.g. "Expect the button element prop style to be 'primary'" reads better than "Expect the button element prop style is equal 'primary'"
So change it to expect(btnElement.prop('bsStyle')).to.be('primary');
The same goes for the others.
const element = shallow( | ||
<SpinnerButton loadingNode="loading..">Test</SpinnerButton> | ||
); | ||
expect(element.children().first().text()).to.contain('loading..'); |
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.
For future reference, it's better to use the ellipsis character (…)
const size = t.oneOf(['lg', 'large'])(props.bsSize)? null: 'medium' | ||
loadingElement = React.cloneElement(loadingElement, { size }) | ||
*/ | ||
const buttonProps = _.pick(props, isButtonProp); |
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.
_.pick
takes array as props filter, so you can use following instead extra function:
const buttonProps = _.pick(props, _.keys(Button.propTypes));
Current coverage is 100% (diff: 100%)
|
} | ||
|
||
&-medium { | ||
margin-top: -3px; |
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.
that spinner is 30x30
the -3px is for 24x24 spinner which is in http://github.comAdslot/alexandria#356
I thought 30 was a bit too big as it leaves no padding for the button.
@omgaz what do you think?
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 see what you mean. It's ok for now, maybe we can re-visit it later.
4c545bb
to
330482a
Compare
|
||
return ( | ||
<Button | ||
disabled={isLoading} |
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 still want the button to be disabled if it's disabled, not just loading.
props.disabled || props.isLoading
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 line right bellow covers that
{...propsIncludingDisabledPassed}
Main.jsx has that disabled example too.
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.
@sonhanguyen yep, although I could not find the line you mention. @omgaz do we will need to pass it in like
disabled={disabled || isLoading}
in which case, you would still need to pull it out of props just above.
Same here... Amend commit instead of pushing new commits. 😉 |
@@ -0,0 +1,48 @@ | |||
.spinner-button { | |||
@mixin layer-fillparent { |
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.
fillparent should be fill-parent. Also, I'm a bit confused about layer fill parent and what it means. I think just fill-area or fill-element or something may do the job just as well.
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.
by layer I mean position absolute, changed to "fill-area" anyway
aside from the layer-fillparent; LGTM |
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.
LGTM
expect(element.children().first().text()).to.contains('loading…'); | ||
}); | ||
|
||
it('should be disabled in loading mode', () => { |
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.
@omgaz with the design, is there a case where the button is disabled without showing the spinner (disabled
prop) prop and disabled showing the spinner (isLoading
prop)?
@sonhanguyen depending on Garry's answer:
should be disabled in loading mode when passed isLoading
for this test and maybe another test should be disabled when passed isLoading
for the disabled
prop talked about above.
0507793
to
f16ce44
Compare
isLoading, | ||
children, | ||
loadingHoverElement = children, | ||
} = props; |
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.
oh, I am sorry to come with late request, but can you add support for passing data-test-selector property down to the button? (https://github.com/Adslot/adslot-ui/blob/8fc657aa6cefd760fecff5f5e063d6f0d1ecaa55/src/components/adslotUi/SplitPaneComponent.jsx as inspiration)
loadingHoverElement = children,
dts #yeah, I know it is a shortcut, but that's what we use...
} = props;
...
<Button
{..._.pick(props, _.keys(Button.propTypes))}
expandDts(dts)
...
dts: PropTypes.string
pseudocode
209e8a4
to
e1fb7cd
Compare
|
||
it('should pass props to button', () => { | ||
const element = shallow( | ||
<SpinnerButton bsStyle="primary" bsSize="lg">Test</SpinnerButton> |
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 add the dts
prop as well? expecting data-test-selector to be a buttonElement 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.
All react components accept data as do bootstrap components. We should be able to use data-test-selector
directly on the component. @sonhanguyen do you just want to verify that data-test-selector
gets passed through correctly and without duplication/errors.
e1fb7cd
to
f936b36
Compare
Alexandria pr has merged and released as 4.0.1; you can update your npm package in this PR to pull in those changes :) Also, squash your commits down to one and we should be good to merge |
f936b36
to
fe3f253
Compare
a53bac3
to
2be5a1f
Compare
2be5a1f
to
61a9705
Compare
Changes
Add SpinnerButton component
Background context
In Alexandria we now have a spinner component that comes in three sizes.
In Adslot UI we have buttons.
We'd now like to export a new SpinnerButton, one that allows us to show progress.
Trello Cards
https://trello.com/c/FoelX6UK/5932-adslot-ui-spinner-button-component
What it currently looks like:
What it is expected to look like after https://github.com/Adslot/alexandria/pull/356 is merged