diff --git a/src/views/Test.js b/src/views/Test.js deleted file mode 100644 index 24c7e318..00000000 --- a/src/views/Test.js +++ /dev/null @@ -1,82 +0,0 @@ -import * as React from 'react'; -import styled from 'styled-components'; -import Sticky from 'react-sticky-el'; - -import DeviceHeader from 'components/DeviceHeader'; - -const App = styled.div` - border: 2px dashed lime; - width: 100vh; - padding: 150px; - height: 1800px; -`; - -const Wrapper = styled(Sticky)` - max-width: 400px; - padding: 20px; - height: 100vh; - display: flex; - border: 2px dashed blue; - flex-direction: column; -`; - -const Content = styled.div` - flex: 1; - overflow: auto; - height: 100%; - border: 2px solid gold; -`; - -const Header = styled(Sticky)` - flex-shrink: 0; - border: 2px solid red; - background: white; -`; - -const Footer = styled(Sticky)` - border: 2px solid tan; -`; - -const Item = styled.div` - width: 100%; - height: 35px; - border: 1px solid navy; - padding: 5px; - margin: 10px 0; -`; - -const T = () => ( - - -
- -
- - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - aaa - - - -
-
-); - -export default T; diff --git a/src/views/Wallet/components/LeftNavigation/components/StickyContainer/index.js b/src/views/Wallet/components/LeftNavigation/components/StickyContainer/index.js new file mode 100644 index 00000000..dd20c6f2 --- /dev/null +++ b/src/views/Wallet/components/LeftNavigation/components/StickyContainer/index.js @@ -0,0 +1,182 @@ +/* @flow */ + + +// https://github.com/KyleAMathews/react-headroom/blob/master/src/shouldUpdate.js + +import * as React from 'react'; +import raf from 'raf'; +import { getViewportHeight, getScrollY } from 'utils/windowUtils'; +import styled from 'styled-components'; +import colors from 'config/colors'; + +type Props = { + location: string, + deviceSelection: boolean, + children?: React.Node, +} + +const AsideWrapper = styled.aside` + position: relative; + top: 0; + width: 320px; + overflow: hidden; + background: ${colors.MAIN}; + border-right: 1px solid ${colors.DIVIDER}; + + .fixed { + position: fixed; + border-right: 1px solid ${colors.DIVIDER}; + } + + .fixed-bottom { + padding-bottom: 60px; + .sticky-bottom { + position: fixed; + bottom: 0; + background: ${colors.MAIN}; + border-right: 1px solid ${colors.DIVIDER}; + } + } +`; + +const StickyContainerWrapper = styled.div` + position: relative; + top: 0; + width: 320px; + overflow: hidden; +`; + +export default class StickyContainer extends React.PureComponent { + // Class variables. + currentScrollY: number = 0; + + lastKnownScrollY: number = 0; + + topOffset: number = 0; + + firstRender: boolean = false; + + framePending: boolean = false; + + stickToBottom: boolean = false; + + top: number = 0; + + aside: ?HTMLElement; + + wrapper: ?HTMLElement; + + subscribers = []; + + // handleResize = (event: Event) => { + + // } + + shouldUpdate = () => { + const { wrapper, aside }: {wrapper: ?HTMLElement, aside: ?HTMLElement} = this; + if (!wrapper || !aside) return; + const bottom: ?HTMLElement = wrapper.querySelector('.sticky-bottom'); + if (!bottom) return; + + const viewportHeight: number = getViewportHeight(); + const bottomBounds = bottom.getBoundingClientRect(); + const asideBounds = aside.getBoundingClientRect(); + const wrapperBounds = wrapper.getBoundingClientRect(); + const scrollDirection = this.currentScrollY >= this.lastKnownScrollY ? 'down' : 'up'; + const distanceScrolled = Math.abs(this.currentScrollY - this.lastKnownScrollY); + + console.warn(wrapper, bottom); + + if (asideBounds.top < 0) { + wrapper.classList.add('fixed'); + let maxTop: number = 1; + if (wrapperBounds.height > viewportHeight) { + const bottomOutOfBounds: boolean = (bottomBounds.bottom <= viewportHeight && scrollDirection === 'down'); + const topOutOfBounds: boolean = (wrapperBounds.top > 0 && scrollDirection === 'up'); + if (!bottomOutOfBounds && !topOutOfBounds) { + this.topOffset += scrollDirection === 'down' ? -distanceScrolled : distanceScrolled; + } + maxTop = viewportHeight - wrapperBounds.height; + } + + if (this.topOffset > 0) this.topOffset = 0; + if (maxTop < 0 && this.topOffset < maxTop) this.topOffset = maxTop; + wrapper.style.top = `${this.topOffset}px`; + } else { + wrapper.classList.remove('fixed'); + wrapper.style.top = '0px'; + this.topOffset = 0; + } + + if (wrapperBounds.height > viewportHeight) { + wrapper.classList.remove('fixed-bottom'); + } else if (wrapper.classList.contains('fixed-bottom')) { + if (bottomBounds.top < wrapperBounds.bottom - bottomBounds.height) { + wrapper.classList.remove('fixed-bottom'); + } + } else if (bottomBounds.bottom < viewportHeight) { + wrapper.classList.add('fixed-bottom'); + } + + aside.style.minHeight = `${wrapperBounds.height}px`; + } + + update = () => { + this.currentScrollY = getScrollY(); + this.shouldUpdate(); + this.framePending = false; + this.lastKnownScrollY = this.currentScrollY; + } + + componentDidMount() { + window.addEventListener('scroll', this.handleScroll); + window.addEventListener('resize', this.handleScroll); + raf(this.update); + } + + componentDidUpdate(prevProps: Props) { + if (this.props.location !== prevProps.location && this.aside) { + const asideBounds = this.aside.getBoundingClientRect(); + if (asideBounds.top < 0) { + window.scrollTo(0, getScrollY() + asideBounds.top); + this.topOffset = 0; + } + raf(this.update); + } else if (this.props.deviceSelection !== prevProps.deviceSelection) { + raf(this.update); + } else if (!this.firstRender) { + raf(this.update); + this.firstRender = true; + } + } + + componentWillUnmount() { + window.removeEventListener('scroll', this.handleScroll); + window.removeEventListener('resize', this.handleScroll); + } + + handleScroll = (/* event: ?Event */) => { + if (!this.framePending) { + this.framePending = true; + raf(this.update); + } + } + + render() { + return ( + { this.aside = node; }} + onScroll={this.handleScroll} + onTouchStart={this.handleScroll} + onTouchMove={this.handleScroll} + onTouchEnd={this.handleScroll} + > + { this.wrapper = node; }} + > + {this.props.children} + + + ); + } +} \ No newline at end of file diff --git a/src/views/Wallet/components/LeftNavigation/index.js b/src/views/Wallet/components/LeftNavigation/index.js index 47703bf4..dc57e112 100644 --- a/src/views/Wallet/components/LeftNavigation/index.js +++ b/src/views/Wallet/components/LeftNavigation/index.js @@ -10,14 +10,7 @@ import DeviceHeader from 'components/DeviceHeader'; import AccountMenu from './components/AccountMenu'; import CoinMenu from './components/CoinMenu'; import DeviceMenu from './components/DeviceMenu'; - -const Wrapper = styled.div` - width: 320px; - overflow: scroll; - border: 2px solid red; - background: ${colors.MAIN}; - border-right: 1px solid ${colors.DIVIDER}; -`; +import StickyContainer from './components/StickyContainer'; const Header = styled(DeviceHeader)``; @@ -157,27 +150,24 @@ class LeftNavigation extends Component { render() { return ( - - -
this.handleOpen()} - device={this.props.wallet.selectedDevice} - transport={this.props.connect.transport} - devices={this.props.devices} - isOpen={this.props.deviceDropdownOpened} - {...this.props} - /> - +
this.handleOpen()} + device={this.props.wallet.selectedDevice} + transport={this.props.connect.transport} + devices={this.props.devices} + isOpen={this.props.deviceDropdownOpened} + {...this.props} + /> {this.state.shouldRenderDeviceSelection && } {this.shouldRenderAccounts() && this.getMenuTransition()} {this.shouldRenderCoins() && this.getMenuTransition()} - -