Skip to content

Commit dcd034b

Browse files
committed
feat: add TagFilterSelector componenet
1 parent f25e5e3 commit dcd034b

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

ui/src/tag/TagFilterSelector.tsx

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import React from 'react';
2+
import Downshift from 'downshift';
3+
import {makeStyles, Theme} from '@material-ui/core/styles';
4+
import TextField from '@material-ui/core/TextField';
5+
import Paper from '@material-ui/core/Paper';
6+
import MenuItem from '@material-ui/core/MenuItem';
7+
import Chip from '@material-ui/core/Chip';
8+
import {useQuery} from '@apollo/react-hooks';
9+
import {Tags} from '../gql/__generated__/Tags';
10+
import {TagSelectorEntry, label} from './tagSelectorEntry';
11+
import * as gqlTags from '../gql/tags';
12+
import {useSuggest} from './suggest';
13+
14+
const useStyles = makeStyles((theme: Theme) => ({
15+
root: {
16+
flexGrow: 1,
17+
height: 250,
18+
},
19+
container: {
20+
flexGrow: 1,
21+
position: 'relative',
22+
},
23+
paper: {
24+
position: 'absolute',
25+
zIndex: 1,
26+
marginTop: theme.spacing(1),
27+
left: 0,
28+
right: 0,
29+
},
30+
chip: {
31+
margin: theme.spacing(0.5, 0.25),
32+
},
33+
inputRoot: {
34+
flexWrap: 'wrap',
35+
},
36+
inputInput: {
37+
width: 'auto',
38+
flexGrow: 1,
39+
},
40+
}));
41+
42+
interface TagFilterSelectorProps {
43+
value: TagSelectorEntry[];
44+
type: string;
45+
onChange: (entries: TagSelectorEntry[]) => void;
46+
disabled?: boolean;
47+
}
48+
49+
export const TagFilterSelector: React.FC<TagFilterSelectorProps> = ({value: selectedItem, type, onChange, disabled = false}) => {
50+
const classes = useStyles();
51+
const [inputValue, setInputValue] = React.useState('');
52+
53+
const tagsResult = useQuery<Tags>(gqlTags.Tags);
54+
const suggestions = useSuggest(tagsResult, inputValue, [])
55+
.filter((t) => !t.tag.create && !t.tag.alreadyUsed)
56+
.reverse();
57+
58+
if (tagsResult.error || tagsResult.loading || !tagsResult.data || !tagsResult.data.tags) {
59+
return null;
60+
}
61+
62+
function handleKeyDown(event: React.KeyboardEvent) {
63+
if (selectedItem.length && !inputValue.length && event.key === 'Backspace') {
64+
onChange(selectedItem.slice(0, selectedItem.length - 1));
65+
}
66+
}
67+
68+
function handleInputChange(event: React.ChangeEvent<{value: string}>) {
69+
setInputValue(event.target.value);
70+
}
71+
72+
function handleChange(item: TagSelectorEntry) {
73+
if (!item.value) {
74+
setInputValue(item.tag.key + ':');
75+
return;
76+
}
77+
let newSelectedItem = [...selectedItem];
78+
if (newSelectedItem.indexOf(item) === -1) {
79+
newSelectedItem = [...newSelectedItem, item];
80+
}
81+
setInputValue('');
82+
onChange(newSelectedItem);
83+
}
84+
85+
const handleDelete = (item: TagSelectorEntry) => () => {
86+
const newSelectedItem = [...selectedItem];
87+
newSelectedItem.splice(newSelectedItem.indexOf(item), 1);
88+
onChange(newSelectedItem);
89+
};
90+
91+
return (
92+
<Downshift
93+
id="downshift-multiple"
94+
inputValue={inputValue}
95+
onChange={handleChange}
96+
itemToString={(item) => (item ? label(item) : '')}
97+
defaultIsOpen={false}>
98+
{({getInputProps, getItemProps, getLabelProps, isOpen, highlightedIndex}) => {
99+
const {onBlur, onChange: downshiftOnChange, onFocus, ...inputProps} = getInputProps({
100+
onKeyDown: handleKeyDown,
101+
placeholder: `Select ${type} Tags`,
102+
});
103+
return (
104+
<div className={classes.container}>
105+
<TextField
106+
InputLabelProps={getLabelProps()}
107+
required={true}
108+
disabled={disabled}
109+
InputProps={{
110+
classes: {
111+
root: classes.inputRoot,
112+
input: classes.inputInput,
113+
},
114+
startAdornment: selectedItem.map((item) => (
115+
<Chip
116+
key={label(item)}
117+
tabIndex={-1}
118+
label={label(item)}
119+
className={classes.chip}
120+
onDelete={disabled ? undefined : handleDelete(item)}
121+
/>
122+
)),
123+
onBlur,
124+
onChange: (event) => {
125+
handleInputChange(event);
126+
downshiftOnChange!(event as React.ChangeEvent<HTMLInputElement>);
127+
},
128+
onFocus,
129+
}}
130+
label={`${type} Tags`}
131+
fullWidth
132+
inputProps={inputProps}
133+
/>
134+
{isOpen ? (
135+
<Paper className={classes.paper} square>
136+
{suggestions.map((suggestion, index) => (
137+
<MenuItem
138+
{...getItemProps({item: suggestion})}
139+
key={label(suggestion)}
140+
selected={highlightedIndex === index}
141+
component="div">
142+
{label(suggestion)}
143+
</MenuItem>
144+
))}
145+
</Paper>
146+
) : null}
147+
</div>
148+
);
149+
}}
150+
</Downshift>
151+
);
152+
};

0 commit comments

Comments
 (0)