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

Scroll to the error message by default #1023

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ The appearance of the widget and the mechanics of authentication can be customiz
+ **type {String}**: The message type, it should be `error` or `success`.
+ **text {String}**: The text to show.
- **allowAutocomplete {Boolean}**: Determines whether or not the the email or username inputs will allow autocomplete (`<input autocomplete />`). Defaults to `false`.
- **scrollGlobalMessagesIntoView {Boolean}**: Determines whether or not a globalMessage should be scrolled into the user's viewport. Defaults to `true`.

#### Theming options

Expand Down
47 changes: 47 additions & 0 deletions src/__tests__/ui/box/global_message.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,53 @@ describe('GlobalMessage', () => {
it('renders correctly given an error type', () => {
expectComponent(<GlobalMessage type="error" message="An error occurred." />).toMatchSnapshot();
});
it('should call scrollIntoView if parameter is set and top < 0', () => {
const wrapper = mount(<GlobalMessage type="success" message="foo" scrollIntoView={true} />);
const getBoundingClientRectSpy = jest.fn().mockReturnValue({ top: -1 });
const scrollIntoViewSpy = jest.fn();
wrapper.getDOMNode().getBoundingClientRect = getBoundingClientRectSpy;
wrapper.getDOMNode().scrollIntoView = scrollIntoViewSpy;

wrapper.getNode().componentDidMount();

expect(getBoundingClientRectSpy).toHaveBeenCalled();
expect(scrollIntoViewSpy).toHaveBeenCalledWith(true);
});
it('should not call scrollIntoView if parameter is set and top >= 0', () => {
const wrapper = mount(<GlobalMessage type="success" message="foo" scrollIntoView={true} />);
const getBoundingClientRectSpy = jest.fn().mockReturnValue({ top: 0 });
const scrollIntoViewSpy = jest.fn();
wrapper.getDOMNode().getBoundingClientRect = getBoundingClientRectSpy;
wrapper.getDOMNode().scrollIntoView = scrollIntoViewSpy;

wrapper.getNode().componentDidMount();

expect(getBoundingClientRectSpy).toHaveBeenCalled();
expect(scrollIntoViewSpy).not.toHaveBeenCalled();
});
it('should call scrollIntoView if parameter is not set (default is true)', () => {
const wrapper = mount(<GlobalMessage type="success" message="foo" />);
const getBoundingClientRectSpy = jest.fn().mockReturnValue({ top: -1 });
const scrollIntoViewSpy = jest.fn();
wrapper.getDOMNode().getBoundingClientRect = getBoundingClientRectSpy;
wrapper.getDOMNode().scrollIntoView = scrollIntoViewSpy;

wrapper.getNode().componentDidMount();

expect(getBoundingClientRectSpy).toHaveBeenCalled();
expect(scrollIntoViewSpy).toHaveBeenCalledWith(true);
});
it('should not call scrollIntoView if parameter is set to false', () => {
const wrapper = mount(<GlobalMessage type="success" message="foo" scrollIntoView={false} />);
const getBoundingClientRectSpy = jest.fn().mockReturnValue({ top: -1 });
const scrollIntoViewSpy = jest.fn();
wrapper.getDOMNode().getBoundingClientRect = getBoundingClientRectSpy;
wrapper.getDOMNode().scrollIntoView = scrollIntoViewSpy;

wrapper.getNode().componentDidMount();

expect(scrollIntoViewSpy).not.toHaveBeenCalled();
});
it('should NOT strip out HTML tags if given a React node', () => {
const message = React.createElement('span', {
dangerouslySetInnerHTML: { __html: '<b>Success!</b>' }
Expand Down
3 changes: 2 additions & 1 deletion src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ export default class Base extends EventEmitter {
tabs: screen.renderTabs(m),
terms: screen.renderTerms(m, i18nProp.html('signUpTerms')),
title: getScreenTitle(m),
transitionName: screen.name === 'loading' ? 'fade' : 'horizontal-fade'
transitionName: screen.name === 'loading' ? 'fade' : 'horizontal-fade',
scrollGlobalMessagesIntoView: l.ui.scrollGlobalMessagesIntoView(m)
};
render(l.ui.containerID(m), props);

Expand Down
8 changes: 6 additions & 2 deletions src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ function extractUIOptions(id, options) {
primaryColor: typeof primaryColor === 'string' ? primaryColor : undefined,
rememberLastLogin: undefined === options.rememberLastLogin ? true : !!options.rememberLastLogin,
allowAutocomplete: !!options.allowAutocomplete,
authButtonsTheme: typeof authButtons === 'object' ? authButtons : {}
authButtonsTheme: typeof authButtons === 'object' ? authButtons : {},
scrollGlobalMessagesIntoView: undefined === options.scrollGlobalMessagesIntoView
? true
: !!options.scrollGlobalMessagesIntoView
});
}

Expand Down Expand Up @@ -185,7 +188,8 @@ export const ui = {
primaryColor: lock => getUIAttribute(lock, 'primaryColor'),
authButtonsTheme: lock => getUIAttribute(lock, 'authButtonsTheme'),
rememberLastLogin: m => tget(m, 'rememberLastLogin', getUIAttribute(m, 'rememberLastLogin')),
allowAutocomplete: m => tget(m, 'allowAutocomplete', getUIAttribute(m, 'allowAutocomplete'))
allowAutocomplete: m => tget(m, 'allowAutocomplete', getUIAttribute(m, 'allowAutocomplete')),
scrollGlobalMessagesIntoView: lock => getUIAttribute(lock, 'scrollGlobalMessagesIntoView')
};

const { get: getAuthAttribute } = dataFns(['core', 'auth']);
Expand Down
36 changes: 18 additions & 18 deletions src/ui/box/chrome.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,6 @@ export default class Chrome extends React.Component {
setTimeout(() => input.focus(), 17);
}
}

return;
}

if (!prevProps.error && error) {
const input = this.findAutofocusInput();

if (input) {
// TODO clear timeout
setTimeout(() => input.focus(), 17);
}

return;
}
}

Expand Down Expand Up @@ -185,7 +172,8 @@ export default class Chrome extends React.Component {
success,
terms,
title,
transitionName
transitionName,
scrollGlobalMessagesIntoView
} = this.props;

const { delayingShowSubmitButton, moving, reverse } = this.state;
Expand Down Expand Up @@ -217,10 +205,20 @@ export default class Chrome extends React.Component {
}

const globalError = error
? <GlobalMessage key="global-error" message={wrapGlobalMessage(error)} type="error" />
? <GlobalMessage
key="global-error"
message={wrapGlobalMessage(error)}
type="error"
scrollIntoView={scrollGlobalMessagesIntoView}
/>
: null;
const globalSuccess = success
? <GlobalMessage key="global-success" message={wrapGlobalMessage(success)} type="success" />
? <GlobalMessage
key="global-success"
message={wrapGlobalMessage(success)}
type="success"
scrollIntoView={scrollGlobalMessagesIntoView}
/>
: null;

const Content = contentComponent;
Expand Down Expand Up @@ -313,11 +311,13 @@ Chrome.propTypes = {
success: PropTypes.node,
terms: PropTypes.element,
title: PropTypes.string,
transitionName: PropTypes.string.isRequired
transitionName: PropTypes.string.isRequired,
scrollGlobalMessagesIntoView: PropTypes.bool
};

Chrome.defaultProps = {
autofocus: false,
disableSubmitButton: false,
showSubmitButton: true
showSubmitButton: true,
scrollGlobalMessagesIntoView: true
};
10 changes: 7 additions & 3 deletions src/ui/box/container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ export default class Container extends React.Component {
tabs,
terms,
title,
transitionName
transitionName,
scrollGlobalMessagesIntoView
} = this.props;

const badge = showBadge ? <BottomBadge link={badgeLink} /> : null;
Expand Down Expand Up @@ -210,6 +211,7 @@ export default class Container extends React.Component {
terms={terms}
title={title}
transitionName={transitionName}
scrollGlobalMessagesIntoView={scrollGlobalMessagesIntoView}
/>
</div>
</form>
Expand Down Expand Up @@ -242,7 +244,8 @@ Container.propTypes = {
tabs: PropTypes.bool,
terms: PropTypes.element,
title: PropTypes.string,
transitionName: PropTypes.string.isRequired
transitionName: PropTypes.string.isRequired,
scrollGlobalMessagesIntoView: PropTypes.bool
// escHandler
// submitHandler,
};
Expand All @@ -260,5 +263,6 @@ export const defaultProps = (Container.defaultProps = {
isSubmitting: false,
logo: `${isFileProtocol ? 'https:' : ''}//cdn.auth0.com/styleguide/components/1.0.8/media/logos/img/badge.png`,
primaryColor: '#ea5323',
showBadge: true
showBadge: true,
scrollGlobalMessagesIntoView: true
});
24 changes: 22 additions & 2 deletions src/ui/box/global_message.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@ import PropTypes from 'prop-types';
import React from 'react';

export default class GlobalMessage extends React.Component {
componentDidMount() {
const methodIsSupported =
this.messageNode && typeof this.messageNode.scrollIntoView === 'function';
if (methodIsSupported && this.props.scrollIntoView) {
const boundingRect = this.messageNode.getBoundingClientRect();
if (boundingRect.top < 0) {
this.messageNode.scrollIntoView(true);
}
}
}
render() {
const { message, type } = this.props;
const className = `auth0-global-message auth0-global-message-${type}`;
return (
<div className={className}>
<div
className={className}
ref={messageNode => {
this.messageNode = messageNode;
}}
>
<span className="animated fadeInUp">{message}</span>
</div>
);
Expand All @@ -15,5 +30,10 @@ export default class GlobalMessage extends React.Component {

GlobalMessage.propTypes = {
message: PropTypes.node.isRequired,
type: PropTypes.oneOf(['error', 'success']).isRequired
type: PropTypes.oneOf(['error', 'success']).isRequired,
scrollIntoView: PropTypes.bool
};

GlobalMessage.defaultProps = {
scrollIntoView: true
};
2 changes: 1 addition & 1 deletion support/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<div id="container"></div>
<script src="/build/lock.js"></script>
<script>
const cid = "nj4hF7eaFFgt45b7brhUg8S8qewcVmYw";
const cid = "BWDP9XS89CJq1w6Nzq7iFOHsTh6ChS2b";
const domain = "brucke.auth0.com";
const options = {
oidcConformant: true,
Expand Down