Mobile-friendly search filters (#37)

* Remove affixed filters. Replace with responsive drawer filters.

* Fix ts error
This commit is contained in:
William O'Beirne 2018-09-13 17:49:44 -04:00 committed by Daniel Ternyak
parent 0e3c6c6fc0
commit e4779d1410
3 changed files with 124 additions and 26 deletions

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Select, Checkbox, Radio, Card, Divider, Affix } from 'antd'; import { Select, Checkbox, Radio, Card, Divider } from 'antd';
import { RadioChangeEvent } from 'antd/lib/radio'; import { RadioChangeEvent } from 'antd/lib/radio';
import { import {
PROPOSAL_SORT, PROPOSAL_SORT,
@ -27,7 +27,7 @@ export default class ProposalFilters extends React.Component<Props> {
const { sort, filters, handleChangeSort } = this.props; const { sort, filters, handleChangeSort } = this.props;
return ( return (
<Affix offsetTop={20}> <div>
<Card title="Sort"> <Card title="Sort">
<Select onChange={handleChangeSort} value={sort} style={{ width: '100%' }}> <Select onChange={handleChangeSort} value={sort} style={{ width: '100%' }}>
{Object.keys(PROPOSAL_SORT).map((s: PROPOSAL_SORT) => ( {Object.keys(PROPOSAL_SORT).map((s: PROPOSAL_SORT) => (
@ -70,7 +70,7 @@ export default class ProposalFilters extends React.Component<Props> {
</div> </div>
))} ))}
</Card> </Card>
</Affix> </div>
); );
} }

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import DocumentTitle from 'react-document-title';
import { compose } from 'recompose'; import { compose } from 'recompose';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { proposalActions } from 'modules/proposals'; import { proposalActions } from 'modules/proposals';
@ -7,11 +6,12 @@ import { getProposals } from 'modules/proposals/selectors';
import { ProposalWithCrowdFund } from 'modules/proposals/reducers'; import { ProposalWithCrowdFund } from 'modules/proposals/reducers';
import { bindActionCreators, Dispatch } from 'redux'; import { bindActionCreators, Dispatch } from 'redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { Input, Row, Col, Divider, Spin } from 'antd'; import { Input, Divider, Spin, Drawer, Icon, Button } from 'antd';
import ProposalResults from './Results'; import ProposalResults from './Results';
import ProposalFilters, { Filters } from './Filters'; import ProposalFilters, { Filters } from './Filters';
import { PROPOSAL_SORT } from 'api/constants'; import { PROPOSAL_SORT } from 'api/constants';
import Web3Container from 'lib/Web3Container'; import Web3Container from 'lib/Web3Container';
import * as Styled from './styled';
type ProposalSortFn = (p1: ProposalWithCrowdFund, p2: ProposalWithCrowdFund) => number; type ProposalSortFn = (p1: ProposalWithCrowdFund, p2: ProposalWithCrowdFund) => number;
const sortFunctions: { [key in PROPOSAL_SORT]: ProposalSortFn } = { const sortFunctions: { [key in PROPOSAL_SORT]: ProposalSortFn } = {
@ -64,6 +64,8 @@ interface State {
searchQuery: string; searchQuery: string;
sort: PROPOSAL_SORT; sort: PROPOSAL_SORT;
filters: Filters; filters: Filters;
isFiltersDrawered: boolean;
isDrawerShowing: boolean;
} }
class Proposals extends React.Component<Props, State> { class Proposals extends React.Component<Props, State> {
@ -113,42 +115,84 @@ class Proposals extends React.Component<Props, State> {
categories: [], categories: [],
stage: null, stage: null,
}, },
isFiltersDrawered: false,
isDrawerShowing: false,
}; };
componentDidMount() { componentDidMount() {
this.props.fetchProposals(); this.props.fetchProposals();
window.addEventListener('resize', this.handleResize);
this.handleResize();
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
} }
render() { render() {
const { proposalsError, isFetchingProposals } = this.props; const { proposalsError, isFetchingProposals } = this.props;
const { processedProposals, sort, filters } = this.state; const {
processedProposals,
sort,
filters,
isFiltersDrawered,
isDrawerShowing,
} = this.state;
const filtersComponent = (
<ProposalFilters
sort={sort}
filters={filters}
handleChangeSort={this.handleChangeSort}
handleChangeFilters={this.handleChangeFilters}
/>
);
return ( return (
<DocumentTitle title="Proposals"> <Styled.Container>
<Row gutter={40}> {isFiltersDrawered ? (
<Col lg={5} md={24}> <Drawer
<ProposalFilters placement="right"
sort={sort} visible={isDrawerShowing}
filters={filters} onClose={this.closeFilterDrawer}
handleChangeSort={this.handleChangeSort} closable={false}
handleChangeFilters={this.handleChangeFilters} width={300}
/> >
</Col> {filtersComponent}
<Button
type="primary"
onClick={this.closeFilterDrawer}
style={{ marginTop: '1rem' }}
block
>
Done
</Button>
</Drawer>
) : (
<Styled.Filters>{filtersComponent}</Styled.Filters>
)}
<Col lg={19} md={24}> <Styled.Results>
<Styled.Search>
<Input.Search <Input.Search
placeholder="Search for a proposal" placeholder="Search for a proposal"
onChange={this.handleChangeSearch} onChange={this.handleChangeSearch}
size="large" size="large"
/> />
<Divider /> <Styled.SearchFilterButton
<ProposalResults type="primary"
proposals={processedProposals} size="large"
proposalsError={proposalsError} onClick={this.openFilterDrawer}
isFetchingProposals={isFetchingProposals} >
/> <Icon type="filter" /> Filters
</Col> </Styled.SearchFilterButton>
</Row> </Styled.Search>
</DocumentTitle> <Divider />
<ProposalResults
proposals={processedProposals}
proposalsError={proposalsError}
isFetchingProposals={isFetchingProposals}
/>
</Styled.Results>
</Styled.Container>
); );
} }
@ -166,6 +210,23 @@ class Proposals extends React.Component<Props, State> {
private handleChangeFilters = (filters: Filters) => { private handleChangeFilters = (filters: Filters) => {
this.setState({ filters }); this.setState({ filters });
}; };
private handleResize = () => {
if (this.state.isFiltersDrawered && window.innerWidth > 640) {
this.setState({
isFiltersDrawered: false,
isDrawerShowing: false,
});
} else if (!this.state.isFiltersDrawered && window.innerWidth <= 640) {
this.setState({
isFiltersDrawered: true,
isDrawerShowing: false,
});
}
};
private openFilterDrawer = () => this.setState({ isDrawerShowing: true });
private closeFilterDrawer = () => this.setState({ isDrawerShowing: false });
} }
function mapStateToProps(state: AppState) { function mapStateToProps(state: AppState) {

View File

@ -0,0 +1,37 @@
import styled from 'styled-components';
import { Button } from 'antd';
const smallQuery = '@media (max-width: 640px)';
export const Container = styled.div`
display: flex;
flex-direction: row;
`;
export const Filters = styled.div`
width: 220px;
margin-right: 3rem;
${smallQuery} {
display: none;
}
`;
export const Results = styled.div`
flex: 1;
width: 100%;
`;
export const Search = styled.div`
display: flex;
`;
// Typescript throws errors if we don't cast as Button.
export const SearchFilterButton: typeof Button = styled(Button)`
display: none;
margin-left: 0.5rem;
${smallQuery} {
display: block;
}
` as any;