Merge pull request #3436 from benjiwheeler/join-flow-toggle-info-button

Join flow info button has larger touch target, can be clicked to toggle
This commit is contained in:
Benjamin Wheeler 2020-04-21 11:00:27 -04:00 committed by GitHub
commit 099d5a1007
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 143 additions and 23 deletions

View file

@ -11,19 +11,45 @@ class InfoButton extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleHideMessage',
'handleShowMessage'
'handleClick',
'handleMouseLeave',
'handleShowMessage',
'setButtonRef'
]);
this.state = {
requireClickToClose: false, // default to closing on mouseout
visible: false
};
}
handleHideMessage () {
this.setState({visible: false});
componentWillMount () {
window.addEventListener('mousedown', this.handleClick, false);
}
componentWillUnmount () {
window.removeEventListener('mousedown', this.handleClick, false);
}
handleClick (e) {
if (this.buttonRef) { // only handle click if we can tell whether it happened in this component
let newVisibleState = false; // for most clicks, hide the info message
if (this.buttonRef.contains(e.target)) { // if the click was inside the info icon...
newVisibleState = !this.state.requireClickToClose; // toggle it
}
this.setState({
requireClickToClose: newVisibleState,
visible: newVisibleState
});
}
}
handleMouseLeave () {
if (this.state.visible && !this.state.requireClickToClose) {
this.setState({visible: false});
}
}
handleShowMessage () {
this.setState({visible: true});
}
setButtonRef (element) {
this.buttonRef = element;
}
render () {
const messageJsx = this.state.visible && (
<div className="info-button-message">
@ -34,8 +60,8 @@ class InfoButton extends React.Component {
<React.Fragment>
<div
className="info-button"
onClick={this.handleShowMessage}
onMouseOut={this.handleHideMessage}
ref={this.setButtonRef}
onMouseLeave={this.handleMouseLeave}
onMouseOver={this.handleShowMessage}
>
<MediaQuery minWidth={frameless.desktop}>

View file

@ -4,23 +4,21 @@
.info-button {
position: relative;
display: inline-block;
width: 1rem;
height: 1rem;
margin-left: .375rem;
margin-top: -.25rem;
border-radius: 50%;
background-color: $type-gray-60percent;
width: 2rem;
height: 2rem;
margin-left: -.125rem;
margin-top: -.75rem;
background-image: url("/svgs/info-button/info-button.svg");
background-size: cover;
top: .1875rem;
top: .6875rem;
}
.info-button-message {
$arrow-border-width: 1rem;
display: block;
position: absolute;
top: 0;
left: 0;
top: .375rem;
left: .5rem;
transform: translate(1rem, -1rem);
width: 16.5rem;
min-height: 1rem;
@ -66,6 +64,7 @@
we need to center this element within its width. */
margin: 0 calc((100% - 16.5rem) / 2);;
top: .125rem;
left: 0;
&:before {
display: none;

View file

@ -127,10 +127,11 @@
}
.join-flow-privacy-message {
margin: 1rem auto;
margin: .5rem auto 1rem;
font-size: .75rem;
font-weight: 500;
color: $type-gray-60percent;
text-align: center;
}
.join-flow-inner-username-step {

View file

@ -1 +1 @@
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><g fill-rule="evenodd"><path d="M10 0a10 10 0 1 0 10 10A10 10 0 0 0 10 0" fill="#9a9eac"/><path d="M10 13.39a1.33 1.33 0 1 1-1.33 1.33A1.33 1.33 0 0 1 10 13.39zm2.68-8.77a3 3 0 0 1 1.42 2.31 3.15 3.15 0 0 1-.95 2.56 8.37 8.37 0 0 1-1.59 1.1c-.7.39-.7.41-.7.77 0 .55 0 1-1 1s-1-.45-1-1a2.65 2.65 0 0 1 1.72-2.52A6.61 6.61 0 0 0 11.79 8a1.22 1.22 0 0 0 .3-.91 1 1 0 0 0-.5-.79 2.8 2.8 0 0 0-2.87.2c-.98.64-.81 1.55-1.72 1.3s-.92-1-.53-1.7a3.94 3.94 0 0 1 1.9-1.66 4.67 4.67 0 0 1 4.3.18z" fill="#fff"/></g></svg>
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="40" height="40"><g fill-rule="evenodd"><path d="M20 10a10 10 0 1 0 10 10 10 10 0 0 0-10-10" fill="#9a9eac"/><path d="M20 23.39a1.33 1.33 0 1 1-1.33 1.33A1.34 1.34 0 0 1 20 23.39zm2.68-8.77a3 3 0 0 1 1.42 2.31 3.14 3.14 0 0 1-1 2.56 8.2 8.2 0 0 1-1.59 1.1c-.7.39-.7.41-.7.77 0 .55 0 1-1 1s-1-.45-1-1a2.64 2.64 0 0 1 1.72-2.52 6.71 6.71 0 0 0 1.26-.84 1.22 1.22 0 0 0 .3-.91 1 1 0 0 0-.5-.79 2.8 2.8 0 0 0-2.87.2c-1 .64-.81 1.55-1.72 1.3s-.92-1-.53-1.7a3.9 3.9 0 0 1 1.9-1.66 4.67 4.67 0 0 1 4.3.18z" fill="#fff"/></g></svg>

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 589 B

View file

@ -3,6 +3,15 @@ import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
import InfoButton from '../../../src/components/info-button/info-button';
describe('InfoButton', () => {
// mock window.addEventListener
// for more on this technique, see discussion at https://github.com/airbnb/enzyme/issues/426#issuecomment-253515886
const mockedAddEventListener = {};
/* eslint-disable no-undef */
window.addEventListener = jest.fn((event, cb) => {
mockedAddEventListener[event] = cb;
});
/* eslint-enable no-undef */
test('Info button defaults to not visible', () => {
const component = mountWithIntl(
<InfoButton
@ -11,33 +20,118 @@ describe('InfoButton', () => {
);
expect(component.find('div.info-button-message').exists()).toEqual(false);
});
test('mouseOver on info button makes info message visible', () => {
test('mouseOver on info button makes info message visible', done => {
const component = mountWithIntl(
<InfoButton
message="Here is some info about something!"
/>
);
// mouseOver info button
component.find('div.info-button').simulate('mouseOver');
expect(component.find('div.info-button-message').exists()).toEqual(true);
setTimeout(function () { // necessary because mouseover uses debounce
// crucial: if we don't call update(), then find() below looks through an OLD
// version of the DOM! see https://github.com/airbnb/enzyme/issues/1233#issuecomment-358915200
component.update();
expect(component.find('div.info-button-message').exists()).toEqual(true);
done();
}, 500);
});
test('clicking on info button makes info message visible', () => {
const component = mountWithIntl(
<InfoButton
message="Here is some info about something!"
/>
);
component.find('div.info-button').simulate('click');
const buttonRef = component.instance().buttonRef;
// click on info button
mockedAddEventListener.mousedown({target: buttonRef});
component.update();
expect(component.find('div.info-button').exists()).toEqual(true);
expect(component.find('div.info-button-message').exists()).toEqual(true);
});
test('after message is visible, mouseOut makes it vanish', () => {
test('clicking on info button, then mousing out makes info message still appear', done => {
const component = mountWithIntl(
<InfoButton
message="Here is some info about something!"
/>
);
component.find('div.info-button').simulate('mouseOver');
const buttonRef = component.instance().buttonRef;
// click on info button
mockedAddEventListener.mousedown({target: buttonRef});
component.update();
expect(component.find('div.info-button').exists()).toEqual(true);
expect(component.find('div.info-button-message').exists()).toEqual(true);
component.find('div.info-button').simulate('mouseOut');
// mouseLeave from info button
component.find('div.info-button').simulate('mouseLeave');
setTimeout(function () { // necessary because mouseover uses debounce
component.update();
expect(component.find('div.info-button-message').exists()).toEqual(true);
done();
}, 500);
});
test('clicking on info button, then clicking on it again makes info message go away', () => {
const component = mountWithIntl(
<InfoButton
message="Here is some info about something!"
/>
);
const buttonRef = component.instance().buttonRef;
// click on info button
mockedAddEventListener.mousedown({target: buttonRef});
component.update();
expect(component.find('div.info-button').exists()).toEqual(true);
expect(component.find('div.info-button-message').exists()).toEqual(true);
// click on info button again
mockedAddEventListener.mousedown({target: buttonRef});
component.update();
expect(component.find('div.info-button-message').exists()).toEqual(false);
});
test('clicking on info button, then clicking somewhere else', () => {
const component = mountWithIntl(
<InfoButton
message="Here is some info about something!"
/>
);
const buttonRef = component.instance().buttonRef;
// click on info button
mockedAddEventListener.mousedown({target: buttonRef});
component.update();
expect(component.find('div.info-button').exists()).toEqual(true);
expect(component.find('div.info-button-message').exists()).toEqual(true);
// click on some other target
mockedAddEventListener.mousedown({target: null});
component.update();
expect(component.find('div.info-button-message').exists()).toEqual(false);
});
test('after message is visible, mouseLeave makes it vanish', () => {
const component = mountWithIntl(
<InfoButton
message="Here is some info about something!"
/>
);
// mouseOver info button
component.find('div.info-button').simulate('mouseOver');
component.update();
expect(component.find('div.info-button-message').exists()).toEqual(true);
// mouseLeave away from info button
component.find('div.info-button').simulate('mouseLeave');
component.update();
expect(component.find('div.info-button-message').exists()).toEqual(false);
});
});