Skip to content
This repository has been archived by the owner on Mar 12, 2020. It is now read-only.

Commit

Permalink
Merge pull request #2096 from artsy/filter-nav-stack
Browse files Browse the repository at this point in the history
Implement Initial Collections Artwork Filter Navigation Stack
  • Loading branch information
sweir27 authored Feb 21, 2020
2 parents 10f76fa + 570dd84 commit bab77fb
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 1,268 deletions.
73 changes: 73 additions & 0 deletions src/lib/Components/ArtworkFilterOptions/SortOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ArrowLeftIcon, Box, Flex, Sans, Serif, space } from "@artsy/palette"
import { BackgroundFill, OptionListItem } from "lib/Components/FilterModal"
import React from "react"
import { FlatList, TouchableOpacity } from "react-native"
import NavigatorIOS from "react-native-navigator-ios"
import styled from "styled-components/native"

interface SortOptionsScreenProps {
navigator: NavigatorIOS
}

export class SortOptionsScreen extends React.Component<SortOptionsScreenProps> {
handleBackNavigation() {
this.props.navigator.pop()
}

render() {
return (
<Flex flexGrow={1}>
<SortHeader>
<Flex alignItems="flex-end" mt={0.5} mb={2}>
<ArrowLeftIconContainer onPress={() => this.handleBackNavigation()}>
<ArrowLeftIcon fill="black100" />
</ArrowLeftIconContainer>
</Flex>
<Sans mt={2} weight="medium" size="4">
Sort
</Sans>
<Box></Box>
</SortHeader>
<Flex>
<FlatList<string>
keyExtractor={(_item, index) => String(index)}
data={SortOptions}
renderItem={({ item }) => (
<Box>
{
<OptionListItem>
<Flex p={2} flexDirection="row" justifyContent="space-between" flexGrow={1}>
<Serif size="3">{item}</Serif>
</Flex>
</OptionListItem>
}
</Box>
)}
/>
</Flex>
<BackgroundFill />
</Flex>
)
}
}

const SortOptions = [
"Default",
"Price (low to high)",
"Price (high to low)",
"Recently Updated",
"Recently Added",
"Artwork year (descending)",
"Artwork year (ascending)",
]

export const SortHeader = styled(Flex)`
flex-direction: row;
justify-content: space-between;
padding-right: ${space(2)};
`

export const ArrowLeftIconContainer = styled(TouchableOpacity)`
margin-top: ${space(2)};
margin-left: ${space(2)};
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Theme } from "@artsy/palette"
import { FakeNavigator as MockNavigator } from "lib/Components/Bidding/__tests__/Helpers/FakeNavigator"
import { OptionListItem } from "lib/Components/FilterModal"
import React from "react"
import * as renderer from "react-test-renderer"
import { SortOptionsScreen as SortOptions } from "../SortOptions"

describe("Sort Options Screen", () => {
let mockNavigator: MockNavigator

beforeEach(() => {
mockNavigator = new MockNavigator()
})

it("renders the correct number of sort options", () => {
const root = renderer.create(
<Theme>
<SortOptions navigator={mockNavigator as any} />
</Theme>
).root

expect(root.findAllByType(OptionListItem)).toHaveLength(7)
})
})
240 changes: 182 additions & 58 deletions src/lib/Components/FilterModal.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,32 @@
import { Box, Button, CloseIcon, Flex, Sans } from "@artsy/palette"
import { ArrowRightIcon, Box, Button, CloseIcon, color, Flex, Sans, Serif, space } from "@artsy/palette"
import { SortOptionsScreen as SortOptions } from "lib/Components/ArtworkFilterOptions/SortOptions"
import React from "react"
import { LayoutAnimation, Modal as RNModal, TouchableWithoutFeedback, ViewProperties } from "react-native"
import {
FlatList,
LayoutAnimation,
Modal as RNModal,
TouchableOpacity,
TouchableWithoutFeedback,
ViewProperties,
} from "react-native"
import NavigatorIOS from "react-native-navigator-ios"
import styled from "styled-components/native"

interface ModalProps extends ViewProperties {
visible?: boolean
interface FilterModalProps extends ViewProperties {
closeModal?: () => void
navigator?: NavigatorIOS
isFilterArtworksModalVisible: boolean
}

// TODO: Define a TypeScript interface to represent possible filter states (take a look at the Pick TypeScript generic)
interface State {
interface FilterModalState {
isComponentMounted: boolean
sortableItems: Array<{ type: string; data: any }>
}

const ModalBackgroundView = styled.View`
background-color: #00000099;
flex: 1;
flex-direction: column;
`

const ModalInnerView = styled.View<{ visible: boolean }>`
flex-direction: column;
width: 100%;
background-color: white;
height: ${({ visible }) => (visible ? "auto" : "0")};
padding: ${({ visible }) => (visible ? "20px" : "0")};
border-top-left-radius: 10px;
border-top-right-radius: 10px;
`

export class FilterModal extends React.Component<ModalProps, State> {
constructor(props) {
super(props)

this.state = { isComponentMounted: false }
export class FilterModalNavigator extends React.Component<FilterModalProps, FilterModalState> {
state: FilterModalState = {
isComponentMounted: false,
sortableItems: [],
}

componentDidMount() {
Expand All @@ -50,42 +43,173 @@ export class FilterModal extends React.Component<ModalProps, State> {
}

render() {
const { isFilterArtworksModalVisible } = this.props
const { isComponentMounted } = this.state

return (
<RNModal animationType="fade" transparent={true} visible={this.props.visible}>
<TouchableWithoutFeedback>
<ModalBackgroundView>
<>
{isFilterArtworksModalVisible && (
<RNModal animationType="fade" transparent={true} visible={isFilterArtworksModalVisible}>
<TouchableWithoutFeedback onPress={null}>
<>
<Flex onTouchStart={() => this.closeModal()} style={{ flexGrow: 1 }} />
<ModalInnerView visible={this.state.isComponentMounted}>
<Flex flexDirection="row" justifyContent="space-between">
<Flex alignItems="flex-end" mt={0.5} mb={2}>
<Box onTouchStart={() => this.closeModal()}>
<CloseIcon fill="black100" />
</Box>
</Flex>
<Sans weight="medium" size="4">
Filter
</Sans>
<Sans size="4">Clear all</Sans>
</Flex>
{this.props.children}
<Button
onPress={() => {
this.closeModal()
<ModalBackgroundView>
<TouchableOpacity onPress={this.props.closeModal} style={{ flexGrow: 1 }} />
<ModalInnerView visible={isComponentMounted}>
<NavigatorIOS
navigationBarHidden={true}
initialRoute={{
component: FilterOptions,
passProps: { closeModal: this.props.closeModal },
title: "", // this property (can be an empty string) is required otherwise RN throws a warning
}}
block
width={100}
variant="secondaryOutline"
>
Ok
</Button>
style={{ flex: 1 }}
/>
<Box p={2}>
<Button onPress={() => this.closeModal()} block width={100} variant="secondaryOutline">
Apply
</Button>
</Box>
</ModalInnerView>
</>
</ModalBackgroundView>
</TouchableWithoutFeedback>
</ModalBackgroundView>
</TouchableWithoutFeedback>
</RNModal>
</RNModal>
)}
</>
)
}
}

interface FilterOptionsState {
filterOptions: Array<{ type: string; onTap: () => void }>
}

interface FilterOptionsProps {
closeModal: () => void
navigator: NavigatorIOS
}
export class FilterOptions extends React.Component<FilterOptionsProps, FilterOptionsState> {
state: FilterOptionsState = {
filterOptions: [],
}

componentDidMount() {
const filterOptions = []

filterOptions.push({
type: "Sort by",
onTap: this.handleNavigationToSortScreen,
})

this.setState({
filterOptions,
})
}

handleNavigationToSortScreen = () => {
this.props.navigator.push({
component: SortOptions,
})
}

render() {
const { filterOptions } = this.state

return (
<Flex flexGrow={1}>
<Flex flexDirection="row" justifyContent="space-between">
<Flex alignItems="flex-end" mt={0.5} mb={2}>
<CloseIconContainer onPress={() => this.props.closeModal()}>
<CloseIcon fill="black100" />
</CloseIconContainer>
</Flex>
<FilterHeader weight="medium" size="4">
Filter
</FilterHeader>
<Sans mr={2} mt={2} size="4">
Clear all
</Sans>
</Flex>
<Flex>
<FlatList<{ onTap: () => void; type: string }>
keyExtractor={(_item, index) => String(index)}
data={filterOptions}
renderItem={({ item }) => (
<Box>
{
<OptionListItem>
<Flex p={2} flexDirection="row" justifyContent="space-between" flexGrow={1}>
<Serif size="3">{item.type}</Serif>
<TouchableOptionListItemRow onPress={() => item.onTap()}>
<Serif color={color("black60")} size="3">
Default
</Serif>
<ArrowRightIcon fill="black30" ml={0.3} mt={0.3} />
</TouchableOptionListItemRow>
</Flex>
</OptionListItem>
}
</Box>
)}
/>
</Flex>
<BackgroundFill />
</Flex>
)
}
}

export const FilterHeader = styled(Sans)`
margin-top: 20px;
`

export const BackgroundFill = styled(Flex)`
background-color: ${color("black10")};
flex-grow: 1;
`

export const FilterArtworkButtonContainer = styled(Flex)`
position: absolute;
bottom: 50;
flex: 1;
justify-content: center;
width: 100%;
flex-direction: row;
`

export const FilterArtworkButton = styled(Button)`
border-radius: 100;
width: 110px;
`

export const TouchableOptionListItemRow = styled(TouchableOpacity)`
flex-direction: row;
`
export const CloseIconContainer = styled(TouchableOpacity)`
margin-left: ${space(2)};
margin-top: ${space(2)};
`

export const OptionListItem = styled(Flex)`
flex-direction: row;
justify-content: space-between;
border: solid 0.5px ${color("black10")};
border-right-width: 0;
border-left-width: 0;
flex: 1;
width: 100%;
`

const ModalBackgroundView = styled.View`
background-color: #00000099;
flex: 1;
flex-direction: column;
border-top-left-radius: ${space(1)};
border-top-right-radius: ${space(1)};
`

const ModalInnerView = styled.View<{ visible: boolean }>`
flex-direction: column;
background-color: ${color("white100")};
height: 75%;
border-top-left-radius: ${space(1)};
border-top-right-radius: ${space(1)};
`
Loading

0 comments on commit bab77fb

Please sign in to comment.