refactor: Replace react-bootstrap MenuItems with Antd Menu (#11487)

* Remove MenuItem from CopyToClipboard

* Refactor DateFilterControl

* fixup! Remove MenuItem from CopyToClipboard

* Remove console log

* Refactor LanguagePicker

* Refactor HeaderActionsDropdown

* Remove dir with Menu component

* Add imports to common/components/index

* Fix after rebase
This commit is contained in:
Kamil Gabryjelski 2020-11-04 22:47:25 +01:00 committed by GitHub
parent 69810170f7
commit ad98981d9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 210 additions and 208 deletions

View File

@ -72,9 +72,10 @@ describe('Dashboard top-level controls', () => {
.trigger('click', { force: true }); .trigger('click', { force: true });
// not allow dashboard level force refresh when any chart is loading // not allow dashboard level force refresh when any chart is loading
cy.get('[data-test="refresh-dashboard-menu-item"]') cy.get('[data-test="refresh-dashboard-menu-item"]').should(
.parent() 'have.class',
.should('have.class', 'disabled'); 'ant-menu-item-disabled',
);
// not allow chart level force refresh when it is loading // not allow chart level force refresh when it is loading
cy.get(`#slice_${mapId}-controls`) cy.get(`#slice_${mapId}-controls`)
.next() .next()
@ -84,25 +85,28 @@ describe('Dashboard top-level controls', () => {
.should('have.class', 'disabled'); .should('have.class', 'disabled');
cy.wait(`@postJson_${mapId}_force`); cy.wait(`@postJson_${mapId}_force`);
cy.get('[data-test="refresh-dashboard-menu-item"]') cy.get('[data-test="refresh-dashboard-menu-item"]').should(
.parent() 'not.have.class',
.not('have.class', 'disabled'); 'ant-menu-item-disabled',
);
}); });
it('should allow dashboard level force refresh', () => { it('should allow dashboard level force refresh', () => {
// when charts are not start loading, for example, under a secondary tab, // when charts are not start loading, for example, under a secondary tab,
// should allow force refresh // should allow force refresh
cy.get('[data-test="more-horiz"]').click(); cy.get('[data-test="more-horiz"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]') cy.get('[data-test="refresh-dashboard-menu-item"]').should(
.parent() 'not.have.class',
.not('have.class', 'disabled'); 'ant-menu-item-disabled',
);
// wait the all dash finish loading. // wait the all dash finish loading.
cy.wait(sliceRequests); cy.wait(sliceRequests);
cy.get('[data-test="refresh-dashboard-menu-item"]').click(); cy.get('[data-test="refresh-dashboard-menu-item"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]') cy.get('[data-test="refresh-dashboard-menu-item"]').should(
.parent() 'have.class',
.should('have.class', 'disabled'); 'ant-menu-item-disabled',
);
// wait all charts force refreshed // wait all charts force refreshed
cy.wait(forceRefreshRequests, { responseTimeout: 15000 }).then(xhrs => { cy.wait(forceRefreshRequests, { responseTimeout: 15000 }).then(xhrs => {
@ -115,8 +119,9 @@ describe('Dashboard top-level controls', () => {
}); });
cy.get('[data-test="more-horiz"]').click(); cy.get('[data-test="more-horiz"]').click();
cy.get('[data-test="refresh-dashboard-menu-item"]') cy.get('[data-test="refresh-dashboard-menu-item"]').should(
.parent() 'not.have.class',
.not('have.class', 'disabled'); 'ant-menu-item-disabled',
);
}); });
}); });

View File

@ -18,7 +18,8 @@
*/ */
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { DropdownButton, MenuItem } from 'react-bootstrap'; import { DropdownButton } from 'react-bootstrap';
import { Menu } from 'src/common/components';
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal'; import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
import URLShortLinkModal from 'src/components/URLShortLinkModal'; import URLShortLinkModal from 'src/components/URLShortLinkModal';
import HeaderActionsDropdown from 'src/dashboard/components/HeaderActionsDropdown'; import HeaderActionsDropdown from 'src/dashboard/components/HeaderActionsDropdown';
@ -72,9 +73,9 @@ describe('HeaderActionsDropdown', () => {
expect(wrapper.find(SaveModal)).not.toExist(); expect(wrapper.find(SaveModal)).not.toExist();
}); });
it('should render four MenuItems', () => { it('should render five Menu items', () => {
const wrapper = setup(overrideProps); const wrapper = setup(overrideProps);
expect(wrapper.find(MenuItem)).toHaveLength(4); expect(wrapper.find(Menu.Item)).toHaveLength(5);
}); });
it('should render the RefreshIntervalModal', () => { it('should render the RefreshIntervalModal', () => {
@ -106,9 +107,9 @@ describe('HeaderActionsDropdown', () => {
expect(wrapper.find(SaveModal)).toExist(); expect(wrapper.find(SaveModal)).toExist();
}); });
it('should render four MenuItems', () => { it('should render six Menu items', () => {
const wrapper = setup(overrideProps); const wrapper = setup(overrideProps);
expect(wrapper.find(MenuItem)).toHaveLength(4); expect(wrapper.find(Menu.Item)).toHaveLength(6);
}); });
it('should render the RefreshIntervalModal', () => { it('should render the RefreshIntervalModal', () => {
@ -140,9 +141,9 @@ describe('HeaderActionsDropdown', () => {
expect(wrapper.find(SaveModal)).toExist(); expect(wrapper.find(SaveModal)).toExist();
}); });
it('should render three MenuItems', () => { it('should render seven MenuItems', () => {
const wrapper = setup(overrideProps); const wrapper = setup(overrideProps);
expect(wrapper.find(MenuItem)).toHaveLength(3); expect(wrapper.find(Menu.Item)).toHaveLength(7);
}); });
it('should render the RefreshIntervalModal', () => { it('should render the RefreshIntervalModal', () => {

View File

@ -33,8 +33,10 @@ export {
DatePicker, DatePicker,
Dropdown, Dropdown,
Empty, Empty,
Input,
Modal, Modal,
Popover, Popover,
Select,
Skeleton, Skeleton,
Tabs, Tabs,
Tooltip, Tooltip,

View File

@ -18,7 +18,7 @@
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Tooltip, OverlayTrigger, MenuItem } from 'react-bootstrap'; import { Tooltip, OverlayTrigger } from 'react-bootstrap';
import { t } from '@superset-ui/core'; import { t } from '@superset-ui/core';
import withToasts from 'src/messageToasts/enhancers/withToasts'; import withToasts from 'src/messageToasts/enhancers/withToasts';
@ -28,7 +28,6 @@ const propTypes = {
onCopyEnd: PropTypes.func, onCopyEnd: PropTypes.func,
shouldShowText: PropTypes.bool, shouldShowText: PropTypes.bool,
text: PropTypes.string, text: PropTypes.string,
inMenu: PropTypes.bool,
wrapped: PropTypes.bool, wrapped: PropTypes.bool,
tooltipText: PropTypes.string, tooltipText: PropTypes.string,
addDangerToast: PropTypes.func.isRequired, addDangerToast: PropTypes.func.isRequired,
@ -38,7 +37,6 @@ const defaultProps = {
copyNode: <span>Copy</span>, copyNode: <span>Copy</span>,
onCopyEnd: () => {}, onCopyEnd: () => {},
shouldShowText: true, shouldShowText: true,
inMenu: false,
wrapped: true, wrapped: true,
tooltipText: t('Copy to clipboard'), tooltipText: t('Copy to clipboard'),
}; };
@ -160,27 +158,6 @@ class CopyToClipboard extends React.Component {
); );
} }
renderInMenu() {
return (
<OverlayTrigger
placement="top"
overlay={this.renderTooltip()}
trigger={['hover']}
>
<MenuItem>
<span
role="button"
tabIndex={0}
onClick={this.onClick}
onMouseOut={this.onMouseOut}
>
{this.props.copyNode}
</span>
</MenuItem>
</OverlayTrigger>
);
}
renderTooltip() { renderTooltip() {
return ( return (
<Tooltip id="copy-to-clipboard-tooltip">{this.tooltipText()}</Tooltip> <Tooltip id="copy-to-clipboard-tooltip">{this.tooltipText()}</Tooltip>
@ -188,11 +165,11 @@ class CopyToClipboard extends React.Component {
} }
render() { render() {
const { wrapped, inMenu } = this.props; const { wrapped } = this.props;
if (!wrapped) { if (!wrapped) {
return this.renderNotWrapped(); return this.renderNotWrapped();
} }
return inMenu ? this.renderInMenu() : this.renderLink(); return this.renderLink();
} }
} }

View File

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import React from 'react'; import React from 'react';
import { MenuItem } from 'react-bootstrap'; import { Menu } from 'src/common/components';
import NavDropdown from 'src/components/NavDropdown'; import NavDropdown from 'src/components/NavDropdown';
export interface Languages { export interface Languages {
@ -46,17 +46,23 @@ export default function LanguagePicker({
</span> </span>
} }
> >
{Object.keys(languages).map(langKey => <Menu
langKey === locale ? null : ( onSelect={({ key }) => {
<MenuItem key={langKey} href={languages[langKey].url}> window.location.href = languages[key].url;
{' '} }}
<div className="f16"> >
<i className={`flag ${languages[langKey].flag}`} /> -{' '} {Object.keys(languages).map(langKey =>
{languages[langKey].name} langKey === locale ? null : (
</div> <Menu.Item key={langKey}>
</MenuItem> {' '}
), <div className="f16">
)} <i className={`flag ${languages[langKey].flag}`} /> -{' '}
{languages[langKey].name}
</div>
</Menu.Item>
),
)}
</Menu>
</NavDropdown> </NavDropdown>
); );
} }

View File

@ -81,7 +81,6 @@ class CssEditor extends React.PureComponent {
<ModalTrigger <ModalTrigger
triggerNode={this.props.triggerNode} triggerNode={this.props.triggerNode}
modalTitle={t('CSS')} modalTitle={t('CSS')}
isMenuItem
modalBody={ modalBody={
<div> <div>
{this.renderTemplateSelector()} {this.renderTemplateSelector()}

View File

@ -20,8 +20,9 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { SupersetClient, t } from '@superset-ui/core'; import { SupersetClient, t } from '@superset-ui/core';
import { DropdownButton, MenuItem } from 'react-bootstrap'; import { DropdownButton } from 'react-bootstrap';
import { Menu } from 'src/common/components';
import Icon from 'src/components/Icon'; import Icon from 'src/components/Icon';
import CssEditor from './CssEditor'; import CssEditor from './CssEditor';
@ -71,6 +72,18 @@ const defaultProps = {
refreshWarning: null, refreshWarning: null,
}; };
const MENU_KEYS = {
SAVE_MODAL: 'save-modal',
SHARE_DASHBOARD: 'share-dashboard',
REFRESH_DASHBOARD: 'refresh-dashboard',
AUTOREFRESH_MODAL: 'autorefresh-modal',
SET_FILTER_MAPPING: 'set-filter-mapping',
EDIT_PROPERTIES: 'edit-properties',
EDIT_CSS: 'edit-css',
DOWNLOAD_AS_IMAGE: 'download-as-image',
TOGGLE_FULLSCREEN: 'toggle-fullscreen',
};
class HeaderActionsDropdown extends React.PureComponent { class HeaderActionsDropdown extends React.PureComponent {
static discardChanges() { static discardChanges() {
window.location.reload(); window.location.reload();
@ -85,6 +98,7 @@ class HeaderActionsDropdown extends React.PureComponent {
this.changeCss = this.changeCss.bind(this); this.changeCss = this.changeCss.bind(this);
this.changeRefreshInterval = this.changeRefreshInterval.bind(this); this.changeRefreshInterval = this.changeRefreshInterval.bind(this);
this.handleMenuClick = this.handleMenuClick.bind(this);
} }
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
@ -119,12 +133,40 @@ class HeaderActionsDropdown extends React.PureComponent {
this.props.startPeriodicRender(refreshInterval * 1000); this.props.startPeriodicRender(refreshInterval * 1000);
} }
handleMenuClick({ key, domEvent }) {
switch (key) {
case MENU_KEYS.REFRESH_DASHBOARD:
this.props.forceRefreshAllCharts();
break;
case MENU_KEYS.EDIT_PROPERTIES:
this.props.showPropertiesModal();
break;
case MENU_KEYS.DOWNLOAD_AS_IMAGE:
downloadAsImage('.dashboard', this.props.dashboardTitle)(domEvent);
break;
case MENU_KEYS.TOGGLE_FULLSCREEN: {
const hasStandalone = window.location.search.includes(
'standalone=true',
);
const url = getDashboardUrl(
window.location.pathname,
getActiveFilters(),
window.location.hash,
!hasStandalone,
);
window.location.replace(url);
break;
}
default:
break;
}
}
render() { render() {
const { const {
dashboardTitle, dashboardTitle,
dashboardId, dashboardId,
dashboardInfo, dashboardInfo,
forceRefreshAllCharts,
refreshFrequency, refreshFrequency,
shouldPersistRefreshFrequency, shouldPersistRefreshFrequency,
editMode, editMode,
@ -155,105 +197,102 @@ class HeaderActionsDropdown extends React.PureComponent {
style={{ border: 'none', padding: 0, marginLeft: '4px' }} style={{ border: 'none', padding: 0, marginLeft: '4px' }}
pullRight pullRight
> >
{userCanSave && ( <Menu onClick={this.handleMenuClick} selectable={false}>
<> {userCanSave && (
<SaveModal <Menu.Item key={MENU_KEYS.SAVE_MODAL}>
addSuccessToast={this.props.addSuccessToast} <SaveModal
addDangerToast={this.props.addDangerToast} addSuccessToast={this.props.addSuccessToast}
dashboardId={dashboardId} addDangerToast={this.props.addDangerToast}
dashboardTitle={dashboardTitle} dashboardId={dashboardId}
dashboardInfo={dashboardInfo} dashboardTitle={dashboardTitle}
saveType={SAVE_TYPE_NEWDASHBOARD} dashboardInfo={dashboardInfo}
layout={layout} saveType={SAVE_TYPE_NEWDASHBOARD}
expandedSlices={expandedSlices} layout={layout}
refreshFrequency={refreshFrequency} expandedSlices={expandedSlices}
shouldPersistRefreshFrequency={shouldPersistRefreshFrequency} refreshFrequency={refreshFrequency}
lastModifiedTime={lastModifiedTime} shouldPersistRefreshFrequency={shouldPersistRefreshFrequency}
customCss={customCss} lastModifiedTime={lastModifiedTime}
colorNamespace={colorNamespace} customCss={customCss}
colorScheme={colorScheme} colorNamespace={colorNamespace}
onSave={onSave} colorScheme={colorScheme}
isMenuItem onSave={onSave}
triggerNode={ triggerNode={
<span data-test="save-as-menu-item">{t('Save as')}</span> <span data-test="save-as-menu-item">{t('Save as')}</span>
} }
canOverwrite={userCanEdit} canOverwrite={userCanEdit}
/> />
</> </Menu.Item>
)}
<URLShortLinkModal
url={getDashboardUrl(
window.location.pathname,
getActiveFilters(),
window.location.hash,
)} )}
emailSubject={emailSubject} <Menu.Item key={MENU_KEYS.SHARE_DASHBOARD}>
emailContent={emailBody} <URLShortLinkModal
addDangerToast={this.props.addDangerToast} url={getDashboardUrl(
isMenuItem
triggerNode={<span>{t('Share dashboard')}</span>}
/>
<MenuItem
data-test="refresh-dashboard-menu-item"
onClick={forceRefreshAllCharts}
disabled={isLoading}
>
{t('Refresh dashboard')}
</MenuItem>
<MenuItem divider />
<RefreshIntervalModal
refreshFrequency={refreshFrequency}
refreshLimit={refreshLimit}
refreshWarning={refreshWarning}
onChange={this.changeRefreshInterval}
editMode={editMode}
triggerNode={<span>{t('Set auto-refresh interval')}</span>}
/>
{editMode && (
<>
<FilterScopeModal
className="m-r-5"
triggerNode={
<MenuItem bsSize="small">{t('Set filter mapping')}</MenuItem>
}
/>
<MenuItem onClick={this.props.showPropertiesModal}>
{t('Edit dashboard properties')}
</MenuItem>
<CssEditor
triggerNode={<span>{t('Edit CSS')}</span>}
initialCss={this.state.css}
templates={this.state.cssTemplates}
onChange={this.changeCss}
/>
</>
)}
{!editMode && (
<MenuItem onClick={downloadAsImage('.dashboard', dashboardTitle)}>
{t('Download as image')}
</MenuItem>
)}
{!editMode && (
<MenuItem
onClick={() => {
const hasStandalone = window.location.search.includes(
'standalone=true',
);
const url = getDashboardUrl(
window.location.pathname, window.location.pathname,
getActiveFilters(), getActiveFilters(),
window.location.hash, window.location.hash,
!hasStandalone, )}
); emailSubject={emailSubject}
window.location.replace(url); emailContent={emailBody}
}} addDangerToast={this.props.addDangerToast}
triggerNode={<span>{t('Share dashboard')}</span>}
/>
</Menu.Item>
<Menu.Item
key={MENU_KEYS.REFRESH_DASHBOARD}
data-test="refresh-dashboard-menu-item"
disabled={isLoading}
> >
{t('Toggle FullScreen')} {t('Refresh dashboard')}
</MenuItem> </Menu.Item>
)} <Menu.Divider />
<Menu.Item key={MENU_KEYS.AUTOREFRESH_MODAL}>
<RefreshIntervalModal
refreshFrequency={refreshFrequency}
refreshLimit={refreshLimit}
refreshWarning={refreshWarning}
onChange={this.changeRefreshInterval}
editMode={editMode}
triggerNode={<span>{t('Set auto-refresh interval')}</span>}
/>
</Menu.Item>
{editMode && (
<Menu.Item key={MENU_KEYS.SET_FILTER_MAPPING}>
<FilterScopeModal
className="m-r-5"
triggerNode={t('Set filter mapping')}
/>
</Menu.Item>
)}
{editMode && (
<Menu.Item key={MENU_KEYS.EDIT_PROPERTIES}>
{t('Edit dashboard properties')}
</Menu.Item>
)}
{editMode && (
<Menu.Item key={MENU_KEYS.EDIT_CSS}>
<CssEditor
triggerNode={<span>{t('Edit CSS')}</span>}
initialCss={this.state.css}
templates={this.state.cssTemplates}
onChange={this.changeCss}
/>
</Menu.Item>
)}
{!editMode && (
<Menu.Item key={MENU_KEYS.DOWNLOAD_AS_IMAGE}>
{t('Download as image')}
</Menu.Item>
)}
{!editMode && (
<Menu.Item key={MENU_KEYS.TOGGLE_FULLSCREEN}>
{t('Toggle FullScreen')}
</Menu.Item>
)}
</Menu>
</DropdownButton> </DropdownButton>
); );
} }

View File

@ -94,7 +94,6 @@ class RefreshIntervalModal extends React.PureComponent {
<ModalTrigger <ModalTrigger
ref={this.modalRef} ref={this.modalRef}
triggerNode={this.props.triggerNode} triggerNode={this.props.triggerNode}
isMenuItem
modalTitle={t('Refresh Interval')} modalTitle={t('Refresh Interval')}
modalBody={ modalBody={
<div> <div>

View File

@ -41,14 +41,12 @@ const propTypes = {
colorNamespace: PropTypes.string, colorNamespace: PropTypes.string,
colorScheme: PropTypes.string, colorScheme: PropTypes.string,
onSave: PropTypes.func.isRequired, onSave: PropTypes.func.isRequired,
isMenuItem: PropTypes.bool,
canOverwrite: PropTypes.bool.isRequired, canOverwrite: PropTypes.bool.isRequired,
refreshFrequency: PropTypes.number.isRequired, refreshFrequency: PropTypes.number.isRequired,
lastModifiedTime: PropTypes.number.isRequired, lastModifiedTime: PropTypes.number.isRequired,
}; };
const defaultProps = { const defaultProps = {
isMenuItem: false,
saveType: SAVE_TYPE_OVERWRITE, saveType: SAVE_TYPE_OVERWRITE,
colorNamespace: undefined, colorNamespace: undefined,
colorScheme: undefined, colorScheme: undefined,
@ -157,7 +155,6 @@ class SaveModal extends React.PureComponent {
return ( return (
<ModalTrigger <ModalTrigger
ref={this.setModalRef} ref={this.setModalRef}
isMenuItem={this.props.isMenuItem}
triggerNode={this.props.triggerNode} triggerNode={this.props.triggerNode}
modalTitle={t('Save Dashboard')} modalTitle={t('Save Dashboard')}
modalBody={ modalBody={

View File

@ -19,16 +19,15 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
DropdownButton,
FormControl, FormControl,
FormGroup, FormGroup,
InputGroup, InputGroup,
MenuItem,
OverlayTrigger, OverlayTrigger,
Radio, Radio,
Tooltip, Tooltip,
} from 'react-bootstrap'; } from 'react-bootstrap';
import Popover from 'src/common/components/Popover'; import Popover from 'src/common/components/Popover';
import { Select, Input } from 'src/common/components';
import Button from 'src/components/Button'; import Button from 'src/components/Button';
import Datetime from 'react-datetime'; import Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css'; import 'react-datetime/css/react-datetime.css';
@ -387,15 +386,13 @@ class DateFilterControl extends React.Component {
renderPopover() { renderPopover() {
const grainOptions = TIME_GRAIN_OPTIONS.map(grain => ( const grainOptions = TIME_GRAIN_OPTIONS.map(grain => (
<MenuItem <Select.Option
onSelect={value => this.setCustomRange('grain', value)}
key={grain} key={grain}
eventKey={grain} value={grain}
active={grain === this.state.grain} active={grain === this.state.grain}
fullWidth={false}
> >
{grain} {grain}
</MenuItem> </Select.Option>
)); ));
const timeFrames = COMMON_TIME_FRAMES.map(timeFrame => { const timeFrames = COMMON_TIME_FRAMES.map(timeFrame => {
const nextState = getStateFromCommonTimeFrame(timeFrame); const nextState = getStateFromCommonTimeFrame(timeFrame);
@ -425,6 +422,7 @@ class DateFilterControl extends React.Component {
</Styles> </Styles>
); );
}); });
return ( return (
<div <div
id="filter-popover" id="filter-popover"
@ -453,66 +451,45 @@ class DateFilterControl extends React.Component {
> >
<div <div
className="clearfix centered" className="clearfix centered"
style={{ marginTop: '12px' }} style={{ marginTop: '8px', display: 'flex' }}
> >
<div <div className="input-inline">
style={{ width: '60px', marginTop: '-4px' }} <Select
className="input-inline" value={this.state.rel}
> onSelect={value => this.setCustomRange('rel', value)}
<DropdownButton
bsSize="small"
componentClass={InputGroup.Button}
id="input-dropdown-rel"
title={this.state.rel}
onFocus={this.setTypeCustomRange} onFocus={this.setTypeCustomRange}
> >
<MenuItem <Select.Option value={RELATIVE_TIME_OPTIONS.LAST}>
onSelect={value => this.setCustomRange('rel', value)}
key={RELATIVE_TIME_OPTIONS.LAST}
eventKey={RELATIVE_TIME_OPTIONS.LAST}
active={this.state.rel === RELATIVE_TIME_OPTIONS.LAST}
>
Last Last
</MenuItem> </Select.Option>
<MenuItem <Select.Option value={RELATIVE_TIME_OPTIONS.NEXT}>
onSelect={value => this.setCustomRange('rel', value)}
key={RELATIVE_TIME_OPTIONS.NEXT}
eventKey={RELATIVE_TIME_OPTIONS.NEXT}
active={this.state.rel === RELATIVE_TIME_OPTIONS.NEXT}
>
Next Next
</MenuItem> </Select.Option>
</DropdownButton> </Select>
</div> </div>
<div <div
style={{ width: '60px', marginTop: '-4px' }} style={{ width: '60px' }}
className="input-inline m-l-5 m-r-3" className="input-inline m-l-5 m-r-3"
> >
<FormControl <Input
bsSize="small"
type="text" type="text"
onChange={event => onChange={event =>
this.setCustomRange('num', event.target.value) this.setCustomRange('num', event.target.value)
} }
onFocus={this.setTypeCustomRange} onFocus={this.setTypeCustomRange}
onKeyPress={this.onEnter} onPressEnter={this.close}
value={this.state.num} value={this.state.num}
style={{ height: '30px' }}
/> />
</div> </div>
<div <div className="input-inline">
style={{ width: '90px', marginTop: '-4px' }} <Select
className="input-inline" value={this.state.grain}
>
<DropdownButton
bsSize="small"
componentClass={InputGroup.Button}
id="input-dropdown-grain"
title={this.state.grain}
onFocus={this.setTypeCustomRange} onFocus={this.setTypeCustomRange}
onSelect={value => this.setCustomRange('grain', value)}
dropdownMatchSelectWidth={false}
> >
{grainOptions} {grainOptions}
</DropdownButton> </Select>
</div> </div>
</div> </div>
</PopoverSection> </PopoverSection>