0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Members auth ui refinements (#10279)

* Update mobile modal animations
* Member popup input error and placeholder refinements
* Adding close animation to members auth popups
* Improve members auth dialog
* Refine members reset password design
This commit is contained in:
Peter Zimon 2018-12-14 09:57:08 +01:00 committed by GitHub
parent 7dd2b04343
commit 42e013cfae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 261 additions and 102 deletions

View file

@ -12,6 +12,7 @@ function getFreshState() {
formData: {},
query,
formType,
parentContainerClass: 'gm-page-overlay',
showError: false,
submitFail: false
};
@ -204,56 +205,87 @@ export default class App extends Component {
let hash = '';
switch (formType) {
case 'signup':
mainTitle = 'Sign Up';
mainTitle = 'Sign up';
ctaTitle = 'Already a member?';
ctaLabel = 'Log in';
hash = 'signin';
break;
case 'signin':
mainTitle = 'Log In';
mainTitle = 'Log in';
ctaTitle = 'Not a member?';
ctaLabel = 'Sign up';
hash = 'signup';
break;
case 'request-password-reset':
mainTitle = 'Reset password';
ctaTitle = '';
ctaLabel = 'Log in';
hash = 'signin';
break;
case 'password-reset-sent':
mainTitle = 'Reset password';
ctaTitle = '';
ctaLabel = 'Log in';
hash = 'signin';
break;
case 'reset-password':
mainTitle = 'Reset password';
ctaTitle = '';
ctaLabel = 'Log in';
hash = 'signin';
break;
}
let formError = this.renderError({ error: {errorType: "form-submit"}, formType });
return (
<div className="flex flex-column">
<div>
<div className="gm-logo"></div>
<div className="gm-auth-header">
<h1>{ mainTitle }</h1>
<div className="flex items-baseline">
<h4>{ ctaTitle }</h4>
<a href="javascript:;"
onClick={(e) => {window.location.hash = hash}}
>
{ctaLabel}
</a>
</div>
{(ctaTitle ?
<div className="flex items-baseline mt2">
<h4>{ ctaTitle }</h4>
<a href="javascript:;"
onClick={ (e) => { window.location.hash = hash } }
>
{ ctaLabel }
</a>
</div>
: "")}
</div>
{(formError ? <div class="gm-form-errortext"><i>{ IconError }</i> { formError }</div> : "")}
</div>
)
}
renderFormFooters(formType) {
let mainTitle = '';
let ctaTitle = '';
let ctaLabel = '';
let hash = '';
switch (formType) {
case 'request-password-reset':
ctaTitle = 'Back to';
ctaLabel = 'log in';
hash = 'signin';
break;
case 'password-reset-sent':
ctaTitle = 'Back to';
ctaLabel = 'log in';
hash = 'signin';
break;
case 'reset-password':
ctaTitle = 'Back to';
ctaLabel = 'log in';
hash = 'signin';
break;
}
if (ctaTitle) {
return (
<div className="gm-auth-footer">
<div className="flex items-baseline">
<h4>{ ctaTitle }</h4>
<a href="javascript:;"
onClick={ (e) => { window.location.hash = hash } }
>
{ ctaLabel }
</a>
</div>
</div>
)
}
}
renderFormInput({type, name, label, icon, placeholder, required, formType}) {
let value = this.state.formData[name];
let className = "";
@ -262,8 +294,8 @@ export default class App extends Component {
className += (value ? "gm-input-filled" : "") + (forgot ? " gm-forgot-input" : "") + (inputError ? " gm-error" : "");
return (
<div className="mt8">
<div className="gm-floating-input">
<div className="gm-form-element">
<div className="gm-input">
<input
type={ type }
name={ name }
@ -274,18 +306,18 @@ export default class App extends Component {
required = {required}
className={ className }
/>
<label for={ name }> { label }</label>
<label for={ name }> { placeholder }</label>
<i>{ icon }</i>
{ (forgot ? <a href="javascript:;" className="gm-forgot-link" onClick={(e) => {window.location.hash = 'request-password-reset'}}>Forgot</a> : "") }
</div>
<div class="gm-input-errortext">{ inputError }</div>
{/* { (inputError ? <div class="gm-input-errortext">{ inputError }</div> : "")} */}
</div>
)
}
renderFormText({formType}) {
return (
<div className="mt8">
<div className="gm-reset-sent">
<p>Weve sent a recovery email to your inbox. Follow the link in the email to reset your password.</p>
</div>
)
@ -300,7 +332,7 @@ export default class App extends Component {
renderFormSubmit({buttonLabel, formType}) {
return (
<div className="mt8">
<div className="mt6">
<button type="submit" name={ formType } className="gm-btn-blue" onClick={(e) => this.onSubmitClick(e)}>{ buttonLabel }</button>
</div>
)
@ -338,14 +370,17 @@ export default class App extends Component {
let formElements = [];
let buttonLabel = '';
let formContainerClass = 'flex flex-column'
switch (formType) {
case 'signin':
buttonLabel = 'Log in';
formElements = [emailInput, passwordInput, this.renderFormSubmit({formType, buttonLabel})];
formContainerClass += ' mt3'
break;
case 'signup':
buttonLabel = 'Sign up';
formElements = [nameInput, emailInput, passwordInput, this.renderFormSubmit({formType, buttonLabel})];
formContainerClass += ' mt3'
break;
case 'request-password-reset':
buttonLabel = 'Send reset password instructions';
@ -361,7 +396,7 @@ export default class App extends Component {
break;
}
return (
<div className="flex flex-column nt1">
<div className={formContainerClass}>
<form className={ `gm-` + formType + `-form` } onSubmit={(e) => this.submitForm(e)} noValidate>
{ formElements }
</form>
@ -376,6 +411,7 @@ export default class App extends Component {
<a className="gm-modal-close" onClick={ (e) => this.close(e)}>{ IconClose }</a>
{this.renderFormHeaders(formType)}
{this.renderFormSection(formType)}
{this.renderFormFooters(formType)}
</div>
</div>
);
@ -383,13 +419,22 @@ export default class App extends Component {
render() {
return (
<div className="gm-page-overlay" onClick={(e) => this.close(e)}>
<div className={this.state.parentContainerClass} onClick={(e) => this.close(e)}>
{this.renderFormComponent()}
</div>
);
}
close(event) {
window.parent.postMessage('pls-close-auth-popup', '*');
this.setState({
parentContainerClass: 'gm-page-overlay close'
});
window.setTimeout(() => {
this.setState({
parentContainerClass: 'gm-page-overlay'
});
window.parent.postMessage('pls-close-auth-popup', '*');
}, 700);
}
}

View file

@ -13,7 +13,15 @@
position: fixed;
overflow-y: scroll;
background: rgba(10, 17, 23, 0.9);
animation: fadeInOverlay 0.2s ease;
animation: fadeInOverlay 0.2s ease 1 forwards;
}
.gm-page-overlay.close {
animation: fadeOutOverlay 0.2s ease 0.5s 1 forwards;
}
.gm-page-overlay.close .gm-modal {
animation: closeModal 0.5s ease-in 1 forwards;
}
.gm-modal-container {
@ -30,7 +38,7 @@
border-radius: 4px;
padding: 40px;
box-shadow: var(--box-shadow-base);
animation: openModal 0.6s ease;
animation: openModal 0.6s ease 1 forwards;
}
.gm-modal-close {
@ -55,6 +63,11 @@
to {opacity: 1;}
}
@keyframes fadeOutOverlay {
from {opacity: 1;}
to {opacity: 0;}
}
@keyframes openModal { /* Safari and Chrome */
0% {
opacity: 0;
@ -71,6 +84,22 @@
}
}
@keyframes closeModal { /* Safari and Chrome */
0% {
transform: translateY(0);
}
40% {
opacity: 1.0;
transform: translateY(-8px);
}
100% {
opacity: 0;
transform: translateY(85px);
}
}
@media (max-width: 440px) {
.gm-modal-container {
margin: 0;
@ -79,26 +108,58 @@
transform: none;
height: 100vh;
}
.gm-modal {
width: calc(100% - 48px);
height: calc(100vh - 48px);
padding: 24px;
border-radius: 0;
}
.gm-page-overlay {
background: rgba(10, 17, 23, 0.0);
}
@keyframes openModal {
0% {
opacity: 0;
transform: translateY(40px);
}
50% {
opacity: 1.0;
transform: translateY(0);
}
}
@keyframes closeModal {
0% {
opacity: 1.0;
transform: translateY(0);
}
60% {
opacity: 0;
transform: translateY(40px);
}
100% {
opacity: 0;
}
}
}
/* Buttons */
/* ------------------------------------------------------------ */
button {
width: 100%;
height: 44px;
font-weight: 500;
border: 1px solid var(--grey);
color: var(--grey-d3);
text-align: center;
cursor: pointer;
white-space: nowrap;
padding: 0 15px;
padding: 15px 15px 16px;
border-radius: 4px;
outline: none;
transition: all var(--animation-speed-f1) ease-in-out;
@ -169,124 +230,166 @@ select:-webkit-autofill:active {
color: var(--white);
}
.gm-floating-input {
.gm-form-element {
margin: 24px 0 0;
position: relative;
}
.gm-floating-input input {
font-size: var(--text-base);
.gm-input {
position: relative;
}
.gm-input input {
font-size: var(--text-s);
color: var(--grey-d3);
border: none;
border-radius: 0px;
border-bottom: 1px solid var(--grey-l1);
border-radius: 4px;
border: 1px solid var(--grey-l1);
height: 38px;
-webkit-appearance: none;
box-sizing: border-box;
background: var(--white);
height: 44px;
width: 100%;
outline: none;
transition: border var(--animation-speed-f1) ease-in-out;
padding: 0 0 1px 26px; /* 1px bottom padding fixes jump that's caused by the border change */
padding: 20px 14px 22px 38px; /* 1px bottom padding fixes jump that's caused by the border change */
letter-spacing: 0.2px;
line-height: 14px;
}
.gm-floating-input input:hover {
border-bottom: 1px solid var(--grey);
.gm-input input:hover {
border: 1px solid var(--grey);
}
.gm-floating-input input:focus {
border-bottom: 2px solid var(--blue);
padding: 0 0 0 26px;
.gm-input input.gm-error {
border: 1px solid color-mod(var(--red) a(0.8));
background: color-mod(var(--red) a(0.02))
}
.gm-floating-input input.gm-error {
border-bottom: 1px solid var(--red);
.gm-input input:focus {
border: 1px solid color-mod(var(--blue) a(0.8));
box-shadow: 0 0 6px rgba(62, 176, 239, 0.3), 0 0 0px 40px #FFF inset;
}
.gm-floating-input label {
.gm-input label {
display: flex;
align-items: center;
position: absolute;
font-size: var(--text-xs);
font-size: var(--text-s);
padding: 0 0 2px 0;
width: 100%;
top: 15px;
left: 24px;
top: 14px;
left: 38px;
color: var(--grey);
transition: all var(--animation-speed-base) ease-in-out;
transition-delay: 0.15s;
pointer-events: none;
text-transform: uppercase;
letter-spacing: 0.6px;
font-weight: 500;
letter-spacing: 0.4px;
font-weight: 400;
}
.gm-floating-input input.gm-input-filled + label,
.gm-floating-input input:focus + label {
.gm-input input:hover + label {
color: var(--grey-d1);
}
.gm-input input.gm-input-filled + label,
.gm-input input:focus + label {
opacity: 0;
transition-delay: 0s;
}
.gm-floating-input label i svg {
.gm-input input.gm-error + label {
color: color-mod(var(--red) a(0.5));
}
.gm-input label i svg {
width: 16px;
height: 16px;
}
.gm-floating-input label i svg path,
.gm-floating-input i svg path {
.gm-input label i svg path,
.gm-input i svg path {
stroke: var(--grey);
transition: stroke var(--animation-speed-base) ease-in-out;
}
.gm-floating-input input.gm-input-filled + label + i svg path,
.gm-floating-input input:focus + label + i svg path{
.gm-input input:hover + label + i svg path {
stroke: var(--grey-d1);
}
.gm-input input.gm-error + label + i svg path {
stroke: color-mod(var(--red) a(0.5));
}
.gm-input input.gm-input-filled + label + i svg path,
.gm-input input:focus + label + i svg path {
stroke: var(--grey-d2);
}
.gm-floating-input i {
.gm-input i {
position: absolute;
top: 14px;
left: 0;
left: 14px;
opacity: 1.0;
transition: all var(--animation-speed-f1) ease-in-out;
transition: all var(--animation-speed-base) ease-in-out;
transition-delay: 0s;
}
.gm-floating-input input.gm-input-filled + label + i,
.gm-floating-input input:focus + label + i {
opacity: 1.0;
transform: translateX(0px);
transition-delay: 0.15s;
}
.gm-floating-input label i {
font-style: normal;
display: inline-block;
margin: 0 8px 0 0;
}
.gm-input-errortext {
color: var(--red);
font-size: var(--text-s);
letter-spacing: 0.4px;
margin: 4px 0 0;
font-weight: 500;
position: absolute;
top: 46px;
color: color-mod(var(--red) l(+5%) s(-2%));
font-size: var(--text-xs);
letter-spacing: 0.8px;
border-radius: 5px;
z-index: 9999;
animation: slideErrorText 0.4s ease;
}
@keyframes slideErrorText {
from {
opacity: 0;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0px);
}
}
/* Error tooltip triangle */
.gm-input-errortext-xx:before {
position: absolute;
top: -6px;
left: 50%;
margin-left: -6px;
width: 0;
border-bottom: 6px solid var(--grey-d3);
border-bottom: 6px solid var(--grey-d3);
border-right: 6px solid transparent;
border-left: 6px solid transparent;
content: " ";
font-size: 0;
line-height: 0;
}
.gm-form-errortext {
color: var(--red);
font-size: var(--text-s);
letter-spacing: 0.4px;
margin: 28px -40px 0;
margin: 40px -2px -12px;
background: color-mod(var(--red) a(0.08));
padding: 12px 40px;
padding: 11px 14px;
font-weight: 500;
display: flex;
justify-content: start;
align-items: center;
justify-content: center;
align-items: start;
border-radius: 4px;
}
.gm-form-errortext span {
margin: 1px 0 0;
}
.gm-form-errortext i {
margin: 3px 8px 0 0;
margin: 1px 8px 0 0;
}

View file

@ -58,33 +58,33 @@ a:hover {
width: 52px;
height: 52px;
border-radius: 4px;
margin: 0 auto;
background: #343F44 url('../assets/images/ghost-logo.svg') center center no-repeat;
}
.gm-auth-header {
.gm-auth-header,
.gm-auth-footer {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: baseline;
margin: 16px 0 0;
padding: 12px 0 0;
}
.gm-auth-header -cta {
padding: 0 0 3px;
align-items: center;
margin: 24px 0 0;
}
.gm-auth-header h1 {
font-size: var(--text-xl);
}
.gm-auth-header h4 {
.gm-auth-header h4,
.gm-auth-footer h4 {
font-weight: normal;
font-size: var(--text-s);
letter-spacing: 0.4px;
color: var(--grey-d1);
/* color: var(--grey-d1); */
}
.gm-auth-header a {
.gm-auth-header a,
.gm-auth-footer a {
display: block;
font-size: var(--text-s);
letter-spacing: 0.4px;
@ -95,19 +95,30 @@ a:hover {
white-space: nowrap;
}
.gm-auth-header a:hover {
.gm-auth-header a:hover,
.gm-auth-footer a:hover {
color: var(--blue-d3);
}
.gm-forgot-link {
position: absolute;
top: 14px;
right: 0;
right: 14px;
z-index: 9999;
font-size: var(--text-s);
letter-spacing: 0.4px;
}
.gm-reset-sent {
margin: 24px 0 0;
background: color-mod(var(--green) a(0.2));
border-radius: 4px;
color: color-mod(var(--green) l(-30%) s(+8%));
padding: 12px 14px 14px;
}
/* Custom forms */
.gm-floating-input .gm-forgot-input {
padding-right: 60px;
}

View file

@ -98,7 +98,7 @@ Layout use a 4px grid.
/* Shadows */
:root {
--box-shadow-base: 0 0 1px rgba(0,0,0,.12), 0 16px 24px -12px rgba(0,0,0,.2);
--box-shadow-base: 0 0 1px rgba(0, 0, 0, .12), 0 16px 24px -12px rgba(0, 0, 0, .2);
}
/* Animations */