1
0
mirror of https://github.com/trezor/trezor-wallet synced 2025-01-05 22:00:59 +00:00

add responsive sidebar

This commit is contained in:
slowbackspace 2019-01-24 18:49:33 +01:00
parent bc38fd3ade
commit d80e108754
8 changed files with 199 additions and 31 deletions

View File

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

View File

@ -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 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 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';

View File

@ -3,11 +3,15 @@ import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import colors from 'config/colors'; import colors from 'config/colors';
import { SCREEN_SIZE } from 'config/variables';
import type { toggleSidebar as toggleSidebarType } from 'actions/WalletActions';
const Wrapper = styled.header` const Wrapper = styled.header`
width: 100%; width: 100%;
height: 52px; height: 52px;
background: ${colors.HEADER}; background: ${colors.HEADER};
overflow: hidden;
z-index: 200;
svg { svg {
fill: ${colors.WHITE}; fill: ${colors.WHITE};
@ -30,13 +34,47 @@ const LayoutWrapper = styled.div`
} }
`; `;
const Left = styled.div` const MenuToggler = styled.div`
flex: 1; display: none;
display: flex; flex: 0 0 33%;
justify-content: flex-start; 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 Right = styled.div``; const Logo = styled.div`
flex: 1;
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 Projects = styled.div`
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
display: none;
}
`;
const A = styled.a` const A = styled.a`
color: ${colors.WHITE}; color: ${colors.WHITE};
@ -58,10 +96,17 @@ const A = styled.a`
} }
`; `;
const Header = (): React$Element<string> => ( type Props = {
sidebarOpened?: boolean,
toggleSidebar?: toggleSidebarType,
};
const Header = ({ sidebarOpened, toggleSidebar }: Props) => (
<Wrapper> <Wrapper>
<LayoutWrapper> <LayoutWrapper>
<Left> <MenuToggler onClick={toggleSidebar}>{sidebarOpened ? '✕ Close' : '☰ Menu'}</MenuToggler>
<Logo>
<NavLink to="/"> <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"> <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" /> <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 +118,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 " /> <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> </svg>
</NavLink> </NavLink>
</Left> </Logo>
<Right> <MenuLinks>
<A href="https://trezor.io/" target="_blank" rel="noreferrer noopener">Trezor</A> <Projects>
<A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener">Wiki</A> <A href="https://trezor.io/" target="_blank" rel="noreferrer noopener">Trezor</A>
<A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">Blog</A> <A href="https://wiki.trezor.io/" target="_blank" rel="noreferrer noopener">Wiki</A>
<A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">Support</A> <A href="https://blog.trezor.io/" target="_blank" rel="noreferrer noopener">Blog</A>
</Right> <A href="https://trezor.io/support/" target="_blank" rel="noreferrer noopener">Support</A>
</Projects>
</MenuLinks>
</LayoutWrapper> </LayoutWrapper>
</Wrapper> </Wrapper>
); );

View File

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

View File

@ -4,7 +4,7 @@ import * as React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'styled-components'; import styled from 'styled-components';
import Loader from 'components/Loader'; 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 { H1 } from 'components/Heading';
import P from 'components/Paragraph'; import P from 'components/Paragraph';
import colors from 'config/colors'; import colors from 'config/colors';
@ -26,6 +26,10 @@ const Wrapper = styled.div`
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
padding: 40px 35px 40px 35px; padding: 40px 35px 40px 35px;
@media screen and (max-width: ${SCREEN_SIZE.SM}){
padding: 20px 35px;
}
`; `;
const Loading = styled.div` const Loading = styled.div`

View File

@ -0,0 +1,69 @@
/* @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};
@media screen and (max-width: ${SCREEN_SIZE.SM}) {
position: absolute;
height: calc(100% - 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 MobileSidebarWrapper = styled.div`
height: 100%;
display: flex;
flex-direction: column;
`;
export default class MobileSidebar extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
footerFixed: false,
};
}
render() {
return (
<AbsoluteWrapper isOpen={this.props.isOpen}>
<MobileSidebarWrapper>
{React.Children.map(this.props.children, (child) => { // eslint-disable-line arrow-body-style
return child.key === 'sticky-footer' ? React.cloneElement(child, {
position: this.state.footerFixed ? 'fixed' : 'relative',
}) : child;
})}
</MobileSidebarWrapper>
</AbsoluteWrapper>
);
}
}

View File

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

View File

@ -6,35 +6,44 @@ import styled from 'styled-components';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Route, withRouter } from 'react-router-dom'; 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 { State } from 'flowtype';
import type { WalletAction } from 'actions/WalletActions';
import { toggleSidebar } from 'actions/WalletActions';
import { bindActionCreators } from 'redux';
import Header from 'components/Header'; import Header from 'components/Header';
import Footer from 'components/Footer'; import Footer from 'components/Footer';
import ModalContainer from 'components/modals/Container'; import ModalContainer from 'components/modals/Container';
import AppNotifications from 'components/notifications/App'; import AppNotifications from 'components/notifications/App';
import ContextNotifications from 'components/notifications/Context'; import ContextNotifications from 'components/notifications/Context';
import { SCREEN_SIZE } from 'config/variables';
import Log from 'components/Log'; import Log from 'components/Log';
import Backdrop from 'components/Backdrop';
import LeftNavigation from './components/LeftNavigation/Container'; import LeftNavigation from './components/LeftNavigation/Container';
import TopNavigationAccount from './components/TopNavigationAccount'; import TopNavigationAccount from './components/TopNavigationAccount';
import TopNavigationDeviceSettings from './components/TopNavigationDeviceSettings'; import TopNavigationDeviceSettings from './components/TopNavigationDeviceSettings';
type StateProps = {
type WalletContainerProps = {
wallet: $ElementType<State, 'wallet'>, wallet: $ElementType<State, 'wallet'>,
children?: React.Node children?: React.Node,
} }
// type ContentProps = { type DispatchProps = {
// children?: React.Node toggleSidebar: WalletAction,
// } };
type OwnProps = {};
export type Props = StateProps & DispatchProps;
const AppWrapper = styled.div` const AppWrapper = styled.div`
position: relative; position: relative;
min-height: 100%; min-height: 100%;
min-width: 720px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: ${colors.BACKGROUND}; background: ${colors.BACKGROUND};
@ -87,11 +96,20 @@ const Body = styled.div`
flex-direction: column; 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> <AppWrapper>
<Header /> <Header sidebarOpened={props.wallet.showSidebar} toggleSidebar={props.toggleSidebar} />
<AppNotifications /> <AppNotifications />
<WalletWrapper> <WalletWrapper>
<StyledBackdrop show={props.wallet.showSidebar} onClick={props.toggleSidebar} animated />
{props.wallet.selectedDevice && <LeftNavigation />} {props.wallet.selectedDevice && <LeftNavigation />}
<MainContent> <MainContent>
<Navigation> <Navigation>
@ -110,10 +128,14 @@ const Wallet = (props: WalletContainerProps) => (
</AppWrapper> </AppWrapper>
); );
const mapStateToProps: MapStateToProps<State, {}, WalletContainerProps> = (state: State): WalletContainerProps => ({ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State): StateProps => ({
wallet: state.wallet, wallet: state.wallet,
}); });
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => ({
toggleSidebar: bindActionCreators(toggleSidebar, dispatch),
});
export default withRouter( export default withRouter(
connect(mapStateToProps, null)(Wallet), connect(mapStateToProps, mapDispatchToProps)(Wallet),
); );