diff --git a/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx b/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx
index 7248dc0c51..31a1184cc9 100644
--- a/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx
+++ b/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx
@@ -4,73 +4,16 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import {
Checkbox,
- Toolbar as PFToolbar,
- ToolbarGroup as PFToolbarGroup,
- ToolbarItem,
} from '@patternfly/react-core';
import styled from 'styled-components';
+import { SearchIcon } from '@patternfly/react-icons';
+import { DataToolbarGroup, DataToolbarToggleGroup, DataToolbarItem } from '@patternfly/react-core/dist/esm/experimental';
import ExpandCollapse from '../ExpandCollapse';
import Search from '../Search';
import Sort from '../Sort';
-import VerticalSeparator from '../VerticalSeparator';
import { SearchColumns, SortColumns, QSConfig } from '@types';
-const AWXToolbar = styled.div`
- --awx-toolbar--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
- --awx-toolbar--BorderColor: #ebebeb;
- --awx-toolbar--BorderWidth: var(--pf-global--BorderWidth--sm);
-
- --pf-global--target-size--MinHeight: 0;
- --pf-global--target-size--MinWidth: 0;
- --pf-global--FontSize--md: 14px;
-
- border-bottom: var(--awx-toolbar--BorderWidth) solid
- var(--awx-toolbar--BorderColor);
- background-color: var(--awx-toolbar--BackgroundColor);
- display: flex;
- min-height: 70px;
- flex-grow: 1;
-`;
-
-const Toolbar = styled(PFToolbar)`
- flex-grow: 1;
- margin-left: 20px;
- margin-right: 20px;
-`;
-
-const ToolbarGroup = styled(PFToolbarGroup)`
- &&& {
- margin: 0;
- }
-`;
-
-const ColumnLeft = styled.div`
- display: flex;
- flex-basis: ${props => (props.fillWidth ? 'auto' : '100%')};
- flex-grow: ${props => (props.fillWidth ? '1' : '0')};
- justify-content: flex-start;
- align-items: center;
- padding: 10px 0 8px 0;
-
- @media screen and (min-width: 980px) {
- flex-basis: ${props => (props.fillWidth ? 'auto' : '50%')};
- }
-`;
-
-const ColumnRight = styled.div`
- display: flex;
- flex-basis: ${props => (props.fillWidth ? 'auto' : '100%')};
- flex-grow: 0;
- justify-content: flex-start;
- align-items: center;
- padding: 8px 0 10px 0;
-
- @media screen and (min-width: 980px) {
- flex-basis: ${props => (props.fillWidth ? 'auto' : '50%')};
- }
-`;
-
const AdditionalControlsWrapper = styled.div`
display: flex;
flex-grow: 1;
@@ -82,6 +25,18 @@ const AdditionalControlsWrapper = styled.div`
}
`;
+const AdditionalControlsDataToolbarGroup = styled(DataToolbarGroup)`
+ margin-left: auto;
+ margin-right: 0 !important;
+`;
+
+const DataToolbarSeparator = styled(DataToolbarItem)`
+ width: 1px !important;
+ height: 30px !important;
+ margin-left: 3px !important;
+ margin-right: 10px !important;
+`;
+
class DataListToolbar extends React.Component {
render() {
const {
@@ -90,9 +45,9 @@ class DataListToolbar extends React.Component {
showSelectAll,
isAllSelected,
isCompact,
- fillWidth,
onSort,
onSearch,
+ onRemove,
onCompact,
onExpand,
onSelectAll,
@@ -103,58 +58,58 @@ class DataListToolbar extends React.Component {
const showExpandCollapse = onCompact && onExpand;
return (
-
-
-
- {showSelectAll && (
-
-
-
-
-
-
- )}
-
-
+ {showSelectAll && (
+
+
+
-
-
-
-
-
-
-
- {showExpandCollapse && (
-
-
-
-
-
- {additionalControls && }
-
- )}
+
+
+
+ )}
+ } breakpoint="xl">
+
+
+
+
+
+
+
+
+ {showExpandCollapse && (
+
+
+
+
+
+ )}
+
+
+
{additionalControls}
-
-
-
+
+
+
);
}
}
@@ -166,7 +121,6 @@ DataListToolbar.propTypes = {
showSelectAll: PropTypes.bool,
isAllSelected: PropTypes.bool,
isCompact: PropTypes.bool,
- fillWidth: PropTypes.bool,
onCompact: PropTypes.func,
onExpand: PropTypes.func,
onSearch: PropTypes.func,
@@ -179,7 +133,6 @@ DataListToolbar.defaultProps = {
showSelectAll: false,
isAllSelected: false,
isCompact: false,
- fillWidth: false,
onCompact: null,
onExpand: null,
onSearch: null,
diff --git a/awx/ui_next/src/components/FilterTags/FilterTags.jsx b/awx/ui_next/src/components/FilterTags/FilterTags.jsx
index 749c326256..b088d10b8d 100644
--- a/awx/ui_next/src/components/FilterTags/FilterTags.jsx
+++ b/awx/ui_next/src/components/FilterTags/FilterTags.jsx
@@ -1,21 +1,13 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import styled from 'styled-components';
-import { Button } from '@patternfly/react-core';
+import { DataToolbarGroup, DataToolbarItem } from '@patternfly/react-core/dist/esm/experimental';
import { parseQueryString } from '@util/qs';
-import { ChipGroup as _ChipGroup, Chip } from '@components/Chip';
+import { Button, Chip, ChipGroup, ChipGroupToolbarItem } from '@patternfly/react-core';
import VerticalSeparator from '@components/VerticalSeparator';
-const FilterTagsRow = styled.div`
- display: flex;
- padding: 15px 20px;
- border-top: 1px solid #d2d2d2;
- font-size: 14px;
- align-items: center;
-`;
-
const ResultCount = styled.span`
font-weight: bold;
`;
@@ -24,12 +16,6 @@ const FilterLabel = styled.span`
padding-right: 20px;
`;
-const ChipGroup = styled(_ChipGroup)`
- li.pf-m-overflow {
- display: none;
- }
-`;
-
// remove non-default query params so they don't show up as filter tags
const filterDefaultParams = (paramsArr, config) => {
const defaultParamsKeys = Object.keys(config.defaultParams);
@@ -45,7 +31,7 @@ const FilterTags = ({
onRemoveAll,
}) => {
const queryParams = parseQueryString(qsConfig, location.search);
- const queryParamsArr = [];
+ const queryParamsByKey = {};
const nonDefaultParams = filterDefaultParams(
Object.keys(queryParams),
qsConfig
@@ -56,45 +42,45 @@ const FilterTags = ({
.split('_')
.map(word => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
.join(' ');
+ queryParamsByKey[key] = { label, tags: [] };
if (Array.isArray(queryParams[key])) {
queryParams[key].forEach(val =>
- queryParamsArr.push({ key, value: val, label })
+ queryParamsByKey[key].tags.push(val)
);
} else {
- queryParamsArr.push({ key, value: queryParams[key], label });
+ queryParamsByKey[key].tags.push(queryParams[key]);
}
});
return (
- queryParamsArr.length > 0 && (
-
- {i18n._(t`${itemCount} results`)}
-
- {i18n._(t`Active Filters:`)}
-
- {queryParamsArr.map(({ key, label, value }) => (
- onRemove(key, value)}
- >
- {label}: {value}
-
- ))}
-
-
-
-
+
+
+
)
);
};
diff --git a/awx/ui_next/src/components/ListHeader/ListHeader.jsx b/awx/ui_next/src/components/ListHeader/ListHeader.jsx
index 93d52df4c9..e88505cb5f 100644
--- a/awx/ui_next/src/components/ListHeader/ListHeader.jsx
+++ b/awx/ui_next/src/components/ListHeader/ListHeader.jsx
@@ -2,8 +2,8 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import styled from 'styled-components';
+import { DataToolbar, DataToolbarContent } from '@patternfly/react-core/dist/esm/experimental';
import DataListToolbar from '@components/DataListToolbar';
-import FilterTags from '@components/FilterTags';
import {
encodeNonDefaultQueryString,
@@ -84,33 +84,32 @@ class ListHeader extends React.Component {
return (
{isEmpty ? (
-
-
- {emptyStateControls}
-
-
-
+
+
+
+ {emptyStateControls}
+
+
+
) : (
-
- {renderToolbar({
- searchColumns,
- sortColumns,
- onSearch: this.handleSearch,
- onSort: this.handleSort,
- qsConfig,
- })}
-
-
+
+
+ {renderToolbar({
+ searchColumns,
+ sortColumns,
+ onSearch: this.handleSearch,
+ onSort: this.handleSort,
+ onRemove: this.handleRemove,
+ qsConfig,
+ })}
+
+
)}
);
diff --git a/awx/ui_next/src/components/Search/Search.jsx b/awx/ui_next/src/components/Search/Search.jsx
index 6dda72a866..528b428cc2 100644
--- a/awx/ui_next/src/components/Search/Search.jsx
+++ b/awx/ui_next/src/components/Search/Search.jsx
@@ -2,80 +2,33 @@ import React from 'react';
import PropTypes from 'prop-types';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
+import { withRouter } from 'react-router-dom';
import {
- Button as PFButton,
- Dropdown as PFDropdown,
+ Button,
+ ButtonVariant,
+ Dropdown,
DropdownPosition,
DropdownToggle,
DropdownItem,
- Form,
- FormGroup,
- TextInput as PFTextInput,
+ InputGroup,
+ TextInput,
} from '@patternfly/react-core';
+import {
+ DataToolbarGroup,
+ DataToolbarItem,
+ DataToolbarFilter
+} from '@patternfly/react-core/dist/esm/experimental';
import { SearchIcon } from '@patternfly/react-icons';
-
+import { parseQueryString } from '@util/qs';
import { QSConfig, SearchColumns } from '@types';
-
import styled from 'styled-components';
-const TextInput = styled(PFTextInput)`
- min-height: 0px;
- height: 30px;
- --pf-c-form-control--BorderTopColor: var(--pf-global--BorderColor--200);
- --pf-c-form-control--BorderLeftColor: var(--pf-global--BorderColor--200);
-`;
-
-const Button = styled(PFButton)`
- width: 34px;
- padding: 0px;
- ::after {
- border: var(--pf-c-button--BorderWidth) solid
- var(--pf-global--BorderColor--200);
- }
-`;
-
-const Dropdown = styled(PFDropdown)`
- &&& {
- /* Higher specificity required because we are selecting unclassed elements */
- > button {
- min-height: 30px;
- min-width: 70px;
- height: 30px;
- padding: 0 10px;
- margin: 0px;
-
- ::before {
- border-color: var(--pf-global--BorderColor--200);
- border-top-left-radius: 3px;
- border-bottom-left-radius: 3px;
- }
-
- > span {
- /* text element */
- width: auto;
- }
-
- > svg {
- /* caret icon */
- margin: 0px;
- padding-top: 3px;
- padding-left: 3px;
- }
- }
- }
-`;
-
const NoOptionDropdown = styled.div`
align-self: stretch;
- border: 1px solid var(--pf-global--BorderColor--200);
- border-top-left-radius: 3px;
- border-bottom-left-radius: 3px;
- padding: 3px 7px;
+ border: 1px solid var(--pf-global--BorderColor--300);
+ padding: 5px 15px;
white-space: nowrap;
-`;
-
-const InputFormGroup = styled(FormGroup)`
- flex: 1;
+ border-bottom-color: var(--pf-global--BorderColor--200);
`;
class Search extends React.Component {
@@ -94,6 +47,7 @@ class Search extends React.Component {
this.handleDropdownToggle = this.handleDropdownToggle.bind(this);
this.handleDropdownSelect = this.handleDropdownSelect.bind(this);
this.handleSearch = this.handleSearch.bind(this);
+ this.handleTextKeyDown = this.handleTextKeyDown.bind(this);
}
handleDropdownToggle(isSearchDropdownOpen) {
@@ -134,9 +88,15 @@ class Search extends React.Component {
this.setState({ searchValue });
}
+ handleTextKeyDown(e) {
+ if (e.key && e.key === 'Enter') {
+ this.handleSearch(e);
+ }
+ }
+
render() {
const { up } = DropdownPosition;
- const { columns, i18n } = this.props;
+ const { columns, i18n, onRemove, qsConfig, location } = this.props;
const { isSearchDropdownOpen, searchKey, searchValue } = this.state;
const { name: searchColumnName } = columns.find(
({ key }) => key === searchKey
@@ -150,65 +110,95 @@ class Search extends React.Component {
));
+ const filterDefaultParams = (paramsArr, config) => {
+ const defaultParamsKeys = Object.keys(config.defaultParams);
+ return paramsArr.filter(key => defaultParamsKeys.indexOf(key) === -1);
+ };
+
+ const getChipsByKey = () => {
+ const queryParams = parseQueryString(qsConfig, location.search);
+
+ const queryParamsByKey = {};
+ columns.forEach(({name, key}) => {
+ queryParamsByKey[key] = {key, label: name, chips: []};
+ });
+ const nonDefaultParams = filterDefaultParams(
+ Object.keys(queryParams),
+ qsConfig
+ );
+
+ nonDefaultParams.forEach(key => {
+ const columnKey = key
+ .replace('__icontains', '');
+ const label = key
+ .replace('__icontains', '')
+ .split('_')
+ .map(word => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
+ .join(' ');
+
+ queryParamsByKey[columnKey] = { key, label, chips: [] };
+
+ if (Array.isArray(queryParams[key])) {
+ queryParams[key].forEach(val =>
+ queryParamsByKey[columnKey].chips.push(val)
+ );
+ } else {
+ queryParamsByKey[columnKey].chips.push(queryParams[key]);
+ }
+ });
+
+ return queryParamsByKey;
+ }
+
+ const chipsByKey = getChipsByKey();
+
return (
-
+
+
+
+
+ ))}
+
);
}
}
@@ -216,11 +206,13 @@ class Search extends React.Component {
Search.propTypes = {
qsConfig: QSConfig.isRequired,
columns: SearchColumns.isRequired,
- onSearch: PropTypes.func
+ onSearch: PropTypes.func,
+ onRemove: PropTypes.func
};
Search.defaultProps = {
onSearch: null,
+ onRemove: null
};
-export default withI18n()(Search);
+export default withI18n()(withRouter(Search));
diff --git a/awx/ui_next/src/components/Sort/Sort.jsx b/awx/ui_next/src/components/Sort/Sort.jsx
index 5b9ba6f523..fd12908917 100644
--- a/awx/ui_next/src/components/Sort/Sort.jsx
+++ b/awx/ui_next/src/components/Sort/Sort.jsx
@@ -1,15 +1,16 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { withI18n } from '@lingui/react';
import { withRouter } from 'react-router-dom';
import { t } from '@lingui/macro';
import {
Button,
- Dropdown as PFDropdown,
+ ButtonVariant,
+ Dropdown,
DropdownPosition,
DropdownToggle,
DropdownItem,
- Tooltip,
+ InputGroup,
} from '@patternfly/react-core';
import {
SortAlphaDownIcon,
@@ -18,58 +19,11 @@ import {
SortNumericUpIcon,
} from '@patternfly/react-icons';
-import styled from 'styled-components';
-
import {
parseQueryString
} from '@util/qs';
import { SortColumns, QSConfig } from '@types';
-const Dropdown = styled(PFDropdown)`
- &&& {
- > button {
- min-height: 30px;
- min-width: 70px;
- height: 30px;
- padding: 0 10px;
- margin: 0px;
-
- > span {
- /* text element within dropdown */
- width: auto;
- }
-
- > svg {
- /* caret icon */
- margin: 0px;
- padding-top: 3px;
- padding-left: 3px;
- }
- }
- }
-`;
-
-const IconWrapper = styled.span`
- > svg {
- font-size: 18px;
- }
-`;
-
-const SortButton = styled(Button)`
- padding: 5px 8px;
- margin-top: 3px;
-
- &:hover {
- background-color: #0166cc;
- color: white;
- }
-`;
-
-const SortBy = styled.span`
- margin-right: 15px;
- font-size: var(--pf-global--FontSize--md);
-`;
-
class Sort extends React.Component {
constructor(props) {
super(props);
@@ -165,12 +119,10 @@ class Sort extends React.Component {
}
return (
-
+
{sortDropdownItems.length > 0 && (
-
- {i18n._(t`Sort By`)}
+
-
+
+
+
+
)}
- {i18n._(t`Reverse Sort Order`)}}
- position="top"
- >
-
-
-
-
-
-
-
+
);
}
}
diff --git a/awx/ui_next/src/components/VerticalSeparator/VerticalSeparator.jsx b/awx/ui_next/src/components/VerticalSeparator/VerticalSeparator.jsx
index ebe53e5ada..94679dadf7 100644
--- a/awx/ui_next/src/components/VerticalSeparator/VerticalSeparator.jsx
+++ b/awx/ui_next/src/components/VerticalSeparator/VerticalSeparator.jsx
@@ -5,7 +5,7 @@ const Separator = styled.span`
display: inline-block;
width: 1px;
height: 30px;
- margin-right: 20px;
+ margin-right: 27px;
margin-left: 20px;
background-color: #d7d7d7;
vertical-align: middle;