forked from Graylog2/graylog2-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SelectPopover component (Graylog2#4619)
* Add first version of select popover - Display list of elements in a popover - Store selected element in state - Add callbacks to know when the selected element changed * Add element formatting to example * Add optional data filter to SelectPopover * Add clear selection option * Make selectable list scroll This will allow us to set many options in there. * Add props documentation * Revert change in local yarn cache * Make @mariussturm happy
- Loading branch information
Showing
5 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
graylog2-web-interface/src/components/common/SelectPopover.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
:local(.customPopover) { | ||
padding-left: 0; | ||
padding-right: 0; | ||
} | ||
|
||
:local(.customPopover) .popover-content { | ||
min-width: 200px; | ||
padding: 0; | ||
} | ||
|
||
:local(.customPopover) .list-group { | ||
margin-bottom: 0; | ||
} | ||
|
||
:local(.customPopover) .list-group-item { | ||
border-right: 0; | ||
border-left: 0; | ||
padding: 6px 15px; | ||
} | ||
|
||
:local(.customPopover) .list-group-item:first-child { | ||
border-top-right-radius: 0; | ||
border-top-left-radius: 0; | ||
} | ||
|
||
:local(.customPopover) .list-group-item:last-child { | ||
border-bottom: 0; | ||
} | ||
|
||
:local(.scrollableList) { | ||
max-height: 340px; /* 10 items */ | ||
overflow: auto; | ||
} | ||
|
||
:local(.dataFilterInput) { | ||
margin-bottom: 0; | ||
padding: 5px; | ||
} |
147 changes: 147 additions & 0 deletions
147
graylog2-web-interface/src/components/common/SelectPopover.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import React from 'react'; | ||
import createReactClass from 'create-react-class'; | ||
import PropTypes from 'prop-types'; | ||
import { FormControl, FormGroup, ListGroup, ListGroupItem, OverlayTrigger, Popover } from 'react-bootstrap'; | ||
import lodash from 'lodash'; | ||
import IsolatedScroll from 'react-isolated-scroll'; | ||
|
||
import style from './SelectPopover.css'; | ||
|
||
const SelectPopover = createReactClass({ | ||
propTypes: { | ||
/** Provides an ID for this popover element. */ | ||
id: PropTypes.string.isRequired, | ||
/** Indicates where the popover should appear. */ | ||
placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), | ||
/** Title to use in the popover header. */ | ||
title: PropTypes.string.isRequired, | ||
/** React node that will be used as trigger to show/hide the popover. */ | ||
triggerNode: PropTypes.node.isRequired, | ||
/** Event that will show/hide the popover. */ | ||
triggerAction: PropTypes.oneOf(['click', 'hover', 'focus']), | ||
/** | ||
* Array of strings that contain items to be displayed as options in the list. | ||
* You can customize the items appearance by giving an `itemFormatter` prop. | ||
*/ | ||
items: PropTypes.arrayOf(PropTypes.string), | ||
/** | ||
* Function that will be called for each item in the list. It receives the current item | ||
* and must return a React node that will be displayed on screen. | ||
*/ | ||
itemFormatter: PropTypes.func, | ||
/** Indicates which is the selected item. This should be the same string that appears in the `items` list. */ | ||
selectedItem: PropTypes.string, | ||
/** | ||
* Function that will be called when the item selection changes. | ||
* The function will receive the selected item as argument or `undefined` if the selection | ||
* is cleared. | ||
*/ | ||
onItemSelect: PropTypes.func.isRequired, | ||
/** Indicates whether the component should display a text filter or not. */ | ||
displayDataFilter: PropTypes.bool, | ||
/** Placeholder to display in the filter text input. */ | ||
filterPlaceholder: PropTypes.string, | ||
}, | ||
|
||
getDefaultProps() { | ||
return { | ||
placement: 'bottom', | ||
triggerAction: 'click', | ||
items: [], | ||
itemFormatter: item => item, | ||
selectedItem: undefined, | ||
onItemSelect: () => {}, | ||
displayDataFilter: true, | ||
filterPlaceholder: 'Type to filter', | ||
}; | ||
}, | ||
|
||
getInitialState() { | ||
return { | ||
filterText: '', | ||
filteredItems: this.props.items, | ||
selectedItem: this.props.selectedItem, | ||
}; | ||
}, | ||
|
||
componentWillReceiveProps(nextProps) { | ||
if (this.props.selectedItem !== nextProps.selectedItem) { | ||
this.setState({ selectedItem: nextProps.selectedItem }); | ||
} | ||
if (this.props.items !== nextProps.items) { | ||
this.filterData(this.state.filterText, nextProps.items); | ||
} | ||
}, | ||
|
||
handleItemSelection(item) { | ||
return () => { | ||
this.setState({ selectedItem: item }); | ||
this.props.onItemSelect(item); | ||
}; | ||
}, | ||
|
||
filterData(filterText, items) { | ||
const newFilteredItems = items.filter(item => item.match(new RegExp(filterText, 'i'))); | ||
this.setState({ filterText: filterText, filteredItems: newFilteredItems }); | ||
}, | ||
|
||
handleFilterChange(items) { | ||
return (event) => { | ||
const filterText = event.target.value.trim(); | ||
this.filterData(filterText, items); | ||
}; | ||
}, | ||
|
||
pickPopoverProps(props) { | ||
const popoverPropKeys = Object.keys(Popover.propTypes); | ||
return lodash.pick(props, popoverPropKeys); | ||
}, | ||
|
||
renderDataFilter(items) { | ||
return ( | ||
<FormGroup controlId="dataFilterInput" className={style.dataFilterInput}> | ||
<FormControl type="text" placeholder={this.props.filterPlaceholder} onChange={this.handleFilterChange(items)} /> | ||
</FormGroup> | ||
); | ||
}, | ||
|
||
renderClearSelectionItem() { | ||
return ( | ||
<ListGroupItem onClick={this.handleItemSelection()}><i className="fa fa-fw fa-times text-danger" /> Clear selection</ListGroupItem> | ||
); | ||
}, | ||
|
||
render() { | ||
const { displayDataFilter, itemFormatter, items, placement, triggerAction, triggerNode, ...otherProps } = this.props; | ||
const popoverProps = this.pickPopoverProps(otherProps); | ||
const { filteredItems, selectedItem } = this.state; | ||
|
||
const popover = ( | ||
<Popover {...popoverProps} className={style.customPopover}> | ||
{displayDataFilter && this.renderDataFilter(items)} | ||
{selectedItem && this.renderClearSelectionItem()} | ||
<IsolatedScroll className={style.scrollableList}> | ||
<ListGroup> | ||
{filteredItems.map((item) => { | ||
return ( | ||
<ListGroupItem key={item} | ||
onClick={this.handleItemSelection(item)} | ||
active={this.state.selectedItem === item}> | ||
{itemFormatter(item)} | ||
</ListGroupItem> | ||
); | ||
})} | ||
</ListGroup> | ||
</IsolatedScroll> | ||
</Popover> | ||
); | ||
|
||
return ( | ||
<OverlayTrigger trigger={triggerAction} placement={placement} overlay={popover} rootClose> | ||
{triggerNode} | ||
</OverlayTrigger> | ||
); | ||
}, | ||
}); | ||
|
||
export default SelectPopover; |
115 changes: 115 additions & 0 deletions
115
graylog2-web-interface/src/components/common/SelectPopover.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
```js | ||
const createReactClass = require('create-react-class'); | ||
const Button = require('react-bootstrap').Button; | ||
|
||
const items = [ | ||
'Black', | ||
'Blue', | ||
'Green', | ||
'Red', | ||
'White', | ||
'Yellow', | ||
]; | ||
|
||
const SelectPopoverExample = createReactClass({ | ||
getInitialState() { | ||
return { | ||
selectedItem: undefined, | ||
}; | ||
}, | ||
|
||
handleItemSelect(item) { | ||
this.setState({ selectedItem: item }); | ||
}, | ||
|
||
render() { | ||
const selectedItem = this.state.selectedItem; | ||
|
||
return ( | ||
<div> | ||
<div style={{ display: 'inline-block', marginRight: 20 }}> | ||
<SelectPopover id="example-popover" | ||
title="Filter by color" | ||
triggerNode={<Button bsStyle="info" bsSize="small">Select color</Button>} | ||
items={items} | ||
selectedItem={selectedItem} | ||
onItemSelect={this.handleItemSelect} | ||
displayDataFilter={false} /> | ||
</div> | ||
|
||
{selectedItem ? `You have selected ${selectedItem}` : 'Please select a color!'} | ||
</div> | ||
); | ||
} | ||
}); | ||
|
||
<SelectPopoverExample /> | ||
``` | ||
|
||
```js | ||
const createReactClass = require('create-react-class'); | ||
const Badge = require('react-bootstrap').Badge; | ||
const Button = require('react-bootstrap').Button; | ||
|
||
const items = [ | ||
'AliceBlue', | ||
'Aqua', | ||
'Black', | ||
'Blue', | ||
'Brown', | ||
'Cyan', | ||
'DarkMagenta', | ||
'Gold', | ||
'Green', | ||
'Magenta', | ||
'Navy', | ||
'Red', | ||
'SeaGreen', | ||
'Turquoise', | ||
'White', | ||
'Yellow', | ||
]; | ||
|
||
const SelectPopoverFormattedExample = createReactClass({ | ||
getInitialState() { | ||
return { | ||
selectedItem: undefined, | ||
}; | ||
}, | ||
|
||
handleItemSelect(item) { | ||
this.setState({ selectedItem: item }); | ||
}, | ||
|
||
formatItem(item) { | ||
return ( | ||
<span> | ||
<i className="fa fa-fw fa-square" style={{ color: item }} /> {item} | ||
</span> | ||
) | ||
}, | ||
|
||
render() { | ||
const selectedItem = this.state.selectedItem; | ||
|
||
return ( | ||
<div> | ||
<div style={{ display: 'inline-block', marginRight: 20 }}> | ||
<SelectPopover id="example-popover-formatted" | ||
title="Filter by color" | ||
triggerNode={<Button bsStyle="info" bsSize="small">Select color</Button>} | ||
items={items} | ||
itemFormatter={this.formatItem} | ||
selectedItem={selectedItem} | ||
onItemSelect={this.handleItemSelect} | ||
filterPlaceholder="Filter by color" /> | ||
</div> | ||
|
||
{selectedItem ? `You have selected ${selectedItem}` : 'Please select another color!'} | ||
</div> | ||
); | ||
} | ||
}); | ||
|
||
<SelectPopoverFormattedExample /> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters