Refactor Dropdowns, Rainbow Node Selector (#244)
* Convert all dropdowns to use a single dropdown shell component. Restyle header ones to look like v3. * Right align some. * Color dropdown component, which node selector uses. * Prettier fixes.
This commit is contained in:
parent
ad83b5a181
commit
1a09c6a7a6
|
@ -1,90 +1,86 @@
|
|||
import { gasPriceDefaults } from 'config/data';
|
||||
import throttle from 'lodash/throttle';
|
||||
import React, { Component } from 'react';
|
||||
import DropdownShell from 'components/ui/DropdownShell';
|
||||
import './GasPriceDropdown.scss';
|
||||
|
||||
interface Props {
|
||||
value: number;
|
||||
onChange(gasPrice: number): void;
|
||||
}
|
||||
interface State {
|
||||
expanded: boolean;
|
||||
}
|
||||
export default class GasPriceDropdown extends Component<Props, State> {
|
||||
public state = { expanded: false };
|
||||
|
||||
export default class GasPriceDropdown extends Component<Props, {}> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.updateGasPrice = throttle(this.updateGasPrice, 50);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { expanded } = this.state;
|
||||
const { value } = this.props;
|
||||
return (
|
||||
<span className={`dropdown ${expanded ? 'open' : ''}`}>
|
||||
<a
|
||||
aria-haspopup="true"
|
||||
aria-label="adjust gas price"
|
||||
className="dropdown-toggle"
|
||||
onClick={this.toggleExpanded}
|
||||
>
|
||||
<span>Gas Price</span>: {this.props.value} Gwei
|
||||
<i className="caret" />
|
||||
</a>
|
||||
{expanded &&
|
||||
<ul className="dropdown-menu GasPrice-dropdown-menu">
|
||||
<div className="GasPrice-header">
|
||||
<span>Gas Price</span>: {this.props.value} Gwei
|
||||
<input
|
||||
type="range"
|
||||
value={this.props.value}
|
||||
min={gasPriceDefaults.gasPriceMinGwei}
|
||||
max={gasPriceDefaults.gasPriceMaxGwei}
|
||||
onChange={this.handleGasPriceChange}
|
||||
/>
|
||||
<p className="small col-xs-4 text-left GasPrice-padding-reset">
|
||||
Not So Fast
|
||||
</p>
|
||||
<p className="small col-xs-4 text-center GasPrice-padding-reset">
|
||||
Fast
|
||||
</p>
|
||||
<p className="small col-xs-4 text-right GasPrice-padding-reset">
|
||||
Fast AF
|
||||
</p>
|
||||
<p className="small GasPrice-description">
|
||||
Gas Price is the amount you pay per unit of gas.{' '}
|
||||
<code>TX fee = gas price * gas limit</code> & is paid to miners
|
||||
for including your TX in a block. Higher the gas price = faster
|
||||
transaction, but more expensive. Default is <code>21 GWEI</code>.
|
||||
</p>
|
||||
<p>
|
||||
{/* TODO: maybe not hardcode a link? :) */}
|
||||
<a
|
||||
href="https://myetherwallet.groovehq.com/knowledge_base/topics/what-is-gas"
|
||||
target="_blank"
|
||||
>
|
||||
Read more
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</ul>}
|
||||
</span>
|
||||
<DropdownShell
|
||||
color="white"
|
||||
size="smr"
|
||||
ariaLabel={`adjust gas price. current price is ${value} gwei`}
|
||||
renderLabel={this.renderLabel}
|
||||
renderOptions={this.renderOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public toggleExpanded = () => {
|
||||
this.setState(state => {
|
||||
return {
|
||||
expanded: !state.expanded
|
||||
};
|
||||
});
|
||||
private renderLabel = () => {
|
||||
return `Gas Price: ${this.props.value} Gwei`;
|
||||
};
|
||||
|
||||
public updateGasPrice = (value: string) => {
|
||||
private renderOptions = () => {
|
||||
const { value } = this.props;
|
||||
return (
|
||||
<div className="GasPrice-dropdown-menu dropdown-menu dropdown-menu-right">
|
||||
<div className="GasPrice-header">
|
||||
<span>Gas Price</span>: {value} Gwei
|
||||
<input
|
||||
type="range"
|
||||
value={value}
|
||||
min={gasPriceDefaults.gasPriceMinGwei}
|
||||
max={gasPriceDefaults.gasPriceMaxGwei}
|
||||
onChange={this.handleGasPriceChange}
|
||||
/>
|
||||
<p className="small col-xs-4 text-left GasPrice-padding-reset">
|
||||
Not So Fast
|
||||
</p>
|
||||
<p className="small col-xs-4 text-center GasPrice-padding-reset">
|
||||
Fast
|
||||
</p>
|
||||
<p className="small col-xs-4 text-right GasPrice-padding-reset">
|
||||
Fast AF
|
||||
</p>
|
||||
<p className="small GasPrice-description">
|
||||
Gas Price is the amount you pay per unit of gas.{' '}
|
||||
<code>TX fee = gas price * gas limit</code> & is paid to miners for
|
||||
including your TX in a block. Higher the gas price = faster
|
||||
transaction, but more expensive. Default is <code>21 GWEI</code>.
|
||||
</p>
|
||||
<p>
|
||||
{/* TODO: maybe not hardcode a link? :) */}
|
||||
<a
|
||||
href="https://myetherwallet.groovehq.com/knowledge_base/topics/what-is-gas"
|
||||
target="_blank"
|
||||
>
|
||||
Read more
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private updateGasPrice = (value: string) => {
|
||||
this.props.onChange(parseInt(value, 10));
|
||||
};
|
||||
|
||||
public handleGasPriceChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
private handleGasPriceChange = (
|
||||
e: React.SyntheticEvent<HTMLInputElement>
|
||||
) => {
|
||||
this.updateGasPrice((e.target as HTMLInputElement).value);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -92,66 +92,37 @@ $small-size: 900px;
|
|||
padding: 5px 0;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
&-tagline {
|
||||
font-size: 18px;
|
||||
font-weight: 200;
|
||||
color: white;
|
||||
flex: 1 auto;
|
||||
text-align: right;
|
||||
padding: 5px 0;
|
||||
@include small-query {
|
||||
text-align: center;
|
||||
}
|
||||
> * {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&-version {
|
||||
max-width: 395px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
&-right {
|
||||
font-size: 18px;
|
||||
font-weight: 200;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-weight: 400;
|
||||
transition: 250ms all ease;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
opacity: .8;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transition: 250ms all ease;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - Move to dropdown component?
|
||||
.dropdown {
|
||||
margin-left: 15px;
|
||||
padding: 0;
|
||||
flex: 1 auto;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
padding: 0 0 5px;
|
||||
@include small-query {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
right: -10px;
|
||||
left: auto;
|
||||
min-width: auto;
|
||||
left: auto;
|
||||
> * {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
& > li > a {
|
||||
font-size: 15px;
|
||||
padding: 5px 30px 5px 15px;
|
||||
position: relative;
|
||||
&-version {
|
||||
max-width: 395px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
text-decoration: none;
|
||||
color: $brand-primary;
|
||||
background-color: $gray-lightest;
|
||||
}
|
||||
&-dropdown {
|
||||
margin-left: 6px;
|
||||
|
||||
&-add {
|
||||
text-align: center;
|
||||
padding-top: $space-sm !important;
|
||||
padding-bottom: $space-sm !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { TChangeGasPrice, TChangeLanguage, TChangeNode } from 'actions/config';
|
||||
import logo from 'assets/images/logo-myetherwallet.svg';
|
||||
import { Dropdown } from 'components/ui';
|
||||
import { Dropdown, ColorDropdown } from 'components/ui';
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
|
@ -36,7 +36,18 @@ export default class Header extends Component<Props, {}> {
|
|||
const LanguageDropDown = Dropdown as new () => Dropdown<
|
||||
typeof selectedLanguage
|
||||
>;
|
||||
const NodeDropDown = Dropdown as new () => Dropdown<keyof typeof NODES>;
|
||||
const nodeOptions = Object.keys(NODES).map(key => {
|
||||
return {
|
||||
value: key,
|
||||
name: (
|
||||
<span>
|
||||
{NODES[key].network} <small>({NODES[key].service})</small>
|
||||
</span>
|
||||
),
|
||||
color: NETWORKS[NODES[key].network].color
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="Header">
|
||||
{ANNOUNCEMENT_MESSAGE && (
|
||||
|
@ -64,45 +75,51 @@ export default class Header extends Component<Props, {}> {
|
|||
alt="MyEtherWallet"
|
||||
/>
|
||||
</Link>
|
||||
<div className="Header-branding-title-tagline">
|
||||
<span className="Header-branding-title-tagline-version">
|
||||
v{VERSION}
|
||||
</span>
|
||||
<div className="Header-branding-right">
|
||||
<span className="Header-branding-right-version">v{VERSION}</span>
|
||||
|
||||
<GasPriceDropdown
|
||||
value={this.props.gasPriceGwei}
|
||||
onChange={this.props.changeGasPrice}
|
||||
/>
|
||||
<div className="Header-branding-right-dropdown">
|
||||
<GasPriceDropdown
|
||||
value={this.props.gasPriceGwei}
|
||||
onChange={this.props.changeGasPrice}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<LanguageDropDown
|
||||
ariaLabel={`change language. current language ${languages[
|
||||
selectedLanguage
|
||||
]}`}
|
||||
options={Object.values(languages)}
|
||||
value={languages[selectedLanguage]}
|
||||
extra={[
|
||||
<li key={'separator'} role="separator" className="divider" />,
|
||||
<li key={'disclaimer'}>
|
||||
<a data-toggle="modal" data-target="#disclaimerModal">
|
||||
Disclaimer
|
||||
</a>
|
||||
</li>
|
||||
]}
|
||||
onChange={this.changeLanguage}
|
||||
/>
|
||||
<div className="Header-branding-right-dropdown">
|
||||
<LanguageDropDown
|
||||
ariaLabel={`change language. current language ${languages[
|
||||
selectedLanguage
|
||||
]}`}
|
||||
options={Object.values(languages)}
|
||||
value={languages[selectedLanguage]}
|
||||
extra={
|
||||
<li key="disclaimer">
|
||||
<a data-toggle="modal" data-target="#disclaimerModal">
|
||||
Disclaimer
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
onChange={this.changeLanguage}
|
||||
size="smr"
|
||||
color="white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NodeDropDown
|
||||
ariaLabel={`change node. current node ${selectedNode.network} node by ${selectedNode.service}`}
|
||||
options={Object.keys(NODES)}
|
||||
formatTitle={this.nodeNetworkAndService}
|
||||
value={nodeSelection}
|
||||
extra={
|
||||
<li>
|
||||
<a>Add Custom Node</a>
|
||||
</li>
|
||||
}
|
||||
onChange={changeNode}
|
||||
/>
|
||||
<div className="Header-branding-right-dropdown">
|
||||
<ColorDropdown
|
||||
ariaLabel={`change node. current node ${selectedNode.network} node by ${selectedNode.service}`}
|
||||
options={nodeOptions}
|
||||
value={nodeSelection}
|
||||
extra={
|
||||
<li>
|
||||
<a>Add Custom Node</a>
|
||||
</li>
|
||||
}
|
||||
onChange={changeNode}
|
||||
size="smr"
|
||||
color="white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
@ -121,10 +138,4 @@ export default class Header extends Component<Props, {}> {
|
|||
this.props.changeLanguage(key);
|
||||
}
|
||||
};
|
||||
|
||||
private nodeNetworkAndService = (option: string) => [
|
||||
NODES[option].network,
|
||||
' ',
|
||||
<small key="service">({NODES[option].service}) </small>
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import DropdownShell from './DropdownShell';
|
||||
|
||||
interface Option<T> {
|
||||
name: any;
|
||||
value: T;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
interface Props<T> {
|
||||
value: T;
|
||||
options: Option<T>[];
|
||||
label?: string;
|
||||
ariaLabel: string;
|
||||
extra?: any;
|
||||
size?: string;
|
||||
color?: string;
|
||||
menuAlign?: string;
|
||||
onChange(value: T): void;
|
||||
}
|
||||
|
||||
export default class ColorDropdown<T> extends Component<Props<T>, {}> {
|
||||
private dropdownShell: DropdownShell | null;
|
||||
|
||||
public render() {
|
||||
const { ariaLabel, color, size } = this.props;
|
||||
|
||||
return (
|
||||
<DropdownShell
|
||||
renderLabel={this.renderLabel}
|
||||
renderOptions={this.renderOptions}
|
||||
size={size}
|
||||
color={color}
|
||||
ariaLabel={ariaLabel}
|
||||
ref={el => (this.dropdownShell = el)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLabel = () => {
|
||||
const label = this.props.label ? `${this.props.label}:` : '';
|
||||
const activeOption = this.getActiveOption();
|
||||
|
||||
return (
|
||||
<span>
|
||||
{label} {activeOption ? activeOption.name : '-'}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
private renderOptions = () => {
|
||||
const { options, value, menuAlign, extra } = this.props;
|
||||
|
||||
const activeOption = this.getActiveOption();
|
||||
|
||||
const listItems = options.reduce((prev: any[], opt) => {
|
||||
const prevOpt = prev.length ? prev[prev.length - 1] : null;
|
||||
if (prevOpt && !prevOpt.divider && prevOpt.color !== opt.color) {
|
||||
prev.push({ divider: true });
|
||||
}
|
||||
prev.push(opt);
|
||||
return prev;
|
||||
}, []);
|
||||
|
||||
const menuClass = classnames({
|
||||
'dropdown-menu': true,
|
||||
[`dropdown-menu-${menuAlign || ''}`]: !!menuAlign
|
||||
});
|
||||
|
||||
return (
|
||||
<ul className={menuClass}>
|
||||
{listItems.map((option, i) => {
|
||||
if (option.divider) {
|
||||
return <li key={i} role="separator" className="divider" />;
|
||||
} else {
|
||||
return (
|
||||
<li key={i} style={{ borderLeft: `2px solid ${option.color}` }}>
|
||||
<a
|
||||
className={option.value === value ? 'active' : ''}
|
||||
onClick={this.onChange.bind(null, option.value)}
|
||||
>
|
||||
{option.name}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
})}
|
||||
{extra && <li key="separator" role="separator" className="divider" />}
|
||||
{extra}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
private onChange = (value: any) => {
|
||||
this.props.onChange(value);
|
||||
if (this.dropdownShell) {
|
||||
this.dropdownShell.close();
|
||||
}
|
||||
};
|
||||
|
||||
private getActiveOption() {
|
||||
return this.props.options.find(opt => opt.value === this.props.value);
|
||||
}
|
||||
}
|
|
@ -1,77 +1,87 @@
|
|||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import DropdownShell from './DropdownShell';
|
||||
|
||||
interface Props<T> {
|
||||
value: T;
|
||||
options: T[];
|
||||
ariaLabel: string;
|
||||
label?: string;
|
||||
extra?: any;
|
||||
size?: string;
|
||||
color?: string;
|
||||
menuAlign?: string;
|
||||
formatTitle?(option: T): any;
|
||||
onChange(value: T): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
export default class DropdownComponent<T> extends Component<Props<T>, State> {
|
||||
public state = {
|
||||
expanded: false
|
||||
};
|
||||
export default class DropdownComponent<T> extends Component<Props<T>, {}> {
|
||||
private dropdownShell: DropdownShell | null;
|
||||
|
||||
public render() {
|
||||
const { options, value, ariaLabel, extra } = this.props;
|
||||
const { expanded } = this.state;
|
||||
const { ariaLabel, color, size } = this.props;
|
||||
|
||||
return (
|
||||
<span className={`dropdown ${expanded ? 'open' : ''}`}>
|
||||
<a
|
||||
tabIndex={0}
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-label={ariaLabel}
|
||||
className="dropdown-toggle"
|
||||
onClick={this.toggleExpanded}
|
||||
>
|
||||
{this.props.formatTitle ? this.formatTitle(value) : value}
|
||||
<i className="caret" />
|
||||
</a>
|
||||
{expanded && (
|
||||
<ul className="dropdown-menu">
|
||||
{options.map((option, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<a
|
||||
className={option === value ? 'active' : ''}
|
||||
onClick={this.onChange.bind(null, option)}
|
||||
>
|
||||
{this.props.formatTitle ? this.formatTitle(option) : option}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{extra}
|
||||
</ul>
|
||||
)}
|
||||
</span>
|
||||
<DropdownShell
|
||||
renderLabel={this.renderLabel}
|
||||
renderOptions={this.renderOptions}
|
||||
size={size}
|
||||
color={color}
|
||||
ariaLabel={ariaLabel}
|
||||
ref={el => (this.dropdownShell = el)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public formatTitle = (option: any) => {
|
||||
private renderLabel = () => {
|
||||
const { label, value } = this.props;
|
||||
const labelStr = this.props.label ? `${this.props.label}:` : '';
|
||||
return (
|
||||
<span>
|
||||
{labelStr} {this.formatTitle(value)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
private renderOptions = () => {
|
||||
const { options, value, menuAlign, extra } = this.props;
|
||||
const menuClass = classnames({
|
||||
'dropdown-menu': true,
|
||||
[`dropdown-menu-${menuAlign || ''}`]: !!menuAlign
|
||||
});
|
||||
|
||||
return (
|
||||
<ul className={menuClass}>
|
||||
{options.map((option, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<a
|
||||
className={option === value ? 'active' : ''}
|
||||
onClick={this.onChange.bind(null, option)}
|
||||
>
|
||||
{this.props.formatTitle ? this.formatTitle(option) : option}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{extra && <li key={'separator'} role="separator" className="divider" />}
|
||||
{extra}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
private formatTitle = (option: any) => {
|
||||
if (this.props.formatTitle) {
|
||||
return this.props.formatTitle(option);
|
||||
} else {
|
||||
return option;
|
||||
}
|
||||
};
|
||||
|
||||
public toggleExpanded = () => {
|
||||
this.setState(state => {
|
||||
return {
|
||||
expanded: !state.expanded
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
public onChange = (value: any) => {
|
||||
private onChange = (value: any) => {
|
||||
this.props.onChange(value);
|
||||
this.setState({ expanded: false });
|
||||
if (this.dropdownShell) {
|
||||
this.dropdownShell.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
ariaLabel: string;
|
||||
size?: string;
|
||||
color?: string;
|
||||
renderLabel(): any;
|
||||
renderOptions(): any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
export default class DropdownComponent extends Component<Props, State> {
|
||||
public static defaultProps = {
|
||||
color: 'default',
|
||||
size: 'sm'
|
||||
};
|
||||
|
||||
public state: State = {
|
||||
expanded: false
|
||||
};
|
||||
|
||||
private dropdown: HTMLElement | null;
|
||||
|
||||
public componentDidMount() {
|
||||
document.addEventListener('click', this.clickOffHandler);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener('click', this.clickOffHandler);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { ariaLabel, color, size, renderOptions, renderLabel } = this.props;
|
||||
const { expanded } = this.state;
|
||||
const toggleClasses = classnames([
|
||||
'dropdown-toggle',
|
||||
'btn',
|
||||
`btn-${color}`,
|
||||
`btn-${size}`
|
||||
]);
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`dropdown ${expanded ? 'open' : ''}`}
|
||||
ref={el => (this.dropdown = el)}
|
||||
>
|
||||
<a
|
||||
tabIndex={0}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={expanded}
|
||||
aria-label={ariaLabel}
|
||||
className={toggleClasses}
|
||||
onClick={this.toggle}
|
||||
>
|
||||
{renderLabel()}
|
||||
<i className="caret" />
|
||||
</a>
|
||||
{expanded && renderOptions()}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
public toggle = () => {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
};
|
||||
|
||||
public open = () => {
|
||||
this.setState({ expanded: true });
|
||||
};
|
||||
|
||||
public close = () => {
|
||||
this.setState({ expanded: false });
|
||||
};
|
||||
|
||||
private clickOffHandler = (ev: MouseEvent) => {
|
||||
// Only calculate if dropdown is open & we have the ref
|
||||
if (!this.state.expanded || !this.dropdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's an element that's not inside of the dropdown, close it up
|
||||
if (
|
||||
this.dropdown !== ev.target &&
|
||||
ev.target instanceof HTMLElement &&
|
||||
!this.dropdown.contains(ev.target)
|
||||
) {
|
||||
this.setState({ expanded: false });
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
interface Props {
|
||||
value?: string;
|
||||
options: string[];
|
||||
onChange(value: string): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
expanded: boolean;
|
||||
}
|
||||
export default class SimpleDropDown extends Component<Props, State> {
|
||||
public state = {
|
||||
expanded: false
|
||||
};
|
||||
|
||||
public toggleExpanded = () => {
|
||||
this.setState(state => {
|
||||
return { expanded: !state.expanded };
|
||||
});
|
||||
};
|
||||
|
||||
public onClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
|
||||
const value = (event.target as HTMLAnchorElement).getAttribute('data-value') || '';
|
||||
this.props.onChange(value);
|
||||
this.setState({ expanded: false });
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { options, value } = this.props;
|
||||
const { expanded } = this.state;
|
||||
return (
|
||||
<div className={`dropdown ${expanded ? 'open' : ''}`}>
|
||||
<a
|
||||
className="btn btn-default dropdown-toggle"
|
||||
onClick={this.toggleExpanded}
|
||||
>
|
||||
{value}
|
||||
<i className="caret" />
|
||||
</a>
|
||||
|
||||
{expanded &&
|
||||
<ul className="dropdown-menu dropdown-menu-right">
|
||||
{options.map((option, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<a
|
||||
className={option === value ? 'active' : ''}
|
||||
onClick={this.onClick}
|
||||
data-value={option}
|
||||
>
|
||||
{option}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import React, { Component } from 'react';
|
||||
import Dropdown from './Dropdown';
|
||||
|
||||
interface Props {
|
||||
value?: string;
|
||||
options: string[];
|
||||
ariaLabel?: string;
|
||||
onChange(value: string): void;
|
||||
}
|
||||
|
||||
export default class SimpleDropdown extends Component<Props, void> {
|
||||
public render() {
|
||||
const { options, value, onChange, ariaLabel } = this.props;
|
||||
|
||||
const StringDropdown = Dropdown as new () => Dropdown<string>;
|
||||
|
||||
return (
|
||||
<StringDropdown
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ariaLabel={ariaLabel || "dropdown"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
export { default as ColorDropdown } from './ColorDropdown';
|
||||
export { default as Dropdown } from './Dropdown';
|
||||
export { default as DropdownShell } from './DropdownShell';
|
||||
export { default as Identicon } from './Identicon';
|
||||
export { default as Modal } from './Modal';
|
||||
export { default as UnlockHeader } from './UnlockHeader';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import SimpleDropDown from 'components/ui/SimpleDropDown';
|
||||
import React from 'react';
|
||||
import SimpleDropdown from 'components/ui/SimpleDropdown';
|
||||
|
||||
interface UnitDropdownProps {
|
||||
value: string;
|
||||
|
@ -22,7 +22,7 @@ export default class UnitDropdown extends React.Component<
|
|||
|
||||
return (
|
||||
<div className="input-group-btn">
|
||||
<SimpleDropDown
|
||||
<SimpleDropdown
|
||||
value={value}
|
||||
onChange={this.onChange}
|
||||
options={options}
|
||||
|
|
|
@ -43,6 +43,16 @@
|
|||
);
|
||||
padding: .1rem .6rem .2rem;
|
||||
}
|
||||
// This is a "smaller" small, to accomodate overrides done in v3.
|
||||
.btn-smr {
|
||||
@include button-size(
|
||||
.4rem,
|
||||
1rem,
|
||||
14px,
|
||||
$line-height-base,
|
||||
$border-radius
|
||||
);
|
||||
}
|
||||
|
||||
// Custom color
|
||||
.btn-white {
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
// If it's a span, we probably want it inline-block, not inline
|
||||
span.dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
padding: 0;
|
||||
border: none;
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
"react-dom": "16.0.0",
|
||||
"react-markdown": "^2.5.0",
|
||||
"react-redux": "^5.0.6",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"redux": "^3.6.0",
|
||||
"redux-form": "^6.6.3",
|
||||
"redux-logger": "^3.0.1",
|
||||
|
@ -53,8 +53,8 @@
|
|||
"@types/react": "^16.0.5",
|
||||
"@types/react-dom": "^15.5.4",
|
||||
"@types/react-redux": "^5.0.9",
|
||||
"@types/react-router": "^4.0.15",
|
||||
"@types/react-router-dom": "^4.0.8",
|
||||
"@types/react-router": "^4.0.15",
|
||||
"@types/react-router-dom": "^4.0.8",
|
||||
"@types/react-router-redux": "^4.0.50",
|
||||
"@types/redux-form": "^7.0.5",
|
||||
"@types/redux-logger": "^3.0.3",
|
||||
|
|
Loading…
Reference in New Issue