mirror of
https://github.com/ansible/awx.git
synced 2026-03-02 17:28:51 -03:30
Merge pull request #134 from mabashian/toolbar-refactor-v2
Refactor of DataListToolbar v2
This commit is contained in:
23
__tests__/components/ExpandCollapse.test.jsx
Normal file
23
__tests__/components/ExpandCollapse.test.jsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { I18nProvider } from '@lingui/react';
|
||||||
|
import ExpandCollapse from '../../src/components/ExpandCollapse';
|
||||||
|
|
||||||
|
describe('<ExpandCollapse />', () => {
|
||||||
|
const onCompact = jest.fn();
|
||||||
|
const onExpand = jest.fn();
|
||||||
|
const isCompact = false;
|
||||||
|
test('initially renders without crashing', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<ExpandCollapse
|
||||||
|
onCompact={onCompact}
|
||||||
|
onExpand={onExpand}
|
||||||
|
isCompact={isCompact}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
expect(wrapper.length).toBe(1);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
78
__tests__/components/Search.test.jsx
Normal file
78
__tests__/components/Search.test.jsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { I18nProvider } from '@lingui/react';
|
||||||
|
import Search from '../../src/components/Search';
|
||||||
|
|
||||||
|
describe('<Search />', () => {
|
||||||
|
let search;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (search) {
|
||||||
|
search.unmount();
|
||||||
|
search = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it triggers the expected callbacks', () => {
|
||||||
|
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
|
||||||
|
|
||||||
|
const searchBtn = 'button[aria-label="Search"]';
|
||||||
|
const searchTextInput = 'input[aria-label="Search text input"]';
|
||||||
|
|
||||||
|
const onSearch = jest.fn();
|
||||||
|
|
||||||
|
search = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Search
|
||||||
|
sortedColumnKey="name"
|
||||||
|
columns={columns}
|
||||||
|
onSearch={onSearch}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
search.find(searchTextInput).instance().value = 'test-321';
|
||||||
|
search.find(searchTextInput).simulate('change');
|
||||||
|
search.find(searchBtn).simulate('click');
|
||||||
|
|
||||||
|
expect(onSearch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onSearch).toBeCalledWith('test-321');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleDropdownToggle properly updates state', async () => {
|
||||||
|
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
|
||||||
|
const onSearch = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Search
|
||||||
|
sortedColumnKey="name"
|
||||||
|
columns={columns}
|
||||||
|
onSearch={onSearch}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('Search');
|
||||||
|
expect(wrapper.state('isSearchDropdownOpen')).toEqual(false);
|
||||||
|
wrapper.instance().handleDropdownToggle(true);
|
||||||
|
expect(wrapper.state('isSearchDropdownOpen')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleDropdownSelect properly updates state', async () => {
|
||||||
|
const columns = [
|
||||||
|
{ name: 'Name', key: 'name', isSortable: true },
|
||||||
|
{ name: 'Description', key: 'description', isSortable: true }
|
||||||
|
];
|
||||||
|
const onSearch = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Search
|
||||||
|
sortedColumnKey="name"
|
||||||
|
columns={columns}
|
||||||
|
onSearch={onSearch}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('Search');
|
||||||
|
expect(wrapper.state('searchKey')).toEqual('name');
|
||||||
|
wrapper.instance().handleDropdownSelect({ target: { innerText: 'Description' } });
|
||||||
|
expect(wrapper.state('searchKey')).toEqual('description');
|
||||||
|
});
|
||||||
|
});
|
||||||
208
__tests__/components/Sort.test.jsx
Normal file
208
__tests__/components/Sort.test.jsx
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { I18nProvider } from '@lingui/react';
|
||||||
|
import Sort from '../../src/components/Sort';
|
||||||
|
|
||||||
|
describe('<Sort />', () => {
|
||||||
|
let sort;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (sort) {
|
||||||
|
sort.unmount();
|
||||||
|
sort = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it triggers the expected callbacks', () => {
|
||||||
|
const columns = [{ name: 'Name', key: 'name', isSortable: true }];
|
||||||
|
|
||||||
|
const sortBtn = 'button[aria-label="Sort"]';
|
||||||
|
|
||||||
|
const onSort = jest.fn();
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Sort
|
||||||
|
sortedColumnKey="name"
|
||||||
|
sortOrder="ascending"
|
||||||
|
columns={columns}
|
||||||
|
onSort={onSort}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('Sort');
|
||||||
|
|
||||||
|
wrapper.find(sortBtn).simulate('click');
|
||||||
|
|
||||||
|
expect(onSort).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onSort).toBeCalledWith('name', 'descending');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onSort properly passes back descending when ascending was passed as prop', () => {
|
||||||
|
const multipleColumns = [
|
||||||
|
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||||
|
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||||
|
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||||
|
{ name: 'Baz', key: 'baz' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const onSort = jest.fn();
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Sort
|
||||||
|
sortedColumnKey="foo"
|
||||||
|
sortOrder="ascending"
|
||||||
|
columns={multipleColumns}
|
||||||
|
onSort={onSort}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('Sort');
|
||||||
|
const sortDropdownToggle = wrapper.find('Button');
|
||||||
|
expect(sortDropdownToggle.length).toBe(1);
|
||||||
|
sortDropdownToggle.simulate('click');
|
||||||
|
expect(onSort).toHaveBeenCalledWith('foo', 'descending');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onSort properly passes back ascending when descending was passed as prop', () => {
|
||||||
|
const multipleColumns = [
|
||||||
|
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||||
|
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||||
|
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||||
|
{ name: 'Baz', key: 'baz' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const onSort = jest.fn();
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Sort
|
||||||
|
sortedColumnKey="foo"
|
||||||
|
sortOrder="descending"
|
||||||
|
columns={multipleColumns}
|
||||||
|
onSort={onSort}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('Sort');
|
||||||
|
const sortDropdownToggle = wrapper.find('Button');
|
||||||
|
expect(sortDropdownToggle.length).toBe(1);
|
||||||
|
sortDropdownToggle.simulate('click');
|
||||||
|
expect(onSort).toHaveBeenCalledWith('foo', 'ascending');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Changing dropdown correctly passes back new sort key', () => {
|
||||||
|
const multipleColumns = [
|
||||||
|
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||||
|
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||||
|
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||||
|
{ name: 'Baz', key: 'baz' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const onSort = jest.fn();
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Sort
|
||||||
|
sortedColumnKey="foo"
|
||||||
|
sortOrder="ascending"
|
||||||
|
columns={multipleColumns}
|
||||||
|
onSort={onSort}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('Sort');
|
||||||
|
|
||||||
|
wrapper.instance().handleDropdownSelect({ target: { innerText: 'Bar' } });
|
||||||
|
expect(onSort).toBeCalledWith('bar', 'ascending');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Opening dropdown correctly updates state', () => {
|
||||||
|
const multipleColumns = [
|
||||||
|
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||||
|
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||||
|
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||||
|
{ name: 'Baz', key: 'baz' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const onSort = jest.fn();
|
||||||
|
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Sort
|
||||||
|
sortedColumnKey="foo"
|
||||||
|
sortOrder="ascending"
|
||||||
|
columns={multipleColumns}
|
||||||
|
onSort={onSort}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('Sort');
|
||||||
|
expect(wrapper.state('isSortDropdownOpen')).toEqual(false);
|
||||||
|
wrapper.instance().handleDropdownToggle(true);
|
||||||
|
expect(wrapper.state('isSortDropdownOpen')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('It displays correct sort icon', () => {
|
||||||
|
const downNumericIconSelector = 'SortNumericDownIcon';
|
||||||
|
const upNumericIconSelector = 'SortNumericUpIcon';
|
||||||
|
const downAlphaIconSelector = 'SortAlphaDownIcon';
|
||||||
|
const upAlphaIconSelector = 'SortAlphaUpIcon';
|
||||||
|
|
||||||
|
const numericColumns = [{ name: 'ID', key: 'id', isSortable: true, isNumeric: true }];
|
||||||
|
const alphaColumns = [{ name: 'Name', key: 'name', isSortable: true, isNumeric: false }];
|
||||||
|
const onSort = jest.fn();
|
||||||
|
|
||||||
|
sort = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Sort
|
||||||
|
sortedColumnKey="id"
|
||||||
|
sortOrder="descending"
|
||||||
|
columns={numericColumns}
|
||||||
|
onSort={onSort}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const downNumericIcon = sort.find(downNumericIconSelector);
|
||||||
|
expect(downNumericIcon.length).toBe(1);
|
||||||
|
|
||||||
|
sort = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Sort
|
||||||
|
sortedColumnKey="id"
|
||||||
|
sortOrder="ascending"
|
||||||
|
columns={numericColumns}
|
||||||
|
onSort={onSort}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const upNumericIcon = sort.find(upNumericIconSelector);
|
||||||
|
expect(upNumericIcon.length).toBe(1);
|
||||||
|
|
||||||
|
sort = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Sort
|
||||||
|
sortedColumnKey="name"
|
||||||
|
sortOrder="descending"
|
||||||
|
columns={alphaColumns}
|
||||||
|
onSort={onSort}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const downAlphaIcon = sort.find(downAlphaIconSelector);
|
||||||
|
expect(downAlphaIcon.length).toBe(1);
|
||||||
|
|
||||||
|
sort = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Sort
|
||||||
|
sortedColumnKey="name"
|
||||||
|
sortOrder="ascending"
|
||||||
|
columns={alphaColumns}
|
||||||
|
onSort={onSort}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const upAlphaIcon = sort.find(upAlphaIconSelector);
|
||||||
|
expect(upAlphaIcon.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1647,7 +1647,7 @@
|
|||||||
},
|
},
|
||||||
"ansi-colors": {
|
"ansi-colors": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
|
"resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
|
||||||
"integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
|
"integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-wrap": "^0.1.0"
|
"ansi-wrap": "^0.1.0"
|
||||||
@@ -2557,12 +2557,12 @@
|
|||||||
},
|
},
|
||||||
"babel-plugin-syntax-class-properties": {
|
"babel-plugin-syntax-class-properties": {
|
||||||
"version": "6.13.0",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
|
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
|
||||||
"integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94="
|
"integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94="
|
||||||
},
|
},
|
||||||
"babel-plugin-syntax-flow": {
|
"babel-plugin-syntax-flow": {
|
||||||
"version": "6.18.0",
|
"version": "6.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
|
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
|
||||||
"integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0="
|
"integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0="
|
||||||
},
|
},
|
||||||
"babel-plugin-syntax-jsx": {
|
"babel-plugin-syntax-jsx": {
|
||||||
@@ -4701,7 +4701,7 @@
|
|||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-util-is": "~1.0.0",
|
"core-util-is": "~1.0.0",
|
||||||
@@ -5726,7 +5726,7 @@
|
|||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-util-is": "~1.0.0",
|
"core-util-is": "~1.0.0",
|
||||||
@@ -9254,7 +9254,7 @@
|
|||||||
},
|
},
|
||||||
"kind-of": {
|
"kind-of": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
|
"resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
|
||||||
"integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ="
|
"integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ="
|
||||||
},
|
},
|
||||||
"kleur": {
|
"kleur": {
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
import React from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { I18n } from '@lingui/react';
|
import { I18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Dropdown,
|
|
||||||
DropdownPosition,
|
|
||||||
DropdownToggle,
|
|
||||||
DropdownItem,
|
|
||||||
Level,
|
Level,
|
||||||
LevelItem,
|
LevelItem,
|
||||||
TextInput,
|
|
||||||
Toolbar,
|
Toolbar,
|
||||||
ToolbarGroup,
|
ToolbarGroup,
|
||||||
ToolbarItem,
|
ToolbarItem,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import {
|
import {
|
||||||
BarsIcon,
|
|
||||||
EqualsIcon,
|
|
||||||
SortAlphaDownIcon,
|
|
||||||
SortAlphaUpIcon,
|
|
||||||
SortNumericDownIcon,
|
|
||||||
SortNumericUpIcon,
|
|
||||||
TrashAltIcon,
|
TrashAltIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
} from '@patternfly/react-icons';
|
} from '@patternfly/react-icons';
|
||||||
@@ -31,97 +20,13 @@ import {
|
|||||||
Link
|
Link
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
import ExpandCollapse from '../ExpandCollapse';
|
||||||
|
import Search from '../Search';
|
||||||
|
import Sort from '../Sort';
|
||||||
import VerticalSeparator from '../VerticalSeparator';
|
import VerticalSeparator from '../VerticalSeparator';
|
||||||
|
|
||||||
const flexGrowStyling = {
|
|
||||||
flexGrow: '1'
|
|
||||||
};
|
|
||||||
|
|
||||||
const ToolbarActiveStyle = {
|
|
||||||
backgroundColor: '#007bba',
|
|
||||||
color: 'white',
|
|
||||||
padding: '0 5px',
|
|
||||||
};
|
|
||||||
|
|
||||||
class DataListToolbar extends React.Component {
|
class DataListToolbar extends React.Component {
|
||||||
constructor (props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const { sortedColumnKey } = this.props;
|
|
||||||
this.state = {
|
|
||||||
isSearchDropdownOpen: false,
|
|
||||||
isSortDropdownOpen: false,
|
|
||||||
searchKey: sortedColumnKey,
|
|
||||||
searchValue: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleSearchInputChange = this.handleSearchInputChange.bind(this);
|
|
||||||
this.onSortDropdownToggle = this.onSortDropdownToggle.bind(this);
|
|
||||||
this.onSortDropdownSelect = this.onSortDropdownSelect.bind(this);
|
|
||||||
this.onSearchDropdownToggle = this.onSearchDropdownToggle.bind(this);
|
|
||||||
this.onSearchDropdownSelect = this.onSearchDropdownSelect.bind(this);
|
|
||||||
this.onSearch = this.onSearch.bind(this);
|
|
||||||
this.onSort = this.onSort.bind(this);
|
|
||||||
this.onExpand = this.onExpand.bind(this);
|
|
||||||
this.onCompact = this.onCompact.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
onExpand () {
|
|
||||||
const { onExpand } = this.props;
|
|
||||||
onExpand();
|
|
||||||
}
|
|
||||||
|
|
||||||
onCompact () {
|
|
||||||
const { onCompact } = this.props;
|
|
||||||
onCompact();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSortDropdownToggle (isSortDropdownOpen) {
|
|
||||||
this.setState({ isSortDropdownOpen });
|
|
||||||
}
|
|
||||||
|
|
||||||
onSortDropdownSelect ({ target }) {
|
|
||||||
const { columns, onSort, sortOrder } = this.props;
|
|
||||||
const { innerText } = target;
|
|
||||||
|
|
||||||
const [{ key: searchKey }] = columns.filter(({ name }) => name === innerText);
|
|
||||||
|
|
||||||
this.setState({ isSortDropdownOpen: false });
|
|
||||||
onSort(searchKey, sortOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearchDropdownToggle (isSearchDropdownOpen) {
|
|
||||||
this.setState({ isSearchDropdownOpen });
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearchDropdownSelect ({ target }) {
|
|
||||||
const { columns } = this.props;
|
|
||||||
const { innerText } = target;
|
|
||||||
|
|
||||||
const [{ key: searchKey }] = columns.filter(({ name }) => name === innerText);
|
|
||||||
this.setState({ isSearchDropdownOpen: false, searchKey });
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearch () {
|
|
||||||
const { searchValue } = this.state;
|
|
||||||
const { onSearch } = this.props;
|
|
||||||
|
|
||||||
onSearch(searchValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSort () {
|
|
||||||
const { onSort, sortedColumnKey, sortOrder } = this.props;
|
|
||||||
const newSortOrder = sortOrder === 'ascending' ? 'descending' : 'ascending';
|
|
||||||
|
|
||||||
onSort(sortedColumnKey, newSortOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchInputChange (searchValue) {
|
|
||||||
this.setState({ searchValue });
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { up } = DropdownPosition;
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
isAllSelected,
|
isAllSelected,
|
||||||
@@ -129,45 +34,18 @@ class DataListToolbar extends React.Component {
|
|||||||
sortedColumnKey,
|
sortedColumnKey,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
addUrl,
|
addUrl,
|
||||||
showExpandCollapse,
|
|
||||||
showDelete,
|
showDelete,
|
||||||
showSelectAll,
|
showSelectAll,
|
||||||
isLookup,
|
isLookup,
|
||||||
isCompact,
|
isCompact,
|
||||||
|
onSort,
|
||||||
|
onSearch,
|
||||||
|
onCompact,
|
||||||
|
onExpand,
|
||||||
|
add
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
|
||||||
isSearchDropdownOpen,
|
|
||||||
isSortDropdownOpen,
|
|
||||||
searchKey,
|
|
||||||
searchValue,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const [{ name: searchColumnName }] = columns.filter(({ key }) => key === searchKey);
|
const showExpandCollapse = (onCompact && onExpand);
|
||||||
const [{ name: sortedColumnName, isNumeric }] = columns
|
|
||||||
.filter(({ key }) => key === sortedColumnKey);
|
|
||||||
|
|
||||||
const searchDropdownItems = columns
|
|
||||||
.filter(({ key }) => key !== searchKey)
|
|
||||||
.map(({ key, name }) => (
|
|
||||||
<DropdownItem key={key} component="button">
|
|
||||||
{name}
|
|
||||||
</DropdownItem>
|
|
||||||
));
|
|
||||||
|
|
||||||
const sortDropdownItems = columns
|
|
||||||
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
|
|
||||||
.map(({ key, name }) => (
|
|
||||||
<DropdownItem key={key} component="button">
|
|
||||||
{name}
|
|
||||||
</DropdownItem>
|
|
||||||
));
|
|
||||||
|
|
||||||
let SortIcon;
|
|
||||||
if (isNumeric) {
|
|
||||||
SortIcon = sortOrder === 'ascending' ? SortNumericUpIcon : SortNumericDownIcon;
|
|
||||||
} else {
|
|
||||||
SortIcon = sortOrder === 'ascending' ? SortAlphaUpIcon : SortAlphaDownIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18n>
|
<I18n>
|
||||||
@@ -189,102 +67,44 @@ class DataListToolbar extends React.Component {
|
|||||||
<VerticalSeparator />
|
<VerticalSeparator />
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
)}
|
)}
|
||||||
<ToolbarGroup style={flexGrowStyling}>
|
<ToolbarGroup style={{ flexGrow: '1' }}>
|
||||||
<ToolbarItem style={flexGrowStyling}>
|
<ToolbarItem style={{ flexGrow: '1' }}>
|
||||||
<div className="pf-c-input-group">
|
<Search
|
||||||
<Dropdown
|
columns={columns}
|
||||||
className="searchKeyDropdown"
|
onSearch={onSearch}
|
||||||
onToggle={this.onSearchDropdownToggle}
|
sortedColumnKey={sortedColumnKey}
|
||||||
onSelect={this.onSearchDropdownSelect}
|
/>
|
||||||
direction={up}
|
|
||||||
isOpen={isSearchDropdownOpen}
|
|
||||||
toggle={(
|
|
||||||
<DropdownToggle
|
|
||||||
onToggle={this.onSearchDropdownToggle}
|
|
||||||
>
|
|
||||||
{searchColumnName}
|
|
||||||
</DropdownToggle>
|
|
||||||
)}
|
|
||||||
dropdownItems={searchDropdownItems}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
type="search"
|
|
||||||
aria-label={i18n._(t`Search text input`)}
|
|
||||||
value={searchValue}
|
|
||||||
onChange={this.handleSearchInputChange}
|
|
||||||
style={{ height: '30px' }}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="tertiary"
|
|
||||||
aria-label={i18n._(t`Search`)}
|
|
||||||
onClick={this.onSearch}
|
|
||||||
>
|
|
||||||
<i className="fas fa-search" aria-hidden="true" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
<VerticalSeparator />
|
<VerticalSeparator />
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
<ToolbarGroup
|
<ToolbarGroup
|
||||||
className="sortDropdownGroup"
|
className="sortDropdownGroup"
|
||||||
>
|
>
|
||||||
{ sortDropdownItems.length > 1 && (
|
|
||||||
<ToolbarItem>
|
|
||||||
<Dropdown
|
|
||||||
onToggle={this.onSortDropdownToggle}
|
|
||||||
onSelect={this.onSortDropdownSelect}
|
|
||||||
direction={up}
|
|
||||||
isOpen={isSortDropdownOpen}
|
|
||||||
toggle={(
|
|
||||||
<DropdownToggle
|
|
||||||
onToggle={this.onSortDropdownToggle}
|
|
||||||
>
|
|
||||||
{sortedColumnName}
|
|
||||||
</DropdownToggle>
|
|
||||||
)}
|
|
||||||
dropdownItems={sortDropdownItems}
|
|
||||||
/>
|
|
||||||
</ToolbarItem>
|
|
||||||
)}
|
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<Button
|
<Sort
|
||||||
onClick={this.onSort}
|
columns={columns}
|
||||||
variant="plain"
|
onSort={onSort}
|
||||||
aria-label={i18n._(t`Sort`)}
|
sortOrder={sortOrder}
|
||||||
>
|
sortedColumnKey={sortedColumnKey}
|
||||||
<SortIcon />
|
/>
|
||||||
</Button>
|
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
{ (showExpandCollapse || showDelete || addUrl) && (
|
|
||||||
<VerticalSeparator />
|
|
||||||
)}
|
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
|
{ (showExpandCollapse || showDelete || addUrl || add) && (
|
||||||
|
<VerticalSeparator />
|
||||||
|
)}
|
||||||
{showExpandCollapse && (
|
{showExpandCollapse && (
|
||||||
<ToolbarGroup>
|
<Fragment>
|
||||||
<ToolbarItem>
|
<ToolbarGroup>
|
||||||
<Button
|
<ExpandCollapse
|
||||||
variant="plain"
|
isCompact={isCompact}
|
||||||
aria-label={i18n._(t`Collapse`)}
|
onCompact={onCompact}
|
||||||
onClick={this.onCompact}
|
onExpand={onExpand}
|
||||||
style={isCompact ? ToolbarActiveStyle : null}
|
/>
|
||||||
>
|
</ToolbarGroup>
|
||||||
<BarsIcon />
|
{ (showDelete || addUrl || add) && (
|
||||||
</Button>
|
|
||||||
</ToolbarItem>
|
|
||||||
<ToolbarItem>
|
|
||||||
<Button
|
|
||||||
variant="plain"
|
|
||||||
aria-label={i18n._(t`Expand`)}
|
|
||||||
onClick={this.onExpand}
|
|
||||||
style={!isCompact ? ToolbarActiveStyle : null}
|
|
||||||
>
|
|
||||||
<EqualsIcon />
|
|
||||||
</Button>
|
|
||||||
</ToolbarItem>
|
|
||||||
{ (showDelete || addUrl) && (
|
|
||||||
<VerticalSeparator />
|
<VerticalSeparator />
|
||||||
)}
|
)}
|
||||||
</ToolbarGroup>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</LevelItem>
|
</LevelItem>
|
||||||
@@ -312,6 +132,9 @@ class DataListToolbar extends React.Component {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
{add && (
|
||||||
|
<Fragment>{add}</Fragment>
|
||||||
|
)}
|
||||||
</LevelItem>
|
</LevelItem>
|
||||||
</Level>
|
</Level>
|
||||||
</div>
|
</div>
|
||||||
@@ -329,10 +152,13 @@ DataListToolbar.propTypes = {
|
|||||||
onSelectAll: PropTypes.func,
|
onSelectAll: PropTypes.func,
|
||||||
onSort: PropTypes.func,
|
onSort: PropTypes.func,
|
||||||
showDelete: PropTypes.bool,
|
showDelete: PropTypes.bool,
|
||||||
showExpandCollapse: PropTypes.bool,
|
|
||||||
showSelectAll: PropTypes.bool,
|
showSelectAll: PropTypes.bool,
|
||||||
sortOrder: PropTypes.string,
|
sortOrder: PropTypes.string,
|
||||||
sortedColumnKey: PropTypes.string,
|
sortedColumnKey: PropTypes.string,
|
||||||
|
onCompact: PropTypes.func,
|
||||||
|
onExpand: PropTypes.func,
|
||||||
|
isCompact: PropTypes.bool,
|
||||||
|
add: PropTypes.node
|
||||||
};
|
};
|
||||||
|
|
||||||
DataListToolbar.defaultProps = {
|
DataListToolbar.defaultProps = {
|
||||||
@@ -341,11 +167,14 @@ DataListToolbar.defaultProps = {
|
|||||||
onSelectAll: null,
|
onSelectAll: null,
|
||||||
onSort: null,
|
onSort: null,
|
||||||
showDelete: false,
|
showDelete: false,
|
||||||
showExpandCollapse: false,
|
|
||||||
showSelectAll: false,
|
showSelectAll: false,
|
||||||
sortOrder: 'ascending',
|
sortOrder: 'ascending',
|
||||||
sortedColumnKey: 'name',
|
sortedColumnKey: 'name',
|
||||||
isAllSelected: false,
|
isAllSelected: false,
|
||||||
|
onCompact: null,
|
||||||
|
onExpand: null,
|
||||||
|
isCompact: false,
|
||||||
|
add: null
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DataListToolbar;
|
export default DataListToolbar;
|
||||||
|
|||||||
67
src/components/ExpandCollapse/ExpandCollapse.jsx
Normal file
67
src/components/ExpandCollapse/ExpandCollapse.jsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { I18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ToolbarItem
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import {
|
||||||
|
BarsIcon,
|
||||||
|
EqualsIcon,
|
||||||
|
} from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
const ToolbarActiveStyle = {
|
||||||
|
backgroundColor: '#007bba',
|
||||||
|
color: 'white',
|
||||||
|
padding: '0 5px',
|
||||||
|
};
|
||||||
|
|
||||||
|
class ExpandCollapse extends React.Component {
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
onCompact,
|
||||||
|
onExpand,
|
||||||
|
isCompact
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<I18n>
|
||||||
|
{({ i18n }) => (
|
||||||
|
<Fragment>
|
||||||
|
<ToolbarItem>
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
aria-label={i18n._(t`Collapse`)}
|
||||||
|
onClick={onCompact}
|
||||||
|
style={isCompact ? ToolbarActiveStyle : null}
|
||||||
|
>
|
||||||
|
<BarsIcon />
|
||||||
|
</Button>
|
||||||
|
</ToolbarItem>
|
||||||
|
<ToolbarItem>
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
aria-label={i18n._(t`Expand`)}
|
||||||
|
onClick={onExpand}
|
||||||
|
style={!isCompact ? ToolbarActiveStyle : null}
|
||||||
|
>
|
||||||
|
<EqualsIcon />
|
||||||
|
</Button>
|
||||||
|
</ToolbarItem>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</I18n>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpandCollapse.propTypes = {
|
||||||
|
onCompact: PropTypes.func.isRequired,
|
||||||
|
onExpand: PropTypes.func.isRequired,
|
||||||
|
isCompact: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
ExpandCollapse.defaultProps = {};
|
||||||
|
|
||||||
|
export default ExpandCollapse;
|
||||||
3
src/components/ExpandCollapse/index.js
Normal file
3
src/components/ExpandCollapse/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import ExpandCollapse from './ExpandCollapse';
|
||||||
|
|
||||||
|
export default ExpandCollapse;
|
||||||
126
src/components/Search/Search.jsx
Normal file
126
src/components/Search/Search.jsx
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { I18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dropdown,
|
||||||
|
DropdownPosition,
|
||||||
|
DropdownToggle,
|
||||||
|
DropdownItem,
|
||||||
|
TextInput
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
class Search extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const { sortedColumnKey } = this.props;
|
||||||
|
this.state = {
|
||||||
|
isSearchDropdownOpen: false,
|
||||||
|
searchKey: sortedColumnKey,
|
||||||
|
searchValue: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleSearchInputChange = this.handleSearchInputChange.bind(this);
|
||||||
|
this.handleDropdownToggle = this.handleDropdownToggle.bind(this);
|
||||||
|
this.handleDropdownSelect = this.handleDropdownSelect.bind(this);
|
||||||
|
this.handleSearch = this.handleSearch.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDropdownToggle (isSearchDropdownOpen) {
|
||||||
|
this.setState({ isSearchDropdownOpen });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDropdownSelect ({ target }) {
|
||||||
|
const { columns } = this.props;
|
||||||
|
const { innerText } = target;
|
||||||
|
|
||||||
|
const [{ key: searchKey }] = columns.filter(({ name }) => name === innerText);
|
||||||
|
this.setState({ isSearchDropdownOpen: false, searchKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearch () {
|
||||||
|
const { searchValue } = this.state;
|
||||||
|
const { onSearch } = this.props;
|
||||||
|
|
||||||
|
onSearch(searchValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchInputChange (searchValue) {
|
||||||
|
this.setState({ searchValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { up } = DropdownPosition;
|
||||||
|
const {
|
||||||
|
columns
|
||||||
|
} = this.props;
|
||||||
|
const {
|
||||||
|
isSearchDropdownOpen,
|
||||||
|
searchKey,
|
||||||
|
searchValue,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const [{ name: searchColumnName }] = columns.filter(({ key }) => key === searchKey);
|
||||||
|
|
||||||
|
const searchDropdownItems = columns
|
||||||
|
.filter(({ key }) => key !== searchKey)
|
||||||
|
.map(({ key, name }) => (
|
||||||
|
<DropdownItem key={key} component="button">
|
||||||
|
{name}
|
||||||
|
</DropdownItem>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<I18n>
|
||||||
|
{({ i18n }) => (
|
||||||
|
<div className="pf-c-input-group">
|
||||||
|
<Dropdown
|
||||||
|
className="searchKeyDropdown"
|
||||||
|
onToggle={this.handleDropdownToggle}
|
||||||
|
onSelect={this.handleDropdownSelect}
|
||||||
|
direction={up}
|
||||||
|
isOpen={isSearchDropdownOpen}
|
||||||
|
toggle={(
|
||||||
|
<DropdownToggle
|
||||||
|
onToggle={this.handleDropdownToggle}
|
||||||
|
>
|
||||||
|
{searchColumnName}
|
||||||
|
</DropdownToggle>
|
||||||
|
)}
|
||||||
|
dropdownItems={searchDropdownItems}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
type="search"
|
||||||
|
aria-label="Search text input"
|
||||||
|
value={searchValue}
|
||||||
|
onChange={this.handleSearchInputChange}
|
||||||
|
style={{ height: '30px' }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
aria-label={i18n._(t`Search`)}
|
||||||
|
onClick={this.handleSearch}
|
||||||
|
>
|
||||||
|
<i className="fas fa-search" aria-hidden="true" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</I18n>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Search.propTypes = {
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onSearch: PropTypes.func,
|
||||||
|
sortedColumnKey: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
Search.defaultProps = {
|
||||||
|
onSearch: null,
|
||||||
|
sortedColumnKey: 'name'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Search;
|
||||||
3
src/components/Search/index.js
Normal file
3
src/components/Search/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import Search from './Search';
|
||||||
|
|
||||||
|
export default Search;
|
||||||
130
src/components/Sort/Sort.jsx
Normal file
130
src/components/Sort/Sort.jsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { I18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dropdown,
|
||||||
|
DropdownPosition,
|
||||||
|
DropdownToggle,
|
||||||
|
DropdownItem
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import {
|
||||||
|
SortAlphaDownIcon,
|
||||||
|
SortAlphaUpIcon,
|
||||||
|
SortNumericDownIcon,
|
||||||
|
SortNumericUpIcon
|
||||||
|
} from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
class Sort extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isSortDropdownOpen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleDropdownToggle = this.handleDropdownToggle.bind(this);
|
||||||
|
this.handleDropdownSelect = this.handleDropdownSelect.bind(this);
|
||||||
|
this.handleSort = this.handleSort.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDropdownToggle (isSortDropdownOpen) {
|
||||||
|
this.setState({ isSortDropdownOpen });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDropdownSelect ({ target }) {
|
||||||
|
const { columns, onSort, sortOrder } = this.props;
|
||||||
|
const { innerText } = target;
|
||||||
|
|
||||||
|
const [{ key: searchKey }] = columns.filter(({ name }) => name === innerText);
|
||||||
|
|
||||||
|
this.setState({ isSortDropdownOpen: false });
|
||||||
|
onSort(searchKey, sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSort () {
|
||||||
|
const { onSort, sortedColumnKey, sortOrder } = this.props;
|
||||||
|
const newSortOrder = sortOrder === 'ascending' ? 'descending' : 'ascending';
|
||||||
|
|
||||||
|
onSort(sortedColumnKey, newSortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { up } = DropdownPosition;
|
||||||
|
const {
|
||||||
|
columns,
|
||||||
|
sortedColumnKey,
|
||||||
|
sortOrder
|
||||||
|
} = this.props;
|
||||||
|
const {
|
||||||
|
isSortDropdownOpen
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const [{ name: sortedColumnName, isNumeric }] = columns
|
||||||
|
.filter(({ key }) => key === sortedColumnKey);
|
||||||
|
|
||||||
|
const sortDropdownItems = columns
|
||||||
|
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
|
||||||
|
.map(({ key, name }) => (
|
||||||
|
<DropdownItem key={key} component="button">
|
||||||
|
{name}
|
||||||
|
</DropdownItem>
|
||||||
|
));
|
||||||
|
|
||||||
|
let SortIcon;
|
||||||
|
if (isNumeric) {
|
||||||
|
SortIcon = sortOrder === 'ascending' ? SortNumericUpIcon : SortNumericDownIcon;
|
||||||
|
} else {
|
||||||
|
SortIcon = sortOrder === 'ascending' ? SortAlphaUpIcon : SortAlphaDownIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<I18n>
|
||||||
|
{({ i18n }) => (
|
||||||
|
<React.Fragment>
|
||||||
|
{ sortDropdownItems.length > 1 && (
|
||||||
|
<Dropdown
|
||||||
|
style={{ marginRight: '20px' }}
|
||||||
|
onToggle={this.handleDropdownToggle}
|
||||||
|
onSelect={this.handleDropdownSelect}
|
||||||
|
direction={up}
|
||||||
|
isOpen={isSortDropdownOpen}
|
||||||
|
toggle={(
|
||||||
|
<DropdownToggle
|
||||||
|
onToggle={this.handleDropdownToggle}
|
||||||
|
>
|
||||||
|
{sortedColumnName}
|
||||||
|
</DropdownToggle>
|
||||||
|
)}
|
||||||
|
dropdownItems={sortDropdownItems}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={this.handleSort}
|
||||||
|
variant="plain"
|
||||||
|
aria-label={i18n._(t`Sort`)}
|
||||||
|
>
|
||||||
|
<SortIcon />
|
||||||
|
</Button>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
</I18n>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sort.propTypes = {
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onSort: PropTypes.func,
|
||||||
|
sortOrder: PropTypes.string,
|
||||||
|
sortedColumnKey: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
Sort.defaultProps = {
|
||||||
|
onSort: null,
|
||||||
|
sortOrder: 'ascending',
|
||||||
|
sortedColumnKey: 'name'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sort;
|
||||||
3
src/components/Sort/index.js
Normal file
3
src/components/Sort/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import Sort from './Sort';
|
||||||
|
|
||||||
|
export default Sort;
|
||||||
@@ -305,125 +305,122 @@ class OrganizationAccessList extends React.Component {
|
|||||||
showWarning
|
showWarning
|
||||||
} = this.state;
|
} = this.state;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<I18n>
|
||||||
{!error && results.length <= 0 && (
|
{({ i18n }) => (
|
||||||
<h1>Loading...</h1> // TODO: replace with proper loading state
|
|
||||||
)}
|
|
||||||
{error && results.length <= 0 && (
|
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div>{error.message}</div>
|
{!error && results.length <= 0 && (
|
||||||
{error.response && (
|
<h1>Loading...</h1> // TODO: replace with proper loading state
|
||||||
<div>{error.response.data.detail}</div>
|
|
||||||
)}
|
)}
|
||||||
</Fragment> // TODO: replace with proper error handling
|
{error && results.length <= 0 && (
|
||||||
)}
|
<Fragment>
|
||||||
{results.length > 0 && (
|
<div>{error.message}</div>
|
||||||
<Fragment>
|
{error.response && (
|
||||||
<DataListToolbar
|
<div>{error.response.data.detail}</div>
|
||||||
sortedColumnKey={sortedColumnKey}
|
)}
|
||||||
sortOrder={sortOrder}
|
</Fragment> // TODO: replace with proper error handling
|
||||||
columns={this.columns}
|
|
||||||
onSearch={() => { }}
|
|
||||||
onSort={this.onSort}
|
|
||||||
onCompact={this.onCompact}
|
|
||||||
onExpand={this.onExpand}
|
|
||||||
isCompact={isCompact}
|
|
||||||
showExpandCollapse
|
|
||||||
/>
|
|
||||||
{showWarning && (
|
|
||||||
<Alert
|
|
||||||
variant="danger"
|
|
||||||
title={warningTitle}
|
|
||||||
action={<AlertActionCloseButton onClose={this.hideWarning} />}
|
|
||||||
>
|
|
||||||
{warningMsg}
|
|
||||||
<span className="awx-c-form-action-group">
|
|
||||||
<Button variant="danger" aria-label="confirm-delete" onClick={this.confirmDelete}>Delete</Button>
|
|
||||||
<Button variant="secondary" onClick={this.hideWarning}>Cancel</Button>
|
|
||||||
</span>
|
|
||||||
</Alert>
|
|
||||||
)}
|
)}
|
||||||
|
{results.length > 0 && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<I18n>
|
<DataListToolbar
|
||||||
{({ i18n }) => (
|
sortedColumnKey={sortedColumnKey}
|
||||||
<DataList aria-label={i18n._(t`Access List`)}>
|
sortOrder={sortOrder}
|
||||||
{results.map(result => (
|
columns={this.columns}
|
||||||
<DataListItem aria-labelledby={i18n._(t`access-list-item`)} key={result.id}>
|
onSearch={() => { }}
|
||||||
<DataListCell>
|
onSort={this.onSort}
|
||||||
<UserName
|
onCompact={this.onCompact}
|
||||||
value={result.username}
|
onExpand={this.onExpand}
|
||||||
url={result.url}
|
isCompact={isCompact}
|
||||||
/>
|
showExpandCollapse
|
||||||
{result.first_name || result.last_name ? (
|
/>
|
||||||
<Detail
|
{showWarning && (
|
||||||
label={i18n._(t`Name`)}
|
<Alert
|
||||||
value={`${result.first_name} ${result.last_name}`}
|
variant="danger"
|
||||||
url={null}
|
title={warningTitle}
|
||||||
customStyles={isCompact ? hiddenStyle : null}
|
action={<AlertActionCloseButton onClose={this.hideWarning} />}
|
||||||
/>
|
>
|
||||||
) : (
|
{warningMsg}
|
||||||
null
|
<span className="awx-c-form-action-group">
|
||||||
)}
|
<Button variant="danger" aria-label="confirm-delete" onClick={this.confirmDelete}>Delete</Button>
|
||||||
</DataListCell>
|
<Button variant="secondary" onClick={this.hideWarning}>Cancel</Button>
|
||||||
<DataListCell>
|
</span>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<DataList aria-label={i18n._(t`Access List`)}>
|
||||||
|
{results.map(result => (
|
||||||
|
<DataListItem aria-labelledby={i18n._(t`access-list-item`)} key={result.id}>
|
||||||
|
<DataListCell>
|
||||||
|
<UserName
|
||||||
|
value={result.username}
|
||||||
|
url={result.url}
|
||||||
|
/>
|
||||||
|
{result.first_name || result.last_name ? (
|
||||||
<Detail
|
<Detail
|
||||||
label=" "
|
label={i18n._(t`Name`)}
|
||||||
value=" "
|
value={`${result.first_name} ${result.last_name}`}
|
||||||
url={null}
|
url={null}
|
||||||
customStyles={isCompact ? hiddenStyle : null}
|
customStyles={isCompact ? hiddenStyle : null}
|
||||||
/>
|
/>
|
||||||
{result.userRoles.length > 0 && (
|
) : (
|
||||||
<ul style={isCompact
|
null
|
||||||
? { ...userRolesWrapperStyle, ...hiddenStyle }
|
)}
|
||||||
: userRolesWrapperStyle}
|
</DataListCell>
|
||||||
>
|
<DataListCell>
|
||||||
<Text component={TextVariants.h6} style={detailLabelStyle}>{i18n._(t`User Roles`)}</Text>
|
<Detail
|
||||||
{result.userRoles.map(role => (
|
label=" "
|
||||||
<Chip
|
value=" "
|
||||||
key={role.id}
|
url={null}
|
||||||
className="awx-c-chip"
|
customStyles={isCompact ? hiddenStyle : null}
|
||||||
onClick={() => this.handleWarning(role.name, role.id, result.username, result.id, 'users')}
|
/>
|
||||||
>
|
{result.userRoles.length > 0 && (
|
||||||
{role.name}
|
<ul style={isCompact
|
||||||
</Chip>
|
? { ...userRolesWrapperStyle, ...hiddenStyle }
|
||||||
))}
|
: userRolesWrapperStyle}
|
||||||
</ul>
|
>
|
||||||
)}
|
<Text component={TextVariants.h6} style={detailLabelStyle}>{i18n._(t`User Roles`)}</Text>
|
||||||
{result.teamRoles.length > 0 && (
|
{result.userRoles.map(role => (
|
||||||
<ul style={isCompact
|
<Chip
|
||||||
? { ...userRolesWrapperStyle, ...hiddenStyle }
|
key={role.id}
|
||||||
: userRolesWrapperStyle}
|
className="awx-c-chip"
|
||||||
>
|
onClick={() => this.handleWarning(role.name, role.id, result.username, result.id, 'users')}
|
||||||
<Text component={TextVariants.h6} style={detailLabelStyle}>{i18n._(t`Team Roles`)}</Text>
|
>
|
||||||
{result.teamRoles.map(role => (
|
{role.name}
|
||||||
<Chip
|
</Chip>
|
||||||
key={role.id}
|
))}
|
||||||
className="awx-c-chip"
|
</ul>
|
||||||
onClick={() => this.handleWarning(role.name, role.id, role.team_name, role.team_id, 'teams')}
|
)}
|
||||||
>
|
{result.teamRoles.length > 0 && (
|
||||||
{role.name}
|
<ul style={isCompact
|
||||||
</Chip>
|
? { ...userRolesWrapperStyle, ...hiddenStyle }
|
||||||
))}
|
: userRolesWrapperStyle}
|
||||||
</ul>
|
>
|
||||||
)}
|
<Text component={TextVariants.h6} style={detailLabelStyle}>{i18n._(t`Team Roles`)}</Text>
|
||||||
</DataListCell>
|
{result.teamRoles.map(role => (
|
||||||
</DataListItem>
|
<Chip
|
||||||
))}
|
key={role.id}
|
||||||
</DataList>
|
className="awx-c-chip"
|
||||||
)}
|
onClick={() => this.handleWarning(role.name, role.id, role.team_name, role.team_id, 'teams')}
|
||||||
</I18n>
|
>
|
||||||
</Fragment>
|
{role.name}
|
||||||
<Pagination
|
</Chip>
|
||||||
count={count}
|
))}
|
||||||
page={page}
|
</ul>
|
||||||
pageCount={pageCount}
|
)}
|
||||||
page_size={page_size}
|
</DataListCell>
|
||||||
onSetPage={this.onSetPage}
|
</DataListItem>
|
||||||
/>
|
))}
|
||||||
|
</DataList>
|
||||||
|
<Pagination
|
||||||
|
count={count}
|
||||||
|
page={page}
|
||||||
|
pageCount={pageCount}
|
||||||
|
page_size={page_size}
|
||||||
|
onSetPage={this.onSetPage}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</I18n>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user