summaryrefslogtreecommitdiffhomepage
path: root/app
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2017-12-18 18:22:13 +0100
committerAndrej Mihajlov <and@mullvad.net>2017-12-18 18:22:13 +0100
commitf5d2f70b62f435f85f1f3bc1ded25e101d145442 (patch)
treecb8836acdf829de1675bbbf481533c3aacb9b7bd /app
parentc544d50488a0c44498f23912a43137ab44705456 (diff)
parent3474b0c58ffb6fb77fcb4fe989f4546b9760e638 (diff)
downloadmullvadvpn-f5d2f70b62f435f85f1f3bc1ded25e101d145442.tar.xz
mullvadvpn-f5d2f70b62f435f85f1f3bc1ded25e101d145442.zip
Merge branch 'custom-scrollbars'
Diffstat (limited to 'app')
-rw-r--r--app/components/CustomScrollbars.css24
-rw-r--r--app/components/CustomScrollbars.js130
2 files changed, 143 insertions, 11 deletions
diff --git a/app/components/CustomScrollbars.css b/app/components/CustomScrollbars.css
index 24788230b8..074c081209 100644
--- a/app/components/CustomScrollbars.css
+++ b/app/components/CustomScrollbars.css
@@ -1,4 +1,22 @@
-.custom-scrollbars__thumb-vertical {
- background-color: rgba(255, 255, 255, 0.2);
- border-radius: 3px;
+.custom-scrollbars {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+
+.custom-scrollbars__scrollable {
+ width: 100%;
+ height: 100%;
}
+
+.custom-scrollbars__scrollable::-webkit-scrollbar {
+ display: none;
+}
+
+.custom-scrollbars__thumb {
+ background-color: rgba(255, 255, 255, 0.2);
+ border-radius: 4px;
+ width: 8px;
+ transition: height 0.25s ease-in-out, opacity 0.25s ease-in-out;
+ pointer-events: none;
+} \ No newline at end of file
diff --git a/app/components/CustomScrollbars.js b/app/components/CustomScrollbars.js
index 4a0899c1be..6d0ce96daa 100644
--- a/app/components/CustomScrollbars.js
+++ b/app/components/CustomScrollbars.js
@@ -1,19 +1,133 @@
// @flow
import React, { Component } from 'react';
-import { Scrollbars } from 'react-custom-scrollbars';
+
+type ScrollbarUpdateContext = {
+ size: boolean,
+ position: boolean,
+};
export default class CustomScrollbars extends Component {
props: {
- children: ?React.Element<*>
+ thumbInset: { x: number, y: number },
+ children: ?React.Element<*>,
+ };
+
+ static defaultProps = {
+ thumbInset: { x: 2, y: 2 },
+ };
+
+ _scrollableElement: ?HTMLElement;
+ _thumbElement: ?HTMLElement;
+
+ componentDidMount() {
+ this._updateScrollbarsHelper({
+ position: true,
+ size: true
+ });
}
- render(): React.Element<*> {
+ componentDidUpdate() {
+ this._updateScrollbarsHelper({
+ position: true,
+ size: true
+ });
+ }
+
+ render() {
return (
- <Scrollbars
- { ...this.props }
- renderThumbVertical={ () => <div className="custom-scrollbars__thumb-vertical"/> }>
- { this.props.children }
- </Scrollbars>
+ <div className="custom-scrollbars">
+ <div className="custom-scrollbars__thumb"
+ style={{ position: 'absolute', top: 0, right: 0 }}
+ ref={ this._onThumbRef }></div>
+ <div className="custom-scrollbars__scrollable"
+ style={{ overflow: 'auto' }}
+ onScroll={ this._onScroll }
+ ref={ this._onScrollableRef }>
+ { this.props.children }
+ </div>
+ </div>
);
}
+
+
+ _onScrollableRef = (ref) => {
+ this._scrollableElement = ref;
+ }
+
+ _onThumbRef = (ref) => {
+ this._thumbElement = ref;
+ }
+
+ _onScroll = () => {
+ this._updateScrollbarsHelper({ position: true });
+ }
+
+ _computeThumbPosition(scrollable: HTMLElement, thumb: HTMLElement) {
+ // the content height of the scroll view
+ const scrollHeight = scrollable.scrollHeight;
+
+ // the visible height of the scroll view
+ const visibleHeight = scrollable.offsetHeight;
+
+ // scroll offset
+ const scrollTop = scrollable.scrollTop;
+
+ // lowest point of scrollTop
+ const maxScrollTop = scrollHeight - visibleHeight;
+
+ // calculate scroll position within 0..1 range
+ const scrollPosition = scrollHeight > 0 ? scrollTop / maxScrollTop : 0;
+
+ const thumbHeight = thumb.clientHeight;
+
+ // calculate the thumb boundary to make sure that the visual appearance of
+ // a thumb at lowest point matches the bottom of scrollable view
+ const thumbBoundary = visibleHeight - thumbHeight - (this.props.thumbInset.y * 2);
+
+ // calculate thumb position based on scroll progress and thumb boundary
+ // adding vertical inset to adjust the thumb's appearance
+ const thumbPosition = (thumbBoundary * scrollPosition) + this.props.thumbInset.y;
+
+ return {
+ x: -this.props.thumbInset.x,
+ y: thumbPosition,
+ };
+ }
+
+ _computeThumbHeight(scrollable: HTMLElement) {
+ const scrollHeight = scrollable.scrollHeight;
+ const visibleHeight = scrollable.offsetHeight;
+
+ const thumbHeight = (visibleHeight / scrollHeight) * visibleHeight;
+
+ // ensure that the scroll thumb doesn't shrink to nano size
+ return Math.max(thumbHeight, 8);
+ }
+
+ _updateScrollbarsHelper(updateFlags: $Shape<ScrollbarUpdateContext>) {
+ const scrollable = this._scrollableElement;
+ const thumb = this._thumbElement;
+ if(scrollable && thumb) {
+ this._updateScrollbars(scrollable, thumb, updateFlags);
+ }
+ }
+
+ _updateScrollbars(scrollable: HTMLElement, thumb: HTMLElement, context: $Shape<ScrollbarUpdateContext>) {
+ if(context.size) {
+ const thumbHeight = this._computeThumbHeight(scrollable);
+ thumb.style.setProperty('height', thumbHeight + 'px');
+
+ // hide thumb when there is nothing to scroll
+ if(thumbHeight < scrollable.offsetHeight) {
+ thumb.style.setProperty('opacity', '1');
+ } else {
+ thumb.style.setProperty('opacity', '0');
+ }
+ }
+
+ if(context.position) {
+ const { x, y } = this._computeThumbPosition(scrollable, thumb);
+ thumb.style.setProperty('transform', `translate(${x}px, ${y}px)`);
+ }
+ }
}