Merge pull request #350 from trezor/feature/responsive++

Feature/Responsive sidebar
pull/351/head
Vladimir Volek 5 years ago committed by GitHub
commit fa3c40626e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -40,6 +40,8 @@ export type WalletAction = {
devices: Array<TrezorDevice>
} | {
type: typeof WALLET.SHOW_BETA_DISCLAIMER | typeof WALLET.HIDE_BETA_DISCLAIMER | typeof WALLET.SET_FIRST_LOCATION_CHANGE,
} | {
type: typeof WALLET.TOGGLE_SIDEBAR,
}
export const init = (): ThunkAction => (dispatch: Dispatch): void => {
@ -62,6 +64,10 @@ export const toggleDeviceDropdown = (opened: boolean): WalletAction => ({
opened,
});
export const toggleSidebar = (): WalletAction => ({
type: WALLET.TOGGLE_SIDEBAR,
});
// This method will be called after each DEVICE.CONNECT action
// if connected device has different "passphrase_protection" settings than saved instances
// all saved instances will be removed immediately inside DevicesReducer

@ -10,4 +10,6 @@ export const UPDATE_SELECTED_DEVICE: 'wallet__update_selected_device' = 'wallet_
export const SHOW_BETA_DISCLAIMER: 'wallet__show_beta_disclaimer' = 'wallet__show_beta_disclaimer';
export const HIDE_BETA_DISCLAIMER: 'wallet__hide_beta_disclaimer' = 'wallet__hide_beta_disclaimer';
export const CLEAR_UNAVAILABLE_DEVICE_DATA: 'wallet__clear_unavailable_device_data' = 'wallet__clear_unavailable_device_data';
export const CLEAR_UNAVAILABLE_DEVICE_DATA: 'wallet__clear_unavailable_device_data' = 'wallet__clear_unavailable_device_data';
export const TOGGLE_SIDEBAR: 'wallet__toggle_sidebar' = 'wallet__toggle_sidebar';

@ -0,0 +1,37 @@
import React from 'react';
import styled, { css } from 'styled-components';
import PropTypes from 'prop-types';
import { FADE_IN } from 'config/animations';
const StyledBackdrop = styled.div`
width: 100%;
height: 100%;
position: fixed;
z-index: 100;
left: 0;
top: 0;
background-color: rgba(0,0,0,0.5);
${props => props.animated && css`
animation: ${FADE_IN} 0.3s;
`};
`;
const Backdrop = ({
className,
show,
animated,
onClick,
}) => (
show ? <StyledBackdrop className={className} animated={animated} onClick={onClick} /> : null
);
Backdrop.propTypes = {
show: PropTypes.bool.isRequired,
className: PropTypes.string,
animated: PropTypes.bool,
onClick: PropTypes.func,
};
export default Backdrop;

@ -18,8 +18,10 @@ const Wrapper = styled.div`
z-index: 10;
display: flex;
align-items: center;
padding: 0px 25px;
background: ${props => (props.disabled ? colors.GRAY_LIGHT : 'transparent')};
background: ${props => (props.isSelected ? colors.WHITE : 'transparent')};
cursor: pointer;
border-radius: 4px 0 0 0;
box-shadow: ${props => (props.disabled ? 'none' : '0 3px 8px rgba(0, 0, 0, 0.04)')};
@ -28,6 +30,10 @@ const Wrapper = styled.div`
box-shadow: none;
`}
${props => props.disabled && css`
cursor: default;
`}
${props => props.isHoverable && !props.disabled && css`
&:hover {
background: ${colors.GRAY_LIGHT};
@ -35,19 +41,6 @@ const Wrapper = styled.div`
`}
`;
const ClickWrapper = styled.div`
width: 100%;
display: flex;
padding-left: 25px;
height: 100%;
align-items: center;
cursor: pointer;
${props => props.disabled && css`
cursor: default;
`}
`;
const LabelWrapper = styled.div`
flex: 1 1 auto;
padding-left: 18px;
@ -73,7 +66,6 @@ const Status = styled.div`
`;
const IconWrapper = styled.div`
padding-right: 25px;
display: flex;
flex: 1 0 0;
justify-content: flex-end;
@ -115,23 +107,19 @@ const DeviceHeader = ({
isHoverable={isHoverable}
disabled={disabled}
className={className}
onClick={onClickWrapper}
>
<ClickWrapper
disabled={disabled}
onClick={onClickWrapper}
>
<ImageWrapper>
<Dot color={getStatusColor(status)} />
<TrezorImage model={getVersion(device)} />
</ImageWrapper>
<LabelWrapper>
<Name>{device.instanceLabel}</Name>
<Status title={getStatusName(status)}>{getStatusName(status)}</Status>
</LabelWrapper>
<IconWrapper>
{icon && !disabled && isAccessible && icon}
</IconWrapper>
</ClickWrapper>
<ImageWrapper>
<Dot color={getStatusColor(status)} />
<TrezorImage model={getVersion(device)} />
</ImageWrapper>
<LabelWrapper>
<Name>{device.instanceLabel}</Name>
<Status title={getStatusName(status)}>{getStatusName(status)}</Status>
</LabelWrapper>
<IconWrapper>
{icon && !disabled && isAccessible && icon}
</IconWrapper>
</Wrapper>
);
};

@ -3,11 +3,15 @@ import React from 'react';
import styled from 'styled-components';
import { NavLink } from 'react-router-dom';
import colors from 'config/colors';
import { SCREEN_SIZE } from 'config/variables';
import type { toggleSidebar as toggleSidebarType } from 'actions/WalletActions';
const Wrapper = styled.header`
width: 100%;
height: 52px;
background: ${colors.HEADER};
overflow: hidden;
z-index: 200;
svg {
fill: ${colors.WHITE};
@ -31,12 +35,54 @@ const LayoutWrapper = styled.div`
`;
const Left = styled.div`
display: none;
flex: 0 0 33%;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
display: initial;
}
`;
const MenuToggler = styled.div`
display: none;
white-space: nowrap;
color: ${colors.WHITE};
align-self: center;
cursor: pointer;
user-select: none;
padding: 10px 0px;
transition: all .1s ease-in;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
display: initial;
}
`;
const Logo = styled.div`
flex: 1;
display: flex;
justify-content: flex-start;
display: flex;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
flex: 1 0 33%;
justify-content: center;
}
`;
const MenuLinks = styled.div`
flex: 0;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
flex: 0 1 33%;
}
`;
const Right = styled.div``;
const Projects = styled.div`
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
display: none;
}
`;
const A = styled.a`
color: ${colors.WHITE};
@ -58,10 +104,20 @@ const A = styled.a`
}
`;
const Header = (): React$Element<string> => (
type Props = {
sidebarEnabled?: boolean,
sidebarOpened?: boolean,
toggleSidebar?: toggleSidebarType,
};
const Header = ({ sidebarEnabled, sidebarOpened, toggleSidebar }: Props) => (
<Wrapper>
<LayoutWrapper>
<Left>
{ sidebarEnabled && <MenuToggler onClick={toggleSidebar}>{sidebarOpened ? '✕ Close' : '☰ Menu'}</MenuToggler>}
</Left>
<Logo>
<NavLink to="/">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 163.7 41.9" width="100%" height="100%" preserveAspectRatio="xMinYMin meet">
<polygon points="101.1,12.8 118.2,12.8 118.2,17.3 108.9,29.9 118.2,29.9 118.2,35.2 101.1,35.2 101.1,30.7 110.4,18.1 101.1,18.1" />
@ -73,13 +129,15 @@ const Header = (): React$Element<string> => (
<polygon points="40.5,12.8 58.6,12.8 58.6,18.1 52.4,18.1 52.4,35.2 46.6,35.2 46.6,18.1 40.5,18.1 " />
</svg>
</NavLink>
</Left>
<Right>
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener">Trezor</A>
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener">Wiki</A>
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">Blog</A>
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">Support</A>
</Right>
</Logo>
<MenuLinks>
<Projects>
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener">Trezor</A>
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener">Wiki</A>
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">Blog</A>
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">Support</A>
</Projects>
</MenuLinks>
</LayoutWrapper>
</Wrapper>
);

@ -12,10 +12,12 @@ const A = styled.a`
font-size: ${FONT_SIZE.SMALL};
${props => props.isGreen && css`
border-bottom: 1px solid ${colors.GREEN_PRIMARY};
text-decoration: underline;
text-decoration-color: ${colors.GREEN_PRIMARY};
`}
${props => props.isGray && css`
border-bottom: 1px solid ${colors.TEXT_SECONDARY};
text-decoration: underline;
text-decoration-color: ${colors.TEXT_SECONDARY};
`}
&,

@ -8,7 +8,9 @@ import colors from 'config/colors';
import { WHITE_COLOR } from 'config/animations';
import { getPrimaryColor } from 'utils/notification';
import Loader from 'components/Loader';
import { TRANSITION, FONT_SIZE, FONT_WEIGHT } from 'config/variables';
import {
TRANSITION, FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE,
} from 'config/variables';
type Props = {
type: string;
@ -48,6 +50,10 @@ const Wrapper = styled.button`
border: 1px solid ${props => getPrimaryColor(props.type)};
transition: ${TRANSITION.HOVER};
@media screen and (max-width: ${SCREEN_SIZE.SM}){
padding: 12px 24px;
}
&:hover {
color: ${colors.WHITE};
background: ${props => getPrimaryColor(props.type)};

@ -13,7 +13,7 @@ import P from 'components/Paragraph';
import type { Props } from '../../Container';
const Wrapper = styled.div`
width: 390px;
max-width: 390px;
`;
const Header = styled.div`

@ -20,7 +20,7 @@ type Props = {
}
const Wrapper = styled.div`
width: 390px;
max-width: 390px;
`;
const Header = styled.div`

@ -30,7 +30,7 @@ const StyledLink = styled(Link)`
`;
const Wrapper = styled.div`
width: 370px;
max-width: 370px;
padding: 30px 48px;
`;

@ -25,7 +25,6 @@ type Props = {
}
const Wrapper = styled.div`
width: 360px;
`;
const Header = styled.div`

@ -18,7 +18,7 @@ type Props = {
}
const Wrapper = styled.div`
width: 360px;
max-width: 360px;
padding: 30px 48px;
`;

@ -4,7 +4,7 @@ import * as React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import colors from 'config/colors';
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
import { FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables';
type Props = {
onClick: () => void;
@ -24,6 +24,11 @@ const Wrapper = styled.button`
transition: all 0.3s;
cursor: pointer;
@media screen and (max-width: ${SCREEN_SIZE.XS}) {
width: 50px;
height: 50px;
}
&:first-child {
margin-left: 0px;
}

@ -80,4 +80,22 @@ export const SLIDE_DOWN = keyframes`
100% {
transform: translateY(0%);
}
`;
export const SLIDE_RIGHT = keyframes`
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0%);
}
`;
export const SLIDE_LEFT = keyframes`
0% {
transform: translateX(0%);
}
100% {
transform: translateX(-100%);
}
`;

@ -1,3 +1,15 @@
// Bootstrap 3 breakpoints
/* XS - Extra Small Devices, Phones */
/* SM - Small Devices, Tablets */
/* MD - Medium Devices, Desktops */
/* LG - Large Devices, Wide Screens */
export const SCREEN_SIZE = {
XS: '480px',
SM: '768px',
MD: '992px',
LG: '1170px',
};
// OLD UNITS
// SMALLEST: '10px',
// SMALLER: '12px',
@ -13,7 +25,6 @@
// H3: '14px',
// H4: '12px',
// COUNTER: '11px',
export const FONT_SIZE = {
SMALL: '0.8571rem',
BASE: '1rem',

@ -6,6 +6,7 @@ import { DEVICE, TRANSPORT } from 'trezor-connect';
import * as MODAL from 'actions/constants/modal';
import * as WALLET from 'actions/constants/wallet';
import * as CONNECT from 'actions/constants/TrezorConnect';
import * as ACCOUNT from 'actions/constants/account';
import type { Action, RouterLocationState, TrezorDevice } from 'flowtype';
@ -14,6 +15,7 @@ type State = {
online: boolean;
dropdownOpened: boolean;
showBetaDisclaimer: boolean;
showSidebar: boolean;
initialParams: ?RouterLocationState;
initialPathname: ?string;
firstLocationChange: boolean;
@ -27,6 +29,7 @@ const initialState: State = {
dropdownOpened: false,
firstLocationChange: true,
showBetaDisclaimer: false,
showSidebar: true,
initialParams: null,
initialPathname: null,
disconnectRequest: null,
@ -71,6 +74,11 @@ export default function wallet(state: State = initialState, action: Action): Sta
...state,
dropdownOpened: false,
};
case ACCOUNT.UPDATE_SELECTED_ACCOUNT:
return {
...state,
showSidebar: false,
};
case CONNECT.DISCONNECT_REQUEST:
return {
@ -94,6 +102,12 @@ export default function wallet(state: State = initialState, action: Action): Sta
selectedDevice: action.device,
};
case WALLET.TOGGLE_SIDEBAR:
return {
...state,
showSidebar: !state.showSidebar,
};
case WALLET.SHOW_BETA_DISCLAIMER:
return {
...state,

@ -8,7 +8,7 @@ import animationStyles from './Animations';
const baseStyles = createGlobalStyle`
html, body {
width: 100%;
height: 100%;
min-height: 100vh;
position: relative;
font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
font-weight: ${FONT_WEIGHT.NORMAL};
@ -43,7 +43,7 @@ const baseStyles = createGlobalStyle`
}
#trezor-wallet-root {
height: 100%;
min-height: 100vh;
}
${animationStyles};

@ -8,7 +8,9 @@ import { H2 } from 'components/Heading';
import ChromeImage from 'images/browser-chrome.png';
import FirefoxImage from 'images/browser-firefox.png';
const Wrapper = styled.div``;
const Wrapper = styled.div`
padding: 24px 0px;
`;
const ChooseBrowserWrapper = styled.div`
display: flex;

@ -8,7 +8,7 @@ import Button from 'components/Button';
import { H2 } from 'components/Heading';
import { PULSATE } from 'config/animations';
import colors from 'config/colors';
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
import { FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables';
import CaseImage from 'images/macbook.png';
import Link from 'components/Link';
@ -18,31 +18,50 @@ type Props = {
showDisconnect: boolean,
};
const StyledConnectDevice = styled.div`
padding: 0px 48px;
`;
const Title = styled.div`
margin-top: 60px;
`;
const Wrapper = styled.div`
display: flex;
justify-content: space-around;
align-items: center;
width: 400px;
margin: 0 auto;
padding: 36px 0;
justify-content: center;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
align-content: center;
flex-direction: column;
}
`;
const ConnectTrezorWrapper = styled.div`
position: relative;
top: 1px;
margin: 15px 15px 0px 15px;
animation: ${PULSATE} 1.3s ease-out infinite;
color: ${colors.GREEN_PRIMARY};
font-size: ${FONT_SIZE.BIG};
font-weight: ${FONT_WEIGHT.MEDIUM};
`;
const StyledP = styled(P)`
line-height: auto;
margin: 15px 15px 0px 15px;
`;
const StyledButton = styled(Button)`
margin: 15px 15px 5px 15px;
`;
const Image = styled.img`
width: 777px;
min-height: 500px;
width: 100%;
max-width: 777px;
height: auto;
margin: auto;
background-repeat: no-repeat;
background-position: center 0px;
@ -105,7 +124,7 @@ class ConnectDevice extends PureComponent<Props> {
render() {
return (
<div>
<StyledConnectDevice>
<Title>
<H2 claim>The private bank in your hands.</H2>
<P>Trezor Wallet is an easy-to-use interface for your Trezor.</P>
@ -124,10 +143,10 @@ class ConnectDevice extends PureComponent<Props> {
</ConnectTrezorWrapper>
{this.props.showWebUsb && !this.props.showDisconnect && (
<React.Fragment>
<P>and</P>
<Button isWebUsb>
<StyledP>and</StyledP>
<StyledButton isWebUsb>
Check for devices
</Button>
</StyledButton>
</React.Fragment>
)}
</Wrapper>
@ -156,7 +175,7 @@ class ConnectDevice extends PureComponent<Props> {
</StyledLink>
</P>
</Footer>
</div>
</StyledConnectDevice>
);
}
}

@ -5,7 +5,6 @@ import styled from 'styled-components';
import Notification from 'components/Notification';
const Wrapper = styled.div`
min-width: 720px;
width: 100%;
`;

@ -18,8 +18,7 @@ type Props = {
}
const Wrapper = styled.div`
min-height: 100%;
min-width: 720px;
min-height: 100vh;
display: flex;
flex-direction: column;

@ -13,7 +13,6 @@ import Button from 'components/Button';
import LandingWrapper from 'views/Landing/components/LandingWrapper';
const Wrapper = styled.div`
min-width: 720px;
width: 100%;
display: flex;
flex-direction: column;

@ -6,7 +6,7 @@ import colors from 'config/colors';
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
import { Select } from 'components/Select';
import Link from 'components/Link';
import { H1 } from 'components/Heading';
import { H1, H2 } from 'components/Heading';
import Button from 'components/Button';
import P from 'components/Paragraph';
import Icon from 'components/Icon';
@ -42,6 +42,7 @@ const Wrapper = styled.div`
justify-content: center;
align-items: center;
max-width: 500px;
padding: 0 24px;
`;
const Top = styled.div`
@ -62,6 +63,7 @@ const TitleHeader = styled(H1)`
font-size: ${FONT_SIZE.HUGE};
justify-content: center;
align-items: center;
flex-wrap: wrap;
`;
const Version = styled.span`
@ -81,12 +83,15 @@ const LearnMoreText = styled.span`
const SelectWrapper = styled(Select)`
margin-right: 10px;
width: 180px;
margin-bottom: 5px;
`;
const Download = styled.div`
margin: 24px auto;
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: center;
`;
const DownloadBridgeButton = styled(Button)`
@ -94,6 +99,7 @@ const DownloadBridgeButton = styled(Button)`
padding-bottom: 5px;
display: flex;
align-items: center;
margin-bottom: 5px;
`;
const GoBack = styled.span`
@ -110,7 +116,7 @@ const Ol = styled.ul`
margin: 0 auto;
color: ${colors.TEXT_SECONDARY};
font-size: ${FONT_SIZE.BIG};
padding: 10px 0 15px 25px;
padding: 0px 0 15px 25px;
text-align: left;
`;
@ -175,6 +181,7 @@ class InstallBridge extends PureComponent<Props, State> {
</DownloadBridgeButton>
</Link>
</Download>
<H2>Changelog</H2>
<Ol>
{this.props.transport.bridge.changelog.map(entry => (
<Li key={entry}>{entry}</Li>

@ -4,7 +4,7 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import Loader from 'components/Loader';
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
import { FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables';
import { H1 } from 'components/Heading';
import P from 'components/Paragraph';
import colors from 'config/colors';
@ -26,6 +26,10 @@ const Wrapper = styled.div`
flex: 1;
flex-direction: column;
padding: 40px 35px 40px 35px;
@media screen and (max-width: ${SCREEN_SIZE.SM}){
padding: 20px 35px;
}
`;
const Loading = styled.div`
@ -41,6 +45,7 @@ const Title = styled(H1)`
font-weight: ${FONT_WEIGHT.NORMAL};
color: ${props => (props.type === 'progress' ? colors.TEXT_SECONDARY : '')};
margin-left: 10px;
text-align: center;
`;
const Message = styled(P)`

@ -19,6 +19,7 @@ import Divider from '../Divider';
const Wrapper = styled.div`
position: absolute;
z-index: 1;
width: 100%;
padding-bottom: 8px;
border-bottom: 1px solid #E3E3E3;

@ -0,0 +1,60 @@
/* @flow */
import * as React from 'react';
import styled from 'styled-components';
import colors from 'config/colors';
import { SCREEN_SIZE } from 'config/variables';
import { SLIDE_RIGHT, SLIDE_LEFT } from 'config/animations';
type Props = {
children?: React.Node,
isOpen: boolean,
}
type State = {
footerFixed: boolean,
}
const AbsoluteWrapper = styled.aside`
width: 320px;
position: relative;
overflow-y: auto;
background: ${colors.MAIN};
border-top-left-radius: 4px;
border-right: 1px solid ${colors.DIVIDER};
overflow-x: hidden;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
position: absolute;
height: calc(100vh - 52px);
z-index: 200;
top: 52px;
animation: ${props => (props.isOpen ? SLIDE_RIGHT : SLIDE_LEFT)} 0.25s cubic-bezier(0.17, 0.04, 0.03, 0.94) forwards;
}
@media screen and (max-width: ${SCREEN_SIZE.LG}) {
border-top-left-radius: 0px;
}
`;
const SidebarWrapper = styled.div`
height: 100%;
display: flex;
flex-direction: column;
`;
export default class Sidebar extends React.PureComponent<Props, State> {
render() {
return (
<AbsoluteWrapper isOpen={this.props.isOpen}>
<SidebarWrapper>
{this.props.children}
</SidebarWrapper>
</AbsoluteWrapper>
);
}
}

@ -1,189 +0,0 @@
/* @flow */
import * as React from 'react';
import raf from 'raf';
import { getViewportHeight, getScrollX, getScrollY } from 'utils/windowUtils';
import styled from 'styled-components';
import colors from 'config/colors';
type Props = {
children?: React.Node,
}
type State = {
prevScrollY: number;
asideMinHeight: number,
wrapperTopOffset: number,
wrapperLeftOffset: number,
wrapperBottomPadding: number,
footerFixed: boolean,
}
const AsideWrapper = styled.aside.attrs(props => ({
style: { minHeight: props.minHeight },
}))`
position: relative;
top: 0px;
width: 320px;
min-width: 320px;
overflow: hidden;
background: ${colors.MAIN};
border-right: 1px solid ${colors.DIVIDER};
border-top-left-radius: 4px;
@media screen and (max-width: 1170px) {
border-top-left-radius: 0px;
}
`;
const StickyContainerWrapper = styled.div.attrs(props => ({
style: {
top: props.top,
left: props.left,
paddingBottom: props.paddingBottom,
},
}))`
position: fixed;
border-right: 1px solid ${colors.DIVIDER};
width: 320px;
overflow: hidden;
`;
export default class StickyContainer extends React.PureComponent<Props, State> {
constructor() {
super();
this.state = {
prevScrollY: 0,
asideMinHeight: 0,
wrapperTopOffset: 0,
wrapperLeftOffset: 0,
wrapperBottomPadding: 0,
footerFixed: false,
};
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.handleScroll);
this.update();
}
componentDidUpdate(prevProps: Props, newState: State) {
// recalculate view only if props was changed
// ignore when state is changed
if (this.state === newState) raf(this.update);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleScroll);
}
update = () => {
this.recalculatePosition();
}
handleScroll = () => raf(this.update);
asideRefCallback = (element: ?HTMLElement) => {
this.aside = element;
}
wrapperRefCallback = (element: ?HTMLElement) => {
this.wrapper = element;
}
footerRefCallback = (element: ?HTMLElement) => {
this.footer = element;
}
aside: ?HTMLElement;
wrapper: ?HTMLElement;
footer: ?HTMLElement;
recalculatePosition() {
const { aside, wrapper, footer } = this;
if (!aside || !wrapper || !footer) return;
const viewportHeight = getViewportHeight();
const asideBounds = aside.getBoundingClientRect();
const wrapperBounds = wrapper.getBoundingClientRect();
const footerBounds = footer.getBoundingClientRect();
const isHeaderFixed = asideBounds.top < 0;
const isWrapperBiggerThanViewport = wrapperBounds.height > viewportHeight;
const state = { ...this.state };
const scrollX = getScrollX();
const scrollY = getScrollY();
if (isHeaderFixed) {
if (isWrapperBiggerThanViewport) {
const scrollDirection = scrollY >= state.prevScrollY ? 'down' : 'up';
const topOutOfBounds: boolean = (wrapperBounds.top > 0 && scrollDirection === 'up');
const bottomOutOfBounds: boolean = (footerBounds.bottom <= viewportHeight && scrollDirection === 'down');
if (!topOutOfBounds && !bottomOutOfBounds) {
// neither "top" or "bottom" was reached
// scroll whole wrapper
const distanceScrolled = Math.abs(scrollY - state.prevScrollY);
state.wrapperTopOffset += scrollDirection === 'down' ? -distanceScrolled : distanceScrolled;
}
}
// make sure that wrapper will not be over scrolled
if (state.wrapperTopOffset > 0) state.wrapperTopOffset = 0;
const maxScrollTop = viewportHeight - wrapperBounds.height;
if (maxScrollTop < 0 && state.wrapperTopOffset < maxScrollTop) state.wrapperTopOffset = maxScrollTop;
} else {
// update wrapper "top" to be same as "aside" element
state.wrapperTopOffset = asideBounds.top;
}
if (isWrapperBiggerThanViewport) {
state.footerFixed = false;
} else if (state.footerFixed) {
if (footerBounds.top < wrapperBounds.bottom - footerBounds.height) {
state.footerFixed = false;
}
} else if (footerBounds.bottom < viewportHeight) {
state.footerFixed = asideBounds.height > wrapperBounds.height;
}
state.prevScrollY = scrollY;
state.asideMinHeight = wrapperBounds.height;
state.wrapperBottomPadding = state.footerFixed ? footerBounds.height : 0;
// update wrapper "left" position
state.wrapperLeftOffset = scrollX > 0 ? -scrollX : asideBounds.left;
this.setState(state);
}
render() {
return (
<AsideWrapper
footerFixed={this.state.footerFixed}
minHeight={this.state.asideMinHeight}
ref={this.asideRefCallback}
onScroll={this.handleScroll}
onTouchStart={this.handleScroll}
onTouchMove={this.handleScroll}
onTouchEnd={this.handleScroll}
>
<StickyContainerWrapper
paddingBottom={this.state.wrapperBottomPadding}
top={this.state.wrapperTopOffset}
left={this.state.wrapperLeftOffset}
ref={this.wrapperRefCallback}
>
{React.Children.map(this.props.children, (child) => { // eslint-disable-line arrow-body-style
return child.key === 'sticky-footer' ? React.cloneElement(child, {
ref: this.footerRefCallback,
position: this.state.footerFixed ? 'fixed' : 'relative',
}) : child;
})}
</StickyContainerWrapper>
</AsideWrapper>
);
}
}

@ -15,12 +15,13 @@ import * as deviceUtils from 'utils/device';
import AccountMenu from './components/AccountMenu';
import CoinMenu from './components/CoinMenu';
import DeviceMenu from './components/DeviceMenu';
import StickyContainer from './components/StickyContainer';
import Sidebar from './components/Sidebar';
import type { Props } from './components/common';
const Header = styled(DeviceHeader)`
border-right: 1px solid ${colors.BACKGROUND};
flex: 0 0 auto;
`;
const Counter = styled.div`
@ -56,6 +57,7 @@ const Footer = styled.div.attrs(props => ({
`;
const Body = styled.div`
flex: 1 0 auto;
width: 320px;
min-height: ${props => (props.minHeight ? `${props.minHeight}px` : '0px')};
`;
@ -202,10 +204,7 @@ class LeftNavigation extends React.PureComponent<Props, State> {
const { selectedDevice, dropdownOpened } = props.wallet;
const isDeviceAccessible = deviceUtils.isDeviceAccessible(selectedDevice);
return (
<StickyContainer
location={props.router.location.pathname}
deviceSelection={this.props.wallet.dropdownOpened}
>
<Sidebar isOpen={props.wallet.showSidebar}>
<Header
isSelected
isHoverable={false}
@ -249,7 +248,7 @@ class LeftNavigation extends React.PureComponent<Props, State> {
</A>
</Help>
</Footer>
</StickyContainer>
</Sidebar>
);
}
}

@ -2,7 +2,7 @@
import styled from 'styled-components';
import React from 'react';
import { FONT_SIZE, FONT_WEIGHT } from 'config/variables';
import { FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables';
import { NavLink } from 'react-router-dom';
import { connect } from 'react-redux';
import colors from 'config/colors';
@ -24,6 +24,14 @@ const Wrapper = styled.div`
padding: 0px 30px 0 35px;
overflow-y: hidden;
overflow-x: auto;
@media screen and (max-width: ${SCREEN_SIZE.MD}) {
justify-content: space-between;
}
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
padding: 0px 16px;
}
`;
const StyledNavLink = styled(NavLink)`
@ -34,6 +42,15 @@ const StyledNavLink = styled(NavLink)`
padding: 20px 35px;
white-space: nowrap;
@media screen and (max-width: ${SCREEN_SIZE.MD}) {
padding: 20px 10px;
}
@media screen and (max-width: ${SCREEN_SIZE.XS}) {
font-size: ${FONT_SIZE.BASE};
padding: 20px 10px;
}
&.active,
&:hover {
transition: all 0.3s ease-in-out;

@ -2,39 +2,48 @@
import * as React from 'react';
import colors from 'config/colors';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import { connect } from 'react-redux';
import { Route, withRouter } from 'react-router-dom';
import type { MapStateToProps } from 'react-redux';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import type { State } from 'flowtype';
import type { WalletAction } from 'actions/WalletActions';
import { toggleSidebar } from 'actions/WalletActions';
import { bindActionCreators } from 'redux';
import Header from 'components/Header';
import Footer from 'components/Footer';
import ModalContainer from 'components/modals/Container';
import AppNotifications from 'components/notifications/App';
import ContextNotifications from 'components/notifications/Context';
import { SCREEN_SIZE } from 'config/variables';
import Log from 'components/Log';
import Backdrop from 'components/Backdrop';
import LeftNavigation from './components/LeftNavigation/Container';
import TopNavigationAccount from './components/TopNavigationAccount';
import TopNavigationDeviceSettings from './components/TopNavigationDeviceSettings';
type WalletContainerProps = {
type StateProps = {
wallet: $ElementType<State, 'wallet'>,
children?: React.Node
children?: React.Node,
}
// type ContentProps = {
// children?: React.Node
// }
type DispatchProps = {
toggleSidebar: WalletAction,
};
type OwnProps = {};
export type Props = StateProps & DispatchProps;
const AppWrapper = styled.div`
position: relative;
min-height: 100%;
min-width: 720px;
min-height: 100vh;
display: flex;
flex-direction: column;
background: ${colors.BACKGROUND};
@ -67,7 +76,15 @@ const MainContent = styled.article`
flex-direction: column;
overflow: auto;
border-top-right-radius: 4px;
@media screen and (max-width: ${SCREEN_SIZE.SM}){
${props => props.preventBgScroll && css`
position: fixed;
width: 100%;
min-height: calc(100vh - 52px);
`}
}
@media screen and (max-width: 1170px) {
border-top-right-radius: 0px;
}
@ -87,13 +104,22 @@ const Body = styled.div`
flex-direction: column;
`;
const Wallet = (props: WalletContainerProps) => (
const StyledBackdrop = styled(Backdrop)`
display: none;
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
display: initial;
}
`;
const Wallet = (props: Props) => (
<AppWrapper>
<Header />
<Header sidebarEnabled={!!props.wallet.selectedDevice} sidebarOpened={props.wallet.showSidebar} toggleSidebar={props.toggleSidebar} />
<AppNotifications />
<WalletWrapper>
<StyledBackdrop show={props.wallet.showSidebar} onClick={props.toggleSidebar} animated />
{props.wallet.selectedDevice && <LeftNavigation />}
<MainContent>
<MainContent preventBgScroll={props.wallet.showSidebar}>
<Navigation>
<Route path="/device/:device/network/:network/account/:account" component={TopNavigationAccount} />
<Route path="/device/:device/device-settings" component={TopNavigationDeviceSettings} />
@ -110,10 +136,14 @@ const Wallet = (props: WalletContainerProps) => (
</AppWrapper>
);
const mapStateToProps: MapStateToProps<State, {}, WalletContainerProps> = (state: State): WalletContainerProps => ({
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
wallet: state.wallet,
});
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
toggleSidebar: bindActionCreators(toggleSidebar, dispatch),
});
export default withRouter(
connect(mapStateToProps, null)(Wallet),
connect(mapStateToProps, mapDispatchToProps)(Wallet),
);

@ -7,6 +7,8 @@ import Title from 'views/Wallet/components/Title';
import Button from 'components/Button';
import Content from 'views/Wallet/components/Content';
import colors from 'config/colors';
import { SCREEN_SIZE } from 'config/variables';
import type { Props } from './Container';
@ -14,6 +16,7 @@ const Wrapper = styled.div`
display: flex;
flex: 1;
flex-direction: row;
flex-wrap: wrap;
background: ${colors.WHITE};
`;
@ -52,14 +55,22 @@ const StyledButton = styled(Button)`
const Column = styled.div`
display: flex;
flex: 1;
flex: 1 1 50%;
flex-direction: column;
@media screen and (max-width: ${SCREEN_SIZE.XS}) {
flex: 1 1 100%;
}
`;
const Sign = styled(Column)``;
const Verify = styled(Column)`
padding-left: 20px;
@media screen and (max-width: ${SCREEN_SIZE.XS}) {
padding-left: 0px;
}
`;
class SignVerify extends Component <Props> {

@ -16,6 +16,10 @@ const StyledSVG = styled.svg`
margin-bottom: 24px;
`;
const StyledP = styled(P)`
text-align: center;
`;
const AddTokenMessage = () => (
<Wrapper>
<StyledSVG width="84px" height="76px" viewBox="0 0 84 76">
@ -51,7 +55,7 @@ const AddTokenMessage = () => (
</g>
</StyledSVG>
<H2>Add your tokens</H2>
<P isSmaller>Search for the token or add them manually by pasting token address into search input.</P>
<StyledP isSmaller>Search for the token or add them manually by pasting token address into search input.</StyledP>
</Wrapper>
);
export default AddTokenMessage;

@ -21,7 +21,7 @@ type State = {
};
const Wrapper = styled.div`
padding-bottom: 28px;
padding-bottom: ${props => (props.isHidden ? '0px' : '28px')};
position: relative;
display: flex;
@ -116,7 +116,7 @@ class AccountBalance extends PureComponent<Props, State> {
}
return (
<Wrapper>
<Wrapper isHidden={this.state.isHidden}>
<HideBalanceIconWrapper
onClick={() => this.handleHideBalanceIconClick()}
>

@ -50,10 +50,6 @@ const AccountTitle = styled.div`
color: ${colors.WALLET_TITLE};
`;
const StyledCoinLogo = styled(CoinLogo)`
margin-right: 10px;
`;
const StyledIcon = styled(Icon)`
position: relative;
top: -7px;
@ -93,7 +89,7 @@ const AccountSummary = (props: Props) => {
<React.Fragment>
<AccountHeading>
<AccountName>
<StyledCoinLogo network={account.network} />
<CoinLogo network={account.network} />
<AccountTitle>Account #{parseInt(account.index, 10) + 1}</AccountTitle>
</AccountName>
<Link href={explorerLink} isGray>See full transaction history</Link>
@ -123,7 +119,7 @@ const AccountSummary = (props: Props) => {
defaultOptions
value={null}
isMulti={false}
placeholder="Type in a token name or paste a token address directly"
placeholder="Type in a token name or a token address"
loadingMessage={() => 'Loading...'}
noOptionsMessage={() => 'Token not found'}
onChange={(token) => {

@ -22,7 +22,7 @@ type State = {
};
const Wrapper = styled.div`
padding-bottom: 28px;
padding-bottom: ${props => (props.isHidden ? '0px' : '28px')};
position: relative;
display: flex;
@ -117,7 +117,7 @@ class AccountBalance extends PureComponent<Props, State> {
}
return (
<Wrapper>
<Wrapper isHidden={this.state.isHidden}>
<HideBalanceIconWrapper
onClick={() => this.handleHideBalanceIconClick()}
>

@ -47,10 +47,6 @@ const AccountTitle = styled.div`
color: ${colors.WALLET_TITLE};
`;
const StyledCoinLogo = styled(CoinLogo)`
margin-right: 10px;
`;
const StyledIcon = styled(Icon)`
position: relative;
top: -7px;
@ -86,7 +82,7 @@ const AccountSummary = (props: Props) => {
<React.Fragment>
<AccountHeading>
<AccountName>
<StyledCoinLogo network={account.network} />
<CoinLogo network={account.network} />
<AccountTitle>Account #{parseInt(account.index, 10) + 1}</AccountTitle>
</AccountName>
{ !account.empty && <Link href={explorerLink} isGray>See full transaction history</Link> }

@ -22,10 +22,14 @@ const StyledP = styled(P)`
text-align: center;
`;
const StyledH1 = styled(H1)`
text-align: center;
`;
const Bootloader = () => (
<Wrapper>
<Row>
<H1>Your device is in firmware update mode</H1>
<StyledH1>Your device is in firmware update mode</StyledH1>
<StyledP>Please re-connect it</StyledP>
</Row>
</Wrapper>

@ -28,6 +28,10 @@ const StyledP = styled(P)`
text-align: center;
`;
const StyledH1 = styled(H1)`
text-align: center;
`;
const DeviceSettings = () => (
<Content>
<Section>
@ -37,7 +41,7 @@ const DeviceSettings = () => (
color={colors.WARNING_PRIMARY}
icon={ICONS.WARNING}
/>
<H1>Device settings is under construction</H1>
<StyledH1>Device settings is under construction</StyledH1>
<StyledP>Please use Bitcoin wallet interface to change your device settings</StyledP>
<Link href="https://beta-wallet.trezor.io/">
<Button>Take me to the Bitcoin wallet</Button>

@ -23,6 +23,10 @@ const Row = styled.div`
padding: 50px 0;
`;
const StyledH1 = styled(H1)`
text-align: center;
`;
const WalletSettings = () => (
<Content>
<Section>
@ -32,7 +36,7 @@ const WalletSettings = () => (
color={colors.WARNING_PRIMARY}
icon={icons.WARNING}
/>
<H1>Wallet settings is under construction</H1>
<StyledH1>Wallet settings is under construction</StyledH1>
<Link to="/">
<Button>Take me back</Button>
</Link>

Loading…
Cancel
Save