Merge pull request #2264 from matrix-org/bwindels/nativescrollbars

Redesign: use native auto-hiding scrollbars in room sub lists
This commit is contained in:
David Baker 2018-11-02 10:55:12 +00:00 committed by GitHub
commit b07514d21f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 196 additions and 20 deletions

View file

@ -1,5 +1,6 @@
// autogenerated by rethemendex.sh
@import "./_common.scss";
@import "./structures/_AutoHideScrollbar.scss";
@import "./structures/_CompatibilityPage.scss";
@import "./structures/_ContextualMenu.scss";
@import "./structures/_CreateRoom.scss";

View file

@ -0,0 +1,56 @@
/*
Copyright 2018 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.
*/
/*
1. for browsers that support native overlay auto-hiding scrollbars
*/
.mx_AutoHideScrollbar {
overflow-y: auto;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
/*
2. webkit also supports overflow:overlay where the scrollbars don't take any space
in the layout but they don't autohide, so do that only on hover
*/
body.mx_scrollbar_overlay_noautohide .mx_AutoHideScrollbar {
overflow-y: hidden;
}
body.mx_scrollbar_overlay_noautohide .mx_AutoHideScrollbar:hover {
overflow-y: overlay;
}
/*
3. as a last fallback, compensate for the scrollbar taking up space in the layout
by playing with the paddings. the default below will add a right padding
of the scrollbar width and clear that on hover.
this won't work well on classes that also need to set their padding,
so this needs to be overriden and adjust the padding with calc like so:
```
body.mx_scrollbar_nooverlay .componentClass.mx_AutoHideScrollbar_overflow:hover {
padding-right: calc(15px - var(--scrollbar-width)) !important;
}
```
*/
body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar {
box-sizing: border-box;
overflow-y: hidden;
padding-right: var(--scrollbar-width);
}
body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar:hover {
overflow-y: auto;
padding-right: 0;
}

View file

@ -16,14 +16,12 @@ limitations under the License.
.mx_RoomSubList {
min-height: 31px;
flex: 0 0 auto;
flex: 0 1 auto;
display: flex;
flex-direction: column;
}
.mx_RoomSubList_nonEmpty {
min-height: 80px;
flex: 1;
margin-bottom: 8px;
}
@ -131,9 +129,18 @@ limitations under the License.
transform: rotateZ(-90deg);
}
.mx_RoomSubList .gm-scrollbar-container {
.mx_RoomSubList_scroll {
/* let rooms list grab all available space */
flex: 1;
flex: 0 1 auto;
padding: 0 15px !important;
}
/*
for browsers that don't support overlay scrollbars,
subtract scrollbar width from right padding on hover when overflowing
so the content doesn't jump when showing the scrollbars
*/
body.mx_scrollbar_nooverlay .mx_RoomSubList_scroll.mx_AutoHideScrollbar_overflow:hover {
padding-right: calc(15px - var(--scrollbar-width)) !important;
}
.collapsed {

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
.mx_TopLeftMenuButton {
height: 52px;
flex: 0 0 52px;
border-bottom: 1px solid $panel-divider-color;
color: $topleftmenu-color;
background-color: $primary-bg-color;

View file

@ -17,7 +17,7 @@ limitations under the License.
.mx_RoomList {
/* take up remaining space below TopLeftMenu */
flex: 1;
flex: 1 1 auto;
/* use flexbox to layout sublists */
display: flex;
flex-direction: column;

View file

@ -20,7 +20,7 @@ limitations under the License.
align-items: center;
cursor: pointer;
height: 40px;
margin: 0 12px;
margin: 0;
padding: 2px 12px;
position: relative;
background-color: $secondary-accent-color;

View file

@ -0,0 +1,118 @@
/*
Copyright 2018 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";
// derived from code from github.com/noeldelgado/gemini-scrollbar
// Copyright (c) Noel Delgado <pixelia.me@gmail.com> (pixelia.me)
function getScrollbarWidth(alternativeOverflow) {
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.top = '-9999px';
div.style.width = '100px';
div.style.height = '100px';
div.style.overflow = "scroll";
if (alternativeOverflow) {
div.style.overflow = alternativeOverflow;
}
div.style.msOverflowStyle = '-ms-autohiding-scrollbar';
document.body.appendChild(div);
const scrollbarWidth = (div.offsetWidth - div.clientWidth);
document.body.removeChild(div);
return scrollbarWidth;
}
function install() {
const scrollbarWidth = getScrollbarWidth();
if (scrollbarWidth !== 0) {
const hasForcedOverlayScrollbar = getScrollbarWidth('overlay') === 0;
// overflow: overlay on webkit doesn't auto hide the scrollbar
if (hasForcedOverlayScrollbar) {
document.body.classList.add("mx_scrollbar_overlay_noautohide");
} else {
document.body.classList.add("mx_scrollbar_nooverlay");
const style = document.createElement('style');
style.type = 'text/css';
style.innerText =
`body.mx_scrollbar_nooverlay { --scrollbar-width: ${scrollbarWidth}px; }`;
document.head.appendChild(style);
}
}
}
const installBodyClassesIfNeeded = (function() {
let installed = false;
return function() {
if (!installed) {
install();
installed = true;
}
};
})();
export default class AutoHideScrollbar extends React.Component {
constructor(props) {
super(props);
this.onOverflow = this.onOverflow.bind(this);
this.onUnderflow = this.onUnderflow.bind(this);
this._collectContainerRef = this._collectContainerRef.bind(this);
}
onOverflow() {
this.containerRef.classList.add("mx_AutoHideScrollbar_overflow");
this.containerRef.classList.remove("mx_AutoHideScrollbar_underflow");
}
onUnderflow() {
this.containerRef.classList.remove("mx_AutoHideScrollbar_overflow");
this.containerRef.classList.add("mx_AutoHideScrollbar_underflow");
}
_collectContainerRef(ref) {
if (ref && !this.containerRef) {
this.containerRef = ref;
const needsOverflowListener =
document.body.classList.contains("mx_scrollbar_nooverlay");
if (needsOverflowListener) {
this.containerRef.addEventListener("overflow", this.onOverflow);
this.containerRef.addEventListener("underflow", this.onUnderflow);
}
if (ref.scrollHeight > ref.clientHeight) {
this.onOverflow();
} else {
this.onUnderflow();
}
}
}
componentWillUnmount() {
if (this.containerRef) {
this.containerRef.removeEventListener("overflow", this.onOverflow);
this.containerRef.removeEventListener("underflow", this.onUnderflow);
}
}
render() {
installBodyClassesIfNeeded();
return (<div
ref={this._collectContainerRef}
className={["mx_AutoHideScrollbar", this.props.className].join(" ")}
>
{ this.props.children }
</div>);
}
}

View file

@ -179,10 +179,9 @@ const LeftPanel = React.createClass({
const RoomList = sdk.getComponent('rooms.RoomList');
const TagPanel = sdk.getComponent('structures.TagPanel');
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
const BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
const CallPreview = sdk.getComponent('voip.CallPreview');
let topBox = <TopLeftMenuButton collapsed={ this.props.collapsed }/>;
const topBox = <TopLeftMenuButton collapsed={ this.props.collapsed } />;
/*
const SearchBox = sdk.getComponent('structures.SearchBox');
const topBox = <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />;

View file

@ -23,6 +23,7 @@ import dis from '../../dispatcher';
import Unread from '../../Unread';
import * as RoomNotifs from '../../RoomNotifs';
import * as FormattingUtils from '../../utils/FormattingUtils';
import AutoHideScrollbar from './AutoHideScrollbar';
import { KeyCode } from '../../Keyboard';
import { Group } from 'matrix-js-sdk';
import PropTypes from 'prop-types';
@ -348,19 +349,17 @@ const RoomSubList = React.createClass({
{this._getHeaderJsx()}
</div>;
} else {
const heightEstimation = (len * 40) + 31 + (8 + 8);
const heightEstimation = (len * 44) + 31 + (8 + 8);
const style = {
flexGrow: `${heightEstimation}`,
maxHeight: `${heightEstimation}px`,
};
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
const tiles = this.makeRoomTiles();
tiles.push(...this.props.extraTiles);
return <div className={subListClasses} style={style}>
return <div style={style} className={subListClasses}>
{this._getHeaderJsx()}
<GeminiScrollbarWrapper>
<AutoHideScrollbar className="mx_RoomSubList_scroll">
{ tiles }
</GeminiScrollbarWrapper>
</AutoHideScrollbar>
</div>;
}
} else {

View file

@ -26,7 +26,6 @@ import Avatar from '../../Avatar';
const AVATAR_SIZE = 28;
export default class TopLeftMenuButton extends React.Component {
static propTypes = {
collapsed: PropTypes.bool.isRequired,
};

View file

@ -19,7 +19,6 @@ import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
export class TopLeftMenu extends React.Component {
constructor() {
super();
this.openSettings = this.openSettings.bind(this);

View file

@ -74,7 +74,6 @@ class CollapseDistributor extends FixedDistributor {
}
class PercentageDistributor {
constructor(sizer, item, _config, items, container) {
this.container = container;
this.totalSize = sizer.getTotalSize();

View file

@ -34,7 +34,6 @@ class RoomSizer extends Sizer {
item.style.maxHeight = `${Math.round(size)}px`;
}
}
}
class RoomDistributor extends FixedDistributor {