Filter modal implementation

- Orderby picker
- Radius picker
- Checkbox styling in ui-theme
- handlers for cancel and apply
This commit is contained in:
Bart Akeley 2017-11-12 13:06:57 -06:00
parent e15c5b4df6
commit acd4015848
8 changed files with 196 additions and 37 deletions

20
js/components/CheckBox.js Normal file
View file

@ -0,0 +1,20 @@
// @flow
import React from 'react';
import { TouchableOpacity } from 'react-native';
import { Icon } from 'react-native-material-ui';
type Props = {
checked?: boolean,
onChange?: (checked: boolean) => void,
style?: { [string]: string | number },
};
const CheckBox = ({ checked, onChange, style }: Props) => {
return (
<TouchableOpacity onPress={() => onChange && onChange(!checked)}>
<Icon glyph={checked ? 'checkbox-marked' : 'checkbox-blank-outline'} style={style} />
</TouchableOpacity>
);
};
export default CheckBox;

View file

@ -1,49 +1,114 @@
// @flow
import React from 'react';
import { View, TouchableOpacity, Text } from 'react-native';
import CheckBox from 'react-native-checkbox';
// import CheckBox from 'react-native-checkbox';
import { Checkbox } from 'react-native-material-ui';
import Modal from './Modal';
// import { Icon } from 'react-native-material-ui';
import { withFilter } from '../enhancers/filterEnhancers';
import typeof FilterRecord from '../records/FilterRecord';
import { CATEGORIES, type Category } from '../constants/CategoryConstants';
import { getCategoryText } from '../helpers/CategoryHelpers';
// import { Set } from 'immutable';
import OrderbyPicker, { type Orderby } from './OrderbyPicker';
import RadiusPicker from './RadiusPicker';
import { compose, withHandlers, withState, onlyUpdateForKeys } from 'recompose';
import theme from '../ui-theme';
const { palette } = theme;
type Props = {
isVisible: boolean,
onClose: () => void,
filter: FilterRecord,
setFilter: (f: FilterRecord) => void,
currentFilter: FilterRecord,
setCurrentFilter: (currentFilter: FilterRecord) => void,
toggleCategory: (category: Category, isChecked: boolean) => () => void,
applyChanges: () => void,
updateOrderby: (orderby: Orderby) => void,
updateRadius: (radius: number) => void,
};
const FilterModal = withFilter(({ isVisible, onClose, filter, setFilter }: Props) => {
const { orderby, categories, radius } = filter;
const toggleCategory = category => checked => {
setFilter(
filter.update('categories', categories => {
return checked ? categories.delete(category) : categories.add(category);
})
);
};
const FilterModal = (props: Props) => {
const {
isVisible,
onClose,
currentFilter: { orderby, categories, radius },
toggleCategory,
applyChanges,
updateOrderby,
updateRadius,
} = props;
return (
<Modal isVisible={isVisible}>
<TouchableOpacity onPress={onClose}>
<Text style={{ fontSize: 30, fontWeight: 'bold' }}>Filters</Text>
{CATEGORIES.map((category: Category) => (
<CheckBox
key={category}
style={{ margin: 20 }}
checked={categories.has(category)}
onChange={toggleCategory(category)}
label={getCategoryText(category)}
<Text style={{ marginLeft: 20, marginTop: 15, fontSize: 20, fontWeight: 'bold' }}>Filters</Text>
<View style={{ padding: 15, flexDirection: 'column', justifyContent: 'space-around', height: 400 }}>
{CATEGORIES.map((category: Category) => {
const isChecked = categories.has(category);
return (
<View key={category} style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text style={{ fontSize: 15 }}>{getCategoryText(category)}</Text>
<View style={{ width: 40 }}>
<Checkbox
value={isChecked ? 0 : 1}
checked={isChecked}
onCheck={toggleCategory(category, isChecked)}
/>
</View>
</View>
);
})}
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ fontSize: 15 }}>Sort By</Text>
<OrderbyPicker
selected={orderby}
onValueChange={updateOrderby}
style={{ height: 40, width: 150 }}
/>
))}
</TouchableOpacity>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ fontSize: 15 }}>Search Radius</Text>
<RadiusPicker selected={radius} onValueChange={updateRadius} style={{ height: 40, width: 150 }} />
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end' }}>
<View style={{ width: 100, flexDirection: 'row', justifyContent: 'space-between' }}>
<TouchableOpacity onPress={onClose}>
<Text style={{ color: palette.accentColor }}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity onPress={applyChanges}>
<Text style={{ color: palette.accentColor }}>Apply</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
);
});
};
export default FilterModal;
export default compose(
withFilter,
withState('currentFilter', 'setCurrentFilter', (props: Props) => props.filter),
withHandlers({
toggleCategory: ({ currentFilter, setCurrentFilter }: Props) => (
category: Category,
isChecked: boolean
) => () => {
setCurrentFilter(
currentFilter.update('categories', categories => {
return isChecked ? categories.delete(category) : categories.add(category);
})
);
},
updateOrderby: ({ currentFilter, setCurrentFilter }: Props) => (orderby: Orderby) => {
setCurrentFilter(currentFilter.set('orderby', orderby));
},
updateRadius: ({ currentFilter, setCurrentFilter }: Props) => (radius: number) => {
setCurrentFilter(currentFilter.set('radius', radius));
},
applyChanges: ({ onClose, currentFilter, setFilter }: Props) => () => {
setFilter(currentFilter);
onClose();
},
}),
onlyUpdateForKeys(['isVisible', 'currentFilter'])
)(FilterModal);

View file

@ -6,13 +6,11 @@ import { Icon } from 'react-native-material-ui';
type Props = { glyph: string, text: string, route: string, onPress: Function, color?: string };
export default pure(({ glyph, text, color = 'grey', ...others }: Props) =>
export default pure(({ glyph, text, color = 'grey', ...others }: Props) => (
<TouchableOpacity {...others}>
<View style={{ flexDirection: 'row', marginTop: 5 }}>
<Icon name={glyph} size={35} color={color} style={{ marginRight: 10 }} />
<Text style={{ lineHeight: 26 }}>
{text}
</Text>
<Text style={{ lineHeight: 26 }}>{text}</Text>
</View>
</TouchableOpacity>
);
));

View file

@ -8,7 +8,7 @@ import { Icon } from 'react-native-material-ui';
export default pure(({ children, isVisible }) => {
return (
<Modal isVisible={isVisible} backdropColor="black" backdropOpacity={1.0}>
<Modal isVisible={isVisible} backdropColor="black" backdropOpacity={0.7}>
<View
style={{
flexDirection: 'column',

View file

@ -0,0 +1,42 @@
// @flow
import React from 'react';
import { Picker, View } from 'react-native';
import theme from '../ui-theme';
import { pure } from 'recompose';
import debounce from '../helpers/debounce';
const { picker: { color: selectedColor } } = theme;
const defaultColor = 'black';
const getItemColor = (selected, current) => (selected === current ? selectedColor : defaultColor);
export type Orderby = 'distance' | 'lastupdated' | 'quantity';
export const ORDER_BY = ['distance', 'lastupdated', 'quantity'];
export const getOrderbyText = (orderby: Orderby) => {
switch (orderby) {
case 'quantity':
return 'Quantity';
case 'lastupdated':
return 'Most Recently Updated';
default:
return 'Distance';
}
};
/* eslint-disable react/display-name */
const renderItem = (selected: Orderby) => (item: Orderby) => (
<Picker.Item key={item} label={getOrderbyText(item)} value={item} color={getItemColor(selected, item)} />
);
type orderbyPickerProps = { selected: Orderby, onValueChange: Function, style: Object };
const orderbyPicker = pure(({ selected, onValueChange, style }: orderbyPickerProps) => (
<View style={style}>
<Picker selectedValue={selected} onValueChange={debounce(onValueChange)} style={{ flex: 1 }}>
{ORDER_BY.map(renderItem(selected))}
</Picker>
</View>
));
export default orderbyPicker;

View file

@ -0,0 +1,29 @@
// @flow
import React from 'react';
import { Picker, View } from 'react-native';
import theme from '../ui-theme';
import { pure } from 'recompose';
import debounce from '../helpers/debounce';
const { picker: { color: selectedColor } } = theme;
const defaultColor = 'black';
const getItemColor = (selected, current) => (selected === current ? selectedColor : defaultColor);
export const CHOICES = [5, 10, 15, 20, 25, 30];
/* eslint-disable react/display-name */
const renderItem = (selected: number) => (item: number) => (
<Picker.Item key={item} label={`${item}`} value={item} color={getItemColor(selected, item)} />
);
type radiusPickerProps = { selected: number, onValueChange: Function, style: Object };
const radiusPicker = pure(({ selected, onValueChange, style }: radiusPickerProps) => (
<View style={style}>
<Picker selectedValue={selected} onValueChange={debounce(onValueChange)} style={{ flex: 1 }}>
{CHOICES.map(renderItem(selected))}
</Picker>
</View>
));
export default radiusPicker;

View file

@ -1,12 +1,12 @@
// @flow
import { Subject, Observable, ReplaySubject } from 'rxjs';
import { ReplaySubject } from 'rxjs';
import FilterRecord from '../records/FilterRecord';
const multicaster = new ReplaySubject();
const multicaster: ReplaySubject<FilterRecord> = new ReplaySubject();
export const emitter = val => multicaster.next(val);
// Observable.from([new FilterRecord()]).subscribe(multicaster);
export function emitter(val: typeof FilterRecord) {
multicaster.next(val);
}
emitter(new FilterRecord());

View file

@ -23,6 +23,11 @@ export default {
elevation: 0,
},
},
checkbox: {
icon: {
color: '#0E6E9E',
},
},
page: {
container: { flex: 1, backgroundColor: COLOR.grey100 },
},