From 7de7820ee96d7b3c51cde48a35791842bec88a9d Mon Sep 17 00:00:00 2001 From: cchaos Date: Mon, 20 Aug 2018 20:06:14 -0400 Subject: [PATCH 01/22] Quick element piecing --- src-docs/src/views/header/global_query.js | 124 ++++++++++++++++++++ src-docs/src/views/header/header_example.js | 26 ++++ src/components/form/_form.scss | 10 ++ 3 files changed, 160 insertions(+) create mode 100644 src-docs/src/views/header/global_query.js diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js new file mode 100644 index 00000000000..4f0d842b7f3 --- /dev/null +++ b/src-docs/src/views/header/global_query.js @@ -0,0 +1,124 @@ +import React, { Component } from 'react'; + +import { + EuiFilterButton, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiBadge, + EuiButtonEmpty, + EuiButtonIcon, +} from '../../../../src/components'; + +export default class extends Component { + constructor(props) { + super(props); + + this.state = { + isFiltersVisible: true, + filters: [1, 2, 3, 4, 5, 6], + query: '', + }; + } + + toggleFilterVisibility = () => { + this.setState(prevState => ({ + isFiltersVisible: !prevState.isFiltersVisible, + })); + }; + + onQueryChange = e => { + this.setState({ + query: e.target.value, + }); + }; + + render() { + const filterTriggerButton = ( + 0} + numFilters={this.state.filters.length > 0 ? this.state.filters.length : null} + > + Filters + + ); + + return ( + + + {this.state.isFiltersVisible && } + + ); + } +} + +function GlobalFilterGroup({ filters }) { + const filterItems = filters.map((filter, index) => { + return ( + + @tags.keyword: "late" + + ); + }); + + return ( + + + + + + + + + + + + + + + + + @tags.keyword: "late" + + + @tags.keyword: "late" + + + + + {filterItems} + + + + + Add filter + + + + + + + + + + + + ); +} diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 3bbb1fa6970..956fac0a519 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -16,6 +16,7 @@ import { EuiCode, EuiHeaderLinks, EuiHeaderLink, + EuiCallOut, } from '../../../../src/components'; import Header from './header'; @@ -26,6 +27,10 @@ import HeaderLinks from './header_links'; const headerLinksSource = require('!!raw-loader!./header_links'); const headerLinksHtml = renderToHtml(HeaderLinks); +import GlobalQuery from './global_query'; +const globalQuerySource = require('!!raw-loader!./global_query'); +const globalQueryHtml = renderToHtml(GlobalQuery); + export const HeaderExample = { title: 'Header', sections: [{ @@ -70,5 +75,26 @@ export const HeaderExample = { EuiHeaderLink }, demo: , + }, { + title: 'Global query and filters', + source: [{ + type: GuideSectionTypes.JS, + code: globalQuerySource, + }, { + type: GuideSectionTypes.HTML, + code: globalQueryHtml, + }], + text: ( +
+ +

+ This documents a visual pattern for the eventual replacement of Kibana's + global query and filter bars. It uses all EUI components without any custom styles. +

+
+
+ ), + // props: { }, + demo: , }], }; diff --git a/src/components/form/_form.scss b/src/components/form/_form.scss index 087f4efcb57..3182d9edb09 100644 --- a/src/components/form/_form.scss +++ b/src/components/form/_form.scss @@ -6,3 +6,13 @@ .euiForm__errors { margin-bottom: $euiSize; } + +.euiGenericFormStyle { + // Match just the regular drop shadow of inputs + @include euiFormControlDefaultShadow; + display: flex; + align-items: center; + padding: 4px; + min-height: $euiFormControlHeight; + margin: 0; +} From fa67bb4411760cfcce0dcecab46c84ac0168e88f Mon Sep 17 00:00:00 2001 From: cchaos Date: Mon, 20 Aug 2018 23:12:22 -0400 Subject: [PATCH 02/22] Adding filter states --- src-docs/src/views/header/global_query.js | 188 +++++++++++++++++++--- 1 file changed, 164 insertions(+), 24 deletions(-) diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index 4f0d842b7f3..d6d4f9d0253 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -8,16 +8,64 @@ import { EuiBadge, EuiButtonEmpty, EuiButtonIcon, + EuiPopover, + EuiContextMenu, } from '../../../../src/components'; +function flattenPanelTree(tree, array = []) { + array.push(tree); + + if (tree.items) { + tree.items.forEach(item => { + if (item.panel) { + flattenPanelTree(item.panel, array); + item.panel = item.panel.id; + } + }); + } + + return array; +} + export default class extends Component { constructor(props) { super(props); this.state = { isFiltersVisible: true, - filters: [1, 2, 3, 4, 5, 6], + filters: [ + { + id: 'filter0', + field: '@tags.keyword', + value: 'value', + disabled: false, + pinned: true, + excluded: false, + }, { + id: 'filter1', + field: '@tags.keyword', + value: 'value', + disabled: true, + pinned: false, + excluded: false, + }, { + id: 'filter2', + field: '@tags.keyword', + value: 'value', + disabled: false, + pinned: true, + excluded: true, + }, { + id: 'filter3', + field: '@tags.keyword', + value: 'value', + disabled: false, + pinned: false, + excluded: false, + }, + ], query: '', + idOfOpenPopover: null, }; } @@ -33,6 +81,16 @@ export default class extends Component { }); }; + togglePopover = (id) => { + this.setState(prevState => ({ + idOfOpenPopover: prevState.idOfOpenPopover === null || prevState.idOfOpenPopover !== id ? id : null + })); + } + + closePopover = () => { + //this.setState({ idOfOpenPopover: null }); + } + render() { const filterTriggerButton = ( ); + const pinnedFilters = this.state.filters.filter(filter => filter.pinned); + const unpinnedFilters = this.state.filters.filter(filter => !filter.pinned); + return ( - {this.state.isFiltersVisible && } + {this.state.isFiltersVisible && + + {pinnedFilters.length && // Show pinned filters first and in a specific group + + + + + + + {this.renderFilterItems(pinnedFilters)} + + + + } + {this.renderFilterItems(unpinnedFilters)} + + } ); } -} -function GlobalFilterGroup({ filters }) { - const filterItems = filters.map((filter, index) => { + /** + * Rendering of items + */ + + renderFilterItems = (filters) => { + return filters.map((filter, index) => { + let icon; + let badgeColor = 'hollow'; + + if (filter.disabled) { + icon = 'eyeClosed'; + badgeColor = 'default'; + } else if (filter.excluded) { + icon = 'minusInCircle'; + badgeColor = 'danger'; + } + + const badge = ( + this.togglePopover(filter.id)}> + {filter.field}: + "{filter.value}" + + ); + + return ( + + {this._createFilterContextMenu(filter, index, badge)} + + ); + }); + } + + _createFilterContextMenu = (filter, index, button) => { + const panelTree = { + id: 0, + items: [{ + name: `${filter.disabled ? 'Enable' : 'Disable'}`, + icon: `${filter.disabled ? 'eye' : 'eyeClosed'}`, + onClick: () => { this.closePopover(); }, + }, { + name: `${filter.pinned ? 'Unpin' : 'Pin'}`, + icon: 'pin', + onClick: () => { this.closePopover(); }, + }, { + name: `${filter.excluded ? 'Include' : 'Exclude'}`, + icon: `${filter.excluded ? 'plusInCircle' : 'minusInCircle'}`, + onClick: () => { this.closePopover(); }, + }, { + name: 'Remove', + icon: 'trash', + onClick: () => { this.closePopover(); }, + }, { + name: 'Edit', + icon: 'pencil', + panel: { + id: 1, + content: ( +
+ Form here +
+ ), + }, + }], + }; + + const panels = flattenPanelTree(panelTree); + return ( - - @tags.keyword: "late" - + + + ); - }); + } +} +function GlobalFilterGroup({ children }) { return ( @@ -89,21 +243,7 @@ function GlobalFilterGroup({ filters }) { style={{ marginTop: 8 }} > - - - - - - - @tags.keyword: "late" - - - @tags.keyword: "late" - - - - - {filterItems} + {children} From c4d711fc5b7682f4a3a7b22154b86426945ee7e7 Mon Sep 17 00:00:00 2001 From: cchaos Date: Mon, 20 Aug 2018 23:44:39 -0400 Subject: [PATCH 03/22] Added menus --- src-docs/src/views/header/global_query.js | 77 ++++++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index d6d4f9d0253..2b38f47beef 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -10,6 +10,8 @@ import { EuiButtonIcon, EuiPopover, EuiContextMenu, + EuiToolTip, + EuiPopoverTitle, } from '../../../../src/components'; function flattenPanelTree(tree, array = []) { @@ -117,12 +119,14 @@ export default class extends Component { icon="console" /> {this.state.isFiltersVisible && - + {pinnedFilters.length && // Show pinned filters first and in a specific group - + + + {this.renderFilterItems(pinnedFilters)} @@ -145,17 +149,20 @@ export default class extends Component { return filters.map((filter, index) => { let icon; let badgeColor = 'hollow'; + const style = {}; if (filter.disabled) { icon = 'eyeClosed'; badgeColor = 'default'; + style.fontStyle = 'italic'; + style.fontWeight = '400'; } else if (filter.excluded) { icon = 'minusInCircle'; - badgeColor = 'danger'; + style.textDecoration = 'line-through'; } const badge = ( - this.togglePopover(filter.id)}> + this.togglePopover(filter.id)}> {filter.field}: "{filter.value}" @@ -202,8 +209,6 @@ export default class extends Component { }], }; - const panels = flattenPanelTree(panelTree); - return ( + + ); + } + + _createFilterBarContextMenu = () => { + const menuId = 'GlobalFilterBarContextMenu'; + const panelTree = { + id: 0, + items: [{ + name: 'Enable', + icon: 'eye', + onClick: () => { this.closePopover(); }, + }, { + name: 'Disable', + icon: 'eyeClosed', + onClick: () => { this.closePopover(); }, + }, { + name: 'Pin', + icon: 'pin', + onClick: () => { this.closePopover(); }, + }, { + name: 'Unpin', + icon: 'pin', + onClick: () => { this.closePopover(); }, + }, { + name: 'Invert', + icon: 'invert', + onClick: () => { this.closePopover(); }, + }, { + name: 'Toggle visibility', + icon: 'eye', + onClick: () => { this.closePopover(); }, + }, { + name: 'Remove all', + icon: 'trash', + onClick: () => { this.closePopover(); }, + }] + }; + + return ( + this.togglePopover(menuId)} color="text" iconType="gear" aria-label="Change all filters" />} + anchorPosition="downCenter" + panelPaddingSize="none" + withTitle + > + Change all filters + ); } } -function GlobalFilterGroup({ children }) { +function GlobalFilterGroup({ children, allFiltersButton }) { return ( @@ -251,7 +310,7 @@ function GlobalFilterGroup({ children }) { Add filter - + {allFiltersButton} From fc2f02963a267d04f6fe67cafae62f5e85d0e33f Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 21 Aug 2018 01:05:27 -0400 Subject: [PATCH 04/22] Added add filter menu --- src-docs/src/views/header/global_query.js | 360 +++++++++++++++------- 1 file changed, 244 insertions(+), 116 deletions(-) diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index 2b38f47beef..d1860e1b3d8 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { EuiFilterButton, @@ -12,6 +13,10 @@ import { EuiContextMenu, EuiToolTip, EuiPopoverTitle, + EuiFormRow, + EuiSelect, + EuiComboBox, + EuiButton, } from '../../../../src/components'; function flattenPanelTree(tree, array = []) { @@ -43,21 +48,24 @@ export default class extends Component { disabled: false, pinned: true, excluded: false, - }, { + }, + { id: 'filter1', field: '@tags.keyword', value: 'value', disabled: true, pinned: false, excluded: false, - }, { + }, + { id: 'filter2', field: '@tags.keyword', value: 'value', disabled: false, pinned: true, excluded: true, - }, { + }, + { id: 'filter3', field: '@tags.keyword', value: 'value', @@ -68,7 +76,11 @@ export default class extends Component { ], query: '', idOfOpenPopover: null, + addFilterMenuWidth: null, + addFilterMenuIsOpen: false, }; + + this.filterBarRef = React.createRef(); } toggleFilterVisibility = () => { @@ -83,15 +95,30 @@ export default class extends Component { }); }; - togglePopover = (id) => { + togglePopover = id => { this.setState(prevState => ({ - idOfOpenPopover: prevState.idOfOpenPopover === null || prevState.idOfOpenPopover !== id ? id : null + idOfOpenPopover: + prevState.idOfOpenPopover === null || prevState.idOfOpenPopover !== id ? id : null, })); - } + }; + + toggleAddFilterMenu = () => { + this.setState(prevState => ({ + addFilterMenuIsOpen: !prevState.addFilterMenuIsOpen, + })); + + console.log(this.filterBarRef); + + requestAnimationFrame(() => { + this.setState({ + addFilterMenuWidth: this.filterBarRef.current.getBoundingClientRect().width - 24, // account for border not inner shadow + }); + }); + }; closePopover = () => { //this.setState({ idOfOpenPopover: null }); - } + }; render() { const filterTriggerButton = ( @@ -118,10 +145,18 @@ export default class extends Component { fullWidth icon="console" /> - {this.state.isFiltersVisible && - - {pinnedFilters.length && // Show pinned filters first and in a specific group - + {this.state.isFiltersVisible && ( + + {pinnedFilters.length && ( // Show pinned filters first and in a specific group + @@ -130,13 +165,12 @@ export default class extends Component { {this.renderFilterItems(pinnedFilters)} - - } + )} {this.renderFilterItems(unpinnedFilters)} - } + )} ); } @@ -145,7 +179,7 @@ export default class extends Component { * Rendering of items */ - renderFilterItems = (filters) => { + renderFilterItems = filters => { return filters.map((filter, index) => { let icon; let badgeColor = 'hollow'; @@ -162,7 +196,12 @@ export default class extends Component { } const badge = ( - this.togglePopover(filter.id)}> + this.togglePopover(filter.id)} + > {filter.field}: "{filter.value}" @@ -174,39 +213,49 @@ export default class extends Component { ); }); - } + }; _createFilterContextMenu = (filter, index, button) => { const panelTree = { id: 0, - items: [{ - name: `${filter.disabled ? 'Enable' : 'Disable'}`, - icon: `${filter.disabled ? 'eye' : 'eyeClosed'}`, - onClick: () => { this.closePopover(); }, - }, { - name: `${filter.pinned ? 'Unpin' : 'Pin'}`, - icon: 'pin', - onClick: () => { this.closePopover(); }, - }, { - name: `${filter.excluded ? 'Include' : 'Exclude'}`, - icon: `${filter.excluded ? 'plusInCircle' : 'minusInCircle'}`, - onClick: () => { this.closePopover(); }, - }, { - name: 'Remove', - icon: 'trash', - onClick: () => { this.closePopover(); }, - }, { - name: 'Edit', - icon: 'pencil', - panel: { - id: 1, - content: ( -
- Form here -
- ), + items: [ + { + name: `${filter.disabled ? 'Enable' : 'Disable'}`, + icon: `${filter.disabled ? 'eye' : 'eyeClosed'}`, + onClick: () => { + this.closePopover(); + }, + }, + { + name: `${filter.pinned ? 'Unpin' : 'Pin'}`, + icon: 'pin', + onClick: () => { + this.closePopover(); + }, + }, + { + name: `${filter.excluded ? 'Include' : 'Exclude'}`, + icon: `${filter.excluded ? 'plusInCircle' : 'minusInCircle'}`, + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Remove', + icon: 'trash', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Edit', + icon: 'pencil', + panel: { + id: 1, + content:
Form here
, + }, }, - }], + ], }; return ( @@ -219,47 +268,66 @@ export default class extends Component { anchorPosition="downCenter" panelPaddingSize="none" > - + ); - } + }; _createFilterBarContextMenu = () => { const menuId = 'GlobalFilterBarContextMenu'; const panelTree = { id: 0, - items: [{ - name: 'Enable', - icon: 'eye', - onClick: () => { this.closePopover(); }, - }, { - name: 'Disable', - icon: 'eyeClosed', - onClick: () => { this.closePopover(); }, - }, { - name: 'Pin', - icon: 'pin', - onClick: () => { this.closePopover(); }, - }, { - name: 'Unpin', - icon: 'pin', - onClick: () => { this.closePopover(); }, - }, { - name: 'Invert', - icon: 'invert', - onClick: () => { this.closePopover(); }, - }, { - name: 'Toggle visibility', - icon: 'eye', - onClick: () => { this.closePopover(); }, - }, { - name: 'Remove all', - icon: 'trash', - onClick: () => { this.closePopover(); }, - }] + items: [ + { + name: 'Enable', + icon: 'eye', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Disable', + icon: 'eyeClosed', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Pin', + icon: 'pin', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Unpin', + icon: 'pin', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Invert', + icon: 'invert', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Toggle visibility', + icon: 'eye', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Remove all', + icon: 'trash', + onClick: () => { + this.closePopover(); + }, + }, + ], }; return ( @@ -267,57 +335,117 @@ export default class extends Component { id={menuId} isOpen={this.state.idOfOpenPopover === menuId} closePopover={this.closePopover} - button={ this.togglePopover(menuId)} color="text" iconType="gear" aria-label="Change all filters" />} + button={ + this.togglePopover(menuId)} + color="text" + iconType="gear" + aria-label="Change all filters" + /> + } anchorPosition="downCenter" panelPaddingSize="none" withTitle > Change all filters - + ); - } -} + }; -function GlobalFilterGroup({ children, allFiltersButton }) { - return ( - - - - - - - - - + _createFilterBarAddMenu = () => { + const menuId = 'GlobalFilterBarAddMenu'; - {children} + return ( + + + Add filter + + } + anchorPosition="downCenter" + withTitle + > + Add a filter +
+ + + + + + + + + + + + + + + + + + + + Save + + + Cancel + + + + Edit Query DSL + + +
+
+ ); + }; +} + +// eslint-disable-next-line react/no-multi-comp +class GlobalFilterGroup extends Component { + static propTypes = { + children: PropTypes.node, + allFiltersButton: PropTypes.node, + addFilterButton: PropTypes.node, + }; + render() { + return ( +
+ + + + + + + - - - Add filter - - - {allFiltersButton} + + {this.props.children} + + + + {this.props.addFilterButton} + {this.props.allFiltersButton} + - - - - - ); +
+ ); + } } From 769f5f9fc6db56423c3b469f263509d7542801e9 Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 21 Aug 2018 14:43:19 -0400 Subject: [PATCH 05/22] Using combobox for adding filters --- src-docs/src/views/header/global_query.js | 224 +++++++++++++++------- 1 file changed, 159 insertions(+), 65 deletions(-) diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index d1860e1b3d8..02a6d7433ad 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -14,11 +14,65 @@ import { EuiToolTip, EuiPopoverTitle, EuiFormRow, - EuiSelect, EuiComboBox, EuiButton, } from '../../../../src/components'; +const fieldOptions = [ + { + label: 'Fields', + isGroupLabelOption: true, + }, + { + label: 'field_1', + }, + { + label: 'field_2', + }, + { + label: 'field_3', + }, + { + label: 'field_4', + }, +]; +const operatorOptions = [ + { + label: 'Operators', + isGroupLabelOption: true, + }, + { + label: 'IS', + }, + { + label: 'IS NOT', + }, + { + label: 'IS ONE OF', + }, + { + label: 'EXISTS', + }, +]; +const valueOptions = [ + { + label: 'Values', + isGroupLabelOption: true, + }, + { + label: 'Value 1', + }, + { + label: 'Value 2', + }, + { + label: 'Value 3', + }, + { + label: 'Value 4', + }, +]; + function flattenPanelTree(tree, array = []) { array.push(tree); @@ -78,9 +132,12 @@ export default class extends Component { idOfOpenPopover: null, addFilterMenuWidth: null, addFilterMenuIsOpen: false, + isComboBoxLoading: false, + isComboBoxOpen: false, + selectedComboBoxOptions: [], + comboBoxOptions: fieldOptions, }; - this.filterBarRef = React.createRef(); } toggleFilterVisibility = () => { @@ -106,20 +163,52 @@ export default class extends Component { this.setState(prevState => ({ addFilterMenuIsOpen: !prevState.addFilterMenuIsOpen, })); + }; - console.log(this.filterBarRef); + onComboBoxChange = selectedComboBoxOptions => { + let options = fieldOptions; + if (selectedComboBoxOptions.length === 1) { + options = operatorOptions; + } else if (selectedComboBoxOptions.length > 1) { + options = valueOptions; + } - requestAnimationFrame(() => { - this.setState({ - addFilterMenuWidth: this.filterBarRef.current.getBoundingClientRect().width - 24, // account for border not inner shadow - }); + this.setState({ + selectedComboBoxOptions, + comboBoxOptions: options, }); }; + onSearchChange = () => { + // let options = this.state.comboBoxOptions; + // this.setState({ + // isComboBoxLoading: true, + // comboBoxOptions: [], + // }); + // clearTimeout(this.searchTimeout); + // if (this.state.selectedComboBoxOptions.length === 1) { + // options = operatorOptions; + // } else if (this.state.selectedComboBoxOptions.length > 1) { + // options = valueOptions; + // } + // this.searchTimeout = setTimeout(() => { + // // Simulate a remotely-executed search. + // this.setState({ + // isComboBoxLoading: false, + // comboBoxOptions: options.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())), + // }); + // }, 200); + }; + closePopover = () => { //this.setState({ idOfOpenPopover: null }); }; + componentDidMount() { + // Simulate initial load. + this.onSearchChange(''); + } + render() { const filterTriggerButton = ( {this.state.isFiltersVisible && ( @@ -354,13 +442,21 @@ export default class extends Component { }; _createFilterBarAddMenu = () => { - const menuId = 'GlobalFilterBarAddMenu'; + const label = ( + + Field, Operator, Value(s) + + + Edit as Query DSL + + + + ); return ( this.setState({ addFilterMenuIsOpen: false })} button={ + Add filter @@ -370,35 +466,33 @@ export default class extends Component { withTitle > Add a filter -
- - - - - - - - - - - - - - - - - - +
+ + + + {/* + + + + + */} + + - Save + Add Cancel - - Edit Query DSL -
@@ -406,7 +500,7 @@ export default class extends Component { }; } -// eslint-disable-next-line react/no-multi-comp +// eslint-disable-next-line react/no-multi-comp, react/prefer-stateless-function class GlobalFilterGroup extends Component { static propTypes = { children: PropTypes.node, @@ -416,36 +510,36 @@ class GlobalFilterGroup extends Component { render() { return ( -
- - - - - - - - - - {this.props.children} - - - - {this.props.addFilterButton} - {this.props.allFiltersButton} - - - - - -
+ + + + + + + + + + + {this.props.children} + + + + {this.props.addFilterButton} + {this.props.allFiltersButton} + + + + + + ); } } From 51d03aa5d0f582a3bdf94a3c565abceb4f4f4d88 Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 21 Aug 2018 16:47:41 -0400 Subject: [PATCH 06/22] Couple extras --- src-docs/src/views/header/global_query.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index 02a6d7433ad..ee0a1401a1d 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -16,6 +16,7 @@ import { EuiFormRow, EuiComboBox, EuiButton, + EuiLink, } from '../../../../src/components'; const fieldOptions = [ @@ -179,7 +180,8 @@ export default class extends Component { }); }; - onSearchChange = () => { + // eslint-disable-next-line no-unused-vars + onSearchChange = searchValue => { // let options = this.state.comboBoxOptions; // this.setState({ // isComboBoxLoading: true, @@ -245,7 +247,7 @@ export default class extends Component { grow={false} style={{ background: 'aquamarine', padding: 4 }} > - + @@ -446,9 +448,7 @@ export default class extends Component { Field, Operator, Value(s) - - Edit as Query DSL - + Edit as Query DSL ); From 55150b142e82ef728a5492e95221f7ad30b21a5d Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 21 Aug 2018 21:10:35 -0400 Subject: [PATCH 07/22] Refactor --- src-docs/src/theme_dark.scss | 1 + src-docs/src/theme_k6_dark.scss | 1 + src-docs/src/theme_k6_light.scss | 1 + src-docs/src/theme_light.scss | 1 + .../src/views/header/_global_filter_bar.scss | 18 + .../src/views/header/_global_filter_form.scss | 0 .../views/header/_global_filter_group.scss | 11 + .../src/views/header/_global_filter_item.scss | 12 + .../src/views/header/global_filter_add.js | 54 ++ .../src/views/header/global_filter_bar.js | 84 +++ .../src/views/header/global_filter_form.js | 164 ++++++ .../src/views/header/global_filter_item.js | 167 ++++++ .../src/views/header/global_filter_options.js | 126 +++++ src-docs/src/views/header/global_query.js | 483 +----------------- src/components/form/_form.scss | 10 - 15 files changed, 666 insertions(+), 467 deletions(-) create mode 100644 src-docs/src/views/header/_global_filter_bar.scss create mode 100644 src-docs/src/views/header/_global_filter_form.scss create mode 100644 src-docs/src/views/header/_global_filter_group.scss create mode 100644 src-docs/src/views/header/_global_filter_item.scss create mode 100644 src-docs/src/views/header/global_filter_add.js create mode 100644 src-docs/src/views/header/global_filter_bar.js create mode 100644 src-docs/src/views/header/global_filter_form.js create mode 100644 src-docs/src/views/header/global_filter_item.js create mode 100644 src-docs/src/views/header/global_filter_options.js diff --git a/src-docs/src/theme_dark.scss b/src-docs/src/theme_dark.scss index 37e1be026cf..4c26933b660 100644 --- a/src-docs/src/theme_dark.scss +++ b/src-docs/src/theme_dark.scss @@ -1,2 +1,3 @@ @import "../../src/theme_dark"; @import "./components/guide_components"; +@import "./views/header/global_filter_group"; diff --git a/src-docs/src/theme_k6_dark.scss b/src-docs/src/theme_k6_dark.scss index c42c1f6f630..270a2dbea0e 100644 --- a/src-docs/src/theme_k6_dark.scss +++ b/src-docs/src/theme_k6_dark.scss @@ -1,2 +1,3 @@ @import "../../src/theme_k6_dark"; @import "./components/guide_components"; +@import "./views/header/global_filter_group"; diff --git a/src-docs/src/theme_k6_light.scss b/src-docs/src/theme_k6_light.scss index 3a483817792..a899b00f3d6 100644 --- a/src-docs/src/theme_k6_light.scss +++ b/src-docs/src/theme_k6_light.scss @@ -1,2 +1,3 @@ @import "../../src/theme_k6_light"; @import "./components/guide_components"; +@import "./views/header/global_filter_group"; diff --git a/src-docs/src/theme_light.scss b/src-docs/src/theme_light.scss index ca47427d340..ce8c5b7e19f 100644 --- a/src-docs/src/theme_light.scss +++ b/src-docs/src/theme_light.scss @@ -1,3 +1,4 @@ @import "../../src/theme_light"; @import "./components/guide_components"; +@import "./views/header/global_filter_group"; diff --git a/src-docs/src/views/header/_global_filter_bar.scss b/src-docs/src/views/header/_global_filter_bar.scss new file mode 100644 index 00000000000..5e11e71b1e7 --- /dev/null +++ b/src-docs/src/views/header/_global_filter_bar.scss @@ -0,0 +1,18 @@ +.globalFilterBar { + // Match just the regular drop shadow of inputs + @include euiFormControlDefaultShadow; + display: flex; + align-items: center; + padding: 4px; + min-height: $euiFormControlHeight; + margin: 0; +} + +.globalFilterBar__pinned { + padding: $euiSizeXS; + background-color: saturate(tintOrShade($euiColorSecondary, 40%, 50%), 50%); +} + +.globalFilterBar__pinnedIcon { + margin: -2px 0; +} diff --git a/src-docs/src/views/header/_global_filter_form.scss b/src-docs/src/views/header/_global_filter_form.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src-docs/src/views/header/_global_filter_group.scss b/src-docs/src/views/header/_global_filter_group.scss new file mode 100644 index 00000000000..14bfdd787ee --- /dev/null +++ b/src-docs/src/views/header/_global_filter_group.scss @@ -0,0 +1,11 @@ +@import 'global_filter_bar'; +@import 'global_filter_item'; +@import 'global_filter_form'; + +.globalFilterGroup__filterBar { + margin-top: 8px; +} + +.globalFilterGroup__branch { + fill: $euiColorLightShade; +} diff --git a/src-docs/src/views/header/_global_filter_item.scss b/src-docs/src/views/header/_global_filter_item.scss new file mode 100644 index 00000000000..6e133658631 --- /dev/null +++ b/src-docs/src/views/header/_global_filter_item.scss @@ -0,0 +1,12 @@ +.globalFilterItem { + +} + +.globalFilterItem-isDisabled { + font-weight: $euiFontWeightRegular; + font-style: italic; +} + +.globalFilterItem-isExcluded { + text-decoration: line-through; +} diff --git a/src-docs/src/views/header/global_filter_add.js b/src-docs/src/views/header/global_filter_add.js new file mode 100644 index 00000000000..0ac6b0a7608 --- /dev/null +++ b/src-docs/src/views/header/global_filter_add.js @@ -0,0 +1,54 @@ +import React, { Component } from 'react'; + +import { + EuiButtonEmpty, + EuiPopover, + EuiPopoverTitle, +} from '../../../../src/components'; + +import GlobalFilterForm from './global_filter_form'; + +export default class GlobalFilterAdd extends Component { + static propTypes = { + } + + constructor(props) { + super(props); + + this.state = { + isPopoverOpen: false, + }; + } + + togglePopover = () => { + this.setState(prevState => ({ + isPopoverOpen: !prevState.isPopoverOpen, + })); + }; + + closePopover = () => { + this.setState({ isPopoverOpen: false }); + }; + + render() { + const { isPopoverOpen } = this.state; + + return ( + + + Add filter + + } + anchorPosition="downCenter" + withTitle + > + Add a filter + + + + ); + } +} diff --git a/src-docs/src/views/header/global_filter_bar.js b/src-docs/src/views/header/global_filter_bar.js new file mode 100644 index 00000000000..d6175a09387 --- /dev/null +++ b/src-docs/src/views/header/global_filter_bar.js @@ -0,0 +1,84 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiToolTip, + EuiButtonIcon, +} from '../../../../src/components'; +import GlobalFilterAdd from './global_filter_add'; +import GlobalFilterOptions from './global_filter_options'; +import { GlobalFilterItem } from './global_filter_item'; + +export const GlobalFilterBar = ({ + filters, + className, + ...rest, +}) => { + + const classes = classNames( + 'globalFilterBar', + className, + ); + + const pinnedFilters = filters.filter(filter => filter.isPinned).map((filter) => { + return ( + + + + ); + }); + + const unpinnedFilters = filters.filter(filter => !filter.isPinned).map((filter) => { + return ( + + + + ); + }); + + return ( + + {pinnedFilters.length && ( // Show pinned filters first and in a specific group + + + + + + + + + {pinnedFilters} + + + + )} + + {unpinnedFilters} + + + + + + + + + ); +}; + + +GlobalFilterBar.propTypes = { + filters: PropTypes.array, +}; diff --git a/src-docs/src/views/header/global_filter_form.js b/src-docs/src/views/header/global_filter_form.js new file mode 100644 index 00000000000..2269732f57c --- /dev/null +++ b/src-docs/src/views/header/global_filter_form.js @@ -0,0 +1,164 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiFormRow, + EuiComboBox, + EuiButton, + EuiLink, +} from '../../../../src/components'; + +const fieldOptions = [ + { + label: 'Fields', + isGroupLabelOption: true, + }, + { + label: 'field_1', + }, + { + label: 'field_2', + }, + { + label: 'field_3', + }, + { + label: 'field_4', + }, +]; +const operatorOptions = [ + { + label: 'Operators', + isGroupLabelOption: true, + }, + { + label: 'IS', + }, + { + label: 'IS NOT', + }, + { + label: 'IS ONE OF', + }, + { + label: 'EXISTS', + }, +]; +const valueOptions = [ + { + label: 'Values', + isGroupLabelOption: true, + }, + { + label: 'Value 1', + }, + { + label: 'Value 2', + }, + { + label: 'Value 3', + }, + { + label: 'Value 4', + }, +]; + +export default class GlobalFilterForm extends Component { + static propTypes = { + onAdd: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + } + + constructor(props) { + super(props); + + this.state = { + isComboBoxLoading: false, + selectedComboBoxOptions: [], + comboBoxOptions: fieldOptions, + }; + } + + onComboBoxChange = selectedComboBoxOptions => { + let options = fieldOptions; + if (selectedComboBoxOptions.length === 1) { + options = operatorOptions; + } else if (selectedComboBoxOptions.length > 1) { + options = valueOptions; + } + + this.setState({ + selectedComboBoxOptions, + comboBoxOptions: options, + }); + }; + + // eslint-disable-next-line no-unused-vars + onSearchChange = searchValue => { + // let options = this.state.comboBoxOptions; + // this.setState({ + // isComboBoxLoading: true, + // comboBoxOptions: [], + // }); + // clearTimeout(this.searchTimeout); + // if (this.state.selectedComboBoxOptions.length === 1) { + // options = operatorOptions; + // } else if (this.state.selectedComboBoxOptions.length > 1) { + // options = valueOptions; + // } + // this.searchTimeout = setTimeout(() => { + // // Simulate a remotely-executed search. + // this.setState({ + // isComboBoxLoading: false, + // comboBoxOptions: options.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())), + // }); + // }, 200); + }; + + componentDidMount() { + // Simulate initial load. + this.onSearchChange(''); + } + + render() { + const { onAdd, onCancel, ...rest } = this.props; + + const label = ( + + Field, Operator, Value(s) + + Edit as Query DSL + + + ); + + return ( +
+ + + + + + + Add + + + Cancel + + + +
+ ); + } +} diff --git a/src-docs/src/views/header/global_filter_item.js b/src-docs/src/views/header/global_filter_item.js new file mode 100644 index 00000000000..e38c9afc915 --- /dev/null +++ b/src-docs/src/views/header/global_filter_item.js @@ -0,0 +1,167 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { + EuiBadge, + EuiPopover, + EuiContextMenu, +} from '../../../../src/components'; +import GlobalFilterForm from './global_filter_form'; + +function flattenPanelTree(tree, array = []) { + array.push(tree); + + if (tree.items) { + tree.items.forEach(item => { + if (item.panel) { + flattenPanelTree(item.panel, array); + item.panel = item.panel.id; + } + }); + } + + return array; +} + +export class GlobalFilterItem extends Component { + static propTypes = { + className: PropTypes.string, + id: PropTypes.string.isRequired, + field: PropTypes.string.isRequired, + operator: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + isDisabled: PropTypes.bool.isRequired, + isPinned: PropTypes.bool.isRequired, + isExcluded: PropTypes.bool.isRequired, + } + + constructor(props) { + super(props); + + this.state = { + isPopoverOpen: false, + }; + } + + togglePopover = () => { + this.setState(prevState => ({ + isPopoverOpen: !prevState.isPopoverOpen, + })); + } + + closePopover = () => { + this.setState({ + isPopoverOpen: false, + }); + } + + + render() { + const { + className, + id, + field, + operator, // eslint-disable-line no-unused-vars + value, + isDisabled, + isPinned, + isExcluded, + ...rest + } = this.props; + + const classes = classNames( + 'globalFilterItem', + { + 'globalFilterItem-isDisabled': isDisabled, + 'globalFilterItem-isPinned': isPinned, + 'globalFilterItem-isExcluded': isExcluded, + }, + className, + ); + + let icon; + let badgeColor = 'hollow'; + + if (isDisabled) { + icon = 'eyeClosed'; + badgeColor = 'default'; + } else if (isExcluded) { + icon = 'minusInCircle'; + } + + const badge = ( + + {field}: + "{value}" + + ); + + return this._createFilterContextMenu(this.props, badge); + } + + _createFilterContextMenu = (filter, button) => { + const panelTree = { + id: 0, + items: [ + { + name: `${filter.isDisabled ? 'Enable' : 'Disable'}`, + icon: `${filter.isDisabled ? 'eye' : 'eyeClosed'}`, + onClick: () => { + this.closePopover(); + }, + }, + { + name: `${filter.isPinned ? 'Unpin' : 'Pin'}`, + icon: 'pin', + onClick: () => { + this.closePopover(); + }, + }, + { + name: `${filter.isExcluded ? 'Include' : 'Exclude'}`, + icon: `${filter.isExcluded ? 'plusInCircle' : 'minusInCircle'}`, + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Remove', + icon: 'trash', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Edit', + icon: 'pencil', + panel: { + id: 1, + content:
, + }, + }, + ], + }; + + return ( + + + + ); + }; +} diff --git a/src-docs/src/views/header/global_filter_options.js b/src-docs/src/views/header/global_filter_options.js new file mode 100644 index 00000000000..f2e6c7361ef --- /dev/null +++ b/src-docs/src/views/header/global_filter_options.js @@ -0,0 +1,126 @@ +import React, { Component } from 'react'; + +import { + EuiButtonIcon, + EuiPopover, + EuiContextMenu, + EuiPopoverTitle, +} from '../../../../src/components'; + +function flattenPanelTree(tree, array = []) { + array.push(tree); + + if (tree.items) { + tree.items.forEach(item => { + if (item.panel) { + flattenPanelTree(item.panel, array); + item.panel = item.panel.id; + } + }); + } + + return array; +} + +export default class GlobalFilterOptions extends Component { + static propTypes = { + } + + constructor(props) { + super(props); + + this.state = { + isPopoverOpen: false, + }; + } + + togglePopover = () => { + this.setState(prevState => ({ + isPopoverOpen: !prevState.isPopoverOpen, + })); + }; + + closePopover = () => { + this.setState({ isPopoverOpen: false }); + }; + + render() { + const { isPopoverOpen } = this.state; + + const panelTree = { + id: 0, + items: [ + { + name: 'Enable', + icon: 'eye', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Disable', + icon: 'eyeClosed', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Pin', + icon: 'pin', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Unpin', + icon: 'pin', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Invert', + icon: 'invert', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Toggle visibility', + icon: 'eye', + onClick: () => { + this.closePopover(); + }, + }, + { + name: 'Remove all', + icon: 'trash', + onClick: () => { + this.closePopover(); + }, + }, + ], + }; + + return ( + + } + anchorPosition="downCenter" + panelPaddingSize="none" + withTitle + > + Change all filters + + + ); + } +} diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index ee0a1401a1d..37997ae7a24 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -1,93 +1,13 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { EuiFilterButton, EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiBadge, - EuiButtonEmpty, - EuiButtonIcon, - EuiPopover, - EuiContextMenu, - EuiToolTip, - EuiPopoverTitle, - EuiFormRow, - EuiComboBox, - EuiButton, - EuiLink, } from '../../../../src/components'; -const fieldOptions = [ - { - label: 'Fields', - isGroupLabelOption: true, - }, - { - label: 'field_1', - }, - { - label: 'field_2', - }, - { - label: 'field_3', - }, - { - label: 'field_4', - }, -]; -const operatorOptions = [ - { - label: 'Operators', - isGroupLabelOption: true, - }, - { - label: 'IS', - }, - { - label: 'IS NOT', - }, - { - label: 'IS ONE OF', - }, - { - label: 'EXISTS', - }, -]; -const valueOptions = [ - { - label: 'Values', - isGroupLabelOption: true, - }, - { - label: 'Value 1', - }, - { - label: 'Value 2', - }, - { - label: 'Value 3', - }, - { - label: 'Value 4', - }, -]; - -function flattenPanelTree(tree, array = []) { - array.push(tree); - - if (tree.items) { - tree.items.forEach(item => { - if (item.panel) { - flattenPanelTree(item.panel, array); - item.panel = item.panel.id; - } - }); - } - - return array; -} +import { GlobalFilterBar } from './global_filter_bar'; export default class extends Component { constructor(props) { @@ -99,44 +19,41 @@ export default class extends Component { { id: 'filter0', field: '@tags.keyword', + operator: 'AND', value: 'value', - disabled: false, - pinned: true, - excluded: false, + isDisabled: false, + isPinned: true, + isExcluded: false, }, { id: 'filter1', field: '@tags.keyword', + operator: 'AND', value: 'value', - disabled: true, - pinned: false, - excluded: false, + isDisabled: true, + isPinned: false, + isExcluded: false, }, { id: 'filter2', field: '@tags.keyword', + operator: 'AND', value: 'value', - disabled: false, - pinned: true, - excluded: true, + isDisabled: false, + isPinned: true, + isExcluded: true, }, { id: 'filter3', field: '@tags.keyword', + operator: 'AND', value: 'value', - disabled: false, - pinned: false, - excluded: false, + isDisabled: false, + isPinned: false, + isExcluded: false, }, ], query: '', - idOfOpenPopover: null, - addFilterMenuWidth: null, - addFilterMenuIsOpen: false, - isComboBoxLoading: false, - isComboBoxOpen: false, - selectedComboBoxOptions: [], - comboBoxOptions: fieldOptions, }; } @@ -153,64 +70,6 @@ export default class extends Component { }); }; - togglePopover = id => { - this.setState(prevState => ({ - idOfOpenPopover: - prevState.idOfOpenPopover === null || prevState.idOfOpenPopover !== id ? id : null, - })); - }; - - toggleAddFilterMenu = () => { - this.setState(prevState => ({ - addFilterMenuIsOpen: !prevState.addFilterMenuIsOpen, - })); - }; - - onComboBoxChange = selectedComboBoxOptions => { - let options = fieldOptions; - if (selectedComboBoxOptions.length === 1) { - options = operatorOptions; - } else if (selectedComboBoxOptions.length > 1) { - options = valueOptions; - } - - this.setState({ - selectedComboBoxOptions, - comboBoxOptions: options, - }); - }; - - // eslint-disable-next-line no-unused-vars - onSearchChange = searchValue => { - // let options = this.state.comboBoxOptions; - // this.setState({ - // isComboBoxLoading: true, - // comboBoxOptions: [], - // }); - // clearTimeout(this.searchTimeout); - // if (this.state.selectedComboBoxOptions.length === 1) { - // options = operatorOptions; - // } else if (this.state.selectedComboBoxOptions.length > 1) { - // options = valueOptions; - // } - // this.searchTimeout = setTimeout(() => { - // // Simulate a remotely-executed search. - // this.setState({ - // isComboBoxLoading: false, - // comboBoxOptions: options.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())), - // }); - // }, 200); - }; - - closePopover = () => { - //this.setState({ idOfOpenPopover: null }); - }; - - componentDidMount() { - // Simulate initial load. - this.onSearchChange(''); - } - render() { const filterTriggerButton = ( ); - const pinnedFilters = this.state.filters.filter(filter => filter.pinned); - const unpinnedFilters = this.state.filters.filter(filter => !filter.pinned); - return ( - {this.state.isFiltersVisible && ( - - {pinnedFilters.length && ( // Show pinned filters first and in a specific group - - - - - - - - - {this.renderFilterItems(pinnedFilters)} - - - )} - {this.renderFilterItems(unpinnedFilters)} - - )} - - ); - } - - /** - * Rendering of items - */ - - renderFilterItems = filters => { - return filters.map((filter, index) => { - let icon; - let badgeColor = 'hollow'; - const style = {}; - - if (filter.disabled) { - icon = 'eyeClosed'; - badgeColor = 'default'; - style.fontStyle = 'italic'; - style.fontWeight = '400'; - } else if (filter.excluded) { - icon = 'minusInCircle'; - style.textDecoration = 'line-through'; - } - - const badge = ( - this.togglePopover(filter.id)} - > - {filter.field}: - "{filter.value}" - - ); - - return ( - - {this._createFilterContextMenu(filter, index, badge)} - - ); - }); - }; - - _createFilterContextMenu = (filter, index, button) => { - const panelTree = { - id: 0, - items: [ - { - name: `${filter.disabled ? 'Enable' : 'Disable'}`, - icon: `${filter.disabled ? 'eye' : 'eyeClosed'}`, - onClick: () => { - this.closePopover(); - }, - }, - { - name: `${filter.pinned ? 'Unpin' : 'Pin'}`, - icon: 'pin', - onClick: () => { - this.closePopover(); - }, - }, - { - name: `${filter.excluded ? 'Include' : 'Exclude'}`, - icon: `${filter.excluded ? 'plusInCircle' : 'minusInCircle'}`, - onClick: () => { - this.closePopover(); - }, - }, - { - name: 'Remove', - icon: 'trash', - onClick: () => { - this.closePopover(); - }, - }, - { - name: 'Edit', - icon: 'pencil', - panel: { - id: 1, - content:
Form here
, - }, - }, - ], - }; - return ( - - - - ); - }; - - _createFilterBarContextMenu = () => { - const menuId = 'GlobalFilterBarContextMenu'; - const panelTree = { - id: 0, - items: [ - { - name: 'Enable', - icon: 'eye', - onClick: () => { - this.closePopover(); - }, - }, - { - name: 'Disable', - icon: 'eyeClosed', - onClick: () => { - this.closePopover(); - }, - }, - { - name: 'Pin', - icon: 'pin', - onClick: () => { - this.closePopover(); - }, - }, - { - name: 'Unpin', - icon: 'pin', - onClick: () => { - this.closePopover(); - }, - }, - { - name: 'Invert', - icon: 'invert', - onClick: () => { - this.closePopover(); - }, - }, - { - name: 'Toggle visibility', - icon: 'eye', - onClick: () => { - this.closePopover(); - }, - }, - { - name: 'Remove all', - icon: 'trash', - onClick: () => { - this.closePopover(); - }, - }, - ], - }; - - return ( - this.togglePopover(menuId)} - color="text" - iconType="gear" - aria-label="Change all filters" - /> - } - anchorPosition="downCenter" - panelPaddingSize="none" - withTitle - > - Change all filters - - - ); - }; - - _createFilterBarAddMenu = () => { - const label = ( - - Field, Operator, Value(s) - - Edit as Query DSL - - - ); - - return ( - this.setState({ addFilterMenuIsOpen: false })} - button={ - - + Add filter - - } - anchorPosition="downCenter" - withTitle - > - Add a filter -
- - - - {/* - - - - - */} - - - - Add - + {this.state.isFiltersVisible && + - Cancel + + + + - - -
-
- ); - }; -} - -// eslint-disable-next-line react/no-multi-comp, react/prefer-stateless-function -class GlobalFilterGroup extends Component { - static propTypes = { - children: PropTypes.node, - allFiltersButton: PropTypes.node, - addFilterButton: PropTypes.node, - }; - - render() { - return ( - - - - - - - - - - - {this.props.children} - - {this.props.addFilterButton} - {this.props.allFiltersButton} - + - - + } + ); } } diff --git a/src/components/form/_form.scss b/src/components/form/_form.scss index 3182d9edb09..087f4efcb57 100644 --- a/src/components/form/_form.scss +++ b/src/components/form/_form.scss @@ -6,13 +6,3 @@ .euiForm__errors { margin-bottom: $euiSize; } - -.euiGenericFormStyle { - // Match just the regular drop shadow of inputs - @include euiFormControlDefaultShadow; - display: flex; - align-items: center; - padding: 4px; - min-height: $euiFormControlHeight; - margin: 0; -} From 8a86fa98688239b69107e62c98bbd8134a111a14 Mon Sep 17 00:00:00 2001 From: cchaos Date: Wed, 22 Aug 2018 01:12:14 -0400 Subject: [PATCH 08/22] Almost have it working with combobox --- .../src/views/header/global_filter_form.js | 102 +++++++++++++++--- .../src/views/header/global_filter_item.js | 37 +++++-- src-docs/src/views/header/global_query.js | 8 +- 3 files changed, 115 insertions(+), 32 deletions(-) diff --git a/src-docs/src/views/header/global_filter_form.js b/src-docs/src/views/header/global_filter_form.js index 2269732f57c..b7334137fe3 100644 --- a/src-docs/src/views/header/global_filter_form.js +++ b/src-docs/src/views/header/global_filter_form.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { pull } from 'lodash'; import { EuiFlexGroup, @@ -70,7 +71,8 @@ export default class GlobalFilterForm extends Component { static propTypes = { onAdd: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, - } + selectedObject: PropTypes.array, + }; constructor(props) { super(props); @@ -78,27 +80,85 @@ export default class GlobalFilterForm extends Component { this.state = { isComboBoxLoading: false, selectedComboBoxOptions: [], - comboBoxOptions: fieldOptions, + comboBoxOptions: [], + editingOption: null, }; } onComboBoxChange = selectedComboBoxOptions => { - let options = fieldOptions; - if (selectedComboBoxOptions.length === 1) { - options = operatorOptions; - } else if (selectedComboBoxOptions.length > 1) { - options = valueOptions; + const selectedOptions = selectedComboBoxOptions || []; + const numOfSelections = selectedOptions.length; + const lastUpdate = selectedOptions[selectedOptions.length - 1]; + const current = {}; + + // If length is less than 3, then move on to the next + if (numOfSelections < 3) { + switch (numOfSelections) { + case 0: + current.selectedComboBoxOptions = []; + current.editingOption = 'field'; + current.comboBoxOptions = fieldOptions; + break; + case 1: + current.selectedComboBoxOptions = selectedOptions; + current.editingOption = 'operator'; + current.comboBoxOptions = operatorOptions; + break; + default: + // 2 or more + current.selectedComboBoxOptions = selectedOptions; + current.editingOption = 'value'; + current.comboBoxOptions = valueOptions; + break; + } + } else { + // else stay on and just update the value + switch (this.state.editingOption) { + case 'field': + pull(selectedOptions, lastUpdate); + selectedOptions[0] = lastUpdate; + break; + case 'operator': + pull(selectedOptions, lastUpdate); + selectedOptions[1] = lastUpdate; + break; + default: + // 'value' + break; + } + + current.selectedComboBoxOptions = selectedOptions; + } + + // Add the appropriate click handlers to the first two selected options + // (if they exist) + if (numOfSelections > 0) { + current.selectedComboBoxOptions[0].onClick = this.fieldClicked; + } + if (numOfSelections > 1) { + current.selectedComboBoxOptions[1].onClick = this.opClicked; } + this.setState({ ...current }); + }; + + fieldClicked = () => { + this.setState({ + comboBoxOptions: fieldOptions, + editingOption: 'field', + }); + }; + + opClicked = () => { this.setState({ - selectedComboBoxOptions, - comboBoxOptions: options, + comboBoxOptions: operatorOptions, + editingOption: 'operator', }); }; // eslint-disable-next-line no-unused-vars onSearchChange = searchValue => { - // let options = this.state.comboBoxOptions; + //const options = this.state.comboBoxOptions; // this.setState({ // isComboBoxLoading: true, // comboBoxOptions: [], @@ -111,20 +171,26 @@ export default class GlobalFilterForm extends Component { // } // this.searchTimeout = setTimeout(() => { // // Simulate a remotely-executed search. - // this.setState({ - // isComboBoxLoading: false, - // comboBoxOptions: options.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())), - // }); + //this.setState({ + // isComboBoxLoading: false, + //comboBoxOptions: options.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())), + //}); // }, 200); }; componentDidMount() { // Simulate initial load. - this.onSearchChange(''); + //this.onSearchChange(''); + this.onComboBoxChange(this.props.selectedObject); } render() { - const { onAdd, onCancel, ...rest } = this.props; + const { + onAdd, + onCancel, + selectedObject, // eslint-disable-line no-unused-vars + ...rest + } = this.props; const label = ( @@ -151,7 +217,9 @@ export default class GlobalFilterForm extends Component { - Add + + Add + Cancel diff --git a/src-docs/src/views/header/global_filter_item.js b/src-docs/src/views/header/global_filter_item.js index e38c9afc915..2cedf05b0f2 100644 --- a/src-docs/src/views/header/global_filter_item.js +++ b/src-docs/src/views/header/global_filter_item.js @@ -2,11 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { - EuiBadge, - EuiPopover, - EuiContextMenu, -} from '../../../../src/components'; +import { EuiBadge, EuiPopover, EuiContextMenu } from '../../../../src/components'; import GlobalFilterForm from './global_filter_form'; function flattenPanelTree(tree, array = []) { @@ -34,7 +30,7 @@ export class GlobalFilterItem extends Component { isDisabled: PropTypes.bool.isRequired, isPinned: PropTypes.bool.isRequired, isExcluded: PropTypes.bool.isRequired, - } + }; constructor(props) { super(props); @@ -48,14 +44,13 @@ export class GlobalFilterItem extends Component { this.setState(prevState => ({ isPopoverOpen: !prevState.isPopoverOpen, })); - } + }; closePopover = () => { this.setState({ isPopoverOpen: false, }); - } - + }; render() { const { @@ -77,7 +72,7 @@ export class GlobalFilterItem extends Component { 'globalFilterItem-isPinned': isPinned, 'globalFilterItem-isExcluded': isExcluded, }, - className, + className ); let icon; @@ -109,6 +104,18 @@ export class GlobalFilterItem extends Component { } _createFilterContextMenu = (filter, button) => { + const selectedObject = [ + { + label: filter.field, + }, + { + label: filter.operator, + }, + { + label: filter.value, + }, + ]; + const panelTree = { id: 0, items: [ @@ -145,7 +152,15 @@ export class GlobalFilterItem extends Component { icon: 'pencil', panel: { id: 1, - content:
, + content: ( +
+ +
+ ), }, }, ], diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index 37997ae7a24..067e3c6d6de 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -19,7 +19,7 @@ export default class extends Component { { id: 'filter0', field: '@tags.keyword', - operator: 'AND', + operator: 'IS', value: 'value', isDisabled: false, isPinned: true, @@ -28,7 +28,7 @@ export default class extends Component { { id: 'filter1', field: '@tags.keyword', - operator: 'AND', + operator: 'IS', value: 'value', isDisabled: true, isPinned: false, @@ -37,7 +37,7 @@ export default class extends Component { { id: 'filter2', field: '@tags.keyword', - operator: 'AND', + operator: 'IS NOT', value: 'value', isDisabled: false, isPinned: true, @@ -46,7 +46,7 @@ export default class extends Component { { id: 'filter3', field: '@tags.keyword', - operator: 'AND', + operator: 'IS', value: 'value', isDisabled: false, isPinned: false, From 223f919c3f832b44b05eb6e143707e48b06e3a8f Mon Sep 17 00:00:00 2001 From: cchaos Date: Wed, 22 Aug 2018 15:29:05 -0400 Subject: [PATCH 09/22] Switching form to use multiple comboboxes --- .../src/views/header/global_filter_add.js | 14 +- .../src/views/header/global_filter_form.js | 286 ++++++++++++------ .../src/views/header/global_filter_item.js | 16 +- 3 files changed, 205 insertions(+), 111 deletions(-) diff --git a/src-docs/src/views/header/global_filter_add.js b/src-docs/src/views/header/global_filter_add.js index 0ac6b0a7608..f4033481cbf 100644 --- a/src-docs/src/views/header/global_filter_add.js +++ b/src-docs/src/views/header/global_filter_add.js @@ -4,6 +4,8 @@ import { EuiButtonEmpty, EuiPopover, EuiPopoverTitle, + EuiFlexGroup, + EuiFlexItem, } from '../../../../src/components'; import GlobalFilterForm from './global_filter_form'; @@ -16,7 +18,7 @@ export default class GlobalFilterAdd extends Component { super(props); this.state = { - isPopoverOpen: false, + isPopoverOpen: true, }; } @@ -45,7 +47,15 @@ export default class GlobalFilterAdd extends Component { anchorPosition="downCenter" withTitle > - Add a filter + + + Add a filter + + {/* This button should open a modal */} + Edit as Query DSL + + + diff --git a/src-docs/src/views/header/global_filter_form.js b/src-docs/src/views/header/global_filter_form.js index b7334137fe3..261e73ef7fb 100644 --- a/src-docs/src/views/header/global_filter_form.js +++ b/src-docs/src/views/header/global_filter_form.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { pull } from 'lodash'; +// import { pull } from 'lodash'; import { EuiFlexGroup, @@ -9,7 +9,9 @@ import { EuiFormRow, EuiComboBox, EuiButton, - EuiLink, + EuiSpacer, + EuiSwitch, + EuiFieldText, } from '../../../../src/components'; const fieldOptions = [ @@ -71,90 +73,91 @@ export default class GlobalFilterForm extends Component { static propTypes = { onAdd: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, - selectedObject: PropTypes.array, + selectedObject: PropTypes.object, }; constructor(props) { super(props); this.state = { - isComboBoxLoading: false, - selectedComboBoxOptions: [], - comboBoxOptions: [], - editingOption: null, + selectedField: this.props.selectedObject ? this.props.selectedObject.field : [], + selectedOperand: this.props.selectedObject ? this.props.selectedObject.operand : [], + selectedValues: this.props.selectedObject ? this.props.selectedObject.values : [], + useCustomLabel: false, + customLabel: null, }; } - onComboBoxChange = selectedComboBoxOptions => { - const selectedOptions = selectedComboBoxOptions || []; - const numOfSelections = selectedOptions.length; - const lastUpdate = selectedOptions[selectedOptions.length - 1]; - const current = {}; - - // If length is less than 3, then move on to the next - if (numOfSelections < 3) { - switch (numOfSelections) { - case 0: - current.selectedComboBoxOptions = []; - current.editingOption = 'field'; - current.comboBoxOptions = fieldOptions; - break; - case 1: - current.selectedComboBoxOptions = selectedOptions; - current.editingOption = 'operator'; - current.comboBoxOptions = operatorOptions; - break; - default: - // 2 or more - current.selectedComboBoxOptions = selectedOptions; - current.editingOption = 'value'; - current.comboBoxOptions = valueOptions; - break; - } - } else { - // else stay on and just update the value - switch (this.state.editingOption) { - case 'field': - pull(selectedOptions, lastUpdate); - selectedOptions[0] = lastUpdate; - break; - case 'operator': - pull(selectedOptions, lastUpdate); - selectedOptions[1] = lastUpdate; - break; - default: - // 'value' - break; - } - - current.selectedComboBoxOptions = selectedOptions; - } - - // Add the appropriate click handlers to the first two selected options - // (if they exist) - if (numOfSelections > 0) { - current.selectedComboBoxOptions[0].onClick = this.fieldClicked; - } - if (numOfSelections > 1) { - current.selectedComboBoxOptions[1].onClick = this.opClicked; - } - - this.setState({ ...current }); - }; + // onComboBoxChange = selectedComboBoxOptions => { + // const selectedOptions = selectedComboBoxOptions || []; + // const numOfSelections = selectedOptions.length; + // const lastUpdate = selectedOptions[selectedOptions.length - 1]; + // const current = {}; - fieldClicked = () => { - this.setState({ - comboBoxOptions: fieldOptions, - editingOption: 'field', - }); - }; + // // If length is less than 3, then move on to the next + // if (numOfSelections < 3) { + // switch (numOfSelections) { + // case 0: + // current.selectedComboBoxOptions = []; + // current.editingOption = 'field'; + // current.comboBoxOptions = fieldOptions; + // break; + // case 1: + // current.selectedComboBoxOptions = selectedOptions; + // current.editingOption = 'operator'; + // current.comboBoxOptions = operatorOptions; + // break; + // default: + // // 2 or more + // current.selectedComboBoxOptions = selectedOptions; + // current.editingOption = 'value'; + // current.comboBoxOptions = valueOptions; + // break; + // } + // } else { + // // else stay on and just update the value + // switch (this.state.editingOption) { + // case 'field': + // pull(selectedOptions, lastUpdate); + // selectedOptions[0] = lastUpdate; + // break; + // case 'operator': + // pull(selectedOptions, lastUpdate); + // selectedOptions[1] = lastUpdate; + // break; + // default: + // // 'value' + // break; + // } - opClicked = () => { - this.setState({ - comboBoxOptions: operatorOptions, - editingOption: 'operator', - }); - }; + // current.selectedComboBoxOptions = selectedOptions; + // } + + // // Add the appropriate click handlers to the first two selected options + // // (if they exist) + // if (numOfSelections > 0) { + // current.selectedComboBoxOptions[0].onClick = this.fieldClicked; + // } + // if (numOfSelections > 1) { + // current.selectedComboBoxOptions[1].onClick = this.opClicked; + // } + + // this.setState({ ...current }); + // }; + + // fieldClicked = () => { + // this.setState({ + // comboBoxOptions: fieldOptions, + // editingOption: 'field', + // }); + // }; + + // opClicked = () => { + // this.setState({ + // comboBoxOptions: operatorOptions, + // editingOption: 'operator', + // }); + // }; // eslint-disable-next-line no-unused-vars onSearchChange = searchValue => { @@ -178,46 +181,128 @@ export default class GlobalFilterForm extends Component { // }, 200); }; + onFieldChange = selectedOptions => { + // We should only get back either 0 or 1 options. + this.setState({ + selectedField: selectedOptions, + }); + }; + + onOperandChange = selectedOptions => { + // We should only get back either 0 or 1 options. + this.setState({ + selectedOperand: selectedOptions, + }); + }; + + onValuesChange = selectedOptions => { + this.setState({ + selectedValues: selectedOptions, + }); + }; + + onCustomLabelSwitchChange = e => { + this.setState({ + useCustomLabel: e.target.checked, + }); + }; + + resetForm = () => { + this.setState({ + selectedField: [], + selectedOperand: [], + selectedValues: [], + useCustomLabel: false, + customLabel: null, + }); + } + componentDidMount() { // Simulate initial load. //this.onSearchChange(''); - this.onComboBoxChange(this.props.selectedObject); + // this.onComboBoxChange(this.props.selectedObject); } render() { const { onAdd, onCancel, - selectedObject, // eslint-disable-line no-unused-vars + selectedObject, ...rest } = this.props; - const label = ( - - Field, Operator, Value(s) - - Edit as Query DSL - - - ); - return (
- - - + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + {this.state.useCustomLabel && +
+ + + + +
+ } + + - + Add @@ -225,6 +310,11 @@ export default class GlobalFilterForm extends Component { Cancel + + + {selectedObject ? 'Delete' : 'Reset form'} + +
); diff --git a/src-docs/src/views/header/global_filter_item.js b/src-docs/src/views/header/global_filter_item.js index 2cedf05b0f2..1e7955ae380 100644 --- a/src-docs/src/views/header/global_filter_item.js +++ b/src-docs/src/views/header/global_filter_item.js @@ -104,17 +104,11 @@ export class GlobalFilterItem extends Component { } _createFilterContextMenu = (filter, button) => { - const selectedObject = [ - { - label: filter.field, - }, - { - label: filter.operator, - }, - { - label: filter.value, - }, - ]; + const selectedObject = { + field: [{ label: filter.field }], + operand: [{ label: filter.operator }], + values: [{ label: filter.value }], + }; const panelTree = { id: 0, From fd3df58fe3703f7234d6829c4b3da6c79ecaa09c Mon Sep 17 00:00:00 2001 From: cchaos Date: Wed, 22 Aug 2018 15:44:30 -0400 Subject: [PATCH 10/22] Changing buttons around --- .../src/views/header/global_filter_form.js | 8 ++-- .../src/views/header/global_filter_item.js | 44 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src-docs/src/views/header/global_filter_form.js b/src-docs/src/views/header/global_filter_form.js index 261e73ef7fb..98577c48f42 100644 --- a/src-docs/src/views/header/global_filter_form.js +++ b/src-docs/src/views/header/global_filter_form.js @@ -307,13 +307,13 @@ export default class GlobalFilterForm extends Component {
- Cancel + + {selectedObject ? 'Cancel' : 'Reset form'} + - - {selectedObject ? 'Delete' : 'Reset form'} - + {selectedObject && Delete}
diff --git a/src-docs/src/views/header/global_filter_item.js b/src-docs/src/views/header/global_filter_item.js index 1e7955ae380..ff32400caa8 100644 --- a/src-docs/src/views/header/global_filter_item.js +++ b/src-docs/src/views/header/global_filter_item.js @@ -114,47 +114,47 @@ export class GlobalFilterItem extends Component { id: 0, items: [ { - name: `${filter.isDisabled ? 'Enable' : 'Disable'}`, - icon: `${filter.isDisabled ? 'eye' : 'eyeClosed'}`, + name: `${filter.isPinned ? 'Unpin' : 'Pin across all apps'}`, + icon: 'pin', onClick: () => { this.closePopover(); }, }, { - name: `${filter.isPinned ? 'Unpin' : 'Pin'}`, - icon: 'pin', - onClick: () => { - this.closePopover(); + name: 'Edit filter query', + icon: 'pencil', + panel: { + id: 1, + content: ( +
+ +
+ ), }, }, { - name: `${filter.isExcluded ? 'Include' : 'Exclude'}`, + name: `${filter.isExcluded ? 'Include results' : 'Exclude results'}`, icon: `${filter.isExcluded ? 'plusInCircle' : 'minusInCircle'}`, onClick: () => { this.closePopover(); }, }, { - name: 'Remove', - icon: 'trash', + name: `${filter.isDisabled ? 'Re-enable' : 'Temporarily disable'}`, + icon: `${filter.isDisabled ? 'eye' : 'eyeClosed'}`, onClick: () => { this.closePopover(); }, }, { - name: 'Edit', - icon: 'pencil', - panel: { - id: 1, - content: ( -
- -
- ), + name: 'Delete', + icon: 'trash', + onClick: () => { + this.closePopover(); }, }, ], From 2a1c5f18981c9a600661ced572ace18be4438b3f Mon Sep 17 00:00:00 2001 From: cchaos Date: Wed, 22 Aug 2018 16:29:11 -0400 Subject: [PATCH 11/22] options menu wording --- src-docs/src/views/header/global_filter_add.js | 2 +- src-docs/src/views/header/global_filter_options.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src-docs/src/views/header/global_filter_add.js b/src-docs/src/views/header/global_filter_add.js index f4033481cbf..33707525984 100644 --- a/src-docs/src/views/header/global_filter_add.js +++ b/src-docs/src/views/header/global_filter_add.js @@ -18,7 +18,7 @@ export default class GlobalFilterAdd extends Component { super(props); this.state = { - isPopoverOpen: true, + isPopoverOpen: false, }; } diff --git a/src-docs/src/views/header/global_filter_options.js b/src-docs/src/views/header/global_filter_options.js index f2e6c7361ef..25187c5cd9d 100644 --- a/src-docs/src/views/header/global_filter_options.js +++ b/src-docs/src/views/header/global_filter_options.js @@ -51,42 +51,42 @@ export default class GlobalFilterOptions extends Component { id: 0, items: [ { - name: 'Enable', + name: 'Enable all', icon: 'eye', onClick: () => { this.closePopover(); }, }, { - name: 'Disable', + name: 'Disable all', icon: 'eyeClosed', onClick: () => { this.closePopover(); }, }, { - name: 'Pin', + name: 'Pin all', icon: 'pin', onClick: () => { this.closePopover(); }, }, { - name: 'Unpin', + name: 'Unpin all', icon: 'pin', onClick: () => { this.closePopover(); }, }, { - name: 'Invert', + name: 'Invert inclusion', icon: 'invert', onClick: () => { this.closePopover(); }, }, { - name: 'Toggle visibility', + name: 'Invert visibility', icon: 'eye', onClick: () => { this.closePopover(); From 490593e7dffed8a5fc3a5d5932aa9ac072d89ec4 Mon Sep 17 00:00:00 2001 From: cchaos Date: Wed, 22 Aug 2018 17:00:25 -0400 Subject: [PATCH 12/22] Animating in filter bar --- .../views/header/_global_filter_group.scss | 5 ++ .../src/views/header/global_filter_bar.js | 2 +- src-docs/src/views/header/global_query.js | 76 +++++++++++++++---- 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src-docs/src/views/header/_global_filter_group.scss b/src-docs/src/views/header/_global_filter_group.scss index 14bfdd787ee..099aeeddace 100644 --- a/src-docs/src/views/header/_global_filter_group.scss +++ b/src-docs/src/views/header/_global_filter_group.scss @@ -9,3 +9,8 @@ .globalFilterGroup__branch { fill: $euiColorLightShade; } + +.globalFilterGroup__wrapper { + overflow: hidden; + transition: height $euiAnimSpeedNormal $euiAnimSlightResistance; +} diff --git a/src-docs/src/views/header/global_filter_bar.js b/src-docs/src/views/header/global_filter_bar.js index d6175a09387..52274a91680 100644 --- a/src-docs/src/views/header/global_filter_bar.js +++ b/src-docs/src/views/header/global_filter_bar.js @@ -56,7 +56,7 @@ export const GlobalFilterBar = ({ - + diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index 067e3c6d6de..8333053dc36 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -1,10 +1,12 @@ import React, { Component } from 'react'; +import classNames from 'classnames'; import { EuiFilterButton, EuiFieldText, EuiFlexGroup, EuiFlexItem, + EuiMutationObserver, } from '../../../../src/components'; import { GlobalFilterBar } from './global_filter_bar'; @@ -55,7 +57,21 @@ export default class extends Component { ], query: '', }; + } + + setFilterBarHeight = () => { + requestAnimationFrame(() => { + const height = this.filterBar && this.state.isFiltersVisible ? this.filterBar.clientHeight + 4 : 0; + this.filterBarWrapper && this.filterBarWrapper.setAttribute('style', `height: ${height}px`); + }); + } + + componentDidMount() { + this.setFilterBarHeight(); + } + componentDidUpdate() { + this.setFilterBarHeight(); } toggleFilterVisibility = () => { @@ -70,18 +86,31 @@ export default class extends Component { }); }; + setFilterBarRef = (node) => { + this.filterBar = node; + } + render() { const filterTriggerButton = ( 0} + hasActiveFilters={this.state.isFiltersVisible} numFilters={this.state.filters.length > 0 ? this.state.filters.length : null} + aria-controls="GlobalFilterGroup" + aria-expanded={!!this.state.isFiltersVisible} > Filters ); + const classes = classNames( + 'globalFilterGroup__wrapper', + { + 'globalFilterGroup__wrapper-isVisible': this.state.isFiltersVisible, + }, + ); + return ( - {this.state.isFiltersVisible && - - - - - - - - - - - - - } +
{ this.filterBarWrapper = node; }} + className={classes} + > + +
+ + + + + + + + + + + + +
+ +
+
); From 1850872d5e4ff5e749dbce0c874ca48a5b9a927b Mon Sep 17 00:00:00 2001 From: cchaos Date: Wed, 22 Aug 2018 17:04:26 -0400 Subject: [PATCH 13/22] updating doc --- src-docs/src/views/header/header_example.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src-docs/src/views/header/header_example.js b/src-docs/src/views/header/header_example.js index 956fac0a519..8f9a91bceca 100644 --- a/src-docs/src/views/header/header_example.js +++ b/src-docs/src/views/header/header_example.js @@ -19,6 +19,12 @@ import { EuiCallOut, } from '../../../../src/components'; +import { GlobalFilterBar } from './global_filter_bar'; +import GlobalFilterAdd from './global_filter_add'; +import GlobalFilterOptions from './global_filter_options'; +import GlobalFilterForm from './global_filter_form'; +import { GlobalFilterItem } from './global_filter_item'; + import Header from './header'; const headerSource = require('!!raw-loader!./header'); const headerHtml = renderToHtml(Header); @@ -89,12 +95,20 @@ export const HeaderExample = {

This documents a visual pattern for the eventual replacement of Kibana's - global query and filter bars. It uses all EUI components without any custom styles. + global query and filter bars. The filter bar has been broken down into multiple components. There + are still bugs and not all the logical is well-formed.

), - // props: { }, + props: { + GlobalQuery, + GlobalFilterBar, + GlobalFilterOptions, + GlobalFilterAdd, + GlobalFilterForm, + GlobalFilterItem, + }, demo: , }], }; From 65dd480acc1abb0d5176f376b150f38c2ef69f21 Mon Sep 17 00:00:00 2001 From: cchaos Date: Thu, 23 Aug 2018 16:18:25 -0400 Subject: [PATCH 14/22] Better styling of filter items --- .../src/views/header/_global_filter_bar.scss | 18 --------- .../views/header/_global_filter_group.scss | 8 ++-- .../src/views/header/_global_filter_item.scss | 12 +++++- .../src/views/header/global_filter_bar.js | 31 ++------------- .../src/views/header/global_filter_item.js | 38 ++++++++++++++----- .../src/views/header/global_filter_options.js | 1 + src-docs/src/views/header/global_query.js | 12 +++--- 7 files changed, 55 insertions(+), 65 deletions(-) delete mode 100644 src-docs/src/views/header/_global_filter_bar.scss diff --git a/src-docs/src/views/header/_global_filter_bar.scss b/src-docs/src/views/header/_global_filter_bar.scss deleted file mode 100644 index 5e11e71b1e7..00000000000 --- a/src-docs/src/views/header/_global_filter_bar.scss +++ /dev/null @@ -1,18 +0,0 @@ -.globalFilterBar { - // Match just the regular drop shadow of inputs - @include euiFormControlDefaultShadow; - display: flex; - align-items: center; - padding: 4px; - min-height: $euiFormControlHeight; - margin: 0; -} - -.globalFilterBar__pinned { - padding: $euiSizeXS; - background-color: saturate(tintOrShade($euiColorSecondary, 40%, 50%), 50%); -} - -.globalFilterBar__pinnedIcon { - margin: -2px 0; -} diff --git a/src-docs/src/views/header/_global_filter_group.scss b/src-docs/src/views/header/_global_filter_group.scss index 099aeeddace..890c123ccbe 100644 --- a/src-docs/src/views/header/_global_filter_group.scss +++ b/src-docs/src/views/header/_global_filter_group.scss @@ -1,13 +1,15 @@ -@import 'global_filter_bar'; @import 'global_filter_item'; @import 'global_filter_form'; .globalFilterGroup__filterBar { - margin-top: 8px; + margin-top: $euiSizeM - 1px; } .globalFilterGroup__branch { - fill: $euiColorLightShade; + padding: $euiSize $euiSize $euiSizeS $euiSizeS; + background-repeat: no-repeat; + background-position: right top; + background-image: url("data:image/svg+xml,%0A%3Csvg width='28px' height='28px' viewBox='0 0 28 28' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='#{hexToRGB($euiColorLightShade)}'%3E%3Crect x='14' y='27' width='14' height='1'%3E%3C/rect%3E%3Crect x='0' y='0' width='1' height='14'%3E%3C/rect%3E%3C/g%3E%3C/svg%3E"); } .globalFilterGroup__wrapper { diff --git a/src-docs/src/views/header/_global_filter_item.scss b/src-docs/src/views/header/_global_filter_item.scss index 6e133658631..824c013b04b 100644 --- a/src-docs/src/views/header/_global_filter_item.scss +++ b/src-docs/src/views/header/_global_filter_item.scss @@ -1,12 +1,20 @@ .globalFilterItem { + line-height: $euiSizeL + $euiSizeXS; + border: none; + color: $euiTextColor; + &:not(.globalFilterItem-isDisabled) { + @include euiFormControlDefaultShadow; + } } .globalFilterItem-isDisabled { + background-color: transparentize($euiColorLightShade, .4); + text-decoration: line-through; font-weight: $euiFontWeightRegular; font-style: italic; } -.globalFilterItem-isExcluded { - text-decoration: line-through; +.globalFilterItem-isPinned { + border-left: $euiSizeXS solid $euiColorSuccess; } diff --git a/src-docs/src/views/header/global_filter_bar.js b/src-docs/src/views/header/global_filter_bar.js index 52274a91680..87f5ce586c4 100644 --- a/src-docs/src/views/header/global_filter_bar.js +++ b/src-docs/src/views/header/global_filter_bar.js @@ -5,11 +5,8 @@ import classNames from 'classnames'; import { EuiFlexGroup, EuiFlexItem, - EuiToolTip, - EuiButtonIcon, } from '../../../../src/components'; import GlobalFilterAdd from './global_filter_add'; -import GlobalFilterOptions from './global_filter_options'; import { GlobalFilterItem } from './global_filter_item'; export const GlobalFilterBar = ({ @@ -45,35 +42,15 @@ export const GlobalFilterBar = ({ wrap={true} responsive={false} gutterSize="xs" + alignItems="center" {...rest} > - {pinnedFilters.length && ( // Show pinned filters first and in a specific group - - - - - - - - - {pinnedFilters} - - - - )} + {/* Show pinned filters first and in a specific group */} + {pinnedFilters} {unpinnedFilters} - - - - - - +
); }; diff --git a/src-docs/src/views/header/global_filter_item.js b/src-docs/src/views/header/global_filter_item.js index ff32400caa8..3c0a357b0eb 100644 --- a/src-docs/src/views/header/global_filter_item.js +++ b/src-docs/src/views/header/global_filter_item.js @@ -52,6 +52,13 @@ export class GlobalFilterItem extends Component { }); }; + deleteFilter = (e) => { + window.alert('Filter would have been deleted.'); + // Make sure it doesn't also trigger the onclick for the whole badge + e.stopPropagation(); + } + + render() { const { className, @@ -75,26 +82,37 @@ export class GlobalFilterItem extends Component { className ); - let icon; - let badgeColor = 'hollow'; + let prefix = null; + if (isExcluded) { + prefix = NOT ; + } - if (isDisabled) { - icon = 'eyeClosed'; - badgeColor = 'default'; - } else if (isExcluded) { - icon = 'minusInCircle'; + let title = `Filter: ${field}: "${value}". Select for more filter actions.`; + if (isPinned) { + title = `Pinned ${title}`; + } else if (isDisabled) { + title = `Disabled ${title}`; } const badge = ( + {prefix} {field}: "{value}" diff --git a/src-docs/src/views/header/global_filter_options.js b/src-docs/src/views/header/global_filter_options.js index 25187c5cd9d..12e69b78635 100644 --- a/src-docs/src/views/header/global_filter_options.js +++ b/src-docs/src/views/header/global_filter_options.js @@ -112,6 +112,7 @@ export default class GlobalFilterOptions extends Component { color="text" iconType="gear" aria-label="Change all filters" + title="Change all filters" /> } anchorPosition="downCenter" diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index 8333053dc36..86bd53dfc86 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -10,6 +10,7 @@ import { } from '../../../../src/components'; import { GlobalFilterBar } from './global_filter_bar'; +import GlobalFilterOptions from './global_filter_options'; export default class extends Component { constructor(props) { @@ -90,7 +91,10 @@ export default class extends Component { this.filterBar = node; } + render() { + const filterButtonTitle = `${this.state.filters.length} filters applied. Select to ${this.state.isFiltersVisible ? 'hide' : 'show'}.`; + const filterTriggerButton = ( 0 ? this.state.filters.length : null} aria-controls="GlobalFilterGroup" aria-expanded={!!this.state.isFiltersVisible} + title={filterButtonTitle} > Filters @@ -138,11 +143,8 @@ export default class extends Component { alignItems="flexStart" responsive={false} > - - - - - + + From 08156df91e1ab7b692a76cf984e51c82b4f3b473 Mon Sep 17 00:00:00 2001 From: cchaos Date: Thu, 23 Aug 2018 17:10:29 -0400 Subject: [PATCH 15/22] Forcing max-width on flex item in form and fixing some other layout stuff --- .../views/header/_global_filter_group.scss | 2 +- .../src/views/header/_global_filter_item.scss | 2 +- .../src/views/header/global_filter_form.js | 128 ++++-------------- 3 files changed, 31 insertions(+), 101 deletions(-) diff --git a/src-docs/src/views/header/_global_filter_group.scss b/src-docs/src/views/header/_global_filter_group.scss index 890c123ccbe..8389257876a 100644 --- a/src-docs/src/views/header/_global_filter_group.scss +++ b/src-docs/src/views/header/_global_filter_group.scss @@ -2,7 +2,7 @@ @import 'global_filter_form'; .globalFilterGroup__filterBar { - margin-top: $euiSizeM - 1px; + margin-top: $euiSizeM; } .globalFilterGroup__branch { diff --git a/src-docs/src/views/header/_global_filter_item.scss b/src-docs/src/views/header/_global_filter_item.scss index 824c013b04b..f4fe725e54d 100644 --- a/src-docs/src/views/header/_global_filter_item.scss +++ b/src-docs/src/views/header/_global_filter_item.scss @@ -16,5 +16,5 @@ } .globalFilterItem-isPinned { - border-left: $euiSizeXS solid $euiColorSuccess; + border-left: $euiSizeXS solid $euiColorVis0; } diff --git a/src-docs/src/views/header/global_filter_form.js b/src-docs/src/views/header/global_filter_form.js index 98577c48f42..93e07989ddf 100644 --- a/src-docs/src/views/header/global_filter_form.js +++ b/src-docs/src/views/header/global_filter_form.js @@ -80,107 +80,17 @@ export default class GlobalFilterForm extends Component { super(props); this.state = { + fieldOptions: fieldOptions, + operandOptions: operatorOptions, + valueOptions: valueOptions, selectedField: this.props.selectedObject ? this.props.selectedObject.field : [], selectedOperand: this.props.selectedObject ? this.props.selectedObject.operand : [], selectedValues: this.props.selectedObject ? this.props.selectedObject.values : [], useCustomLabel: false, - customLabel: null, + customLabel: '', }; } - // onComboBoxChange = selectedComboBoxOptions => { - // const selectedOptions = selectedComboBoxOptions || []; - // const numOfSelections = selectedOptions.length; - // const lastUpdate = selectedOptions[selectedOptions.length - 1]; - // const current = {}; - - // // If length is less than 3, then move on to the next - // if (numOfSelections < 3) { - // switch (numOfSelections) { - // case 0: - // current.selectedComboBoxOptions = []; - // current.editingOption = 'field'; - // current.comboBoxOptions = fieldOptions; - // break; - // case 1: - // current.selectedComboBoxOptions = selectedOptions; - // current.editingOption = 'operator'; - // current.comboBoxOptions = operatorOptions; - // break; - // default: - // // 2 or more - // current.selectedComboBoxOptions = selectedOptions; - // current.editingOption = 'value'; - // current.comboBoxOptions = valueOptions; - // break; - // } - // } else { - // // else stay on and just update the value - // switch (this.state.editingOption) { - // case 'field': - // pull(selectedOptions, lastUpdate); - // selectedOptions[0] = lastUpdate; - // break; - // case 'operator': - // pull(selectedOptions, lastUpdate); - // selectedOptions[1] = lastUpdate; - // break; - // default: - // // 'value' - // break; - // } - - // current.selectedComboBoxOptions = selectedOptions; - // } - - // // Add the appropriate click handlers to the first two selected options - // // (if they exist) - // if (numOfSelections > 0) { - // current.selectedComboBoxOptions[0].onClick = this.fieldClicked; - // } - // if (numOfSelections > 1) { - // current.selectedComboBoxOptions[1].onClick = this.opClicked; - // } - - // this.setState({ ...current }); - // }; - - // fieldClicked = () => { - // this.setState({ - // comboBoxOptions: fieldOptions, - // editingOption: 'field', - // }); - // }; - - // opClicked = () => { - // this.setState({ - // comboBoxOptions: operatorOptions, - // editingOption: 'operator', - // }); - // }; - - // eslint-disable-next-line no-unused-vars - onSearchChange = searchValue => { - //const options = this.state.comboBoxOptions; - // this.setState({ - // isComboBoxLoading: true, - // comboBoxOptions: [], - // }); - // clearTimeout(this.searchTimeout); - // if (this.state.selectedComboBoxOptions.length === 1) { - // options = operatorOptions; - // } else if (this.state.selectedComboBoxOptions.length > 1) { - // options = valueOptions; - // } - // this.searchTimeout = setTimeout(() => { - // // Simulate a remotely-executed search. - //this.setState({ - // isComboBoxLoading: false, - //comboBoxOptions: options.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())), - //}); - // }, 200); - }; - onFieldChange = selectedOptions => { // We should only get back either 0 or 1 options. this.setState({ @@ -207,6 +117,24 @@ export default class GlobalFilterForm extends Component { }); }; + onFieldSearchChange = searchValue => { + this.setState({ + fieldOptions: fieldOptions.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())), + }); + }; + + onOperandSearchChange = searchValue => { + this.setState({ + operandOptions: operatorOptions.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())), + }); + }; + + onValuesSearchChange = searchValue => { + this.setState({ + valueOptions: valueOptions.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())), + }); + }; + resetForm = () => { this.setState({ selectedField: [], @@ -234,30 +162,32 @@ export default class GlobalFilterForm extends Component { return (
- + - + @@ -274,7 +204,7 @@ export default class GlobalFilterForm extends Component { : 'Select one or more values' } isDisabled={this.state.selectedField.length < 1 || this.state.selectedOperand.length < 1} - options={valueOptions} + options={this.state.valueOptions} selectedOptions={this.state.selectedValues} onChange={this.onValuesChange} onSearchChange={this.onValuesSearchChange} From 144963feedab68c51093ea45ff55d23cd9662b4f Mon Sep 17 00:00:00 2001 From: cchaos Date: Mon, 27 Aug 2018 10:03:05 -0400 Subject: [PATCH 16/22] Revert "Address PR comments and Changelog" This reverts commit 1c3ad3e1cbd3642883f6c850fcaedf7033764b87. --- src/components/combo_box/combo_box.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/combo_box/combo_box.js b/src/components/combo_box/combo_box.js index 4d097b43d23..908d01780a4 100644 --- a/src/components/combo_box/combo_box.js +++ b/src/components/combo_box/combo_box.js @@ -33,9 +33,7 @@ export class EuiComboBox extends Component { async: PropTypes.bool, singleSelection: PropTypes.oneOfType([ PropTypes.bool, - PropTypes.shape({ - asPlainText: PropTypes.bool, - }), + PropTypes.object, ]), noSuggestions: PropTypes.bool, options: PropTypes.array, From ae6eed6f16a7f000441aa21bb0e1aca97a5a5081 Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 4 Sep 2018 14:01:55 -0400 Subject: [PATCH 17/22] Using pseudo element for pinned indicator instead of border because a border was causing the box-shadow to be off --- src-docs/src/views/header/_global_filter_item.scss | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src-docs/src/views/header/_global_filter_item.scss b/src-docs/src/views/header/_global_filter_item.scss index f4fe725e54d..d0fb76ad63d 100644 --- a/src-docs/src/views/header/_global_filter_item.scss +++ b/src-docs/src/views/header/_global_filter_item.scss @@ -16,5 +16,15 @@ } .globalFilterItem-isPinned { - border-left: $euiSizeXS solid $euiColorVis0; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: $euiSizeXS; + background-color: $euiColorVis0; + } } From fa3f802aa009c14878122babe834dc547c88bf33 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Thu, 6 Sep 2018 12:21:00 -0600 Subject: [PATCH 18/22] use resize observer instead of mutation observer --- src-docs/src/views/header/global_query.js | 45 +++++++++++------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src-docs/src/views/header/global_query.js b/src-docs/src/views/header/global_query.js index 86bd53dfc86..72f2068eaf5 100644 --- a/src-docs/src/views/header/global_query.js +++ b/src-docs/src/views/header/global_query.js @@ -1,12 +1,11 @@ import React, { Component } from 'react'; import classNames from 'classnames'; - +import ResizeObserver from 'resize-observer-polyfill'; import { EuiFilterButton, EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiMutationObserver, } from '../../../../src/components'; import { GlobalFilterBar } from './global_filter_bar'; @@ -58,6 +57,8 @@ export default class extends Component { ], query: '', }; + + this.ro = new ResizeObserver(this.setFilterBarHeight); } setFilterBarHeight = () => { @@ -69,10 +70,12 @@ export default class extends Component { componentDidMount() { this.setFilterBarHeight(); + this.ro.observe(this.filterBar); } componentDidUpdate() { this.setFilterBarHeight(); + this.ro.unobserve(this.filterBar); } toggleFilterVisibility = () => { @@ -132,28 +135,22 @@ export default class extends Component { ref={node => { this.filterBarWrapper = node; }} className={classes} > - -
- - - - - - - - - -
- -
+
+ + + + + + + + + +
From e80a73d065b37bc51d5b2c24920c96a641f33123 Mon Sep 17 00:00:00 2001 From: cchaos Date: Fri, 7 Sep 2018 15:53:53 -0400 Subject: [PATCH 19/22] Fixing popover width (separate PR for actual popover width fix) --- src-docs/src/views/header/global_filter_item.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src-docs/src/views/header/global_filter_item.js b/src-docs/src/views/header/global_filter_item.js index 3c0a357b0eb..b128197a83e 100644 --- a/src-docs/src/views/header/global_filter_item.js +++ b/src-docs/src/views/header/global_filter_item.js @@ -143,8 +143,9 @@ export class GlobalFilterItem extends Component { icon: 'pencil', panel: { id: 1, + width: 400, content: ( -
+
Date: Tue, 11 Sep 2018 12:17:21 -0400 Subject: [PATCH 20/22] Fix `singleSelection` propType from bad merge --- src/components/combo_box/combo_box.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/combo_box/combo_box.js b/src/components/combo_box/combo_box.js index 908d01780a4..4d097b43d23 100644 --- a/src/components/combo_box/combo_box.js +++ b/src/components/combo_box/combo_box.js @@ -33,7 +33,9 @@ export class EuiComboBox extends Component { async: PropTypes.bool, singleSelection: PropTypes.oneOfType([ PropTypes.bool, - PropTypes.object, + PropTypes.shape({ + asPlainText: PropTypes.bool, + }), ]), noSuggestions: PropTypes.bool, options: PropTypes.array, From 3c1f584a5595710e937e8a5c8816af089ce6482c Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 11 Sep 2018 14:35:05 -0400 Subject: [PATCH 21/22] Remove extra method --- src-docs/src/views/header/global_filter_form.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src-docs/src/views/header/global_filter_form.js b/src-docs/src/views/header/global_filter_form.js index 93e07989ddf..9c4d6cd326a 100644 --- a/src-docs/src/views/header/global_filter_form.js +++ b/src-docs/src/views/header/global_filter_form.js @@ -145,12 +145,6 @@ export default class GlobalFilterForm extends Component { }); } - componentDidMount() { - // Simulate initial load. - //this.onSearchChange(''); - // this.onComboBoxChange(this.props.selectedObject); - } - render() { const { onAdd, From d1322e038227465dfafba84e1458eeb0c2bf63c9 Mon Sep 17 00:00:00 2001 From: cchaos Date: Tue, 11 Sep 2018 14:36:31 -0400 Subject: [PATCH 22/22] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af406eae3ec..d1bfe2d3d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Added `euiFacetButton` and `euiFacetGroup` ([#1167](https://github.com/elastic/eui/pull/1167)) - Added `width` prop to `EuiContextMenu` panels ([#1173](https://github.com/elastic/eui/pull/1173)) +- Added patterns for global query and filters ([#1137](https://github.com/elastic/eui/pull/1137)) ## [`3.10.0`](https://github.com/elastic/eui/tree/v3.10.0)