add search toolbar
This commit is contained in:
parent
19ef947631
commit
fbc71147c8
2 changed files with 139 additions and 27 deletions
|
@ -1,39 +1,78 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Flex,
|
|
||||||
Grid,
|
|
||||||
Heading,
|
|
||||||
IconButton,
|
|
||||||
Image,
|
|
||||||
Stack,
|
|
||||||
PseudoBox,
|
|
||||||
Editable,
|
Editable,
|
||||||
EditablePreview,
|
EditablePreview,
|
||||||
EditableInput,
|
EditableInput,
|
||||||
|
Flex,
|
||||||
|
Grid,
|
||||||
|
Heading,
|
||||||
|
Icon,
|
||||||
|
IconButton,
|
||||||
|
Image,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputLeftElement,
|
||||||
|
InputRightElement,
|
||||||
|
PseudoBox,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
} from "@chakra-ui/core";
|
} from "@chakra-ui/core";
|
||||||
|
|
||||||
import useOutfitState from "./useOutfitState.js";
|
import useOutfitState from "./useOutfitState.js";
|
||||||
|
import { ITEMS } from "./data";
|
||||||
|
|
||||||
function WardrobePage() {
|
function WardrobePage() {
|
||||||
|
const [data, wearItem] = useOutfitState();
|
||||||
|
const [searchQuery, setSearchQuery] = React.useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
// Fullscreen, split into a vertical stack on smaller screens
|
// Fullscreen, split into a vertical stack on smaller screens
|
||||||
// or a horizontal stack on larger ones!
|
// or a horizontal stack on larger ones!
|
||||||
templateRows={{ base: "50% 50%", lg: "none" }}
|
templateAreas={{
|
||||||
templateColumns={{ base: "none", lg: "50% 50%" }}
|
base: `"outfit"
|
||||||
|
"search"
|
||||||
|
"items"`,
|
||||||
|
lg: `"outfit search"
|
||||||
|
"outfit items"`,
|
||||||
|
}}
|
||||||
|
templateRows={{
|
||||||
|
base: "50% auto 1fr",
|
||||||
|
lg: "auto 1fr",
|
||||||
|
}}
|
||||||
|
templateColumns={{
|
||||||
|
base: "100%",
|
||||||
|
lg: "50% 50%",
|
||||||
|
}}
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top="0"
|
top="0"
|
||||||
bottom="0"
|
bottom="0"
|
||||||
left="0"
|
left="0"
|
||||||
right="0"
|
right="0"
|
||||||
>
|
>
|
||||||
<Box boxShadow="md">
|
<Box gridArea="outfit">
|
||||||
<OutfitPreview />
|
<OutfitPreview />
|
||||||
</Box>
|
</Box>
|
||||||
<Box overflow="auto">
|
<Box gridArea="search" boxShadow="sm">
|
||||||
|
<Box px="5" py="3">
|
||||||
|
<SearchToolbar query={searchQuery} onChange={setSearchQuery} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box gridArea="items" overflow="auto">
|
||||||
<Box px="5" py="5">
|
<Box px="5" py="5">
|
||||||
<ItemsPanel />
|
{searchQuery ? (
|
||||||
|
<SearchPanel
|
||||||
|
query={searchQuery}
|
||||||
|
wornItemIds={data.wornItemIds}
|
||||||
|
onWearItem={wearItem}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ItemsPanel
|
||||||
|
zonesAndItems={data.zonesAndItems}
|
||||||
|
onWearItem={wearItem}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -58,9 +97,70 @@ function OutfitPreview() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemsPanel() {
|
function SearchToolbar({ query, onChange }) {
|
||||||
const [zonesAndItems, wearItem] = useOutfitState();
|
return (
|
||||||
|
<InputGroup>
|
||||||
|
<InputLeftElement>
|
||||||
|
<Icon name="search" color="gray.400" />
|
||||||
|
</InputLeftElement>
|
||||||
|
<Input
|
||||||
|
placeholder="Search items…"
|
||||||
|
focusBorderColor="green.600"
|
||||||
|
color="green.800"
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
{query && (
|
||||||
|
<InputRightElement>
|
||||||
|
<IconButton
|
||||||
|
icon="close"
|
||||||
|
color="gray.400"
|
||||||
|
variant="ghost"
|
||||||
|
variantColor="green"
|
||||||
|
aria-label="Clear search"
|
||||||
|
onClick={() => onChange("")}
|
||||||
|
/>
|
||||||
|
</InputRightElement>
|
||||||
|
)}
|
||||||
|
</InputGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SearchPanel({ query, wornItemIds, onWearItem }) {
|
||||||
|
const normalize = (s) => s.toLowerCase();
|
||||||
|
const results = ITEMS.filter((item) =>
|
||||||
|
normalize(item.name).includes(normalize(query))
|
||||||
|
);
|
||||||
|
results.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
const resultsSection =
|
||||||
|
results.length > 0 ? (
|
||||||
|
<ItemList
|
||||||
|
items={results}
|
||||||
|
wornItemIds={wornItemIds}
|
||||||
|
onWearItem={onWearItem}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text color="green.500">
|
||||||
|
We couldn't find any matching items{" "}
|
||||||
|
<span role="img" aria-label="(thinking emoji)">
|
||||||
|
🤔
|
||||||
|
</span>{" "}
|
||||||
|
Try again?
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box color="green.800">
|
||||||
|
<Heading fontFamily="Delicious" fontWeight="800" size="2xl" mb="6">
|
||||||
|
Searching for "{query}"
|
||||||
|
</Heading>
|
||||||
|
{resultsSection}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ItemsPanel({ zonesAndItems, onWearItem }) {
|
||||||
return (
|
return (
|
||||||
<Box color="green.800">
|
<Box color="green.800">
|
||||||
<OutfitHeading />
|
<OutfitHeading />
|
||||||
|
@ -71,7 +171,7 @@ function ItemsPanel() {
|
||||||
zoneName={zoneName}
|
zoneName={zoneName}
|
||||||
items={items}
|
items={items}
|
||||||
wornItemId={wornItemId}
|
wornItemId={wornItemId}
|
||||||
onWearItem={wearItem}
|
onWearItem={onWearItem}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
@ -126,18 +226,28 @@ function ItemsForZone({ zoneName, items, wornItemId, onWearItem }) {
|
||||||
<Heading size="xl" color="green.800" mb="3" fontFamily="Delicious">
|
<Heading size="xl" color="green.800" mb="3" fontFamily="Delicious">
|
||||||
{zoneName}
|
{zoneName}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
<ItemList
|
||||||
|
items={items}
|
||||||
|
wornItemIds={[wornItemId]}
|
||||||
|
onWearItem={onWearItem}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ItemList({ items, wornItemIds, onWearItem }) {
|
||||||
|
return (
|
||||||
<Stack spacing="3">
|
<Stack spacing="3">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<Box key={item.id}>
|
<Box key={item.id}>
|
||||||
<Item
|
<Item
|
||||||
item={item}
|
item={item}
|
||||||
isWorn={item.id === wornItemId}
|
isWorn={wornItemIds.includes(item.id)}
|
||||||
onWear={() => onWearItem(item.id)}
|
onWear={() => onWearItem(item.id)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,9 @@ function useOutfitState() {
|
||||||
return { zoneName, items, wornItemId };
|
return { zoneName, items, wornItemId };
|
||||||
});
|
});
|
||||||
|
|
||||||
return [zonesAndItems, wearItem];
|
const data = { zonesAndItems, wornItemIds };
|
||||||
|
|
||||||
|
return [data, wearItem];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useOutfitState;
|
export default useOutfitState;
|
||||||
|
|
Loading…
Reference in a new issue