Skip to content

Commit

Permalink
Merge pull request #944 from sharetribe/pricing-filter
Browse files Browse the repository at this point in the history
Pricing filter
  • Loading branch information
Gnito authored Oct 31, 2018
2 parents 2b8bbfa + e472dea commit 5db24d7
Show file tree
Hide file tree
Showing 42 changed files with 1,827 additions and 46 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ way to update this template, but currently, we follow a pattern:

---

## v.2.1.1 2018-10-23
## v2.2.0 2018-11-XX

* [add] SearchPage: adds PriceFilter (and RangeSlider, FieldRangeSlider, PriceFilterForm).

**Note:** You must define min and max for the filter in `src/marketplace-custom-config.js`.
Current maximum value for the range is set to 1000 (USD/EUR/currency units). In addition, this
fixes or removes component examples that don't work in StyleguidePage.

[#944](https://github.com/sharetribe/flex-template-web/pull/944)

## v2.1.1 2018-10-23

* [add] Added initial documentation about routing and loading data.
[#941](https://github.com/sharetribe/flex-template-web/pull/941)
Expand Down
15 changes: 10 additions & 5 deletions docs/search-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ filters rely on listing's indexed data.

## Filter types

The Flex template for web has two different filter types: _select single_ and _select multiple_. The
_select single_ one can be used to filter out search result with only one value per search
parameter. The _select multiple_ filters on the other hand can take multiple values for a single
search parameter.
The Flex template for web has three different filter types: _price filter_, _select single_ and
_select multiple_. The _price filter_ is for the specific case of filtering listings with a price
range.

These two filter types are implemented with four different components, a standard and a plain one:
> NOTE: price filter should be configured from `src/marketplace-custom-config.js`. Current maximum
> value for the range is set to 1000 (USD/EUR).
Other two filter types can be used with extended data. The _select single_ one can be used to filter
out search result with only one value per search parameter. The _select multiple_ filters on the
other hand can take multiple values for a single search parameter. These two filter types for
extended data are implemented with four different components, a standard and a plain one:

* Select single filter: `SelectSingleFilter` and `SelectSingleFilterPlain`
* Select multiple filter: `SelectMultipleFilter` and `SelectMultipleFilterPlain`
Expand Down
73 changes: 73 additions & 0 deletions src/components/FieldRangeSlider/FieldRangeSlider.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-disable no-console */
import React from 'react';
import { Form as FinalForm, FormSpy } from 'react-final-form';
import { Button } from '../../components';
import FieldRangeSlider from './FieldRangeSlider';

const formName = 'Styleguide.FieldRangeSlider.Form';

const FormComponent = props => (
<FinalForm
{...props}
formId={formName}
render={fieldRenderProps => {
const {
formId,
handleSubmit,
onChange,
invalid,
pristine,
submitting,
min,
max,
step,
handles,
} = fieldRenderProps;
const submitDisabled = invalid || pristine || submitting;

return (
<form
onSubmit={e => {
e.preventDefault();
handleSubmit(e);
}}
>
<FormSpy onChange={onChange} />

<FieldRangeSlider
id={`${formId}.range`}
name="range"
label="Select range"
min={min}
max={max}
step={step}
handles={handles}
/>

<Button style={{ marginTop: 24 }} type="submit" disabled={submitDisabled}>
Submit
</Button>
</form>
);
}}
/>
);

export const FieldRangeSliderForm = {
component: FormComponent,
props: {
min: 0,
max: 1000,
step: 5,
handles: [333, 666],
onChange: formState => {
if (formState.dirty) {
console.log('form values changed to:', formState.values);
}
},
onSubmit: values => {
console.log('submit values:', values);
},
},
group: 'custom inputs',
};
32 changes: 32 additions & 0 deletions src/components/FieldRangeSlider/FieldRangeSlider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { Field } from 'react-final-form';
import classNames from 'classnames';
import { RangeSlider } from '../../components';

const RangeSliderInput = props => {
const { input, handles, ...rest } = props;
const { value, ...inputProps } = input;

const currentHandles = Array.isArray(value) ? value : handles;
return <RangeSlider {...inputProps} {...rest} handles={currentHandles} />;
};

const FieldRangeSlider = props => {
const { rootClassName, className, id, label, ...rest } = props;

if (label && !id) {
throw new Error('id required when a label is given');
}

const inputProps = { id, ...rest };
const classes = classNames(rootClassName, className);

return (
<div className={classes}>
{label ? <label htmlFor={id}>{label}</label> : null}
<Field component={RangeSliderInput} {...inputProps} />
</div>
);
};

export default FieldRangeSlider;
3 changes: 3 additions & 0 deletions src/components/Footer/Footer.example.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.example {
overflow-x: hidden;
}
3 changes: 2 additions & 1 deletion src/components/Footer/Footer.example.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Footer from './Footer';
import css from './Footer.example.css';

export const Default = {
component: Footer,
props: {},
props: { className: css.example },
};
12 changes: 12 additions & 0 deletions src/components/ImageCarousel/ImageCarousel.example.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import '../../marketplace.css';

.root {
position: relative;
width: 100%;
height: 100%;
padding: 0;

@media (--viewportMedium) {
padding: 100px 10vw;
}
}
7 changes: 4 additions & 3 deletions src/components/ImageCarousel/ImageCarousel.example.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { types as sdkTypes } from '../../util/sdkLoader';
import ImageCarousel from './ImageCarousel';
import css from './ImageCarousel.example.css';

const { UUID } = sdkTypes;

Expand Down Expand Up @@ -122,15 +123,15 @@ const ImageCarouselWrapper = props => {

export const NoImages = {
component: ImageCarouselWrapper,
props: { images: [] },
props: { images: [], rootClassName: css.root },
};

export const SingleImage = {
component: ImageCarouselWrapper,
props: { images: [imageSquare] },
props: { images: [imageSquare], rootClassName: css.root },
};

export const MultipleImages = {
component: ImageCarouselWrapper,
props: { images: [imageLandscape, imagePortrait, imageSquare] },
props: { images: [imageLandscape, imagePortrait, imageSquare], rootClassName: css.root },
};
2 changes: 1 addition & 1 deletion src/components/Menu/Menu.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const MenuBasic = {

const MenuOnRight = () => {
return (
<div style={{ width: '50px', marginLeft: 'auto', marginRight: '36px' }}>
<div style={{ width: '68px', marginLeft: 'auto', marginRight: '36x' }}>
<MenuWrapper />
</div>
);
Expand Down
77 changes: 77 additions & 0 deletions src/components/PriceFilter/PriceFilter.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import { withRouter } from 'react-router-dom';
import { stringify, parse } from '../../util/urlHelpers';

import PriceFilter from './PriceFilter';

const URL_PARAM = 'pub_price';
const RADIX = 10;

// Helper for submitting example
const handleSubmit = (urlParam, values, history) => {
const { minPrice, maxPrice } = values || {};
const queryParams =
minPrice != null && maxPrice != null
? `?${stringify({ [urlParam]: [minPrice, maxPrice].join(',') })}`
: '';
history.push(`${window.location.pathname}${queryParams}`);
};

const PriceFilterWrapper = withRouter(props => {
const { history, location } = props;

const params = parse(location.search);
const price = params[URL_PARAM];
const valuesFromParams = !!price ? price.split(',').map(v => Number.parseInt(v, RADIX)) : [];
const initialValues = !!price
? {
minPrice: valuesFromParams[0],
maxPrice: valuesFromParams[1],
}
: null;

return (
<PriceFilter
{...props}
initialValues={initialValues}
onSubmit={(urlParam, values) => {
console.log('Submit PriceFilterForm with (unformatted) values:', values);
handleSubmit(urlParam, values, history);
}}
/>
);
});

export const PriceFilterPopup = {
component: PriceFilterWrapper,
props: {
id: 'PriceFilterPopupExample',
urlParam: URL_PARAM,
min: 0,
max: 1000,
step: 5,
liveEdit: false,
showAsPopup: true,
contentPlacementOffset: -14,
// initialValues: handled inside wrapper
// onSubmit: handled inside wrapper
},
group: 'misc',
};

export const PriceFilterPlain = {
component: PriceFilterWrapper,
props: {
id: 'PriceFilterPlainExample',
urlParam: URL_PARAM,
min: 0,
max: 1000,
step: 5,
liveEdit: true,
showAsPopup: false,
contentPlacementOffset: -14,
// initialValues: handled inside wrapper
// onSubmit: handled inside wrapper
},
group: 'misc',
};
19 changes: 19 additions & 0 deletions src/components/PriceFilter/PriceFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { bool } from 'prop-types';
import PriceFilterPlain from './PriceFilterPlain';
import PriceFilterPopup from './PriceFilterPopup';

const PriceFilter = props => {
const { showAsPopup, ...rest } = props;
return showAsPopup ? <PriceFilterPopup {...rest} /> : <PriceFilterPlain {...rest} />;
};

PriceFilter.defaultProps = {
showAsPopup: false,
};

PriceFilter.propTypes = {
showAsPopup: bool,
};

export default PriceFilter;
57 changes: 57 additions & 0 deletions src/components/PriceFilter/PriceFilterPlain.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@import '../../marketplace.css';

.root {
position: relative;
padding-top: 24px;
padding-bottom: 17px;
border-bottom: 1px solid var(--matterColorNegative);
}

.filterLabel,
.filterLabelSelected {
@apply --marketplaceH3FontStyles;

/* Baseline adjustment for label text */
margin-top: 0;
margin-bottom: 12px;
padding: 4px 0 2px 0;
}

.filterLabel {
color: var(--matterColorDark);
}

.filterLabelSelected {
color: var(--marketplaceColor);
}

.labelButton {
/* Override button styles */
outline: none;
text-align: left;
border: none;
padding: 0;
cursor: pointer;
}

.clearButton {
@apply --marketplaceH5FontStyles;
font-weight: var(--fontWeightMedium);
color: var(--matterColorAnti);

/* Layout */
display: inline;
float: right;
margin-top: 6px;
padding: 0;

/* Override button styles */
outline: none;
text-align: left;
border: none;

&:focus,
&:hover {
color: var(--matterColor);
}
}
Loading

0 comments on commit 5db24d7

Please sign in to comment.