mirror of
https://github.com/ansible/awx.git
synced 2026-03-06 03:01:06 -03:30
finish usePFSelect hook
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
export { default } from './MultiSelect';
|
export { default } from './MultiSelect';
|
||||||
export { default as TagMultiSelect } from './TagMultiSelect';
|
export { default as TagMultiSelect } from './TagMultiSelect';
|
||||||
|
export { default as usePFSelect } from './usePFSelect';
|
||||||
|
|||||||
51
awx/ui_next/src/components/MultiSelect/usePFSelect.js
Normal file
51
awx/ui_next/src/components/MultiSelect/usePFSelect.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hook for using PatternFly's <Select> component. Guarantees object equality
|
||||||
|
between objects in `value` and the corresponding objects loaded as
|
||||||
|
`options` (based on matched id value).
|
||||||
|
*/
|
||||||
|
export default function usePFSelect(value, onChange) {
|
||||||
|
const [options, setOptions] = useState([]);
|
||||||
|
const [selections, setSelections] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value !== selections && options.length) {
|
||||||
|
const syncedValue = value.map(item =>
|
||||||
|
options.find(i => i.id === item.id)
|
||||||
|
);
|
||||||
|
setSelections(syncedValue);
|
||||||
|
}
|
||||||
|
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
||||||
|
}, [value, options]);
|
||||||
|
|
||||||
|
const onSelect = (event, item) => {
|
||||||
|
if (selections.includes(item)) {
|
||||||
|
onChange(selections.filter(i => i !== item));
|
||||||
|
} else {
|
||||||
|
onChange(selections.concat(item));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
selections: options.length ? addToStringToObjects(selections) : [],
|
||||||
|
onSelect,
|
||||||
|
options,
|
||||||
|
setOptions: newOpts => setOptions(addToStringToObjects(newOpts)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PF uses toString to generate React keys. This is used to ensure
|
||||||
|
all objects in the array have a toString method.
|
||||||
|
*/
|
||||||
|
function addToStringToObjects(items = []) {
|
||||||
|
items.forEach(item => {
|
||||||
|
item.toString = toString;
|
||||||
|
});
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toString() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
@@ -2,40 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { func, arrayOf, number, shape, string, oneOfType } from 'prop-types';
|
import { func, arrayOf, number, shape, string, oneOfType } from 'prop-types';
|
||||||
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
||||||
import { LabelsAPI } from '@api';
|
import { LabelsAPI } from '@api';
|
||||||
|
import usePFSelect from '@components/MultiSelect/usePFSelect';
|
||||||
function setToString(labels) {
|
|
||||||
labels.forEach(label => {
|
|
||||||
label.toString = function toString() {
|
|
||||||
return this.id;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: extract to shared file
|
|
||||||
function usePFSelect(value, options, onChange) {
|
|
||||||
const [currentValue, setCurrentValue] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (value !== currentValue && options.length) {
|
|
||||||
const syncedValue = value.map(item =>
|
|
||||||
options.find(i => i.id === item.id)
|
|
||||||
);
|
|
||||||
setCurrentValue(syncedValue);
|
|
||||||
}
|
|
||||||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
|
||||||
}, [value, options]);
|
|
||||||
|
|
||||||
const handleSelect = (event, item) => {
|
|
||||||
if (currentValue.includes(item)) {
|
|
||||||
onChange(currentValue.filter(i => i.id !== item.id));
|
|
||||||
} else {
|
|
||||||
onChange(currentValue.concat(item));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [currentValue, handleSelect];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadLabelOptions(setLabels, onError) {
|
async function loadLabelOptions(setLabels, onError) {
|
||||||
let labels;
|
let labels;
|
||||||
@@ -45,7 +12,7 @@ async function loadLabelOptions(setLabels, onError) {
|
|||||||
page_size: 200,
|
page_size: 200,
|
||||||
order_by: 'name',
|
order_by: 'name',
|
||||||
});
|
});
|
||||||
labels = setToString(data.results);
|
labels = data.results;
|
||||||
setLabels(labels);
|
setLabels(labels);
|
||||||
if (data.next && data.next.includes('page=2')) {
|
if (data.next && data.next.includes('page=2')) {
|
||||||
const {
|
const {
|
||||||
@@ -55,7 +22,7 @@ async function loadLabelOptions(setLabels, onError) {
|
|||||||
page_size: 200,
|
page_size: 200,
|
||||||
order_by: 'name',
|
order_by: 'name',
|
||||||
});
|
});
|
||||||
setLabels(labels.concat(setToString(results)));
|
setLabels(labels.concat(results));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
onError(err);
|
onError(err);
|
||||||
@@ -63,8 +30,10 @@ async function loadLabelOptions(setLabels, onError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function LabelSelect({ value, placeholder, onChange, onError }) {
|
function LabelSelect({ value, placeholder, onChange, onError }) {
|
||||||
const [options, setOptions] = useState([]);
|
const { selections, onSelect, options, setOptions } = usePFSelect(
|
||||||
const [currentValue, handleSelect] = usePFSelect(value, options, onChange);
|
value,
|
||||||
|
onChange
|
||||||
|
);
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
const toggleExpanded = () => {
|
const toggleExpanded = () => {
|
||||||
@@ -73,7 +42,8 @@ function LabelSelect({ value, placeholder, onChange, onError }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadLabelOptions(setOptions, onError);
|
loadLabelOptions(setOptions, onError);
|
||||||
}, [onError]);
|
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
||||||
|
}, []);
|
||||||
|
|
||||||
const renderOptions = opts => {
|
const renderOptions = opts => {
|
||||||
return opts.map(option => (
|
return opts.map(option => (
|
||||||
@@ -88,14 +58,14 @@ function LabelSelect({ value, placeholder, onChange, onError }) {
|
|||||||
variant={SelectVariant.typeaheadMulti}
|
variant={SelectVariant.typeaheadMulti}
|
||||||
aria-label="Select a state"
|
aria-label="Select a state"
|
||||||
onToggle={toggleExpanded}
|
onToggle={toggleExpanded}
|
||||||
onSelect={handleSelect}
|
onSelect={onSelect}
|
||||||
onClear={() => onChange([])}
|
onClear={() => onChange([])}
|
||||||
onFilter={event => {
|
onFilter={event => {
|
||||||
const str = event.target.value.toLowerCase();
|
const str = event.target.value.toLowerCase();
|
||||||
const matches = options.filter(o => o.name.toLowerCase().includes(str));
|
const matches = options.filter(o => o.name.toLowerCase().includes(str));
|
||||||
return renderOptions(matches);
|
return renderOptions(matches);
|
||||||
}}
|
}}
|
||||||
selections={options.length ? setToString(currentValue) : []}
|
selections={selections}
|
||||||
isExpanded={isExpanded}
|
isExpanded={isExpanded}
|
||||||
ariaLabelledBy="label-select"
|
ariaLabelledBy="label-select"
|
||||||
placeholderText={placeholder}
|
placeholderText={placeholder}
|
||||||
|
|||||||
Reference in New Issue
Block a user