From bfe120fbf49e9d7134ae241624dd2140a40e02cb Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 1 Mar 2019 14:39:09 +0000 Subject: [PATCH 01/10] Convert registration to Field component This converts most fields in the registration form to use the Field component, except for the phone number, which is a left as a separate task because of the country dropdown menu. --- res/css/structures/auth/_Login.scss | 1 + res/css/views/auth/_AuthBody.scss | 3 +- src/components/views/auth/RegistrationForm.js | 58 ++++++++++++------- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index d4b5e7402c7..e65b69163b5 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -15,6 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// TODO: Should be removable once all fields are using Field component .mx_Login_field { width: 100%; box-sizing: border-box; diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index e8f49cdbd25..b65c41946fa 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -78,7 +78,8 @@ limitations under the License. margin-bottom: 10px; } -.mx_AuthBody_fieldRow > * { +.mx_AuthBody_fieldRow > *, +.mx_AuthBody_fieldRow > .mx_Field { margin: 0 5px; flex: 1; } diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index d031dc7bdd0..1a328ef34a6 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -306,6 +306,8 @@ module.exports = React.createClass({ }, render: function() { + const Field = sdk.getComponent('elements.Field'); + let yourMatrixAccountText = _t('Create your Matrix account'); if (this.props.hsName) { yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', { @@ -338,14 +340,16 @@ module.exports = React.createClass({ _t("Email (optional)"); emailSection = ( -
- -
+ ); } @@ -385,8 +389,6 @@ module.exports = React.createClass({ ); - const placeholderUsername = _t("Username"); - return (

@@ -395,22 +397,36 @@ module.exports = React.createClass({

- + label={_t("Username")} + defaultValue={this.props.defaultUsername} + onBlur={this.onUsernameBlur} + />
- - + + />
{ emailSection } From ae5c32d28b1e7e4e8f56daab5bdf29275c1fb86b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 1 Mar 2019 16:45:33 +0000 Subject: [PATCH 02/10] Lift border up to the Field root By placing the Field's border on the Field component root instead of the input, it's easier to wrap it around additional elements that we'll soon stuff inside the field. --- res/css/views/elements/_Field.scss | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index 02f0e548fb0..5d5357da39c 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -19,6 +19,9 @@ limitations under the License. .mx_Field { position: relative; margin: 1em 0; + border-radius: 4px; + transition: border-color 0.25s; + border: 1px solid $input-border-color; } .mx_Field input, @@ -27,9 +30,10 @@ limitations under the License. font-weight: normal; font-family: $font-family; font-size: 14px; + border: none; + // Even without a border here, we still need this avoid overlapping the rounded + // corners on the field above. border-radius: 4px; - transition: border-color 0.25s; - border: 1px solid $input-border-color; padding: 8px 9px; color: $primary-fg-color; background-color: $primary-bg-color; @@ -55,11 +59,14 @@ limitations under the License. pointer-events: none; } +.mx_Field:focus-within { + border-color: $input-focused-border-color; +} + .mx_Field input:focus, .mx_Field select:focus, .mx_Field textarea:focus { outline: 0; - border-color: $input-focused-border-color; } .mx_Field input::placeholder, From 26b2aa174b93a8edb1a4096ffc04fda6dfbaa481 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 5 Mar 2019 15:13:12 +0000 Subject: [PATCH 03/10] Add prefix support to Fields This allows Fields to have an optional prefix component which is placed inside the border of the Field and to the left of the input. Since this label animation would be complex to get right for this case, it is instead fixed to the top left if there is a prefix component. This canonical example of this today would be a phone number field which includes a country dropdown. --- res/css/views/elements/_Field.scss | 8 +++++++- src/components/views/elements/Field.js | 22 ++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index 5d5357da39c..1d3a3652fbe 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -17,6 +17,7 @@ limitations under the License. /* TODO: Consider unifying with general input styles in _light.scss */ .mx_Field { + display: flex; position: relative; margin: 1em 0; border-radius: 4px; @@ -24,6 +25,10 @@ limitations under the License. border: 1px solid $input-border-color; } +.mx_Field_prefix { + border-right: 1px solid $input-border-color; +} + .mx_Field input, .mx_Field select, .mx_Field textarea { @@ -106,7 +111,8 @@ limitations under the License. .mx_Field input:not(:placeholder-shown) + label, .mx_Field textarea:focus + label, .mx_Field textarea:not(:placeholder-shown) + label, -.mx_Field select + label /* Always show a select's label on top to not collide with the value */ { +.mx_Field select + label /* Always show a select's label on top to not collide with the value */, +.mx_Field_labelAlwaysTopLeft label { transition: font-size 0.25s ease-out 0s, color 0.25s ease-out 0s, diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 1b7d9fdd73a..d43cb7423c3 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -16,6 +16,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; export default class Field extends React.PureComponent { static propTypes = { @@ -30,6 +31,8 @@ export default class Field extends React.PureComponent { label: PropTypes.string, // The field's placeholder string. Defaults to the label. placeholder: PropTypes.string, + // Optional component to include inside the field before the input. + prefix: PropTypes.node, // All other props pass through to the . }; @@ -46,7 +49,7 @@ export default class Field extends React.PureComponent { } render() { - const { element, children, ...inputProps } = this.props; + const { element, prefix, children, ...inputProps } = this.props; const inputElement = element || "input"; @@ -57,7 +60,22 @@ export default class Field extends React.PureComponent { const fieldInput = React.createElement(inputElement, inputProps, children); - return
+ let prefixContainer = null; + if (prefix) { + prefixContainer = + {prefix} + ; + } + + const classes = classNames("mx_Field", `mx_Field_${inputElement}`, { + // If we have a prefix element, leave the label always at the top left and + // don't animate it, as it looks a bit clunky and would add complexity to do + // properly. + mx_Field_labelAlwaysTopLeft: prefix, + }); + + return
+ {prefixContainer} {fieldInput}
; From 5b1d361577022f5a2f3d959948552f39507d4fff Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 5 Mar 2019 15:16:07 +0000 Subject: [PATCH 04/10] Convert registration phone number to Field component Now that we have prefix support in the Field component, we can also convert the phone number with country dropdown on registration. --- res/css/structures/auth/_Login.scss | 2 + res/css/views/elements/_Field.scss | 11 +++++ src/components/views/auth/RegistrationForm.js | 43 ++++++++----------- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index e65b69163b5..75e2f93d7b9 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -95,6 +95,7 @@ limitations under the License. flex: 1 1 auto; } +// TODO-START: Remove up to TODO-END when all the country dropdowns are in Fields .mx_Login_field_prefix { height: 38px; padding: 0px 5px; @@ -141,3 +142,4 @@ limitations under the License. margin: 3px; vertical-align: top; } +// TODO-END: Remove from TODO-START when all the country dropdowns are in Fields diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index 1d3a3652fbe..4a74262fd4f 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -140,3 +140,14 @@ limitations under the License. background-color: $field-focused-label-bg-color; color: $greyed-fg-color; } + +// Customise other components when placed inside a Field + +.mx_Field .mx_Dropdown_input { + border: initial; + border-radius: initial; +} + +.mx_Field .mx_CountryDropdown { + width: 67px; +} diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 1a328ef34a6..4275a24b49e 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -357,32 +357,27 @@ module.exports = React.createClass({ const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); let phoneSection; if (threePidLogin && this._authStepIsUsed('m.login.msisdn')) { - const phonePlaceholder = this._authStepIsRequired('m.login.msisdn') ? + const phoneLabel = this._authStepIsRequired('m.login.msisdn') ? _t("Phone") : _t("Phone (optional)"); - phoneSection = ( -
- - -
- ); + const phoneCountry = ; + + phoneSection = ; } const registerButton = ( From 302e601fa19ec374724f0a8e8c8035f14b232c4b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 5 Mar 2019 15:39:51 +0000 Subject: [PATCH 05/10] Convert forgot password to Field component This converts all fields in the forgot password form to use the Field component. --- .../structures/auth/ForgotPassword.js | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js index 58deb380e38..f0a53e5063c 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -230,6 +230,8 @@ module.exports = React.createClass({ }, renderForgot() { + const Field = sdk.getComponent('elements.Field'); + let errorText = null; const err = this.state.errorText || this.props.defaultServerDiscoveryError; if (err) { @@ -275,23 +277,33 @@ module.exports = React.createClass({ {errorText}
- + autoFocus + />
- - + + />
{_t( 'A verification email will be sent to your inbox to confirm ' + From 5a491ac05336bfd09d6f19c434ba5f7a94fb18e9 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 5 Mar 2019 17:28:04 +0000 Subject: [PATCH 06/10] Convert login inputs to Field component As with other auth flows, this converts inputs on the login page to use the Field component for consistent styling. The login type dropdown is left as-is for now. --- res/css/structures/auth/_Login.scss | 64 --------------- src/components/views/auth/PasswordLogin.js | 92 ++++++++++++---------- src/i18n/strings/en_EN.json | 5 +- 3 files changed, 52 insertions(+), 109 deletions(-) diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index 75e2f93d7b9..f3cada9d70a 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -15,18 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Should be removable once all fields are using Field component -.mx_Login_field { - width: 100%; - box-sizing: border-box; - border-radius: 3px; - border: 1px solid $strong-input-border-color; - font-weight: 300; - font-size: 13px; - padding: 9px; - margin-bottom: 14px; -} - .mx_Login_submit { @mixin mx_DialogButton; width: 100%; @@ -70,9 +58,6 @@ limitations under the License. color: $warning-color; font-weight: bold; text-align: center; -/* - height: 24px; -*/ margin-top: 12px; margin-bottom: 12px; } @@ -94,52 +79,3 @@ limitations under the License. align-self: flex-end; flex: 1 1 auto; } - -// TODO-START: Remove up to TODO-END when all the country dropdowns are in Fields -.mx_Login_field_prefix { - height: 38px; - padding: 0px 5px; - line-height: 38px; - - background-color: #eee; - border: 1px solid #c7c7c7; - border-right: 0px; - border-radius: 3px 0px 0px 3px; - - text-align: center; -} - -.mx_Login_field_has_prefix { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; -} - -.mx_Login_phoneSection { - display:flex; -} - -.mx_Login_phoneCountry { - margin-bottom: 14px; - - /* To override mx_Login_field_prefix */ - text-align: left; - padding: 0px; - background-color: $primary-bg-color; -} - -.mx_Login_field_prefix .mx_Dropdown_input { - /* To use prefix border instead of dropdown border */ - border: 0; -} - -.mx_Login_phoneCountry .mx_Dropdown_option { - /* To match height of mx_Login_field */ - height: 38px; - line-height: 38px; -} - -.mx_Login_phoneCountry .mx_Dropdown_option img { - margin: 3px; - vertical-align: top; -} -// TODO-END: Remove from TODO-START when all the country dropdowns are in Fields diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index 1ad93f60751..f08f75a84bc 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -169,67 +169,70 @@ class PasswordLogin extends React.Component { } renderLoginField(loginType) { - const classes = { - mx_Login_field: true, - }; + const Field = sdk.getComponent('elements.Field'); + + const classes = {}; switch (loginType) { case PasswordLogin.LOGIN_FIELD_EMAIL: classes.error = this.props.loginIncorrect && !this.state.username; - return {this._loginField = e;}} + name="username" // make it a little easier for browser's remember-password key="email_input" type="text" - name="username" // make it a little easier for browser's remember-password - onChange={this.onUsernameChanged} - onBlur={this.onUsernameBlur} + label={_t("Email")} placeholder="joe@example.com" value={this.state.username} + onChange={this.onUsernameChanged} + onBlur={this.onUsernameBlur} autoFocus />; case PasswordLogin.LOGIN_FIELD_MXID: classes.error = this.props.loginIncorrect && !this.state.username; - return {this._loginField = e;}} + name="username" // make it a little easier for browser's remember-password key="username_input" type="text" - name="username" // make it a little easier for browser's remember-password + label={SdkConfig.get().disable_custom_urls ? + _t("Username on %(hs)s", { + hs: this.props.hsUrl.replace(/^https?:\/\//, ''), + }) : _t("Username")} + value={this.state.username} onChange={this.onUsernameChanged} onBlur={this.onUsernameBlur} - placeholder={SdkConfig.get().disable_custom_urls ? - _t("Username on %(hs)s", { - hs: this.props.hsUrl.replace(/^https?:\/\//, ''), - }) : _t("Username")} - value={this.state.username} autoFocus />; case PasswordLogin.LOGIN_FIELD_PHONE: { const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); - classes.mx_Login_field_has_prefix = true; classes.error = this.props.loginIncorrect && !this.state.phoneNumber; - return
- - {this._loginField = e;}} - key="phone_input" - type="text" - name="phoneNumber" - onChange={this.onPhoneNumberChanged} - onBlur={this.onPhoneNumberBlur} - placeholder={_t("Mobile phone number")} - value={this.state.phoneNumber} - autoFocus - /> -
; + + const phoneCountry = ; + + return { this._loginField = e; }} + name="phoneNumber" + key="phone_input" + type="text" + label={_t("Phone")} + value={this.state.phoneNumber} + prefix={phoneCountry} + onChange={this.onPhoneNumberChanged} + onBlur={this.onPhoneNumberBlur} + autoFocus + />; } } } @@ -245,6 +248,8 @@ class PasswordLogin extends React.Component { } render() { + const Field = sdk.getComponent('elements.Field'); + let forgotPasswordJsx; if (this.props.onForgotPasswordClick) { @@ -286,7 +291,6 @@ class PasswordLogin extends React.Component { } const pwFieldClass = classNames({ - mx_Login_field: true, error: this.props.loginIncorrect && !this.isLoginEmpty(), // only error password if error isn't top field }); @@ -320,12 +324,16 @@ class PasswordLogin extends React.Component { { loginType } { loginField } - {this._passwordField = e;}} type="password" + {this._passwordField = e;}} + type="password" name="password" - value={this.state.password} onChange={this.onPasswordChanged} - placeholder={_t('Password')} + label={_t('Password')} + value={this.state.password} + onChange={this.onPasswordChanged} /> -
{ forgotPasswordJsx } Set a new one": "Not sure of your password? Set a new one", "Sign in to your Matrix account": "Sign in to your Matrix account", "Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s", "Change": "Change", "Sign in with": "Sign in with", - "Phone": "Phone", "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", "Create your Matrix account": "Create your Matrix account", "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", - "Email": "Email", "Email (optional)": "Email (optional)", "Phone (optional)": "Phone (optional)", "Confirm": "Confirm", From bc54ea5623839fdf5f1a4000a75811d9ae95210a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 5 Mar 2019 17:53:09 +0000 Subject: [PATCH 07/10] Convert login type to Field component This converts the login type Dropdown on the login flow to also use the Field component so that every presents a similar visual style. --- res/css/structures/auth/_Login.scss | 12 ++++---- src/components/views/auth/PasswordLogin.js | 35 ++++++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index f3cada9d70a..2cf62765570 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -64,18 +64,18 @@ limitations under the License. .mx_Login_type_container { display: flex; - margin-bottom: 14px; + align-items: center; color: $authpage-primary-color; + + .mx_Field { + margin: 0; + } } .mx_Login_type_label { flex-grow: 1; - line-height: 35px; } .mx_Login_type_dropdown { - display: inline-block; - min-width: 170px; - align-self: flex-end; - flex: 1 1 auto; + min-width: 200px; } diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index f08f75a84bc..169d36f0c0c 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -138,7 +138,8 @@ class PasswordLogin extends React.Component { this.props.onUsernameBlur(ev.target.value); } - onLoginTypeChange(loginType) { + onLoginTypeChange(ev) { + const loginType = ev.target.value; this.props.onError(null); // send a null error to clear any error messages this.setState({ loginType: loginType, @@ -294,8 +295,6 @@ class PasswordLogin extends React.Component { error: this.props.loginIncorrect && !this.isLoginEmpty(), // only error password if error isn't top field }); - const Dropdown = sdk.getComponent('elements.Dropdown'); - const loginField = this.renderLoginField(this.state.loginType); let loginType; @@ -303,14 +302,32 @@ class PasswordLogin extends React.Component { loginType = (
- - { _t('Username') } - { _t('Email address') } - { _t('Phone') } - + onChange={this.onLoginTypeChange} + > + + + +
); } From df9888614bf1e27a5611e3c9e8f14fe74a67677b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 6 Mar 2019 10:57:38 +0000 Subject: [PATCH 08/10] Normalize whitespace in PasswordLogin --- src/components/views/auth/PasswordLogin.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index 169d36f0c0c..4b095d405f7 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -180,7 +180,7 @@ class PasswordLogin extends React.Component { return {this._loginField = e;}} + ref={(e) => { this._loginField = e; }} name="username" // make it a little easier for browser's remember-password key="email_input" type="text" @@ -196,13 +196,13 @@ class PasswordLogin extends React.Component { return {this._loginField = e;}} + ref={(e) => { this._loginField = e; }} name="username" // make it a little easier for browser's remember-password key="username_input" type="text" label={SdkConfig.get().disable_custom_urls ? _t("Username on %(hs)s", { - hs: this.props.hsUrl.replace(/^https?:\/\//, ''), + hs: this.props.hsUrl.replace(/^https?:\/\//, ''), }) : _t("Username")} value={this.state.username} onChange={this.onUsernameChanged} @@ -339,19 +339,19 @@ class PasswordLogin extends React.Component { {editLink} - { loginType } - { loginField } + {loginType} + {loginField} {this._passwordField = e;}} + ref={(e) => { this._passwordField = e; }} type="password" name="password" label={_t('Password')} value={this.state.password} onChange={this.onPasswordChanged} /> - { forgotPasswordJsx } + {forgotPasswordJsx} Date: Wed, 6 Mar 2019 10:58:54 +0000 Subject: [PATCH 09/10] One line for prefix container --- src/components/views/elements/Field.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index d43cb7423c3..c6a2113adb0 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -62,9 +62,7 @@ export default class Field extends React.PureComponent { let prefixContainer = null; if (prefix) { - prefixContainer = - {prefix} - ; + prefixContainer = {prefix}; } const classes = classNames("mx_Field", `mx_Field_${inputElement}`, { From dde36459e6ac912201ef24b567616cb34ce1d90f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 6 Mar 2019 11:11:24 +0000 Subject: [PATCH 10/10] Simplify field row / field styles --- res/css/views/auth/_AuthBody.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index b65c41946fa..e7a6e04e8b6 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -78,17 +78,16 @@ limitations under the License. margin-bottom: 10px; } -.mx_AuthBody_fieldRow > *, .mx_AuthBody_fieldRow > .mx_Field { margin: 0 5px; flex: 1; } -.mx_AuthBody_fieldRow > *:first-child { +.mx_AuthBody_fieldRow > .mx_Field:first-child { margin-left: 0; } -.mx_AuthBody_fieldRow > *:last-child { +.mx_AuthBody_fieldRow > .mx_Field:last-child { margin-right: 0; }