1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-11-28 03:08:30 +00:00

Fixed header and footer using sticky

This commit is contained in:
Vladimir Volek 2018-09-10 16:29:30 +02:00
parent 160210890f
commit 2788355811
4 changed files with 48 additions and 194 deletions

View File

@ -47,6 +47,7 @@
"react-router-redux": "next", "react-router-redux": "next",
"react-scale-text": "^1.2.2", "react-scale-text": "^1.2.2",
"react-select": "2.0.0", "react-select": "2.0.0",
"react-sticky-el": "^1.0.20",
"react-transition-group": "^2.2.1", "react-transition-group": "^2.2.1",
"redbox-react": "^1.6.0", "redbox-react": "^1.6.0",
"redux": "4.0.0", "redux": "4.0.0",

View File

@ -1,165 +0,0 @@
/* @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;
width: 320px;
min-width: 320px;
overflow-x: hidden;
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<Props> {
// 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);
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 (
<AsideWrapper
innerRef={(node) => { this.aside = node; }}
onScroll={this.handleScroll}
onTouchStart={this.handleScroll}
onTouchMove={this.handleScroll}
onTouchEnd={this.handleScroll}
>
<StickyContainerWrapper
innerRef={(node) => { this.wrapper = node; }}
>
{this.props.children}
</StickyContainerWrapper>
</AsideWrapper>
);
}
}

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import colors from 'config/colors'; import colors from 'config/colors';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import Sticky from 'react-sticky-el';
import icons from 'config/icons'; import icons from 'config/icons';
import { TransitionGroup, CSSTransition } from 'react-transition-group'; import { TransitionGroup, CSSTransition } from 'react-transition-group';
import styled from 'styled-components'; import styled from 'styled-components';
@ -9,7 +10,17 @@ import DeviceHeader from 'components/DeviceHeader';
import AccountMenu from './components/AccountMenu'; import AccountMenu from './components/AccountMenu';
import CoinMenu from './components/CoinMenu'; import CoinMenu from './components/CoinMenu';
import DeviceMenu from './components/DeviceMenu'; import DeviceMenu from './components/DeviceMenu';
import StickyContainer from './components/StickyContainer';
const Wrapper = styled.div`
position: relative;
width: 320px;
min-width: 320px;
overflow-x: hidden;
background: ${colors.MAIN};
border-right: 1px solid ${colors.DIVIDER};
`;
const Header = styled(DeviceHeader)``;
const TransitionGroupWrapper = styled(TransitionGroup)` const TransitionGroupWrapper = styled(TransitionGroup)`
width: 640px; width: 640px;
@ -21,14 +32,15 @@ const TransitionContentWrapper = styled.div`
vertical-align: top; vertical-align: top;
`; `;
const StickyBottom = styled.div` const Footer = styled.div`
position: fixed; position: fixed;
bottom: 0; bottom: 0;
background: ${colors.MAIN}; background: ${colors.MAIN};
border-right: 1px solid ${colors.DIVIDER}; border-right: 1px solid ${colors.DIVIDER};
`; `;
const MenuWrapper = styled.div` const Body = styled.div`
overflow: auto;
background: ${colors.LANDING}; background: ${colors.LANDING};
`; `;
@ -40,11 +52,6 @@ const Help = styled.div`
width: 319px; width: 319px;
padding: 8px 0px; padding: 8px 0px;
border-top: 1px solid ${colors.DIVIDER}; border-top: 1px solid ${colors.DIVIDER};
&.fixed {
position: fixed;
bottom: 0px;
}
`; `;
const A = styled.a` const A = styled.a`
@ -151,11 +158,13 @@ class LeftNavigation extends Component {
render() { render() {
return ( return (
<StickyContainer <Wrapper
className="block"
location={this.props.location.pathname} location={this.props.location.pathname}
deviceSelection={this.props.deviceDropdownOpened} deviceSelection={this.props.deviceDropdownOpened}
> >
<DeviceHeader <Sticky boundaryElement=".block" scrollElement=".scroll-area">
<Header
onClickWrapper={() => this.handleOpen()} onClickWrapper={() => this.handleOpen()}
device={this.props.wallet.selectedDevice} device={this.props.wallet.selectedDevice}
transport={this.props.connect.transport} transport={this.props.connect.transport}
@ -163,12 +172,14 @@ class LeftNavigation extends Component {
isOpen={this.props.deviceDropdownOpened} isOpen={this.props.deviceDropdownOpened}
{...this.props} {...this.props}
/> />
<MenuWrapper> </Sticky>
<Body className="scroll-area">
{this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />} {this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />}
{this.shouldRenderAccounts() && this.getMenuTransition(<AccountMenu {...this.props} />)} {this.shouldRenderAccounts() && this.getMenuTransition(<AccountMenu {...this.props} />)}
{this.shouldRenderCoins() && this.getMenuTransition(<CoinMenu {...this.props} />)} {this.shouldRenderCoins() && this.getMenuTransition(<CoinMenu {...this.props} />)}
</MenuWrapper> </Body>
<StickyBottom> <Sticky mode="bottom" boundaryElement=".block" />
<Footer>
<Help> <Help>
<A <A
href="https://trezor.io/support/" href="https://trezor.io/support/"
@ -178,8 +189,9 @@ class LeftNavigation extends Component {
<Icon size={26} icon={icons.CHAT} color={colors.TEXT_SECONDARY} />Need help? <Icon size={26} icon={icons.CHAT} color={colors.TEXT_SECONDARY} />Need help?
</A> </A>
</Help> </Help>
</StickyBottom> </Footer>
</StickyContainer> <Sticky />
</Wrapper>
); );
} }
} }

View File

@ -7728,6 +7728,13 @@ prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, pr
loose-envify "^1.3.1" loose-envify "^1.3.1"
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types@>=15.5.10, prop-types@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
prop-types@^15.6.1: prop-types@^15.6.1:
version "15.6.1" version "15.6.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
@ -7736,13 +7743,6 @@ prop-types@^15.6.1:
loose-envify "^1.3.1" loose-envify "^1.3.1"
object-assign "^4.1.1" object-assign "^4.1.1"
prop-types@^15.6.2:
version "15.6.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
proxy-addr@~2.0.3: proxy-addr@~2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
@ -8066,6 +8066,12 @@ react-select@2.0.0:
react-input-autosize "^2.2.1" react-input-autosize "^2.2.1"
react-transition-group "^2.2.1" react-transition-group "^2.2.1"
react-sticky-el@^1.0.20:
version "1.0.20"
resolved "https://registry.yarnpkg.com/react-sticky-el/-/react-sticky-el-1.0.20.tgz#b3c5e7128218633f440dc67aec239d1cd078342d"
dependencies:
prop-types ">=15.5.10"
react-transition-group@^2.2.1: react-transition-group@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10"