make token list a separate screen inside tokens list modal, so adding custom token may be implemented
This commit is contained in:
parent
e4453c7d0c
commit
399aa9f84b
|
@ -3,7 +3,7 @@ import * as React from 'react'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Img from '~/components/layout/Img'
|
import Img from '~/components/layout/Img'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import TokenPlaceholder from '../Tokens/assets/token_placeholder.svg'
|
import TokenPlaceholder from '../assets/token_placeholder.svg'
|
||||||
|
|
||||||
const setImageToPlaceholder = (e) => {
|
const setImageToPlaceholder = (e) => {
|
||||||
e.target.onerror = null
|
e.target.onerror = null
|
||||||
|
|
|
@ -1,31 +1,16 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { List, Set } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import classNames from 'classnames/bind'
|
|
||||||
import SearchBar from 'material-ui-search-bar'
|
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import MuiList from '@material-ui/core/List'
|
|
||||||
import Img from '~/components/layout/Img'
|
|
||||||
import Block from '~/components/layout/Block'
|
|
||||||
import ListItem from '@material-ui/core/ListItem'
|
|
||||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
|
||||||
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
|
|
||||||
import ListItemText from '@material-ui/core/ListItemText'
|
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import Search from '@material-ui/icons/Search'
|
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Button from '~/components/layout/Button'
|
|
||||||
import Switch from '@material-ui/core/Switch'
|
|
||||||
import Divider from '~/components/layout/Divider'
|
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import Spacer from '~/components/Spacer'
|
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
import TokenList from '~/routes/safe/component/Balances/Tokens/screens/tokenList'
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
import { type Token } from '~/logic/tokens/store/model/token'
|
||||||
import actions, { type Actions } from './actions'
|
import actions, { type Actions } from './actions'
|
||||||
import TokenPlaceholder from './assets/token_placeholder.svg'
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
type Props = Actions & {
|
type Props = Actions & {
|
||||||
|
@ -37,145 +22,40 @@ type Props = Actions & {
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
filter: string,
|
activeScreen: string,
|
||||||
activeTokensAddresses: Set<string>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterBy = (filter: string, tokens: List<Token>): List<Token> => tokens.filter(
|
|
||||||
(token: Token) => !filter
|
|
||||||
|| token.symbol.toLowerCase().includes(filter.toLowerCase())
|
|
||||||
|| token.name.toLowerCase().includes(filter.toLowerCase()),
|
|
||||||
)
|
|
||||||
|
|
||||||
// OPTIMIZATION IDEA (Thanks Andre)
|
|
||||||
// Calculate active tokens on component mount, store it in component state
|
|
||||||
// After user closes modal, dispatch an action so we dont have 100500 actions
|
|
||||||
// And selectors dont recalculate
|
|
||||||
|
|
||||||
class Tokens extends React.Component<Props, State> {
|
class Tokens extends React.Component<Props, State> {
|
||||||
state = {
|
state = {
|
||||||
activeScreen: '',
|
activeScreen: 'tokenList',
|
||||||
filter: '',
|
|
||||||
activeTokensAddresses: Set([]),
|
|
||||||
activeTokensCalculated: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { fetchTokens, safeAddress } = this.props
|
|
||||||
|
|
||||||
fetchTokens(safeAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
|
||||||
// I moved this logic here because if placed in ComponentDidMount
|
|
||||||
// the user would see Switches switch and this method fires before the component mounts
|
|
||||||
|
|
||||||
if (!prevState.activeTokensCalculated) {
|
|
||||||
const { activeTokens } = nextProps
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeTokensAddresses: Set(activeTokens.map(({ address }) => address)),
|
|
||||||
activeTokensCalculated: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
const { activeTokensAddresses } = this.state
|
|
||||||
const { updateActiveTokens, safeAddress } = this.props
|
|
||||||
|
|
||||||
updateActiveTokens(safeAddress, activeTokensAddresses.toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
onCancelSearch = () => {
|
|
||||||
this.setState(() => ({ filter: '' }))
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeSearchBar = (value) => {
|
|
||||||
this.setState(() => ({ filter: value }))
|
|
||||||
}
|
|
||||||
|
|
||||||
onSwitch = (token: Token) => () => {
|
|
||||||
const { activeTokensAddresses } = this.state
|
|
||||||
|
|
||||||
if (activeTokensAddresses.has(token.address)) {
|
|
||||||
this.setState({
|
|
||||||
activeTokensAddresses: activeTokensAddresses.remove(token.address),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
activeTokensAddresses: activeTokensAddresses.add(token.address),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setImageToPlaceholder = (e) => {
|
|
||||||
e.target.onerror = null
|
|
||||||
e.target.src = TokenPlaceholder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { onClose, classes, tokens } = this.props
|
const {
|
||||||
const { filter, activeTokensAddresses } = this.state
|
onClose, classes, tokens, activeTokens, fetchTokens, updateActiveTokens, safeAddress,
|
||||||
const searchClasses = {
|
} = this.props
|
||||||
input: classes.searchInput,
|
const { activeScreen } = this.state
|
||||||
root: classes.searchRoot,
|
|
||||||
iconButton: classes.searchIcon,
|
|
||||||
searchContainer: classes.searchContainer,
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredTokens = filterBy(filter, tokens)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Block className={classes.root}>
|
<Row align="center" grow className={classes.heading}>
|
||||||
<Row align="center" grow className={classes.heading}>
|
<Paragraph className={classes.manage} noMargin>
|
||||||
<Paragraph className={classes.manage} noMargin>
|
Manage Tokens
|
||||||
Manage Tokens
|
</Paragraph>
|
||||||
</Paragraph>
|
<IconButton onClick={onClose} disableRipple>
|
||||||
<IconButton onClick={onClose} disableRipple>
|
<Close className={classes.close} />
|
||||||
<Close className={classes.close} />
|
</IconButton>
|
||||||
</IconButton>
|
</Row>
|
||||||
</Row>
|
<Hairline />
|
||||||
<Hairline />
|
{activeScreen === 'tokenList' && (
|
||||||
<Row align="center" className={classNames(classes.padding, classes.actions)}>
|
<TokenList
|
||||||
<Search className={classes.search} />
|
tokens={tokens}
|
||||||
<SearchBar
|
activeTokens={activeTokens}
|
||||||
placeholder="Search by name or symbol"
|
fetchTokens={fetchTokens}
|
||||||
classes={searchClasses}
|
updateActiveTokens={updateActiveTokens}
|
||||||
searchIcon={<div />}
|
safeAddress={safeAddress}
|
||||||
onChange={this.onChangeSearchBar}
|
/>
|
||||||
onCancelSearch={this.onCancelSearch}
|
)}
|
||||||
/>
|
|
||||||
<Spacer />
|
|
||||||
<Divider />
|
|
||||||
<Spacer />
|
|
||||||
<Button variant="contained" size="small" color="secondary" className={classes.add}>
|
|
||||||
+ ADD CUSTOM TOKEN
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
<Hairline />
|
|
||||||
</Block>
|
|
||||||
<MuiList className={classes.list}>
|
|
||||||
{filteredTokens.map((token: Token) => {
|
|
||||||
const isActive = activeTokensAddresses.has(token.address)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListItem key={token.address} className={classes.token}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Img src={token.logoUri} height={28} alt={token.name} onError={this.setImageToPlaceholder} />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={token.symbol} secondary={token.name} />
|
|
||||||
{token.address !== ETH_ADDRESS && (
|
|
||||||
<ListItemSecondaryAction>
|
|
||||||
<Switch onChange={this.onSwitch(token)} checked={isActive} />
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
)}
|
|
||||||
</ListItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</MuiList>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { Component } from 'react'
|
|
||||||
|
|
||||||
export default class TokenLIst extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
token list
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
// @flow
|
|
||||||
import TokenList from './TokenList'
|
|
||||||
|
|
||||||
export default TokenList
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
|
import { List, Set } from 'immutable'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import SearchBar from 'material-ui-search-bar'
|
||||||
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
import MuiList from '@material-ui/core/List'
|
||||||
|
import Img from '~/components/layout/Img'
|
||||||
|
import Block from '~/components/layout/Block'
|
||||||
|
import ListItem from '@material-ui/core/ListItem'
|
||||||
|
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||||
|
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText'
|
||||||
|
import Search from '@material-ui/icons/Search'
|
||||||
|
import Button from '~/components/layout/Button'
|
||||||
|
import Switch from '@material-ui/core/Switch'
|
||||||
|
import Divider from '~/components/layout/Divider'
|
||||||
|
import Hairline from '~/components/layout/Hairline'
|
||||||
|
import Spacer from '~/components/Spacer'
|
||||||
|
import Row from '~/components/layout/Row'
|
||||||
|
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
|
import { type Token } from '~/logic/tokens/store/model/token'
|
||||||
|
import { setImageToPlaceholder } from '~/routes/safe/component/Balances/utils'
|
||||||
|
import { styles } from './style'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
classes: Object,
|
||||||
|
tokens: List<Token>,
|
||||||
|
safeAddress: string,
|
||||||
|
activeTokens: List<Token>,
|
||||||
|
fetchTokens: Function,
|
||||||
|
updateActiveTokens: Function,
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
filter: string,
|
||||||
|
activeTokensAddresses: Set<string>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterBy = (filter: string, tokens: List<Token>): List<Token> => tokens.filter(
|
||||||
|
(token: Token) => !filter
|
||||||
|
|| token.symbol.toLowerCase().includes(filter.toLowerCase())
|
||||||
|
|| token.name.toLowerCase().includes(filter.toLowerCase()),
|
||||||
|
)
|
||||||
|
|
||||||
|
// OPTIMIZATION IDEA (Thanks Andre)
|
||||||
|
// Calculate active tokens on component mount, store it in component state
|
||||||
|
// After user closes modal, dispatch an action so we dont have 100500 actions
|
||||||
|
// And selectors dont recalculate
|
||||||
|
|
||||||
|
class Tokens extends React.Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
filter: '',
|
||||||
|
activeTokensAddresses: Set([]),
|
||||||
|
activeTokensCalculated: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { fetchTokens } = this.props
|
||||||
|
|
||||||
|
fetchTokens()
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
|
// I moved this logic here because if placed in ComponentDidMount
|
||||||
|
// the user would see Switches switch and this method fires before the component mounts
|
||||||
|
|
||||||
|
if (!prevState.activeTokensCalculated) {
|
||||||
|
const { activeTokens } = nextProps
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeTokensAddresses: Set(activeTokens.map(({ address }) => address)),
|
||||||
|
activeTokensCalculated: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const { activeTokensAddresses } = this.state
|
||||||
|
const { updateActiveTokens, safeAddress } = this.props
|
||||||
|
|
||||||
|
updateActiveTokens(safeAddress, activeTokensAddresses.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancelSearch = () => {
|
||||||
|
this.setState(() => ({ filter: '' }))
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeSearchBar = (value) => {
|
||||||
|
this.setState(() => ({ filter: value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
onSwitch = (token: Token) => () => {
|
||||||
|
const { activeTokensAddresses } = this.state
|
||||||
|
|
||||||
|
if (activeTokensAddresses.has(token.address)) {
|
||||||
|
this.setState({
|
||||||
|
activeTokensAddresses: activeTokensAddresses.remove(token.address),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
activeTokensAddresses: activeTokensAddresses.add(token.address),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes, tokens } = this.props
|
||||||
|
const { filter, activeTokensAddresses } = this.state
|
||||||
|
const searchClasses = {
|
||||||
|
input: classes.searchInput,
|
||||||
|
root: classes.searchRoot,
|
||||||
|
iconButton: classes.searchIcon,
|
||||||
|
searchContainer: classes.searchContainer,
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredTokens = filterBy(filter, tokens)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Block className={classes.root}>
|
||||||
|
<Row align="center" className={cn(classes.padding, classes.actions)}>
|
||||||
|
<Search className={classes.search} />
|
||||||
|
<SearchBar
|
||||||
|
placeholder="Search by name or symbol"
|
||||||
|
classes={searchClasses}
|
||||||
|
searchIcon={<div />}
|
||||||
|
onChange={this.onChangeSearchBar}
|
||||||
|
onCancelSearch={this.onCancelSearch}
|
||||||
|
/>
|
||||||
|
<Spacer />
|
||||||
|
<Divider />
|
||||||
|
<Spacer />
|
||||||
|
<Button variant="contained" size="small" color="secondary" className={classes.add}>
|
||||||
|
+ ADD CUSTOM TOKEN
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
<Hairline />
|
||||||
|
</Block>
|
||||||
|
<MuiList className={classes.list}>
|
||||||
|
{filteredTokens.map((token: Token) => {
|
||||||
|
const isActive = activeTokensAddresses.has(token.address)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem key={token.address} className={classes.token}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={token.symbol} secondary={token.name} />
|
||||||
|
{token.address !== ETH_ADDRESS && (
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Switch onChange={this.onSwitch(token)} checked={isActive} />
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
)}
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</MuiList>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const TokenComponent = withStyles(styles)(Tokens)
|
||||||
|
|
||||||
|
export default TokenComponent
|
|
@ -0,0 +1,63 @@
|
||||||
|
// @flow
|
||||||
|
import {
|
||||||
|
md, sm, xs, mediumFontSize, border,
|
||||||
|
} from '~/theme/variables'
|
||||||
|
|
||||||
|
export const styles = () => ({
|
||||||
|
root: {
|
||||||
|
minHeight: '48px',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
color: '#a2a8ba',
|
||||||
|
paddingLeft: sm,
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
padding: `0 ${md}`,
|
||||||
|
},
|
||||||
|
add: {
|
||||||
|
fontWeight: 'normal',
|
||||||
|
paddingRight: md,
|
||||||
|
paddingLeft: md,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
overflow: 'hidden',
|
||||||
|
overflowY: 'scroll',
|
||||||
|
padding: 0,
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
token: {
|
||||||
|
minHeight: '50px',
|
||||||
|
borderBottom: `1px solid ${border}`,
|
||||||
|
},
|
||||||
|
searchInput: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
lineHeight: 'initial',
|
||||||
|
fontSize: mediumFontSize,
|
||||||
|
padding: 0,
|
||||||
|
'& > input::placeholder': {
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
fontSize: mediumFontSize,
|
||||||
|
color: 'black',
|
||||||
|
},
|
||||||
|
'& > input': {
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
searchContainer: {
|
||||||
|
width: '180px',
|
||||||
|
marginLeft: xs,
|
||||||
|
marginRight: xs,
|
||||||
|
},
|
||||||
|
searchRoot: {
|
||||||
|
letterSpacing: '-0.5px',
|
||||||
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
|
fontSize: mediumFontSize,
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
searchIcon: {
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'transparent !important',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -1,76 +1,18 @@
|
||||||
// @flow
|
// @flow
|
||||||
import {
|
import { lg, sm } from '~/theme/variables'
|
||||||
lg, md, sm, xs, mediumFontSize, border,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
|
|
||||||
export const styles = () => ({
|
export const styles = () => ({
|
||||||
root: {
|
|
||||||
minHeight: '127px',
|
|
||||||
},
|
|
||||||
heading: {
|
heading: {
|
||||||
padding: `${sm} ${lg}`,
|
padding: `${sm} ${lg}`,
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
|
maxHeight: '75px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
},
|
},
|
||||||
manage: {
|
manage: {
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
},
|
},
|
||||||
actions: {
|
|
||||||
height: '50px',
|
|
||||||
},
|
|
||||||
close: {
|
close: {
|
||||||
height: '35px',
|
height: '35px',
|
||||||
width: '35px',
|
width: '35px',
|
||||||
},
|
},
|
||||||
search: {
|
|
||||||
color: '#a2a8ba',
|
|
||||||
paddingLeft: sm,
|
|
||||||
},
|
|
||||||
padding: {
|
|
||||||
padding: `0 ${md}`,
|
|
||||||
},
|
|
||||||
add: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
paddingRight: md,
|
|
||||||
paddingLeft: md,
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
overflow: 'hidden',
|
|
||||||
overflowY: 'scroll',
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
token: {
|
|
||||||
minHeight: '50px',
|
|
||||||
borderBottom: `1px solid ${border}`,
|
|
||||||
},
|
|
||||||
searchInput: {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
lineHeight: 'initial',
|
|
||||||
fontSize: mediumFontSize,
|
|
||||||
padding: 0,
|
|
||||||
'& > input::placeholder': {
|
|
||||||
letterSpacing: '-0.5px',
|
|
||||||
fontSize: mediumFontSize,
|
|
||||||
color: 'black',
|
|
||||||
},
|
|
||||||
'& > input': {
|
|
||||||
letterSpacing: '-0.5px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
searchContainer: {
|
|
||||||
width: '180px',
|
|
||||||
marginLeft: xs,
|
|
||||||
marginRight: xs,
|
|
||||||
},
|
|
||||||
searchRoot: {
|
|
||||||
letterSpacing: '-0.5px',
|
|
||||||
fontFamily: 'Roboto Mono, monospace',
|
|
||||||
fontSize: mediumFontSize,
|
|
||||||
border: 'none',
|
|
||||||
boxShadow: 'none',
|
|
||||||
},
|
|
||||||
searchIcon: {
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: 'transparent !important',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export * from './setTokenImgToPlaceholder'
|
|
@ -0,0 +1,7 @@
|
||||||
|
// @flow
|
||||||
|
import TokenPlaceholder from '~/routes/safe/component/Balances/assets/token_placeholder.svg'
|
||||||
|
|
||||||
|
export const setImageToPlaceholder = (e) => {
|
||||||
|
e.target.onerror = null
|
||||||
|
e.target.src = TokenPlaceholder
|
||||||
|
}
|
Loading…
Reference in New Issue