diff --git a/res/css/structures/_AutoHideScrollbar.scss b/res/css/structures/_AutoHideScrollbar.scss
index 70dbbe8d07..60aea7ce8f 100644
--- a/res/css/structures/_AutoHideScrollbar.scss
+++ b/res/css/structures/_AutoHideScrollbar.scss
@@ -41,14 +41,26 @@ is overflowing. This is what Firefox ends up using. Overflow is detected
in javascript, and adds the mx_AutoHideScrollbar_overflow class to the container.
This only works in Firefox, which should be fine as this fallback is only needed there.
*/
-body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar {
- overflow-y: hidden;
-}
+body.mx_scrollbar_nooverlay {
+ .mx_AutoHideScrollbar {
+ overflow-y: hidden;
+ }
-body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar:hover {
- overflow-y: auto;
-}
+ .mx_AutoHideScrollbar:hover {
+ overflow-y: auto;
+ }
-body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow > .mx_AutoHideScrollbar_offset {
- margin-right: calc(-1 * var(--scrollbar-width));
+ /*
+ offset scrollbar width with negative margin-right
+
+ include before and after psuedo-elements here so they can
+ be used to do something interesting like scroll-indicating
+ gradients (see IndicatorScrollBar)
+ */
+ .mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow > .mx_AutoHideScrollbar_offset,
+ .mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow::before,
+ .mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow::after
+ {
+ margin-right: calc(-1 * var(--scrollbar-width));
+ }
}
diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss
index 142a43af95..c156f1f07e 100644
--- a/res/css/structures/_RoomSubList.scss
+++ b/res/css/structures/_RoomSubList.scss
@@ -110,6 +110,36 @@ limitations under the License.
padding: 0 8px;
}
+.mx_RoomSubList_scroll.mx_IndicatorScrollbar_topOverflow::before,
+.mx_RoomSubList_scroll.mx_IndicatorScrollbar_bottomOverflow::after {
+ position: sticky;
+ left: 0;
+ right: 0;
+ height: 40px;
+ content: "";
+ display: block;
+ z-index: 100;
+ pointer-events: none;
+}
+
+
+.mx_RoomSubList_scroll.mx_IndicatorScrollbar_topOverflow > .mx_AutoHideScrollbar_offset {
+ margin-top: -40px;
+}
+.mx_RoomSubList_scroll.mx_IndicatorScrollbar_bottomOverflow > .mx_AutoHideScrollbar_offset {
+ margin-bottom: -40px;
+}
+
+.mx_RoomSubList_scroll.mx_IndicatorScrollbar_topOverflow::before {
+ top: 0;
+ background: linear-gradient($secondary-accent-color, transparent);
+}
+
+.mx_RoomSubList_scroll.mx_IndicatorScrollbar_bottomOverflow::after {
+ bottom: 0;
+ background: linear-gradient(transparent, $secondary-accent-color);
+}
+
.collapsed {
.mx_RoomSubList_scroll {
diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.js
index a462b2bf14..a328d478bc 100644
--- a/src/components/structures/AutoHideScrollbar.js
+++ b/src/components/structures/AutoHideScrollbar.js
@@ -97,6 +97,9 @@ export default class AutoHideScrollbar extends React.Component {
this.onUnderflow();
}
}
+ if (this.props.wrappedRef) {
+ this.props.wrappedRef(ref);
+ }
}
componentWillUnmount() {
diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js
new file mode 100644
index 0000000000..247131cfab
--- /dev/null
+++ b/src/components/structures/IndicatorScrollbar.js
@@ -0,0 +1,62 @@
+/*
+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";
+import AutoHideScrollbar from "./AutoHideScrollbar";
+
+export default class IndicatorScrollbar extends React.Component {
+ constructor(props) {
+ super(props);
+ this._collectScroller = this._collectScroller.bind(this);
+ this.checkOverflow = this.checkOverflow.bind(this);
+ }
+
+ _collectScroller(scroller) {
+ if (scroller && !this._scroller) {
+ this._scroller = scroller;
+ this._scroller.addEventListener("scroll", this.checkOverflow);
+ this.checkOverflow();
+ }
+ }
+
+ checkOverflow() {
+ const hasTopOverflow = this._scroller.scrollTop > 0;
+ const hasBottomOverflow = this._scroller.scrollHeight >
+ (this._scroller.scrollTop + this._scroller.clientHeight);
+ if (hasTopOverflow) {
+ this._scroller.classList.add("mx_IndicatorScrollbar_topOverflow");
+ } else {
+ this._scroller.classList.remove("mx_IndicatorScrollbar_topOverflow");
+ }
+ if (hasBottomOverflow) {
+ this._scroller.classList.add("mx_IndicatorScrollbar_bottomOverflow");
+ } else {
+ this._scroller.classList.remove("mx_IndicatorScrollbar_bottomOverflow");
+ }
+ }
+
+ componentWillUnmount() {
+ if (this._scroller) {
+ this._scroller.removeEventListener("scroll", this.checkOverflow);
+ }
+ }
+
+ render() {
+ return (