Skip to content

Commit

Permalink
feat: add item search to both grid and table views
Browse files Browse the repository at this point in the history
  • Loading branch information
codeofmochi committed Jul 5, 2021
1 parent cb8335c commit 51ae774
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 50 deletions.
99 changes: 99 additions & 0 deletions src/components/item/ItemSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import OutlinedInput from '@material-ui/core/OutlinedInput';
import { makeStyles } from '@material-ui/core/styles';
import SearchIcon from '@material-ui/icons/Search';
import PropTypes from 'prop-types';
import React, { useState } from 'react';

const useSearchStyles = makeStyles((theme) => ({
search: {
position: 'relative',
borderRadius: theme.shape.borderRadius,
marginLeft: 0,
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
width: 'auto',
},
},
searchIcon: {
padding: theme.spacing(0, 2),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
inputRoot: {
color: 'inherit',
},
inputInput: {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
width: '12ch',
'&:focus': {
width: '20ch',
},
},
},
}));

const ItemSearchInput = (props) => {
const { searchInputHandler, searchTextState } = props;
const classes = useSearchStyles();

return (
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<OutlinedInput
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
onChange={searchInputHandler}
value={searchTextState}
inputProps={{ 'aria-label': 'search' }}
/>
</div>
);
};

ItemSearchInput.propTypes = {
searchInputHandler: PropTypes.func,
searchTextState: PropTypes.string,
};

ItemSearchInput.defaultProps = {
searchInputHandler: () => {},
searchTextState: '',
};

const useItemSearch = (items) => {
const [searchText, setSearchText] = useState('');

const handleSearchInput = (event) => {
const text = event.target.value;
setSearchText(text.toLowerCase());
};

const searchResults = items.filter((it) =>
it.name.toLowerCase().includes(searchText),
);

const itemSearchInput = (
<ItemSearchInput
searchInputHandler={handleSearchInput}
searchTextState={searchText}
/>
);
return { searchResults, itemSearchInput };
};

export { useItemSearch, ItemSearchInput };
26 changes: 21 additions & 5 deletions src/components/main/Items.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import React, { useContext } from 'react';
import { List } from 'immutable';
import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import { ITEM_LAYOUT_MODES } from '../../enums';
import ItemsTable from './ItemsTable';
import ItemsGrid from './ItemsGrid';
import { LayoutContext } from '../context/LayoutContext';
import { useItemSearch } from '../item/ItemSearch';
import ItemsGrid from './ItemsGrid';
import ItemsTable from './ItemsTable';

const Items = ({ items, title, id }) => {
const { mode } = useContext(LayoutContext);
const { searchResults, itemSearchInput } = useItemSearch(items);

switch (mode) {
case ITEM_LAYOUT_MODES.GRID:
return <ItemsGrid id={id} title={title} items={items} />;
return (
<ItemsGrid
id={id}
title={title}
items={searchResults}
searchInput={itemSearchInput}
/>
);
case ITEM_LAYOUT_MODES.LIST:
default:
return <ItemsTable id={id} tableTitle={title} items={items} />;
return (
<ItemsTable
id={id}
tableTitle={title}
items={searchResults}
searchInput={itemSearchInput}
/>
);
}
};

Expand Down
39 changes: 24 additions & 15 deletions src/components/main/ItemsGrid.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import React, { Component } from 'react';
import Grid from '@material-ui/core/Grid';
import { withStyles } from '@material-ui/core/styles';
import { List } from 'immutable';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import NewItemButton from './NewItemButton';
import Item from './Item';
import { ItemSearchInput } from '../item/ItemSearch';
import EmptyItem from './EmptyItem';
import Item from './Item';
import TableToolbar from './TableToolbar';

const styles = (theme) => ({
empty: { padding: '5px 20px' },
title: {
display: 'flex',
alignItems: 'center',
marginBottom: theme.spacing(1),
},
});

class ItemsGrid extends Component {
static propTypes = {
items: PropTypes.instanceOf(List).isRequired,
Expand All @@ -24,15 +26,25 @@ class ItemsGrid extends Component {
}).isRequired,
classes: PropTypes.shape({
title: PropTypes.string.isRequired,
empty: PropTypes.string.isRequired,
}).isRequired,
title: PropTypes.string.isRequired,
searchInput: PropTypes.instanceOf(ItemSearchInput),
};

static defaultProps = {
searchInput: null,
};

renderItems = () => {
const { items } = this.props;
const { classes, items } = this.props;

if (!items || !items.size) {
return <EmptyItem />;
return (
<div className={classes.empty}>
<EmptyItem />
</div>
);
}

return items.map((item) => (
Expand All @@ -43,17 +55,14 @@ class ItemsGrid extends Component {
};

render() {
const { classes, title } = this.props;
const { title, searchInput } = this.props;
return (
<>
<Typography className={classes.title} variant="h4">
{title}
<NewItemButton />
</Typography>
<div>
<TableToolbar tableTitle={title} itemSearchInput={searchInput} />
<Grid container spacing={1}>
{this.renderItems()}
</Grid>
</>
</div>
);
}
}
Expand Down
55 changes: 32 additions & 23 deletions src/components/main/ItemsTable.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,42 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { List } from 'immutable';
import { MUTATION_KEYS } from '@graasp/query-client';
import Checkbox from '@material-ui/core/Checkbox';
import Paper from '@material-ui/core/Paper';
import { lighten, makeStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import { useHistory, useParams } from 'react-router';
import { useTranslation } from 'react-i18next';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TablePagination from '@material-ui/core/TablePagination';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import Checkbox from '@material-ui/core/Checkbox';
import { MUTATION_KEYS } from '@graasp/query-client';
import ItemMenu from './ItemMenu';
import { List } from 'immutable';
import PropTypes from 'prop-types';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router';
import { ROWS_PER_PAGE_OPTIONS, USER_ITEM_ORDER } from '../../config/constants';
import { buildItemPath } from '../../config/paths';
import { ORDERING, ITEM_DATA_TYPES, ITEM_TYPES } from '../../enums';
import { getComparator, stableSort, getRowsForPage } from '../../utils/table';
import { formatDate } from '../../utils/date';
import EditButton from '../common/EditButton';
import ShareButton from '../common/ShareButton';
import DeleteButton from '../common/DeleteButton';
import { hooks, useMutation } from '../../config/queryClient';
import {
buildItemsTableRowId,
ITEMS_TABLE_BODY,
ITEMS_TABLE_EMPTY_ROW_ID,
ITEMS_TABLE_ROW_CHECKBOX_CLASS,
} from '../../config/selectors';
import TableToolbar from './TableToolbar';
import TableHead from './TableHead';
import ItemIcon from './ItemIcon';
import { ITEM_DATA_TYPES, ITEM_TYPES, ORDERING } from '../../enums';
import { formatDate } from '../../utils/date';
import { getChildrenOrderFromFolderExtra } from '../../utils/item';
import { getShortcutTarget } from '../../utils/itemExtra';
import { ROWS_PER_PAGE_OPTIONS, USER_ITEM_ORDER } from '../../config/constants';
import DroppableTableBody from '../common/DroppableTableBody';
import { getComparator, getRowsForPage, stableSort } from '../../utils/table';
import DeleteButton from '../common/DeleteButton';
import DraggableTableRow from '../common/DraggableTableRow';
import { hooks, useMutation } from '../../config/queryClient';
import { getChildrenOrderFromFolderExtra } from '../../utils/item';
import DroppableTableBody from '../common/DroppableTableBody';
import EditButton from '../common/EditButton';
import ShareButton from '../common/ShareButton';
import { ItemSearchInput } from '../item/ItemSearch';
import ItemIcon from './ItemIcon';
import ItemMenu from './ItemMenu';
import TableHead from './TableHead';
import TableToolbar from './TableToolbar';

const { useItem } = hooks;

Expand Down Expand Up @@ -81,7 +82,12 @@ const computeReorderedIdList = (list, startIndex, endIndex) => {
return result.map((i) => i.id);
};

const ItemsTable = ({ items: rows, tableTitle, id: tableId }) => {
const ItemsTable = ({
items: rows,
tableTitle,
id: tableId,
searchInput: itemSearchInput,
}) => {
const { itemId } = useParams();
const { data: parentItem } = useItem(itemId);

Expand Down Expand Up @@ -279,6 +285,7 @@ const ItemsTable = ({ items: rows, tableTitle, id: tableId }) => {
tableTitle={tableTitle}
numSelected={selected.length}
selected={selected}
itemSearchInput={itemSearchInput}
/>
<TableContainer>
<Table
Expand Down Expand Up @@ -382,11 +389,13 @@ ItemsTable.propTypes = {
items: PropTypes.instanceOf(List),
tableTitle: PropTypes.string.isRequired,
id: PropTypes.string,
searchInput: PropTypes.instanceOf(ItemSearchInput),
};

ItemsTable.defaultProps = {
id: '',
items: List(),
searchInput: null,
};

export default ItemsTable;
19 changes: 12 additions & 7 deletions src/components/main/TableToolbar.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { makeStyles } from '@material-ui/core/styles';
import { useTranslation } from 'react-i18next';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import NewItemButton from './NewItemButton';
import DeleteButton from '../common/DeleteButton';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ITEMS_TABLE_DELETE_SELECTED_ITEMS_ID } from '../../config/selectors';
import DeleteButton from '../common/DeleteButton';
import { ItemSearchInput } from '../item/ItemSearch';
import NewItemButton from './NewItemButton';

const useToolbarStyles = makeStyles((theme) => ({
root: {
Expand All @@ -28,7 +29,7 @@ const useToolbarStyles = makeStyles((theme) => ({
const TableToolbar = (props) => {
const classes = useToolbarStyles();
const { t } = useTranslation();
const { numSelected, tableTitle, selected } = props;
const { numSelected, tableTitle, selected, itemSearchInput } = props;

return (
<Toolbar
Expand Down Expand Up @@ -57,6 +58,8 @@ const TableToolbar = (props) => {
</Typography>
)}

{itemSearchInput}

{numSelected > 0 ? (
<DeleteButton
id={ITEMS_TABLE_DELETE_SELECTED_ITEMS_ID}
Expand All @@ -72,10 +75,12 @@ TableToolbar.propTypes = {
numSelected: PropTypes.number.isRequired,
tableTitle: PropTypes.string,
selected: PropTypes.arrayOf(PropTypes.shape({}).isRequired).isRequired,
itemSearchInput: PropTypes.instanceOf(ItemSearchInput),
};

TableToolbar.defaultProps = {
tableTitle: 'Items',
itemSearchInput: null,
};

export default TableToolbar;

0 comments on commit 51ae774

Please sign in to comment.