Mobile-friendly search filters (#37)
* Remove affixed filters. Replace with responsive drawer filters. * Fix ts error
This commit is contained in:
parent
0e3c6c6fc0
commit
e4779d1410
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue