mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 15:21:13 +03:00
fix credential chips in SelectedList, MultiCredential cleanup
This commit is contained in:
parent
4341d67fb0
commit
639b297027
@ -36,12 +36,12 @@ class AnsibleSelect extends React.Component {
|
||||
aria-label={i18n._(t`Select Input`)}
|
||||
isValid={isValid}
|
||||
>
|
||||
{data.map(datum => (
|
||||
{data.map(option => (
|
||||
<FormSelectOption
|
||||
key={datum.key}
|
||||
value={datum.value}
|
||||
label={datum.label}
|
||||
isDisabled={datum.isDisabled}
|
||||
key={option.id}
|
||||
value={option.value}
|
||||
label={option.label}
|
||||
isDisabled={option.isDisabled}
|
||||
/>
|
||||
))}
|
||||
</FormSelect>
|
||||
@ -49,6 +49,13 @@ class AnsibleSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const Option = shape({
|
||||
id: oneOfType([string, number]).isRequired,
|
||||
value: oneOfType([string, number]).isRequired,
|
||||
label: string.isRequired,
|
||||
isDisabled: bool,
|
||||
});
|
||||
|
||||
AnsibleSelect.defaultProps = {
|
||||
data: [],
|
||||
isValid: true,
|
||||
@ -56,7 +63,7 @@ AnsibleSelect.defaultProps = {
|
||||
};
|
||||
|
||||
AnsibleSelect.propTypes = {
|
||||
data: arrayOf(shape()),
|
||||
data: arrayOf(Option),
|
||||
id: string.isRequired,
|
||||
isValid: bool,
|
||||
onBlur: func,
|
||||
|
@ -1,331 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import {
|
||||
string,
|
||||
bool,
|
||||
arrayOf,
|
||||
func,
|
||||
number,
|
||||
oneOfType,
|
||||
shape,
|
||||
} from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { SearchIcon } from '@patternfly/react-icons';
|
||||
import {
|
||||
Button,
|
||||
ButtonVariant,
|
||||
InputGroup as PFInputGroup,
|
||||
Modal,
|
||||
ToolbarItem,
|
||||
} from '@patternfly/react-core';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import AnsibleSelect from '../AnsibleSelect';
|
||||
import PaginatedDataList from '../PaginatedDataList';
|
||||
import VerticalSeperator from '../VerticalSeparator';
|
||||
import DataListToolbar from '../DataListToolbar';
|
||||
import CheckboxListItem from '../CheckboxListItem';
|
||||
import SelectedList from '../SelectedList';
|
||||
import { ChipGroup, CredentialChip } from '../Chip';
|
||||
import { QSConfig } from '@types';
|
||||
|
||||
const SearchButton = styled(Button)`
|
||||
::after {
|
||||
border: var(--pf-c-button--BorderWidth) solid
|
||||
var(--pf-global--BorderColor--200);
|
||||
}
|
||||
`;
|
||||
|
||||
const InputGroup = styled(PFInputGroup)`
|
||||
${props =>
|
||||
props.multiple &&
|
||||
`
|
||||
--pf-c-form-control--Height: 90px;
|
||||
overflow-y: auto;
|
||||
`}
|
||||
`;
|
||||
|
||||
const ChipHolder = styled.div`
|
||||
--pf-c-form-control--BorderTopColor: var(--pf-global--BorderColor--200);
|
||||
--pf-c-form-control--BorderRightColor: var(--pf-global--BorderColor--200);
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
`;
|
||||
|
||||
class CategoryLookup extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// this.assertCorrectValueType();
|
||||
let selectedItems = [];
|
||||
if (props.value) {
|
||||
selectedItems = props.multiple ? [...props.value] : [props.value];
|
||||
}
|
||||
this.state = {
|
||||
isModalOpen: false,
|
||||
selectedItems,
|
||||
error: null,
|
||||
};
|
||||
this.handleModalToggle = this.handleModalToggle.bind(this);
|
||||
this.addItem = this.addItem.bind(this);
|
||||
this.removeItem = this.removeItem.bind(this);
|
||||
this.saveModal = this.saveModal.bind(this);
|
||||
this.clearQSParams = this.clearQSParams.bind(this);
|
||||
}
|
||||
|
||||
// assertCorrectValueType() {
|
||||
// const { multiple, value, selectCategoryOptions } = this.props;
|
||||
// if (selectCategoryOptions) {
|
||||
// return;
|
||||
// }
|
||||
// if (!multiple && Array.isArray(value)) {
|
||||
// throw new Error(
|
||||
// 'CategoryLookup value must not be an array unless `multiple` is set'
|
||||
// );
|
||||
// }
|
||||
// if (multiple && !Array.isArray(value)) {
|
||||
// throw new Error(
|
||||
// 'CategoryLookup value must be an array if `multiple` is set'
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
removeItem(row) {
|
||||
const { selectedItems } = this.state;
|
||||
const { onToggleItem } = this.props;
|
||||
if (onToggleItem) {
|
||||
this.setState({ selectedItems: onToggleItem(selectedItems, row) });
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
selectedItems: selectedItems.filter(item => item.id !== row.id),
|
||||
});
|
||||
}
|
||||
|
||||
addItem(row) {
|
||||
const { selectedItems } = this.state;
|
||||
const { multiple, onToggleItem } = this.props;
|
||||
if (onToggleItem) {
|
||||
this.setState({ selectedItems: onToggleItem(selectedItems, row) });
|
||||
return;
|
||||
}
|
||||
const index = selectedItems.findIndex(item => item.id === row.id);
|
||||
|
||||
if (!multiple) {
|
||||
this.setState({ selectedItems: [row] });
|
||||
return;
|
||||
}
|
||||
if (index > -1) {
|
||||
return;
|
||||
}
|
||||
this.setState({ selectedItems: [...selectedItems, row] });
|
||||
}
|
||||
|
||||
// TODO: clean up
|
||||
handleModalToggle() {
|
||||
const { isModalOpen } = this.state;
|
||||
const { value, multiple, selectCategory } = this.props;
|
||||
// Resets the selected items from parent state whenever modal is opened
|
||||
// This handles the case where the user closes/cancels the modal and
|
||||
// opens it again
|
||||
if (!isModalOpen) {
|
||||
let selectedItems = [];
|
||||
if (value) {
|
||||
selectedItems = multiple ? [...value] : [value];
|
||||
}
|
||||
this.setState({ selectedItems });
|
||||
} else {
|
||||
this.clearQSParams();
|
||||
if (selectCategory) {
|
||||
selectCategory(null, 'Machine');
|
||||
}
|
||||
}
|
||||
this.setState(prevState => ({
|
||||
isModalOpen: !prevState.isModalOpen,
|
||||
}));
|
||||
}
|
||||
|
||||
removeItemAndSave(row) {
|
||||
const { value, onChange, multiple } = this.props;
|
||||
if (multiple) {
|
||||
onChange(value.filter(item => item.id !== row.id));
|
||||
} else if (value.id === row.id) {
|
||||
onChange(null);
|
||||
}
|
||||
}
|
||||
|
||||
saveModal() {
|
||||
const { onChange, multiple } = this.props;
|
||||
const { selectedItems } = this.state;
|
||||
const value = multiple ? selectedItems : selectedItems[0] || null;
|
||||
|
||||
this.handleModalToggle();
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
clearQSParams() {
|
||||
const { qsConfig, history } = this.props;
|
||||
const parts = history.location.search.replace(/^\?/, '').split('&');
|
||||
const ns = qsConfig.namespace;
|
||||
const otherParts = parts.filter(param => !param.startsWith(`${ns}.`));
|
||||
history.push(`${history.location.pathname}?${otherParts.join('&')}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isModalOpen, selectedItems, error } = this.state;
|
||||
const {
|
||||
id,
|
||||
items,
|
||||
count,
|
||||
lookupHeader,
|
||||
value,
|
||||
columns,
|
||||
multiple,
|
||||
name,
|
||||
onBlur,
|
||||
qsConfig,
|
||||
required,
|
||||
selectCategory,
|
||||
selectCategoryOptions,
|
||||
selectedCategory,
|
||||
i18n,
|
||||
} = this.props;
|
||||
const header = lookupHeader || i18n._(t`Items`);
|
||||
const canDelete = !required || (multiple && value.length > 1);
|
||||
return (
|
||||
<Fragment>
|
||||
<InputGroup onBlur={onBlur}>
|
||||
<SearchButton
|
||||
aria-label="Search"
|
||||
id={id}
|
||||
onClick={this.handleModalToggle}
|
||||
variant={ButtonVariant.tertiary}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
<ChipHolder className="pf-c-form-control">
|
||||
<ChipGroup>
|
||||
{(multiple ? value : [value]).map(chip => (
|
||||
<CredentialChip
|
||||
key={chip.id}
|
||||
onClick={() => this.removeItemAndSave(chip)}
|
||||
isReadOnly={!canDelete}
|
||||
credential={chip}
|
||||
/>
|
||||
))}
|
||||
</ChipGroup>
|
||||
</ChipHolder>
|
||||
</InputGroup>
|
||||
<Modal
|
||||
className="awx-c-modal"
|
||||
title={i18n._(t`Select ${header}`)}
|
||||
isOpen={isModalOpen}
|
||||
onClose={this.handleModalToggle}
|
||||
actions={[
|
||||
<Button
|
||||
key="select"
|
||||
variant="primary"
|
||||
onClick={this.saveModal}
|
||||
style={selectedItems.length === 0 ? { display: 'none' } : {}}
|
||||
>
|
||||
{i18n._(t`Select`)}
|
||||
</Button>,
|
||||
<Button
|
||||
key="cancel"
|
||||
variant="secondary"
|
||||
onClick={this.handleModalToggle}
|
||||
>
|
||||
{i18n._(t`Cancel`)}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
{selectCategoryOptions && selectCategoryOptions.length > 0 && (
|
||||
<ToolbarItem css=" display: flex; align-items: center;">
|
||||
<span css="flex: 0 0 25%;">Selected Category</span>
|
||||
<VerticalSeperator />
|
||||
<AnsibleSelect
|
||||
css="flex: 1 1 75%;"
|
||||
id="multiCredentialsLookUp-select"
|
||||
label="Selected Category"
|
||||
data={selectCategoryOptions}
|
||||
value={selectedCategory.label}
|
||||
onChange={selectCategory}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
)}
|
||||
{selectedItems.length > 0 && (
|
||||
<SelectedList
|
||||
label={i18n._(t`Selected`)}
|
||||
selected={selectedItems}
|
||||
showOverflowAfter={5}
|
||||
onRemove={this.removeItem}
|
||||
isReadOnly={!canDelete}
|
||||
isCredentialList={
|
||||
selectCategoryOptions && selectCategoryOptions.length > 0
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<PaginatedDataList
|
||||
items={items}
|
||||
itemCount={count}
|
||||
pluralizedItemName={lookupHeader}
|
||||
qsConfig={qsConfig}
|
||||
toolbarColumns={columns}
|
||||
renderItem={item => (
|
||||
<CheckboxListItem
|
||||
key={item.id}
|
||||
itemId={item.id}
|
||||
name={multiple ? item.name : name}
|
||||
label={item.name}
|
||||
isSelected={selectedItems.some(i => i.id === item.id)}
|
||||
onSelect={() => this.addItem(item)}
|
||||
isRadio={
|
||||
!multiple ||
|
||||
(selectCategoryOptions &&
|
||||
selectCategoryOptions.length &&
|
||||
selectedCategory.value !== 'Vault')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
||||
showPageSizeOptions={false}
|
||||
/>
|
||||
{error ? <div>error: {error.message}</div> : ''}
|
||||
</Modal>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Item = shape({
|
||||
id: number.isRequired,
|
||||
});
|
||||
|
||||
CategoryLookup.propTypes = {
|
||||
id: string,
|
||||
items: arrayOf(shape({})).isRequired,
|
||||
// TODO: change to `header`
|
||||
lookupHeader: string,
|
||||
name: string,
|
||||
onChange: func.isRequired,
|
||||
value: oneOfType([Item, arrayOf(Item)]),
|
||||
multiple: bool,
|
||||
required: bool,
|
||||
qsConfig: QSConfig.isRequired,
|
||||
selectCategory: func.isRequired,
|
||||
selectCategoryOptions: oneOfType(shape({})).isRequired,
|
||||
selectedCategory: shape({}).isRequired,
|
||||
};
|
||||
|
||||
CategoryLookup.defaultProps = {
|
||||
id: 'lookup-search',
|
||||
lookupHeader: null,
|
||||
name: null,
|
||||
value: null,
|
||||
multiple: false,
|
||||
required: false,
|
||||
};
|
||||
|
||||
export { CategoryLookup as _CategoryLookup };
|
||||
export default withI18n()(withRouter(CategoryLookup));
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { Fragment, useState, useEffect } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
@ -12,7 +12,6 @@ import VerticalSeperator from '@components/VerticalSeparator';
|
||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||
import Lookup from './NewLookup';
|
||||
import SelectList from './shared/SelectList';
|
||||
import multiCredentialReducer from './shared/multiCredentialReducer';
|
||||
|
||||
const QS_CONFIG = getQSConfig('credentials', {
|
||||
page: 1,
|
||||
@ -20,50 +19,10 @@ const QS_CONFIG = getQSConfig('credentials', {
|
||||
order_by: 'name',
|
||||
});
|
||||
|
||||
// TODO: move into reducer
|
||||
function toggleCredentialSelection(credentialsToUpdate, newCredential) {
|
||||
let newCredentialsList;
|
||||
const isSelectedCredentialInState =
|
||||
credentialsToUpdate.filter(cred => cred.id === newCredential.id).length > 0;
|
||||
|
||||
if (isSelectedCredentialInState) {
|
||||
newCredentialsList = credentialsToUpdate.filter(
|
||||
cred => cred.id !== newCredential.id
|
||||
);
|
||||
} else {
|
||||
newCredentialsList = credentialsToUpdate.filter(
|
||||
credential =>
|
||||
credential.kind === 'vault' || credential.kind !== newCredential.kind
|
||||
);
|
||||
newCredentialsList = [...newCredentialsList, newCredential];
|
||||
}
|
||||
return newCredentialsList;
|
||||
}
|
||||
|
||||
async function loadCredentialTypes() {
|
||||
const { data } = await CredentialTypesAPI.read();
|
||||
const acceptableTypes = ['machine', 'cloud', 'net', 'ssh', 'vault'];
|
||||
const credentialTypes = [];
|
||||
// TODO: cleanup
|
||||
data.results.forEach(cred => {
|
||||
acceptableTypes.forEach(aT => {
|
||||
if (aT === cred.kind) {
|
||||
// This object has several repeated values as some of it's children
|
||||
// require different field values.
|
||||
cred = {
|
||||
id: cred.id,
|
||||
key: cred.id,
|
||||
kind: cred.kind,
|
||||
type: cred.namespace,
|
||||
value: cred.name,
|
||||
label: cred.name,
|
||||
isDisabled: false,
|
||||
};
|
||||
credentialTypes.push(cred);
|
||||
}
|
||||
});
|
||||
});
|
||||
return credentialTypes;
|
||||
return data.results.filter(type => acceptableTypes.includes(type.kind));
|
||||
}
|
||||
|
||||
async function loadCredentials(params, selectedCredentialTypeId) {
|
||||
@ -78,13 +37,16 @@ function MultiCredentialsLookup(props) {
|
||||
const [selectedType, setSelectedType] = useState(null);
|
||||
const [credentials, setCredentials] = useState([]);
|
||||
const [credentialsCount, setCredentialsCount] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const types = await loadCredentialTypes();
|
||||
setCredentialTypes(types);
|
||||
setSelectedType(types[0]);
|
||||
setSelectedType(
|
||||
types.find(type => type.name === 'Machine') || types[0]
|
||||
);
|
||||
} catch (err) {
|
||||
onError(err);
|
||||
}
|
||||
@ -98,10 +60,12 @@ function MultiCredentialsLookup(props) {
|
||||
}
|
||||
try {
|
||||
const params = parseQueryString(QS_CONFIG, history.location.search);
|
||||
setIsLoading(true);
|
||||
const { results, count } = await loadCredentials(
|
||||
params,
|
||||
selectedType.id
|
||||
);
|
||||
setIsLoading(false);
|
||||
setCredentials(results);
|
||||
setCredentialsCount(count);
|
||||
} catch (err) {
|
||||
@ -111,29 +75,29 @@ function MultiCredentialsLookup(props) {
|
||||
}, [selectedType]);
|
||||
|
||||
const isMultiple = selectedType && selectedType.value === 'Vault';
|
||||
const renderChip = ({ item, removeItem, canDelete }) => (
|
||||
<CredentialChip
|
||||
key={item.id}
|
||||
onClick={() => removeItem(item)}
|
||||
isReadOnly={!canDelete}
|
||||
credential={item}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
|
||||
{tooltip && <FieldTooltip content={tooltip} />}
|
||||
<Lookup
|
||||
reducer={multiCredentialReducer}
|
||||
onToggleItem={toggleCredentialSelection}
|
||||
id="multiCredential"
|
||||
lookupHeader={i18n._(t`Credentials`)}
|
||||
value={value}
|
||||
multiple
|
||||
onChange={onChange}
|
||||
qsConfig={QS_CONFIG}
|
||||
renderItemChip={({ item, removeItem, canDelete }) => (
|
||||
<CredentialChip
|
||||
key={item.id}
|
||||
onClick={() => removeItem(item)}
|
||||
isReadOnly={!canDelete}
|
||||
credential={item}
|
||||
/>
|
||||
)}
|
||||
renderItemChip={renderChip}
|
||||
renderSelectList={({ state, dispatch, canDelete }) => {
|
||||
return (
|
||||
<>
|
||||
<Fragment>
|
||||
{credentialTypes && credentialTypes.length > 0 && (
|
||||
<ToolbarItem css=" display: flex; align-items: center;">
|
||||
<div css="flex: 0 0 25%;">{i18n._(t`Selected Category`)}</div>
|
||||
@ -142,11 +106,16 @@ function MultiCredentialsLookup(props) {
|
||||
css="flex: 1 1 75%;"
|
||||
id="multiCredentialsLookUp-select"
|
||||
label={i18n._(t`Selected Category`)}
|
||||
data={credentialTypes}
|
||||
value={selectedType && selectedType.label}
|
||||
onChange={(e, label) => {
|
||||
data={credentialTypes.map(type => ({
|
||||
id: type.id,
|
||||
value: type.id,
|
||||
label: type.name,
|
||||
isDisabled: false,
|
||||
}))}
|
||||
value={selectedType && selectedType.id}
|
||||
onChange={(e, id) => {
|
||||
setSelectedType(
|
||||
credentialTypes.find(o => o.label === label)
|
||||
credentialTypes.find(o => o.id === parseInt(id, 10))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@ -183,8 +152,9 @@ function MultiCredentialsLookup(props) {
|
||||
});
|
||||
}}
|
||||
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
|
||||
renderItemChip={renderChip}
|
||||
/>
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
@ -50,13 +50,9 @@ const ChipHolder = styled.div`
|
||||
function Lookup(props) {
|
||||
const {
|
||||
id,
|
||||
// items,
|
||||
// count,
|
||||
header,
|
||||
// name,
|
||||
onChange,
|
||||
onBlur,
|
||||
// columns,
|
||||
value,
|
||||
multiple,
|
||||
required,
|
||||
|
@ -1,5 +1,4 @@
|
||||
export { default } from './Lookup';
|
||||
export { default as CategoryLookup } from './CategoryLookup';
|
||||
export { default as InstanceGroupsLookup } from './InstanceGroupsLookup';
|
||||
export { default as InventoryLookup } from './InventoryLookup';
|
||||
export { default as ProjectLookup } from './ProjectLookup';
|
||||
|
@ -28,6 +28,7 @@ function SelectList({
|
||||
readOnly,
|
||||
selectItem,
|
||||
deselectItem,
|
||||
renderItemChip,
|
||||
i18n,
|
||||
}) {
|
||||
return (
|
||||
@ -39,6 +40,7 @@ function SelectList({
|
||||
showOverflowAfter={5}
|
||||
onRemove={item => deselectItem(item)}
|
||||
isReadOnly={readOnly}
|
||||
renderItemChip={renderItemChip}
|
||||
/>
|
||||
)}
|
||||
<PaginatedDataList
|
||||
@ -78,9 +80,11 @@ SelectList.propTypes = {
|
||||
qsConfig: QSConfig.isRequired,
|
||||
selectItem: func.isRequired,
|
||||
deselectItem: func.isRequired,
|
||||
renderItemChip: func,
|
||||
};
|
||||
SelectList.defaultProps = {
|
||||
multiple: false,
|
||||
renderItemChip: null,
|
||||
};
|
||||
|
||||
export default withI18n()(SelectList);
|
||||
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Split as PFSplit, SplitItem } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
import { ChipGroup, Chip, CredentialChip } from '../Chip';
|
||||
import { ChipGroup, Chip } from '../Chip';
|
||||
import VerticalSeparator from '../VerticalSeparator';
|
||||
|
||||
const Split = styled(PFSplit)`
|
||||
@ -26,35 +26,31 @@ class SelectedList extends Component {
|
||||
onRemove,
|
||||
displayKey,
|
||||
isReadOnly,
|
||||
isCredentialList,
|
||||
renderItemChip,
|
||||
} = this.props;
|
||||
// TODO: replace isCredentialList with renderChip ?
|
||||
const chips = isCredentialList
|
||||
? selected.map(item => (
|
||||
<CredentialChip
|
||||
key={item.id}
|
||||
isReadOnly={isReadOnly}
|
||||
onClick={() => onRemove(item)}
|
||||
credential={item}
|
||||
>
|
||||
{item[displayKey]}
|
||||
</CredentialChip>
|
||||
))
|
||||
: selected.map(item => (
|
||||
<Chip
|
||||
key={item.id}
|
||||
isReadOnly={isReadOnly}
|
||||
onClick={() => onRemove(item)}
|
||||
>
|
||||
{item[displayKey]}
|
||||
</Chip>
|
||||
));
|
||||
|
||||
const renderChip =
|
||||
renderItemChip ||
|
||||
(({ item, removeItem }) => (
|
||||
<Chip key={item.id} onClick={removeItem} isReadOnly={isReadOnly}>
|
||||
{item[displayKey]}
|
||||
</Chip>
|
||||
));
|
||||
|
||||
return (
|
||||
<Split>
|
||||
<SplitLabelItem>{label}</SplitLabelItem>
|
||||
<VerticalSeparator />
|
||||
<SplitItem>
|
||||
<ChipGroup numChips={5}>{chips}</ChipGroup>
|
||||
<ChipGroup numChips={5}>
|
||||
{selected.map(item =>
|
||||
renderChip({
|
||||
item,
|
||||
removeItem: () => onRemove(item),
|
||||
canDelete: !isReadOnly,
|
||||
})
|
||||
)}
|
||||
</ChipGroup>
|
||||
</SplitItem>
|
||||
</Split>
|
||||
);
|
||||
@ -67,7 +63,7 @@ SelectedList.propTypes = {
|
||||
onRemove: PropTypes.func,
|
||||
selected: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isReadOnly: PropTypes.bool,
|
||||
isCredentialList: PropTypes.bool,
|
||||
renderItemChip: PropTypes.func,
|
||||
};
|
||||
|
||||
SelectedList.defaultProps = {
|
||||
@ -75,7 +71,7 @@ SelectedList.defaultProps = {
|
||||
label: 'Selected',
|
||||
onRemove: () => null,
|
||||
isReadOnly: false,
|
||||
isCredentialList: false,
|
||||
renderItemChip: null,
|
||||
};
|
||||
|
||||
export default SelectedList;
|
||||
|
Loading…
Reference in New Issue
Block a user