Browse Source

Improve MainSideBar performance

master
meriadec 7 years ago
parent
commit
21be590d49
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 37
      src/components/MainSideBar/AccountListItem.js
  2. 43
      src/components/MainSideBar/AddAccountButton.js
  3. 110
      src/components/MainSideBar/index.js
  4. 23
      src/components/base/SideBar/SideBarList.js
  5. 17
      src/components/base/SideBar/SideBarListItem.js
  6. 1
      src/components/base/SideBar/index.js
  7. 21
      src/components/base/SideBar/stories.js

37
src/components/MainSideBar/AccountListItem.js

@ -0,0 +1,37 @@
// @flow
import React, { PureComponent } from 'react'
import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react'
import type { Account } from '@ledgerhq/live-common/lib/types'
import FormattedVal from 'components/base/FormattedVal'
import { SideBarListItem } from 'components/base/SideBar'
export default class AccountListItem extends PureComponent<{
account: Account,
push: string => void,
isActive: boolean,
}> {
render() {
const { account, push, isActive } = this.props
const accountURL = `/account/${account.id}`
const item = {
label: account.name,
desc: () => (
<FormattedVal
alwaysShowSign={false}
color="graphite"
unit={account.unit}
showCode
val={account.balance || 0}
/>
),
iconActiveColor: account.currency.color,
icon: getCryptoCurrencyIcon(account.currency),
onClick: () => push(accountURL),
isActive,
}
return <SideBarListItem {...item} />
}
}

43
src/components/MainSideBar/AddAccountButton.js

@ -0,0 +1,43 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { Tabbable } from 'components/base/Box'
import Tooltip from 'components/base/Tooltip'
import IconCirclePlus from 'icons/CirclePlus'
import { rgba } from 'styles/helpers'
const PlusWrapper = styled(Tabbable).attrs({
p: 1,
cursor: 'pointer',
borderRadius: 1,
})`
color: ${p => p.theme.colors.smoke};
&:hover {
color: ${p => p.theme.colors.dark};
}
border: 1px solid transparent;
&:focus {
outline: none;
border-color: ${p => rgba(p.theme.colors.wallet, 0.3)};
}
`
export default class AddAccountButton extends PureComponent<{
onClick: () => void,
tooltipText: string,
}> {
render() {
const { onClick, tooltipText } = this.props
return (
<Tooltip render={() => tooltipText}>
<PlusWrapper onClick={onClick}>
<IconCirclePlus size={16} />
</PlusWrapper>
</Tooltip>
)
}
}

110
src/components/MainSideBar.js → src/components/MainSideBar/index.js

@ -1,13 +1,11 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { compose } from 'redux' import { compose } from 'redux'
import { withRouter } from 'react-router' import { withRouter } from 'react-router'
import { push } from 'react-router-redux' import { push } from 'react-router-redux'
import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react'
import type { Location } from 'react-router' import type { Location } from 'react-router'
import type { Account } from '@ledgerhq/live-common/lib/types' import type { Account } from '@ledgerhq/live-common/lib/types'
@ -17,25 +15,23 @@ import type { UpdateStatus } from 'reducers/update'
import { MODAL_RECEIVE, MODAL_SEND } from 'config/constants' import { MODAL_RECEIVE, MODAL_SEND } from 'config/constants'
import { rgba } from 'styles/helpers'
import { accountsSelector } from 'reducers/accounts' import { accountsSelector } from 'reducers/accounts'
import { openModal } from 'reducers/modals' import { openModal } from 'reducers/modals'
import { getUpdateStatus } from 'reducers/update' import { getUpdateStatus } from 'reducers/update'
import Tooltip from 'components/base/Tooltip' import { SideBarList, SideBarListItem } from 'components/base/SideBar'
import { SideBarList } from 'components/base/SideBar' import Box from 'components/base/Box'
import Box, { Tabbable } from 'components/base/Box'
import Space from 'components/base/Space' import Space from 'components/base/Space'
import FormattedVal from 'components/base/FormattedVal'
import IconManager from 'icons/Manager' import IconManager from 'icons/Manager'
import IconPieChart from 'icons/PieChart' import IconPieChart from 'icons/PieChart'
import IconCirclePlus from 'icons/CirclePlus'
import IconReceive from 'icons/Receive' import IconReceive from 'icons/Receive'
import IconSend from 'icons/Send' import IconSend from 'icons/Send'
import IconExchange from 'icons/Exchange' import IconExchange from 'icons/Exchange'
import AccountListItem from './AccountListItem'
import AddAccountButton from './AddAccountButton'
const mapStateToProps = state => ({ const mapStateToProps = state => ({
accounts: accountsSelector(state), accounts: accountsSelector(state),
updateStatus: getUpdateStatus(state), updateStatus: getUpdateStatus(state),
@ -56,7 +52,7 @@ type Props = {
} }
class MainSideBar extends PureComponent<Props> { class MainSideBar extends PureComponent<Props> {
push(to: string) { push = (to: string) => {
const { push } = this.props const { push } = this.props
const { const {
location: { pathname }, location: { pathname },
@ -67,113 +63,93 @@ class MainSideBar extends PureComponent<Props> {
push(to) push(to)
} }
handleClickDashboard = () => this.push('/')
handleOpenSendModal = () => this.props.openModal(MODAL_SEND)
handleOpenReceiveModal = () => this.props.openModal(MODAL_RECEIVE)
handleClickManager = () => this.push('/manager')
handleClickExchange = () => this.push('/exchange')
handleOpenImportModal = () => this.props.openModal('importAccounts')
render() { render() {
const { t, accounts, openModal, location, updateStatus } = this.props const { t, accounts, location, updateStatus } = this.props
const { pathname } = location const { pathname } = location
const navigationItems = [ const navigationItems = [
{ {
value: 'dashboard', key: 'dashboard',
label: t('dashboard:title'), label: t('dashboard:title'),
icon: IconPieChart, icon: IconPieChart,
iconActiveColor: 'wallet', iconActiveColor: 'wallet',
onClick: () => this.push('/'), onClick: this.handleClickDashboard,
isActive: pathname === '/', isActive: pathname === '/',
hasNotif: updateStatus === 'downloaded', hasNotif: updateStatus === 'downloaded',
}, },
{ {
value: 'send', key: 'send',
label: t('send:title'), label: t('send:title'),
icon: IconSend, icon: IconSend,
iconActiveColor: 'wallet', iconActiveColor: 'wallet',
onClick: () => openModal(MODAL_SEND), onClick: this.handleOpenSendModal,
}, },
{ {
value: 'receive', key: 'receive',
label: t('receive:title'), label: t('receive:title'),
icon: IconReceive, icon: IconReceive,
iconActiveColor: 'wallet', iconActiveColor: 'wallet',
onClick: () => openModal(MODAL_RECEIVE), onClick: this.handleOpenReceiveModal,
}, },
{ {
value: 'manager', key: 'manager',
label: t('sidebar:manager'), label: t('sidebar:manager'),
icon: IconManager, icon: IconManager,
iconActiveColor: 'wallet', iconActiveColor: 'wallet',
onClick: () => this.push('/manager'), onClick: this.handleClickManager,
isActive: pathname === '/manager', isActive: pathname === '/manager',
}, },
{ {
value: 'exchange', key: 'exchange',
label: t('sidebar:exchange'), label: t('sidebar:exchange'),
icon: IconExchange, icon: IconExchange,
iconActiveColor: 'wallet', iconActiveColor: 'wallet',
onClick: () => this.push('/exchange'), onClick: this.handleClickExchange,
isActive: pathname === '/exchange', isActive: pathname === '/exchange',
}, },
] ]
const accountsItems = accounts.map(account => {
const accountURL = `/account/${account.id}`
return {
value: account.id,
label: account.name,
desc: () => (
<FormattedVal
alwaysShowSign={false}
color="graphite"
unit={account.unit}
showCode
val={account.balance || 0}
/>
),
iconActiveColor: account.currency.color,
icon: getCryptoCurrencyIcon(account.currency),
onClick: () => this.push(accountURL),
isActive: pathname === accountURL,
}
})
return ( return (
<Box bg="white" style={{ width: 230 }}> <Box bg="white" style={{ width: 230 }}>
<Space of={70} /> <Space of={70} />
<SideBarList title={t('sidebar:menu')} items={navigationItems} /> <SideBarList title={t('sidebar:menu')}>
{navigationItems.map(item => <SideBarListItem key={item.key} {...item} />)}
</SideBarList>
<Space of={40} /> <Space of={40} />
<SideBarList <SideBarList
scroll scroll
title={t('sidebar:accounts')} title={t('sidebar:accounts')}
titleRight={ titleRight={
<Tooltip render={() => t('importAccounts:title')}> <AddAccountButton
<PlusWrapper onClick={() => openModal('importAccounts')}> tooltipText={t('importAccounts:title')}
<IconCirclePlus size={16} /> onClick={this.handleOpenImportModal}
</PlusWrapper> />
</Tooltip>
} }
items={accountsItems}
emptyText={t('emptyState:sidebar.text')} emptyText={t('emptyState:sidebar.text')}
/> >
{accounts.map(account => (
<AccountListItem
key={account.id}
account={account}
push={this.push}
isActive={pathname === `/account/${account.id}`}
/>
))}
</SideBarList>
</Box> </Box>
) )
} }
} }
const PlusWrapper = styled(Tabbable).attrs({
p: 1,
cursor: 'pointer',
borderRadius: 1,
})`
color: ${p => p.theme.colors.smoke};
&:hover {
color: ${p => p.theme.colors.dark};
}
border: 1px solid transparent;
&:focus {
outline: none;
border-color: ${p => rgba(p.theme.colors.wallet, 0.3)};
}
`
const decorate = compose( const decorate = compose(
withRouter, withRouter,
translate(), translate(),

23
src/components/base/SideBar/SideBarList.js

@ -1,28 +1,23 @@
// @flow // @flow
import React, { PureComponent, Fragment } from 'react' import React, { Component, Fragment } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import GrowScroll from 'components/base/GrowScroll' import GrowScroll from 'components/base/GrowScroll'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Space from 'components/base/Space' import Space from 'components/base/Space'
import SideBarListItem from './SideBarListItem'
import type { Item } from './SideBarListItem'
type Props = { type Props = {
items: Item[], children: any,
title?: Node | string, title?: Node | string,
activeValue?: string,
scroll?: boolean, scroll?: boolean,
titleRight?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯ titleRight?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
emptyText?: string, emptyText?: string,
} }
class SideBarList extends PureComponent<Props> { class SideBarList extends Component<Props> {
render() { render() {
const { items, title, activeValue, scroll, titleRight, emptyText, ...props } = this.props const { children, title, scroll, titleRight, emptyText, ...props } = this.props
const ListWrapper = scroll ? GrowScroll : Box const ListWrapper = scroll ? GrowScroll : Box
return ( return (
<Fragment> <Fragment>
@ -35,15 +30,9 @@ class SideBarList extends PureComponent<Props> {
<Space of={20} /> <Space of={20} />
</Fragment> </Fragment>
)} )}
{items.length > 0 ? ( {children ? (
<ListWrapper flow={2} px={3} fontSize={3} {...props}> <ListWrapper flow={2} px={3} fontSize={3} {...props}>
{items.map(item => { {children}
const itemProps = {
item,
isActive: item.isActive || (!!activeValue && activeValue === item.value),
}
return <SideBarListItem key={item.value} {...itemProps} />
})}
</ListWrapper> </ListWrapper>
) : emptyText ? ( ) : emptyText ? (
<Box px={4} ff="Open Sans|Regular" fontSize={3} color="grey"> <Box px={4} ff="Open Sans|Regular" fontSize={3} color="grey">

17
src/components/base/SideBar/SideBarListItem.js

@ -7,7 +7,6 @@ import Box, { Tabbable } from 'components/base/Box'
import { rgba } from 'styles/helpers' import { rgba } from 'styles/helpers'
export type Item = { export type Item = {
value: string,
label: string | (Props => React$Element<any>), label: string | (Props => React$Element<any>),
desc?: Props => any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯ desc?: Props => any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
icon?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯ icon?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
@ -18,20 +17,22 @@ export type Item = {
} }
export type Props = { export type Props = {
item: Item, label: string | (Props => React$Element<any>),
isActive: boolean, desc?: Props => any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
icon?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
iconActiveColor: ?string,
hasNotif?: boolean,
isActive?: boolean,
onClick?: void => void,
isActive?: boolean,
} }
class SideBarListItem extends PureComponent<Props> { class SideBarListItem extends PureComponent<Props> {
render() { render() {
const { const { icon: Icon, label, desc, iconActiveColor, hasNotif, onClick, isActive } = this.props
item: { icon: Icon, label, desc, iconActiveColor, hasNotif, onClick, value },
isActive,
} = this.props
return ( return (
<Container <Container
data-role="side-bar-item" data-role="side-bar-item"
data-roledata={value}
isActive={isActive} isActive={isActive}
iconActiveColor={iconActiveColor} iconActiveColor={iconActiveColor}
onClick={onClick} onClick={onClick}

1
src/components/base/SideBar/index.js

@ -1,3 +1,4 @@
// @flow // @flow
export { default as SideBarList } from './SideBarList' export { default as SideBarList } from './SideBarList'
export { default as SideBarListItem } from './SideBarListItem'

21
src/components/base/SideBar/stories.js

@ -2,9 +2,8 @@
import React from 'react' import React from 'react'
import { storiesOf } from '@storybook/react' import { storiesOf } from '@storybook/react'
import { select } from '@storybook/addon-knobs'
import { SideBarList } from 'components/base/SideBar' import { SideBarList, SideBarListItem } from 'components/base/SideBar'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import IconAccountSettings from 'icons/AccountSettings' import IconAccountSettings from 'icons/AccountSettings'
@ -17,26 +16,27 @@ const stories = storiesOf('Components/base/SideBar', module)
const SIDEBAR_ITEMS = [ const SIDEBAR_ITEMS = [
{ {
value: 'first', key: 'first',
label: 'First', label: 'First',
icon: IconAccountSettings, icon: IconAccountSettings,
iconActiveColor: '#ffae35', iconActiveColor: '#ffae35',
}, },
{ {
value: 'second', key: 'second',
label: 'Second', label: 'Second',
icon: IconPrint, icon: IconPrint,
iconActiveColor: '#0ebdcd', iconActiveColor: '#0ebdcd',
isActive: true,
}, },
{ {
value: 'third', key: 'third',
label: 'Third very very very very long text very long text very long', label: 'Third very very very very long text very long text very long',
icon: IconControls, icon: IconControls,
iconActiveColor: '#27a2db', iconActiveColor: '#27a2db',
hasNotif: true, hasNotif: true,
}, },
{ {
value: 'fourth', key: 'fourth',
label: () => ( label: () => (
<Box> <Box>
{'custom'} {'custom'}
@ -47,7 +47,7 @@ const SIDEBAR_ITEMS = [
iconActiveColor: '#3ca569', iconActiveColor: '#3ca569',
}, },
{ {
value: 'fifth', key: 'fifth',
label: 'Fifth', label: 'Fifth',
icon: IconExclamationCircle, icon: IconExclamationCircle,
iconActiveColor: '#0e76aa', iconActiveColor: '#0e76aa',
@ -55,8 +55,7 @@ const SIDEBAR_ITEMS = [
] ]
stories.add('SideBarList', () => ( stories.add('SideBarList', () => (
<SideBarList <SideBarList>
items={SIDEBAR_ITEMS} {SIDEBAR_ITEMS.map(item => <SideBarListItem key={item.key} {...item} />)}
activeValue={select('activeValue', [...SIDEBAR_ITEMS.map(i => i.value), null], 'third')} </SideBarList>
/>
)) ))

Loading…
Cancel
Save