Merge pull request #2868 from matrix-org/dbkr/dialog_redesign

Design tweaks to dialogs
This commit is contained in:
David Baker 2019-04-08 09:33:34 +01:00 committed by GitHub
commit 3fe6d51fbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 76 additions and 52 deletions

View file

@ -59,7 +59,6 @@ src/languageHandler.js
src/linkify-matrix.js src/linkify-matrix.js
src/Markdown.js src/Markdown.js
src/MatrixClientPeg.js src/MatrixClientPeg.js
src/Modal.js
src/notifications/ContentRules.js src/notifications/ContentRules.js
src/notifications/PushRuleVectorState.js src/notifications/PushRuleVectorState.js
src/notifications/VectorPushRulesDefinitions.js src/notifications/VectorPushRulesDefinitions.js

View file

@ -118,7 +118,7 @@ textarea {
background-color: transparent; background-color: transparent;
color: $input-darker-fg-color; color: $input-darker-fg-color;
border-radius: 4px; border-radius: 4px;
border: 1px solid #c1c1c1; border: 1px solid $dialog-close-fg-color;
// these things should probably not be defined // these things should probably not be defined
// globally // globally
margin: 9px; margin: 9px;
@ -267,14 +267,18 @@ textarea {
font-weight: 300; font-weight: 300;
font-size: 15px; font-size: 15px;
position: relative; position: relative;
padding: 40px 58px 36px 58px; padding: 25px 30px 30px 30px;
width: 60%;
max-width: 704px; max-width: 704px;
box-shadow: 2px 15px 30px 0 $dialog-shadow-color;
max-height: 80%; max-height: 80%;
box-shadow: 2px 15px 30px 0 $dialog-shadow-color;
border-radius: 4px;
overflow-y: auto; overflow-y: auto;
} }
.mx_Dialog_fixedWidth {
width: 60vw;
}
.mx_Dialog_staticWrapper .mx_Dialog { .mx_Dialog_staticWrapper .mx_Dialog {
z-index: 4010; z-index: 4010;
} }
@ -317,13 +321,13 @@ textarea {
.mx_Dialog_header { .mx_Dialog_header {
position: relative; position: relative;
margin-bottom: 20px;
} }
.mx_Dialog_title { .mx_Dialog_title {
font-weight: bold;
font-size: 22px; font-size: 22px;
line-height: 36px; line-height: 36px;
color: $primary-fg-color; color: $dialog-title-fg-color;
} }
.mx_Dialog_header.mx_Dialog_headerWithButton > .mx_Dialog_title { .mx_Dialog_header.mx_Dialog_headerWithButton > .mx_Dialog_title {
@ -338,13 +342,14 @@ textarea {
mask: url('$(res)/img/feather-customised/cancel.svg'); mask: url('$(res)/img/feather-customised/cancel.svg');
mask-repeat: no-repeat; mask-repeat: no-repeat;
mask-position: center; mask-position: center;
width: 36px; mask-size: cover;
height: 36px; width: 14px;
background-color: $primary-fg-color; height: 14px;
background-color: $dialog-close-fg-color;
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
top: 20px; top: 4px;
right: 20px; right: 0px;
} }
.mx_Dialog_content { .mx_Dialog_content {
@ -355,6 +360,7 @@ textarea {
} }
.mx_Dialog_buttons { .mx_Dialog_buttons {
margin-top: 20px;
text-align: right; text-align: right;
} }
@ -370,6 +376,10 @@ textarea {
background-color: $button-secondary-bg-color; background-color: $button-secondary-bg-color;
} }
.mx_Dialog button:last-child {
margin-right: 0px;
}
.mx_Dialog button:hover, .mx_Dialog input[type="submit"]:hover { .mx_Dialog button:hover, .mx_Dialog input[type="submit"]:hover {
@mixin mx_DialogButton_hover; @mixin mx_DialogButton_hover;
} }
@ -381,6 +391,7 @@ textarea {
.mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary { .mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary {
color: $accent-fg-color; color: $accent-fg-color;
background-color: $accent-color; background-color: $accent-color;
min-width: 156px;
} }
.mx_Dialog button.danger, .mx_Dialog input[type="submit"].danger { .mx_Dialog button.danger, .mx_Dialog input[type="submit"].danger {

View file

@ -47,11 +47,11 @@
@import "./views/context_menus/_StatusMessageContextMenu.scss"; @import "./views/context_menus/_StatusMessageContextMenu.scss";
@import "./views/context_menus/_TagTileContextMenu.scss"; @import "./views/context_menus/_TagTileContextMenu.scss";
@import "./views/context_menus/_TopLeftMenu.scss"; @import "./views/context_menus/_TopLeftMenu.scss";
@import "./views/dialogs/_AddressPickerDialog.scss";
@import "./views/dialogs/_Analytics.scss"; @import "./views/dialogs/_Analytics.scss";
@import "./views/dialogs/_BugReportDialog.scss"; @import "./views/dialogs/_BugReportDialog.scss";
@import "./views/dialogs/_ChangelogDialog.scss"; @import "./views/dialogs/_ChangelogDialog.scss";
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss"; @import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
@import "./views/dialogs/_ChatInviteDialog.scss";
@import "./views/dialogs/_ConfirmUserActionDialog.scss"; @import "./views/dialogs/_ConfirmUserActionDialog.scss";
@import "./views/dialogs/_CreateGroupDialog.scss"; @import "./views/dialogs/_CreateGroupDialog.scss";
@import "./views/dialogs/_CreateRoomDialog.scss"; @import "./views/dialogs/_CreateRoomDialog.scss";

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -15,8 +16,8 @@ limitations under the License.
*/ */
/* Using a textarea for this element, to circumvent autofill */ /* Using a textarea for this element, to circumvent autofill */
.mx_ChatInviteDialog_input, .mx_AddressPickerDialog_input,
.mx_ChatInviteDialog_input:focus .mx_AddressPickerDialog_input:focus
{ {
height: 26px; height: 26px;
font-size: 14px; font-size: 14px;
@ -34,11 +35,11 @@ limitations under the License.
word-wrap: nowrap; word-wrap: nowrap;
} }
.mx_ChatInviteDialog .mx_Dialog_content { .mx_AddressPickerDialog .mx_Dialog_content {
min-height: 50px min-height: 50px
} }
.mx_ChatInviteDialog_inputContainer { .mx_AddressPickerDialog_inputContainer {
border-radius: 3px; border-radius: 3px;
border: solid 1px $input-border-color; border: solid 1px $input-border-color;
line-height: 36px; line-height: 36px;
@ -51,19 +52,19 @@ limitations under the License.
overflow-y: auto; overflow-y: auto;
} }
.mx_ChatInviteDialog_error { .mx_AddressPickerDialog_error {
margin-top: 10px; margin-top: 10px;
color: $warning-color; color: $warning-color;
} }
.mx_ChatInviteDialog_cancel { .mx_AddressPickerDialog_cancel {
position: absolute; position: absolute;
right: 11px; right: 11px;
top: 13px; top: 13px;
cursor: pointer; cursor: pointer;
} }
.mx_ChatInviteDialog_cancel object { .mx_AddressPickerDialog_cancel object {
pointer-events: none; pointer-events: none;
} }

View file

@ -21,7 +21,7 @@ limitations under the License.
height: 80%; height: 80%;
border-radius: 4px; border-radius: 4px;
padding-top: 0; padding-top: 0;
padding-right: 0; padding-right: 30px;
padding-left: 0; padding-left: 0;
.mx_TabbedView { .mx_TabbedView {
@ -31,7 +31,7 @@ limitations under the License.
.mx_TabbedView .mx_SettingsTab { .mx_TabbedView .mx_SettingsTab {
box-sizing: border-box; box-sizing: border-box;
min-width: 580px; min-width: 580px;
padding-right: 130px; padding-right: 100px;
// Put some padding on the bottom to avoid the settings tab from // Put some padding on the bottom to avoid the settings tab from
// colliding harshly with the dialog when scrolled down. // colliding harshly with the dialog when scrolled down.
@ -43,5 +43,8 @@ limitations under the License.
margin-top: 16px; margin-top: 16px;
margin-bottom: 24px; margin-bottom: 24px;
} }
.mx_Dialog_fixedWidth {
width: 90vw;
}
} }
} }

View file

@ -30,4 +30,6 @@ limitations under the License.
.mx_UploadConfirmDialog_imagePreview { .mx_UploadConfirmDialog_imagePreview {
max-height: 300px; max-height: 300px;
max-width: 100%; max-width: 100%;
border-radius: 4px;
border: 1px solid $dialog-close-fg-color;
} }

View file

@ -72,7 +72,7 @@ $avatar-bg-color: $bg-color;
$h3-color: $primary-fg-color; $h3-color: $primary-fg-color;
$dialog-title-fg-color: #454545; $dialog-title-fg-color: $base-text-color;
$dialog-backdrop-color: #000; $dialog-backdrop-color: #000;
$dialog-shadow-color: rgba(0, 0, 0, 0.48); $dialog-shadow-color: rgba(0, 0, 0, 0.48);
$dialog-close-fg-color: #9fa9ba; $dialog-close-fg-color: #9fa9ba;

View file

@ -106,10 +106,10 @@ $avatar-bg-color: #ffffff;
$h3-color: #3d3b39; $h3-color: #3d3b39;
$dialog-title-fg-color: #2e2f32; $dialog-title-fg-color: #45474a;
$dialog-backdrop-color: rgba(46, 48, 51, 0.38); $dialog-backdrop-color: rgba(46, 48, 51, 0.38);
$dialog-shadow-color: rgba(0, 0, 0, 0.48); $dialog-shadow-color: rgba(0, 0, 0, 0.48);
$dialog-close-fg-color: #9fa9ba; $dialog-close-fg-color: #c1c1c1;
$dialog-background-bg-color: #e9e9e9; $dialog-background-bg-color: #e9e9e9;
$lightbox-background-bg-color: #000; $lightbox-background-bg-color: #000;

View file

@ -75,10 +75,9 @@ const AsyncWrapper = React.createClass({
}, },
render: function() { render: function() {
const {loader, ...otherProps} = this.props;
if (this.state.component) { if (this.state.component) {
const Component = this.state.component; const Component = this.state.component;
return <Component {...otherProps} />; return <Component {...this.props} />;
} else if (this.state.error) { } else if (this.state.error) {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@ -158,7 +157,7 @@ class ModalManager {
} }
createDialog(Element, ...rest) { createDialog(Element, ...rest) {
return this.createDialogAsync(new Promise(resolve => resolve(Element)), ...rest); return this.createDialogAsync(Promise.resolve(Element), ...rest);
} }
createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) { createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) {
@ -193,36 +192,35 @@ class ModalManager {
* also be removed from the stack. This is not compatible * also be removed from the stack. This is not compatible
* with being a priority modal. Only one modal can be * with being a priority modal. Only one modal can be
* static at a time. * static at a time.
* @returns {object} Object with 'close' parameter being a function that will close the dialog
*/ */
createDialogAsync(prom, props, className, isPriorityModal, isStaticModal) { createDialogAsync(prom, props, className, isPriorityModal, isStaticModal) {
const self = this;
const modal = {}; const modal = {};
// never call this from onFinished() otherwise it will loop // never call this from onFinished() otherwise it will loop
// //
// nb explicit function() rather than arrow function, to get `arguments` const closeDialog = (...args) => {
const closeDialog = function() { if (props && props.onFinished) props.onFinished.apply(null, args);
if (props && props.onFinished) props.onFinished.apply(null, arguments); const i = this._modals.indexOf(modal);
const i = self._modals.indexOf(modal);
if (i >= 0) { if (i >= 0) {
self._modals.splice(i, 1); this._modals.splice(i, 1);
} }
if (self._priorityModal === modal) { if (this._priorityModal === modal) {
self._priorityModal = null; this._priorityModal = null;
// XXX: This is destructive // XXX: This is destructive
self._modals = []; this._modals = [];
} }
if (self._staticModal === modal) { if (this._staticModal === modal) {
self._staticModal = null; this._staticModal = null;
// XXX: This is destructive // XXX: This is destructive
self._modals = []; this._modals = [];
} }
self._reRender(); this._reRender();
}; };
// don't attempt to reuse the same AsyncWrapper for different dialogs, // don't attempt to reuse the same AsyncWrapper for different dialogs,

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017, 2018 New Vector Ltd Copyright 2017, 2018, 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -566,7 +566,7 @@ module.exports = React.createClass({
rows="1" rows="1"
id="textinput" id="textinput"
ref="textinput" ref="textinput"
className="mx_ChatInviteDialog_input" className="mx_AddressPickerDialog_input"
onChange={this.onQueryChanged} onChange={this.onQueryChanged}
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
defaultValue={this.props.value} defaultValue={this.props.value}
@ -578,7 +578,7 @@ module.exports = React.createClass({
let addressSelector; let addressSelector;
if (this.state.error) { if (this.state.error) {
const validTypeDescriptions = this.props.validAddressTypes.map((t) => _t(addressTypeName[t])); const validTypeDescriptions = this.props.validAddressTypes.map((t) => _t(addressTypeName[t]));
error = <div className="mx_ChatInviteDialog_error"> error = <div className="mx_AddressPickerDialog_error">
{ _t("You have entered an invalid address.") } { _t("You have entered an invalid address.") }
<br /> <br />
{ _t("Try using one of the following valid address types: %(validTypesList)s.", { { _t("Try using one of the following valid address types: %(validTypesList)s.", {
@ -586,9 +586,9 @@ module.exports = React.createClass({
}) } }) }
</div>; </div>;
} else if (this.state.searchError) { } else if (this.state.searchError) {
error = <div className="mx_ChatInviteDialog_error">{ this.state.searchError }</div>; error = <div className="mx_AddressPickerDialog_error">{ this.state.searchError }</div>;
} else if (this.state.query.length > 0 && filteredSuggestedList.length === 0 && !this.state.busy) { } else if (this.state.query.length > 0 && filteredSuggestedList.length === 0 && !this.state.busy) {
error = <div className="mx_ChatInviteDialog_error">{ _t("No results") }</div>; error = <div className="mx_AddressPickerDialog_error">{ _t("No results") }</div>;
} else { } else {
addressSelector = ( addressSelector = (
<AddressSelector ref={(ref) => {this.addressSelector = ref;}} <AddressSelector ref={(ref) => {this.addressSelector = ref;}}
@ -601,13 +601,13 @@ module.exports = React.createClass({
} }
return ( return (
<BaseDialog className="mx_ChatInviteDialog" onKeyDown={this.onKeyDown} <BaseDialog className="mx_AddressPickerDialog" onKeyDown={this.onKeyDown}
onFinished={this.props.onFinished} title={this.props.title}> onFinished={this.props.onFinished} title={this.props.title}>
<div className="mx_ChatInviteDialog_label"> <div className="mx_AddressPickerDialog_label">
<label htmlFor="textinput">{ this.props.description }</label> <label htmlFor="textinput">{ this.props.description }</label>
</div> </div>
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
<div className="mx_ChatInviteDialog_inputContainer">{ query }</div> <div className="mx_AddressPickerDialog_inputContainer">{ query }</div>
{ error } { error }
{ addressSelector } { addressSelector }
{ this.props.extraNode } { this.props.extraNode }

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd Copyright 2018, 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -55,6 +55,11 @@ export default React.createClass({
// CSS class to apply to dialog div // CSS class to apply to dialog div
className: PropTypes.string, className: PropTypes.string,
// if true, dialog container is 60% of the viewport width. Otherwise,
// the container will have no fixed size, allowing its contents to
// determine its size. Default: true.
fixedWidth: PropTypes.bool,
// Title for the dialog. // Title for the dialog.
title: PropTypes.node.isRequired, title: PropTypes.node.isRequired,
@ -72,6 +77,7 @@ export default React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
hasCancel: true, hasCancel: true,
fixedWidth: true,
}; };
}, },
@ -113,7 +119,10 @@ export default React.createClass({
return ( return (
<FocusTrap onKeyDown={this._onKeyDown} <FocusTrap onKeyDown={this._onKeyDown}
className={this.props.className} className={classNames({
[this.props.className]: true,
'mx_Dialog_fixedWidth': this.props.fixedWidth,
})}
role="dialog" role="dialog"
aria-labelledby='mx_BaseDialog_title' aria-labelledby='mx_BaseDialog_title'
// This should point to a node describing the dialog. // This should point to a node describing the dialog.
@ -131,8 +140,8 @@ export default React.createClass({
{ this.props.title } { this.props.title }
</div> </div>
{ this.props.headerButton } { this.props.headerButton }
{ cancelButton }
</div> </div>
{ cancelButton }
{ this.props.children } { this.props.children }
</FocusTrap> </FocusTrap>
); );

View file

@ -87,6 +87,7 @@ export default class UploadConfirmDialog extends React.Component {
return ( return (
<BaseDialog className='mx_UploadConfirmDialog' <BaseDialog className='mx_UploadConfirmDialog'
fixedWidth={false}
onFinished={this._onCancelClick} onFinished={this._onCancelClick}
title={title} title={title}
contentId='mx_Dialog_content' contentId='mx_Dialog_content'