From 3f897468a6f6d1b3bfd2ef2880c8459acef075f2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 23 Jan 2019 15:50:41 -0700 Subject: [PATCH] Add a ToggleSwitch and use it for SettingsFlag Also bring in the compact timeline option. Without minor CSS changes, the old user settings are completely unusable with this change. As such, minimal effort has been put in to have it be useful. Similarly, the changes drop the use of radio groups and the old theme selector was the only one that used it. See the comments for more details on how/why this was mitigated the way it was. --- res/css/_components.scss | 1 + res/css/structures/_UserSettings.scss | 12 ++++ res/css/views/elements/_ToggleSwitch.scss | 51 ++++++++++++++ res/css/views/settings/tabs/_SettingsTab.scss | 14 ++++ res/themes/dharma/css/_dharma.scss | 5 ++ res/themes/light/css/_base.scss | 5 ++ src/components/structures/UserSettings.js | 21 +++--- src/components/views/elements/SettingsFlag.js | 47 +++---------- src/components/views/elements/ToggleSwitch.js | 66 +++++++++++++++++++ .../views/settings/tabs/GeneralSettingsTab.js | 2 + 10 files changed, 175 insertions(+), 49 deletions(-) create mode 100644 res/css/views/elements/_ToggleSwitch.scss create mode 100644 src/components/views/elements/ToggleSwitch.js diff --git a/res/css/_components.scss b/res/css/_components.scss index b26998b9d3..d70e1700d3 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -79,6 +79,7 @@ @import "./views/elements/_RoleButton.scss"; @import "./views/elements/_Spinner.scss"; @import "./views/elements/_SyntaxHighlight.scss"; +@import "./views/elements/_ToggleSwitch.scss"; @import "./views/elements/_ToolTipButton.scss"; @import "./views/globals/_MatrixToolbar.scss"; @import "./views/groups/_GroupPublicityToggle.scss"; diff --git a/res/css/structures/_UserSettings.scss b/res/css/structures/_UserSettings.scss index 74d8c2c718..b078a4e242 100644 --- a/res/css/structures/_UserSettings.scss +++ b/res/css/structures/_UserSettings.scss @@ -255,3 +255,15 @@ input.mx_UserSettings_phoneNumberField { .mx_UserSettings_analyticsModal table { margin: 10px 0px; } + + +// Temp styles to keep the layout moderately usable. Not perfect, but better +// than 30 options being impossible to understand. +.mx_UserSettings .mx_SettingsFlag { + height: 30px; +} + +.mx_UserSettings .mx_SettingsFlag .mx_ToggleSwitch { + float: left; + margin-right: 5px; +} \ No newline at end of file diff --git a/res/css/views/elements/_ToggleSwitch.scss b/res/css/views/elements/_ToggleSwitch.scss new file mode 100644 index 0000000000..0955648d50 --- /dev/null +++ b/res/css/views/elements/_ToggleSwitch.scss @@ -0,0 +1,51 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// TODO: Fancy transitions + +.mx_ToggleSwitch { + width: 48px; + height: 24px; + border-radius: 14px; + background-color: $togglesw-off-color; + position: relative; +} + +.mx_ToggleSwitch_enabled { + cursor: pointer; +} + +.mx_ToggleSwitch.mx_ToggleSwitch_on { + background-color: $togglesw-on-color; +} + +.mx_ToggleSwitch_ball { + margin: 2px; + width: 20px; + height: 20px; + border-radius: 20px; + background-color: $togglesw-ball-color; + position: absolute; + top: 0; +} + +.mx_ToggleSwitch:not(.mx_ToggleSwitch_on) > .mx_ToggleSwitch_ball { + left: 2px; +} + +.mx_ToggleSwitch_on > .mx_ToggleSwitch_ball { + right: 2px; +} diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 3ea9d616ba..795cacd1c9 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -36,3 +36,17 @@ limitations under the License. margin: 0; display: block; } + +.mx_SettingsTab_section .mx_SettingsFlag { + margin-right: 100px; + height: 25px; + margin-bottom: 10px; +} + +.mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_label { + vertical-align: middle; +} + +.mx_SettingsTab_section .mx_SettingsFlag .mx_ToggleSwitch { + float: right; +} diff --git a/res/themes/dharma/css/_dharma.scss b/res/themes/dharma/css/_dharma.scss index 03d59cfc9d..476b265699 100644 --- a/res/themes/dharma/css/_dharma.scss +++ b/res/themes/dharma/css/_dharma.scss @@ -216,6 +216,11 @@ $button-danger-bg-color: #f56679; $button-danger-disabled-fg-color: #ffffff; $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color +// Toggle switch +$togglesw-off-color: #c1c9d6; +$togglesw-on-color: #7ac9a1; +$togglesw-ball-color: #fff; + // unused? $progressbar-color: #000; diff --git a/res/themes/light/css/_base.scss b/res/themes/light/css/_base.scss index d9d9bff2d3..c5029c64b5 100644 --- a/res/themes/light/css/_base.scss +++ b/res/themes/light/css/_base.scss @@ -212,6 +212,11 @@ $button-danger-bg-color: #f56679; $button-danger-disabled-fg-color: #ffffff; $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color +// Toggle switch +$togglesw-off-color: #c1c9d6; +$togglesw-on-color: #7ac9a1; +$togglesw-ball-color: #fff; + // unused? $progressbar-color: #000; diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index f8d9e2dd84..180d05e77e 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -637,11 +637,14 @@ module.exports = React.createClass({ // to rebind the onChange each time we render const onChange = (e) => SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); + // HACK: Lack of translations for themes header. We're removing this view in the very near future, + // and the header is really only there to maintain some semblance of the UX the section once was. return (

{ _t("User Interface") }

{ SIMPLE_SETTINGS.map( this._renderAccountSetting ) } +
Themes
{ THEMES.map( this._renderThemeOption ) } @@ -676,18 +679,12 @@ module.exports = React.createClass({ }, _renderThemeOption: function(setting) { - const SettingsFlag = sdk.getComponent("elements.SettingsFlag"); - const onChange = (v) => dis.dispatch({action: 'set_theme', value: setting.value}); - return ( -
- -
- ); + // HACK: Temporary disablement of theme selection. + // We don't support changing themes on experimental anyways, and radio groups aren't + // a thing anymore for setting flags. We're also dropping this view in the very near + // future, so just replace the theme selection with placeholder text. + const currentTheme = SettingsStore.getValue("theme"); + return
{_t(setting.label)} {currentTheme === setting.value ? '(current)' : null}
; }, _renderCryptoInfo: function() { diff --git a/src/components/views/elements/SettingsFlag.js b/src/components/views/elements/SettingsFlag.js index 7f6c74538a..f1bd72f53d 100644 --- a/src/components/views/elements/SettingsFlag.js +++ b/src/components/views/elements/SettingsFlag.js @@ -18,6 +18,7 @@ import React from "react"; import PropTypes from 'prop-types'; import SettingsStore from "../../../settings/SettingsStore"; import { _t } from '../../../languageHandler'; +import ToggleSwitch from "./ToggleSwitch"; module.exports = React.createClass({ displayName: 'SettingsFlag', @@ -29,10 +30,6 @@ module.exports = React.createClass({ onChange: PropTypes.func, isExplicit: PropTypes.bool, manualSave: PropTypes.bool, - - // If group is supplied, then this will create a radio button instead. - group: PropTypes.string, - value: PropTypes.any, // the value for the radio button }, getInitialState: function() { @@ -46,13 +43,12 @@ module.exports = React.createClass({ }; }, - onChange: function(e) { - if (this.props.group && !e.target.checked) return; + onChange: function(checked) { + if (this.props.group && !checked) return; - const newState = this.props.group ? this.props.value : e.target.checked; - if (!this.props.manualSave) this.save(newState); - else this.setState({ value: newState }); - if (this.props.onChange) this.props.onChange(newState); + if (!this.props.manualSave) this.save(checked); + else this.setState({ value: checked }); + if (this.props.onChange) this.props.onChange(checked); }, save: function(val = undefined) { @@ -78,34 +74,11 @@ module.exports = React.createClass({ if (!label) label = SettingsStore.getDisplayName(this.props.name, this.props.level); else label = _t(label); - // We generate a relatively complex ID to avoid conflicts - const id = this.props.name + "_" + this.props.group + "_" + this.props.value + "_" + this.props.level; - let checkbox = ( - - ); - if (this.props.group) { - checkbox = ( - - ); - } - return ( - +
+ {label} + +
); }, }); diff --git a/src/components/views/elements/ToggleSwitch.js b/src/components/views/elements/ToggleSwitch.js new file mode 100644 index 0000000000..5c3aaeb323 --- /dev/null +++ b/src/components/views/elements/ToggleSwitch.js @@ -0,0 +1,66 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from "classnames"; + +export default class ToggleSwitch extends React.Component { + static propTypes = { + // Whether or not this toggle is in the 'on' position. Default false (off). + checked: PropTypes.bool, + + // Whether or not the user can interact with the switch + disabled: PropTypes.bool, + + // Called when the checked state changes. First argument will be the new state. + onChange: PropTypes.func, + }; + + constructor(props) { + super(props); + + this.state = { + checked: props.checked || false, // default false + }; + } + + _onClick = (e) => { + e.stopPropagation(); + e.preventDefault(); + + if (this.props.disabled) return; + + const newState = !this.state.checked; + this.setState({checked: newState}); + if (this.props.onChange) { + this.props.onChange(newState); + } + }; + + render() { + const classes = classNames({ + "mx_ToggleSwitch": true, + "mx_ToggleSwitch_on": this.state.checked, + "mx_ToggleSwitch_enabled": !this.props.disabled, + }); + return ( +
+
+
+ ) + } +} diff --git a/src/components/views/settings/tabs/GeneralSettingsTab.js b/src/components/views/settings/tabs/GeneralSettingsTab.js index 6f4ebed848..fef6abfb17 100644 --- a/src/components/views/settings/tabs/GeneralSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralSettingsTab.js @@ -154,6 +154,7 @@ export default class GeneralSettingsTab extends React.Component { _renderThemeSection() { // TODO: Re-enable theme selection once the themes actually work + const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); return (
{_t("Theme")} @@ -164,6 +165,7 @@ export default class GeneralSettingsTab extends React.Component { +
); }