Merge pull request #947 from influxdata/feature/934-ew-users-hunter
Add stateful class MultiSelectDropdown shared component, add Tooltip, add Storybook stories for testingpull/973/head
commit
a49ef359bb
|
@ -1,28 +0,0 @@
|
|||
const express = require('express');
|
||||
const request = require('request');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use('/', (req, res) => {
|
||||
console.log(`${req.method} ${req.url}`);
|
||||
|
||||
const headers = {};
|
||||
headers['Access-Control-Allow-Origin'] = '*';
|
||||
headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS';
|
||||
headers['Access-Control-Allow-Credentials'] = false;
|
||||
headers['Access-Control-Max-Age'] = '86400'; // 24 hours
|
||||
headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept';
|
||||
res.writeHead(200, headers);
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.end();
|
||||
}
|
||||
else {
|
||||
const url = 'http://localhost:8888' + req.url;
|
||||
req.pipe(request(url)).pipe(res);
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3888, () => {
|
||||
console.log('corsless proxy server now running')
|
||||
});
|
|
@ -17,9 +17,7 @@
|
|||
"test:lint": "npm run lint; npm run test",
|
||||
"test:dev": "nodemon --exec npm run test:lint",
|
||||
"clean": "rm -rf build",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
"proxy": "node ./corsless"
|
||||
"storybook": "node ./storybook"
|
||||
},
|
||||
"author": "",
|
||||
"eslintConfig": {
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
import OnClickOutside from 'shared/components/OnClickOutside'
|
||||
import classNames from 'classnames'
|
||||
import _ from 'lodash'
|
||||
|
||||
const labelText = ({localSelectedItems, isOpen, label}) => {
|
||||
if (label) {
|
||||
return label
|
||||
}
|
||||
else if (localSelectedItems.length) {
|
||||
return localSelectedItems.map((s) => s).join(', ')
|
||||
}
|
||||
else {
|
||||
if (isOpen) {
|
||||
return '0 Selected'
|
||||
}
|
||||
else {
|
||||
return 'Apply Function'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MultiSelectDropdown extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
localSelectedItems: this.props.selectedItems,
|
||||
}
|
||||
|
||||
this.onSelect = ::this.onSelect
|
||||
this.onApplyFunctions = ::this.onApplyFunctions
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!_.isEqual(this.state.localSelectedItems, nextProps.selectedItems)) {
|
||||
this.setState({
|
||||
localSelectedItems: nextProps.selectedItems,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
toggleMenu(e) {
|
||||
e.stopPropagation()
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
onSelect(item, e) {
|
||||
e.stopPropagation()
|
||||
|
||||
const {localSelectedItems} = this.state
|
||||
|
||||
let nextItems
|
||||
if (this.isSelected(item)) {
|
||||
nextItems = localSelectedItems.filter((i) => i !== item)
|
||||
} else {
|
||||
nextItems = localSelectedItems.concat(item)
|
||||
}
|
||||
|
||||
this.setState({localSelectedItems: nextItems})
|
||||
}
|
||||
|
||||
isSelected(item) {
|
||||
return this.state.localSelectedItems.indexOf(item) > -1
|
||||
}
|
||||
|
||||
onApplyFunctions(e) {
|
||||
e.stopPropagation()
|
||||
|
||||
this.setState({isOpen: false})
|
||||
this.props.onApply(this.state.localSelectedItems)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {localSelectedItems, isOpen} = this.state
|
||||
const {label} = this.props
|
||||
|
||||
return (
|
||||
<div className={classNames('dropdown multi-select-dropdown', {open: isOpen})}>
|
||||
<div onClick={::this.toggleMenu} className="btn btn-xs btn-info dropdown-toggle" type="button">
|
||||
<div className="multi-select-dropdown__label">
|
||||
{
|
||||
labelText({localSelectedItems, isOpen, label})
|
||||
}
|
||||
</div>
|
||||
<span className="caret"></span>
|
||||
</div>
|
||||
{this.renderMenu()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderMenu() {
|
||||
const {items} = this.props
|
||||
|
||||
return (
|
||||
<div className="dropdown-options">
|
||||
<li className="multi-select-dropdown__apply" onClick={this.onApplyFunctions} style={{listStyle: 'none'}}>
|
||||
<div className="btn btn-xs btn-info btn-block">Apply</div>
|
||||
</li>
|
||||
<ul className="dropdown-menu multi-select-dropdown__menu" aria-labelledby="dropdownMenu1">
|
||||
{items.map((listItem, i) => {
|
||||
return (
|
||||
<li
|
||||
key={i}
|
||||
className={classNames('multi-select-dropdown__item', {active: this.isSelected(listItem)})}
|
||||
onClick={_.wrap(listItem, this.onSelect)}
|
||||
>
|
||||
<a href="#">{listItem}</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
string,
|
||||
} = PropTypes
|
||||
|
||||
MultiSelectDropdown.propTypes = {
|
||||
onApply: func.isRequired,
|
||||
items: arrayOf(string.isRequired).isRequired,
|
||||
selectedItems: arrayOf(string.isRequired).isRequired,
|
||||
label: string,
|
||||
}
|
||||
|
||||
export default OnClickOutside(MultiSelectDropdown)
|
|
@ -0,0 +1,21 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
|
||||
const Tooltip = ({tip, children}) => (
|
||||
<div>
|
||||
<div data-tip={tip}>{children}</div>
|
||||
<ReactTooltip effect="solid" html={true} offset={{top: 2}} place="bottom" class="influx-tooltip place-bottom" />
|
||||
</div>
|
||||
)
|
||||
|
||||
const {
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
|
||||
Tooltip.propTypes = {
|
||||
tip: string,
|
||||
children: shape({}),
|
||||
}
|
||||
|
||||
export default Tooltip
|
|
@ -56,3 +56,9 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.multi-select-dropdown__label {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react'
|
||||
import {storiesOf, action, linkTo} from '@kadira/storybook'
|
||||
import Center from './components/Center'
|
||||
|
||||
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
|
||||
import Tooltip from 'shared/components/Tooltip'
|
||||
|
||||
storiesOf('MultiSelectDropdown', module)
|
||||
.add('Select Roles w/label', () => (
|
||||
<Center>
|
||||
<MultiSelectDropdown
|
||||
items={[
|
||||
'Admin',
|
||||
'User',
|
||||
'Chrono Giraffe',
|
||||
'Prophet',
|
||||
'Susford',
|
||||
]}
|
||||
selectedItems={[
|
||||
'User',
|
||||
'Chrono Giraffe',
|
||||
]}
|
||||
label={'Select Roles'}
|
||||
onApply={action('onApply')}
|
||||
/>
|
||||
</Center>
|
||||
))
|
||||
.add('Selected Item list', () => (
|
||||
<Center>
|
||||
<MultiSelectDropdown
|
||||
items={[
|
||||
'Admin',
|
||||
'User',
|
||||
'Chrono Giraffe',
|
||||
'Prophet',
|
||||
'Susford',
|
||||
]}
|
||||
selectedItems={[
|
||||
'User',
|
||||
'Chrono Giraffe',
|
||||
]}
|
||||
onApply={action('onApply')}
|
||||
/>
|
||||
</Center>
|
||||
))
|
||||
.add('0 selected items', () => (
|
||||
<Center>
|
||||
<MultiSelectDropdown
|
||||
items={[
|
||||
'Admin',
|
||||
'User',
|
||||
'Chrono Giraffe',
|
||||
'Prophet',
|
||||
'Susford',
|
||||
]}
|
||||
selectedItems={[]}
|
||||
onApply={action('onApply')}
|
||||
/>
|
||||
</Center>
|
||||
))
|
||||
|
||||
storiesOf('Tooltip', module)
|
||||
.add('Delete', () => (
|
||||
<Center>
|
||||
<Tooltip tip={`Are you sure? TrashIcon`}>
|
||||
<div className="btn btn-info btn-sm">
|
||||
Delete
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Center>
|
||||
))
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react'
|
||||
|
||||
const Center = ({children}) => (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%)',
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Center
|
|
@ -3,3 +3,4 @@ import 'src/style/chronograf.scss';
|
|||
|
||||
// Kapacitor Stories
|
||||
import './kapacitor'
|
||||
import './admin'
|
||||
|
|
|
@ -12,7 +12,7 @@ import queryConfigs from './stubs/queryConfigs';
|
|||
|
||||
// Actions for Spies
|
||||
import * as kapacitorActions from 'src/kapacitor/actions/view'
|
||||
import * as queryActions from 'src/chronograf/actions/view';
|
||||
import * as queryActions from 'src/data_explorer/actions/view';
|
||||
|
||||
// Components
|
||||
import KapacitorRule from 'src/kapacitor/components/KapacitorRule';
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
const express = require('express')
|
||||
const request = require('request')
|
||||
const {default: storybook} = require('@kadira/storybook/dist/server/middleware')
|
||||
|
||||
const app = express()
|
||||
|
||||
const handler = (req, res) => {
|
||||
console.log(`${req.method} ${req.url}`)
|
||||
const url = 'http://localhost:8888' + req.url
|
||||
req.pipe(request(url)).pipe(res)
|
||||
}
|
||||
|
||||
app.use(storybook('./.storybook'))
|
||||
app.get('/chronograf/v1/*', handler)
|
||||
app.post('/chronograf/v1/*', handler)
|
||||
|
||||
app.listen(6006, () => {
|
||||
console.log('storybook proxy server now running')
|
||||
})
|
Loading…
Reference in New Issue