diff options
Diffstat (limited to 'webui')
-rw-r--r-- | webui/src/__tests__/query.js | 4 | ||||
-rw-r--r-- | webui/src/list/Filter.js | 77 | ||||
-rw-r--r-- | webui/src/list/FilterToolbar.js | 17 | ||||
-rw-r--r-- | webui/src/list/ListQuery.js | 21 |
4 files changed, 97 insertions, 22 deletions
diff --git a/webui/src/__tests__/query.js b/webui/src/__tests__/query.js index 1415af02..5f4b58eb 100644 --- a/webui/src/__tests__/query.js +++ b/webui/src/__tests__/query.js @@ -7,9 +7,9 @@ it('parses a simple query', () => { }); it('parses a query with multiple filters', () => { - expect(parse('foo:bar baz:foobar')).toEqual({ + expect(parse('foo:bar baz:foo-bar')).toEqual({ foo: ['bar'], - baz: ['foobar'], + baz: ['foo-bar'], }); }); diff --git a/webui/src/list/Filter.js b/webui/src/list/Filter.js index c93b2d35..a6cf3633 100644 --- a/webui/src/list/Filter.js +++ b/webui/src/list/Filter.js @@ -1,13 +1,16 @@ -import React from 'react'; +import React, { useState, useRef } from 'react'; import { Link } from 'react-router-dom'; import { makeStyles } from '@material-ui/styles'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import ArrowDropDown from '@material-ui/icons/ArrowDropDown'; function parse(query) { // TODO: extract the rest of the query? const params = {}; // TODO: support escaping without quotes - const re = /(\w+):(\w+|(["'])(([^\3]|\\.)*)\3)+/g; + const re = /(\w+):([A-Za-z0-9-]+|(["'])(([^\3]|\\.)*)\3)+/g; let matches; while ((matches = re.exec(query)) !== null) { if (!params[matches[1]]) { @@ -58,20 +61,63 @@ const useStyles = makeStyles(theme => ({ ...theme.typography.body2, color: ({ active }) => (active ? '#333' : '#444'), padding: theme.spacing(0, 1), - fontWeight: ({ active }) => (active ? 500 : 400), + fontWeight: ({ active }) => (active ? 600 : 400), textDecoration: 'none', display: 'flex', - alignSelf: ({ end }) => (end ? 'flex-end' : 'auto'), background: 'none', border: 'none', }, + itemActive: { + fontWeight: 600, + }, icon: { paddingRight: theme.spacing(0.5), }, })); -function Filter({ active, to, children, icon: Icon, end, ...props }) { - const classes = useStyles({ active, end }); +function Dropdown({ children, dropdown, itemActive, to, ...props }) { + const [open, setOpen] = useState(false); + const buttonRef = useRef(); + const classes = useStyles(); + + return ( + <> + <button ref={buttonRef} onClick={() => setOpen(!open)} {...props}> + {children} + <ArrowDropDown fontSize="small" /> + </button> + <Menu + getContentAnchorEl={null} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'left', + }} + open={open} + onClose={() => setOpen(false)} + anchorEl={buttonRef.current} + > + {dropdown.map(([key, value]) => ( + <MenuItem + component={Link} + to={to(key)} + className={itemActive(key) ? classes.itemActive : null} + onClick={() => setOpen(false)} + key={key} + > + {value} + </MenuItem> + ))} + </Menu> + </> + ); +} + +function Filter({ active, to, children, icon: Icon, dropdown, ...props }) { + const classes = useStyles({ active }); const content = ( <> @@ -80,6 +126,19 @@ function Filter({ active, to, children, icon: Icon, end, ...props }) { </> ); + if (dropdown) { + return ( + <Dropdown + {...props} + to={to} + dropdown={dropdown} + className={classes.element} + > + {content} + </Dropdown> + ); + } + if (to) { return ( <Link to={to} {...props} className={classes.element}> @@ -88,11 +147,7 @@ function Filter({ active, to, children, icon: Icon, end, ...props }) { ); } - return ( - <button {...props} className={classes.element}> - {content} - </button> - ); + return <div className={classes.element}>{content}</div>; } export default Filter; diff --git a/webui/src/list/FilterToolbar.js b/webui/src/list/FilterToolbar.js index e6d6f4ed..9f5f14c5 100644 --- a/webui/src/list/FilterToolbar.js +++ b/webui/src/list/FilterToolbar.js @@ -32,7 +32,7 @@ function FilterToolbar({ query, queryLocation }) { }; // TODO: open/closed count - // TODO: author/label/sort filters + // TODO: author/label filters return ( <Toolbar className={classes.toolbar}> <Filter @@ -52,7 +52,20 @@ function FilterToolbar({ query, queryLocation }) { <div className={classes.spacer} /> <Filter active={hasKey('author')}>Author</Filter> <Filter active={hasKey('label')}>Label</Filter> - <Filter active={hasKey('sort')}>Sort</Filter> + <Filter + dropdown={[ + ['id', 'ID'], + ['creation', 'Newest'], + ['creation-asc', 'Oldest'], + ['edit', 'Recently updated'], + ['edit-asc', 'Least recently updated'], + ]} + active={hasKey('sort')} + itemActive={key => hasValue('sort', key)} + to={key => replaceParam('sort', key)} + > + Sort + </Filter> </Toolbar> ); } diff --git a/webui/src/list/ListQuery.js b/webui/src/list/ListQuery.js index b6a29702..01113f6c 100644 --- a/webui/src/list/ListQuery.js +++ b/webui/src/list/ListQuery.js @@ -45,11 +45,13 @@ const useStyles = makeStyles(theme => ({ borderWidth: '1px', backgroundColor: fade(theme.palette.primary.main, 0.05), padding: theme.spacing(0, 1), - ':focus': { - // TODO - borderColor: fade(theme.palette.primary.main, 0.4), - backgroundColor: theme.palette.background.paper, - }, + width: ({ searching }) => (searching ? '20rem' : '15rem'), + transition: theme.transitions.create(), + }, + searchFocused: { + borderColor: fade(theme.palette.primary.main, 0.4), + backgroundColor: theme.palette.background.paper, + width: '20rem!important', }, placeholderRow: { padding: theme.spacing(1), @@ -182,7 +184,6 @@ const Error = ({ error }) => { }; function ListQuery() { - const classes = useStyles(); const location = useLocation(); const history = useHistory(); const params = new URLSearchParams(location.search); @@ -190,6 +191,8 @@ function ListQuery() { const [input, setInput] = useState(query); + const classes = useStyles({ searching: !!input }); + // TODO is this the right way to do it? const lastQuery = useRef(); useEffect(() => { @@ -291,9 +294,13 @@ function ListQuery() { <h1>Issues</h1> <form onSubmit={formSubmit}> <InputBase + placeholder="Filter" value={input} onInput={e => setInput(e.target.value)} - className={classes.search} + classes={{ + root: classes.search, + focused: classes.searchFocused, + }} /> <button type="submit" hidden> Search |