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:
parent
160210890f
commit
2788355811
@ -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",
|
||||||
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,24 +158,28 @@ 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">
|
||||||
onClickWrapper={() => this.handleOpen()}
|
<Header
|
||||||
device={this.props.wallet.selectedDevice}
|
onClickWrapper={() => this.handleOpen()}
|
||||||
transport={this.props.connect.transport}
|
device={this.props.wallet.selectedDevice}
|
||||||
devices={this.props.devices}
|
transport={this.props.connect.transport}
|
||||||
isOpen={this.props.deviceDropdownOpened}
|
devices={this.props.devices}
|
||||||
{...this.props}
|
isOpen={this.props.deviceDropdownOpened}
|
||||||
/>
|
{...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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
yarn.lock
20
yarn.lock
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user