diff --git a/packages/patternfly-3/patternfly-react/less/dual-list-selector.less b/packages/patternfly-3/patternfly-react/less/dual-list-selector.less index b0338a93b5b..d13a6a08ae5 100644 --- a/packages/patternfly-3/patternfly-react/less/dual-list-selector.less +++ b/packages/patternfly-3/patternfly-react/less/dual-list-selector.less @@ -117,12 +117,22 @@ color: white; } + &.disabled { + cursor: not-allowed; + background: @color-pf-black-150; + color: @color-pf-black-500; + + input[type='checkbox'] { + cursor: not-allowed; + } + } + &.child { padding-left: 22px; } &:hover { - &:not(.selected) { + &:not(.selected):not(.disabled) { background-color: @color-pf-blue-100; color: inherit; } diff --git a/packages/patternfly-3/patternfly-react/sass/patternfly-react/_dual-list-selector.scss b/packages/patternfly-3/patternfly-react/sass/patternfly-react/_dual-list-selector.scss index 04970f8754d..aeeb3657188 100644 --- a/packages/patternfly-3/patternfly-react/sass/patternfly-react/_dual-list-selector.scss +++ b/packages/patternfly-3/patternfly-react/sass/patternfly-react/_dual-list-selector.scss @@ -117,12 +117,22 @@ $dual-list-scroll-bar-length: 12px; color: white; } + &.disabled { + cursor: not-allowed; + background: $color-pf-black-150; + color: $color-pf-black-500; + + input[type='checkbox'] { + cursor: not-allowed; + } + } + &.child { padding-left: 22px; } &:hover { - &:not(.selected) { + &:not(.selected):not(.disabled) { background-color: $color-pf-blue-100; color: inherit; } diff --git a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualList.js b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualList.js index 00483a172bb..5aaca2735ac 100644 --- a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualList.js +++ b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualList.js @@ -16,12 +16,11 @@ import { isAllItemsChecked, isItemExistOnList, filterByHiding, - getItemsLength, - toggleFilterredItems, getFilterredItemsLength, makeAllItemsVisible, getSelectedFilterredItemsLength, - isItemSelected + isItemSelected, + getFilterredItems } from './helpers'; class DualList extends React.Component { @@ -81,11 +80,12 @@ class DualList extends React.Component { const items = cloneDeep(originalItems); let selectCount = originalSelectCount; if (filterTerm) { - toggleFilterredItems(items, checked); - selectCount += getFilterredItemsLength(items) * (checked ? 1 : -1); + const filterredItems = getFilterredItems(items); + const toggledAmount = toggleAllItems(filterredItems, checked); + selectCount += toggledAmount * (checked ? 1 : -1); } else { - toggleAllItems(items, checked); - selectCount = checked ? getItemsLength(items) : 0; + const toggledAmount = toggleAllItems(items, checked); + selectCount = checked ? selectCount + toggledAmount : 0; } this.props.onMainCheckboxChange({ side, diff --git a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualList.test.js b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualList.test.js index 6c3012bf830..35249381438 100644 --- a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualList.test.js +++ b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualList.test.js @@ -28,7 +28,9 @@ const getProps = () => ({ { value: 'Heather Davis', label: 'Heather Davis' } ] }, - right: { items: [{ value: 'Donald Trump', label: 'Donald Trump' }] } + right: { + items: [{ value: 'Donald Trump', label: 'Donald Trump' }] + } }); test('dual-list render properly ', () => { @@ -170,3 +172,16 @@ test('sorting works ! ', () => { sortingIcon.simulate('click', mockedEvent); expect(component.state().left.items[0]).toBe(originalList[originalList.length - 1]); }); + +test('item is disabled and tooltip exists', () => { + const props = getProps(); + props.right.items.push({ + value: 'Barack Obama', + label: 'Barack Obama', + disabled: true, + tooltipText: 'Barack Obama' + }); + const component = mount(); + expect(component.exists('DualListItemTooltip')).toBeTruthy(); + expect(component.exists('.disabled')).toBeTruthy(); +}); diff --git a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualListMocks.js b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualListMocks.js index cafb4dceced..8aa88f69fff 100644 --- a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualListMocks.js +++ b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/DualListMocks.js @@ -3,7 +3,7 @@ import { MenuItem } from '../../index'; export const items = { left: [ - { value: 'Ann Little', label: 'Ann Little' }, + { value: 'Ann Little', label: 'Ann Little', disabled: true, tooltipText: 'Permission Denied' }, { value: 'Daniel Nguyen', label: 'Daniel Nguyen' }, { value: 'Heather Davis', label: 'Heather Davis' }, { diff --git a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/components/DualListItem.js b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/components/DualListItem.js index ce031a1aaa0..4153320ed0e 100644 --- a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/components/DualListItem.js +++ b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/components/DualListItem.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import DualListItemTooltip from './DualListItemTooltip'; import { TypeAheadSelect } from '../../../components/TypeAheadSelect'; import { noop } from '../../../common/helpers'; @@ -16,10 +17,18 @@ const DualListItem = ({ filterTerm, onChange, side, - hidden + hidden, + disabled, + tooltipID, + tooltipText }) => { - const cx = classNames('dual-list-pf-item', className, checked && ' selected'); - return ( + const cx = classNames('dual-list-pf-item', className, checked && 'selected', disabled && 'disabled'); + const itemLabel = ( + + {label} + + ); + const item = ( ); + const getTooltipID = () => { + let uniqueTooltipID = `dual-list-item-tooltip-${side}`; + if (parentPosition) { + uniqueTooltipID += `-${parentPosition}`; + } + uniqueTooltipID += `-${position}`; + return uniqueTooltipID; + }; + return tooltipText ? ( + + {item} + + ) : ( + item + ); }; DualListItem.propTypes = { @@ -57,7 +80,13 @@ DualListItem.propTypes = { /** The side of the selector. */ side: PropTypes.string, /** Sets the item visibillity when filtering. */ - hidden: PropTypes.bool + hidden: PropTypes.bool, + /** Disable the item to move between lists */ + disabled: PropTypes.bool, + /** unique tooltip ID */ + tooltipID: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + /** text to be shown on the tooltip */ + tooltipText: PropTypes.string }; DualListItem.defaultProps = { @@ -70,7 +99,10 @@ DualListItem.defaultProps = { filterTerm: null, onChange: noop, side: null, - hidden: false + hidden: false, + disabled: false, + tooltipID: null, + tooltipText: null }; export default DualListItem; diff --git a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/components/DualListItemTooltip.js b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/components/DualListItemTooltip.js new file mode 100644 index 00000000000..904e137f68a --- /dev/null +++ b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/components/DualListItemTooltip.js @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { OverlayTrigger, Tooltip } from '../../../index'; + +const DualListItemTooltip = ({ id, text, children }) => { + const tooltip = {text}; + return ( + + {children} + + ); +}; + +DualListItemTooltip.propTypes = { + /** unique tooltip ID */ + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + /** text to be shown on the tooltip */ + text: PropTypes.string, + /** children nodes */ + children: PropTypes.node +}; + +DualListItemTooltip.defaultProps = { + id: null, + text: null, + children: null +}; + +export default DualListItemTooltip; diff --git a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/helpers.js b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/helpers.js index 45136e8de49..0043848eb4e 100644 --- a/packages/patternfly-3/patternfly-react/src/components/DualListSelector/helpers.js +++ b/packages/patternfly-3/patternfly-react/src/components/DualListSelector/helpers.js @@ -155,15 +155,29 @@ export const itemHasChildren = item => item.children !== undefined; export const getItemPosition = (array, position, isSortAsc) => (isSortAsc ? position : array.length - position - 1); export const toggleAllItems = (list, checked) => { + let toggleCount = 0; list.forEach(item => { - item.checked = checked; + if (item.disabled) { + return; + } + if (item.checked !== checked) { + item.checked = checked; + toggleCount += 1; + } if (itemHasChildren(item)) { + let childrenToggleCount = 0; item.children.forEach(childItem => { - childItem.checked = checked; + if (childItem.checked !== checked) { + childItem.checked = checked; + childrenToggleCount += 1; + } }); + if (childrenToggleCount > 0) { + toggleCount += childrenToggleCount - 1; + } } - return item; }); + return toggleCount; }; export const isAllItemsChecked = (items, selectCount) => selectCount > 0 && selectCount === getItemsLength(items); @@ -179,24 +193,7 @@ export const isItemExistOnList = (list, itemLabel) => { return { isParentExist: parentIndex !== null, parentIndex }; }; -export const toggleFilterredItems = (list, checked) => { - list.forEach(item => { - if (!isItemHidden(item)) { - item.checked = checked; - if (itemHasChildren(item)) { - toggleAllItems(item.children, checked); - } - } else if (itemHasChildren(item)) { - item.children.forEach(childItem => { - if (!isItemHidden(childItem)) { - item.checked = checked; - } - }); - } - }); -}; - -export const getFilteredItems = list => { +export const getFilterredItems = list => { const filteredItems = []; list.forEach(item => { if (!isItemHidden(item)) { @@ -217,10 +214,10 @@ export const getFilteredItems = list => { return filteredItems; }; -export const getFilterredItemsLength = list => getItemsLength(getFilteredItems(list)); +export const getFilterredItemsLength = list => getItemsLength(getFilterredItems(list)); export const getSelectedFilterredItemsLength = list => { - const filteredItems = getFilteredItems(list); + const filteredItems = getFilterredItems(list); let selectedAmount = 0; filteredItems.forEach(item => { if (isItemSelected(item)) { @@ -244,3 +241,5 @@ export const getSelectedFilterredItemsLength = list => { export const isItemSelected = item => item.checked; export const isItemHidden = item => item.hidden; + +export const isItemDisabled = item => item.disabled;