Bring in TabbedView nearly verbatim from prior work
Sourced from https://github.com/matrix-org/matrix-react-sdk/pull/1644 and related PRs.
This commit is contained in:
parent
0e42c0892e
commit
5adfc09237
8 changed files with 284 additions and 28 deletions
|
@ -18,6 +18,7 @@
|
||||||
@import "./structures/_RoomSubList.scss";
|
@import "./structures/_RoomSubList.scss";
|
||||||
@import "./structures/_RoomView.scss";
|
@import "./structures/_RoomView.scss";
|
||||||
@import "./structures/_SearchBox.scss";
|
@import "./structures/_SearchBox.scss";
|
||||||
|
@import "./structures/_TabbedView.scss";
|
||||||
@import "./structures/_TagPanel.scss";
|
@import "./structures/_TagPanel.scss";
|
||||||
@import "./structures/_TopLeftMenuButton.scss";
|
@import "./structures/_TopLeftMenuButton.scss";
|
||||||
@import "./structures/_UploadBar.scss";
|
@import "./structures/_UploadBar.scss";
|
||||||
|
|
76
res/css/structures/_TabbedView.scss
Normal file
76
res/css/structures/_TabbedView.scss
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_TabbedView {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: $tab-panel-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TabbedView_tabLabels {
|
||||||
|
width: 300px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: $tab-list-bg-color;
|
||||||
|
color: $tab-list-fg-color;
|
||||||
|
border-right: 1px solid $tab-border-color;
|
||||||
|
border-left: 1px solid $tab-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TabbedView_tabPanels {
|
||||||
|
width: calc(100% - 320px);
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
padding-left: 20px;
|
||||||
|
scroll-snap-type: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TabbedView_tabLabel {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
padding: 20px;
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
border-bottom: 1px solid $tab-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TabbedView_exit {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TabbedView_tabLabel:hover {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TabbedView_tabLabel_active {
|
||||||
|
font-weight: 700;
|
||||||
|
background-color: $tab-list-active-bg-color;
|
||||||
|
color: $tab-list-active-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TabbedView_tabPanel {
|
||||||
|
height: 100vh; // 100% of viewport height
|
||||||
|
scroll-snap-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TabbedView_tabPanelContent {
|
||||||
|
width: 600px;
|
||||||
|
}
|
|
@ -186,6 +186,14 @@ $lightbox-bg-color: #454545;
|
||||||
$lightbox-fg-color: #ffffff;
|
$lightbox-fg-color: #ffffff;
|
||||||
$lightbox-border-color: #ffffff;
|
$lightbox-border-color: #ffffff;
|
||||||
|
|
||||||
|
// Tabbed views
|
||||||
|
$tab-list-bg-color: $secondary-accent-color;
|
||||||
|
$tab-list-fg-color: $accent-color;
|
||||||
|
$tab-list-active-bg-color: $tertiary-accent-color;
|
||||||
|
$tab-list-active-fg-color: $accent-color;
|
||||||
|
$tab-border-color: $tertiary-accent-color;
|
||||||
|
$tab-panel-bg-color: #fff;
|
||||||
|
|
||||||
// unused?
|
// unused?
|
||||||
$progressbar-color: #000;
|
$progressbar-color: #000;
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,7 @@ $primary-hairline-color: #e5e5e5;
|
||||||
|
|
||||||
// used for the border of input text fields
|
// used for the border of input text fields
|
||||||
$input-border-color: #f0f0f0;
|
$input-border-color: #f0f0f0;
|
||||||
|
$input-border-dark-color: #b8b8b8;
|
||||||
|
|
||||||
$input-darker-bg-color: #c1c9d6;
|
$input-darker-bg-color: #c1c9d6;
|
||||||
$input-darker-fg-color: #9fa9ba;
|
$input-darker-fg-color: #9fa9ba;
|
||||||
|
@ -181,6 +182,14 @@ $imagebody-giflabel: rgba(0, 0, 0, 0.7);
|
||||||
$imagebody-giflabel-border: rgba(0, 0, 0, 0.2);
|
$imagebody-giflabel-border: rgba(0, 0, 0, 0.2);
|
||||||
$imagebody-giflabel-color: rgba(255, 255, 255, 1);
|
$imagebody-giflabel-color: rgba(255, 255, 255, 1);
|
||||||
|
|
||||||
|
// Tabbed views
|
||||||
|
$tab-list-bg-color: $secondary-accent-color;
|
||||||
|
$tab-list-fg-color: $accent-color;
|
||||||
|
$tab-list-active-bg-color: $tertiary-accent-color;
|
||||||
|
$tab-list-active-fg-color: $accent-color;
|
||||||
|
$tab-border-color: $tertiary-accent-color;
|
||||||
|
$tab-panel-bg-color: #fff;
|
||||||
|
|
||||||
// unused?
|
// unused?
|
||||||
$progressbar-color: #000;
|
$progressbar-color: #000;
|
||||||
|
|
||||||
|
|
|
@ -612,9 +612,7 @@ export default React.createClass({
|
||||||
break;
|
break;
|
||||||
case 'view_user_settings':
|
case 'view_user_settings':
|
||||||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
||||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {
|
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {});
|
||||||
title: _t("Settings"),
|
|
||||||
});
|
|
||||||
//this._setPage(PageTypes.UserSettings);
|
//this._setPage(PageTypes.UserSettings);
|
||||||
//this.notifyNewScreen('settings');
|
//this.notifyNewScreen('settings');
|
||||||
break;
|
break;
|
||||||
|
|
165
src/components/structures/TabbedView.js
Normal file
165
src/components/structures/TabbedView.js
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
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 * as React from "react";
|
||||||
|
import {_t, _td} from '../../languageHandler';
|
||||||
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
//import scrollSnapPolyfill from 'css-scroll-snap-polyfill';
|
||||||
|
|
||||||
|
const DEFAULT_EXIT_STRING = _td("Return to app");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a tab for the TabbedView
|
||||||
|
*/
|
||||||
|
export class Tab {
|
||||||
|
/**
|
||||||
|
* Creates a new tab
|
||||||
|
* @param {string} tabLabel The untranslated tab label
|
||||||
|
* @param {string} tabJsx The JSX for the tab container.
|
||||||
|
*/
|
||||||
|
constructor(tabLabel, tabJsx) {
|
||||||
|
this.label = tabLabel;
|
||||||
|
this.body = tabJsx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TabbedView extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// This is used to track when the user has scrolled all the way up or down so we
|
||||||
|
// don't immediately start flipping between tabs.
|
||||||
|
this._reachedEndAt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
activeTabIndex: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_getActiveTabIndex() {
|
||||||
|
return this.state ? this.state.activeTabIndex : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the given tab
|
||||||
|
* @param {Tab} tab the tab to show
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_setActiveTab(tab) {
|
||||||
|
const idx = this.props.tabs.indexOf(tab);
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.setState({activeTabIndex: idx});
|
||||||
|
this._reachedEndAt = 0; // reset scroll timer
|
||||||
|
}
|
||||||
|
else console.error("Could not find tab " + tab.label + " in tabs");
|
||||||
|
}
|
||||||
|
|
||||||
|
_nextTab() {
|
||||||
|
let targetIndex = this._getActiveTabIndex() + 1;
|
||||||
|
if (targetIndex < this.props.tabs.length) {
|
||||||
|
this.setState({activeTabIndex: targetIndex});
|
||||||
|
this._reachedEndAt = 0; // reset scroll timer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_previousTab() {
|
||||||
|
let targetIndex = this._getActiveTabIndex() - 1;
|
||||||
|
if (targetIndex >= 0) {
|
||||||
|
this.setState({activeTabIndex: targetIndex});
|
||||||
|
this._reachedEndAt = 0; // reset scroll timer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getTabLabel(tab) {
|
||||||
|
let classes = "mx_TabbedView_tabLabel ";
|
||||||
|
|
||||||
|
const idx = this.props.tabs.indexOf(tab);
|
||||||
|
if (idx === this._getActiveTabIndex()) classes += "mx_TabbedView_tabLabel_active";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={classes} key={"tab_label_ " + tab.label}
|
||||||
|
onClick={() => this._setActiveTab(tab)}>
|
||||||
|
{_t(tab.label)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getTabPanel(tab) {
|
||||||
|
return (
|
||||||
|
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
|
||||||
|
{tab.body}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
console.log("SCROLL SNAP POLYFILL: UPDATE");
|
||||||
|
//scrollSnapPolyfill();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
console.log("SCROLL SNAP POLYFILL: MOUNT");
|
||||||
|
//scrollSnapPolyfill();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const labels = [];
|
||||||
|
const tabs = [];
|
||||||
|
|
||||||
|
for (const tab of this.props.tabs) {
|
||||||
|
labels.push(this._getTabLabel(tab));
|
||||||
|
tabs.push(this._getTabPanel(tab));
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnToApp = (
|
||||||
|
<span className="mx_TabbedView_tabLabel mx_TabbedView_exit" onClick={this.props.onExit}>
|
||||||
|
{_t(this.props.exitLabel || DEFAULT_EXIT_STRING)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx_TabbedView">
|
||||||
|
<div className="mx_TabbedView_tabLabels">
|
||||||
|
{returnToApp}
|
||||||
|
{labels}
|
||||||
|
</div>
|
||||||
|
<div className="mx_TabbedView_tabPanels">
|
||||||
|
{tabs}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TabbedView.PropTypes = {
|
||||||
|
// Called when the user clicks the "Exit" or "Return to app" button
|
||||||
|
onExit: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// The untranslated label for the "Return to app" button.
|
||||||
|
// Default: "Return to app"
|
||||||
|
exitLabel: PropTypes.string,
|
||||||
|
|
||||||
|
// The tabs to show
|
||||||
|
tabs: PropTypes.arrayOf(Tab).isRequired,
|
||||||
|
};
|
|
@ -17,33 +17,30 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import {_t} from '../../../languageHandler';
|
import {Tab, TabbedView} from "../../structures/TabbedView";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import {_td} from "../../../languageHandler";
|
||||||
|
|
||||||
export default React.createClass({
|
export default class UserSettingsDialog extends React.Component {
|
||||||
propTypes: {
|
static propTypes = {
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
};
|
||||||
|
|
||||||
render: function () {
|
_getTabs() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
return [
|
||||||
const UserSettings = sdk.getComponent('structures.UserSettings');
|
new Tab(_td("General"), <div>General Test</div>),
|
||||||
|
new Tab(_td("Account"), <div>Account Test</div>),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_UserSettingsDialog'
|
<TabbedView onExit={this.props.onFinished} tabs={this._getTabs()} />
|
||||||
onFinished={this.props.onFinished}
|
// <UserSettings
|
||||||
title={_t('Settings')}
|
// onClose={this.props.onFinished}
|
||||||
contentId='mx_Dialog_content'
|
// brand={SdkConfig.get().brand}
|
||||||
>
|
// referralBaseUrl={SdkConfig.get().referralBaseUrl}
|
||||||
<div id='mx_Dialog_content'>
|
// teamToken={SdkConfig.get().teamToken}
|
||||||
<UserSettings
|
// />
|
||||||
onClose={this.props.onFinished}
|
|
||||||
brand={SdkConfig.get().brand}
|
|
||||||
referralBaseUrl={SdkConfig.get().referralBaseUrl}
|
|
||||||
teamToken={SdkConfig.get().teamToken}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -1043,6 +1043,8 @@
|
||||||
"Room contains unknown devices": "Room contains unknown devices",
|
"Room contains unknown devices": "Room contains unknown devices",
|
||||||
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
|
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
|
||||||
"Unknown devices": "Unknown devices",
|
"Unknown devices": "Unknown devices",
|
||||||
|
"General": "General",
|
||||||
|
"Account": "Account",
|
||||||
"Unable to load backup status": "Unable to load backup status",
|
"Unable to load backup status": "Unable to load backup status",
|
||||||
"Unable to restore backup": "Unable to restore backup",
|
"Unable to restore backup": "Unable to restore backup",
|
||||||
"No backup found!": "No backup found!",
|
"No backup found!": "No backup found!",
|
||||||
|
@ -1222,6 +1224,7 @@
|
||||||
"Click to unmute audio": "Click to unmute audio",
|
"Click to unmute audio": "Click to unmute audio",
|
||||||
"Click to mute audio": "Click to mute audio",
|
"Click to mute audio": "Click to mute audio",
|
||||||
"Filter room names": "Filter room names",
|
"Filter room names": "Filter room names",
|
||||||
|
"Return to app": "Return to app",
|
||||||
"Clear filter": "Clear filter",
|
"Clear filter": "Clear filter",
|
||||||
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.",
|
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.",
|
||||||
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.",
|
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.",
|
||||||
|
@ -1286,7 +1289,6 @@
|
||||||
"Add email address": "Add email address",
|
"Add email address": "Add email address",
|
||||||
"Profile": "Profile",
|
"Profile": "Profile",
|
||||||
"Display name": "Display name",
|
"Display name": "Display name",
|
||||||
"Account": "Account",
|
|
||||||
"To return to your account in future you need to set a password": "To return to your account in future you need to set a password",
|
"To return to your account in future you need to set a password": "To return to your account in future you need to set a password",
|
||||||
"Logged in as:": "Logged in as:",
|
"Logged in as:": "Logged in as:",
|
||||||
"Access Token:": "Access Token:",
|
"Access Token:": "Access Token:",
|
||||||
|
|
Loading…
Reference in a new issue