Skip to content

Commit

Permalink
New : 술 검색기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
jobkaeHenry committed Nov 28, 2023
1 parent 2557610 commit 3859f00
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 20 deletions.
42 changes: 25 additions & 17 deletions client/src/app/wiki/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
"use client";
import { Paper, Container } from "@mui/material";
import WikiAppbar from "@/components/wiki/WikiAppbar";
import { ReactNode } from "react";
import { ReactNode, useState } from "react";
import WikiPageContext from "@/store/wiki/WikiPageContext";
import WikiSearchDrawer from "@/components/wiki/searchDrawer/WikiSearchDrawer";

const layout = ({ children }: { children: ReactNode }) => {
const [isSearching, setIsSearching] = useState(false);

return (
<Paper>
<WikiAppbar />
<Container sx={{ p: { xs: 0, sm: 4 } }} maxWidth={"lg"}>
<Paper
sx={{
display: "flex",
position: "relative",
flexDirection: "column",
gap: 2,
p: 2,
}}
>
{children}
</Paper>
</Container>
</Paper>
<WikiPageContext.Provider value={{ isSearching, setIsSearching }}>
<Paper>
<WikiAppbar />
<Container sx={{ p: { xs: 0, sm: 4 } }} maxWidth={"lg"}>
<Paper
sx={{
display: "flex",
position: "relative",
flexDirection: "column",
gap: 2,
p: 2,
}}
>
<WikiSearchDrawer />
{children}
</Paper>
</Container>
</Paper>
</WikiPageContext.Provider>
);
};

Expand Down
72 changes: 72 additions & 0 deletions client/src/components/SearchHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Button, Stack, StackProps, Typography } from "@mui/material";
import { useCallback, useState } from "react";
import XIcon from "@/assets/icons/XIcon.svg";

interface SearchHistoryProps extends Omit<StackProps, "onClick"> {
storageKey: string;
onClick: () => void;
}

const SearchHistory = ({ storageKey, onClick }: SearchHistoryProps) => {
const getItems = useCallback(() => {
return JSON.parse(localStorage.getItem(storageKey) ?? "[]") as string[];
}, [storageKey]);

const [searchHistory, setSearchHistory] = useState<string[]>(getItems());

const removeAll = useCallback(() => {
localStorage.setItem(storageKey, "[]");
setSearchHistory(getItems());
}, [storageKey]);

const removeByKeyword = useCallback(
(keyword: string) => {
const filteredHistory = searchHistory.filter(
(prevKeyword) => prevKeyword !== keyword
);
localStorage.setItem(storageKey, JSON.stringify(filteredHistory));
setSearchHistory(getItems());
},
[storageKey]
);

return searchHistory.length > 0 ? (
<>
<Stack direction="row" justifyContent="space-between">
<Typography variant="subtitle1" fontWeight="bold">
최근 검색어
</Typography>
<Button onClick={removeAll} variant="text" sx={{ fontWeight: "bold" }}>
전체 삭제
</Button>
</Stack>
<Stack>
{searchHistory.map((keyword) => (
<Stack
key={keyword}
onClick={onClick}
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Typography variant="subtitle2">{keyword}</Typography>
<Button
onClick={(e) => {
e.stopPropagation();
removeByKeyword(keyword);
}}
variant="text"
sx={{ justifyContent: "end" }}
>
<XIcon />
</Button>
</Stack>
))}
</Stack>
</>
) : (
<></>
);
};

export default SearchHistory;
31 changes: 31 additions & 0 deletions client/src/components/wiki/AlcoholListSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { memo } from "react";
import { Skeleton } from "@mui/material";

import useSkeletonTimer from "@/hooks/useSkeletonTimer";

interface AlcoholListSkeletonInterface {
size?: number;
disableTimer?: boolean;
}

const AlcoholListSkeleton = memo(
({ size = 5, disableTimer }: AlcoholListSkeletonInterface) => {
const isOver200ms = !!disableTimer ? true : useSkeletonTimer();

return isOver200ms ? (
Array.from(new Array(size)).map((_e, i) => (
<Skeleton
key={i}
variant="rectangular"
width={"100%"}
height={40}
sx={{ borderRadius: 2 }}
/>
))
) : (
<></>
);
}
);

export default AlcoholListSkeleton;
9 changes: 6 additions & 3 deletions client/src/components/wiki/WikiAppbar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
'use client'
"use client";
import CustomAppbar from "@/components/CustomAppbar";
import SearchIcon from "@/assets/icons/SearchIcon.svg";
import { memo, useContext } from "react";
import WikiPageContext from "@/store/wiki/WikiPageContext";

const WikiAppbar = () => {
const { setIsSearching } = useContext(WikiPageContext);
return (
<CustomAppbar
title="술백과"
buttonComponent={<SearchIcon />}
onClickButton={() => console.log("눌림")}
onClickButton={() => setIsSearching(true)}
/>
);
};

export default WikiAppbar;
export default memo(WikiAppbar);
47 changes: 47 additions & 0 deletions client/src/components/wiki/searchDrawer/WikiSearchDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { SwipeableDrawer, Stack, styled, Box } from "@mui/material";
import { useContext } from "react";
import WikiPageContext from "@/store/wiki/WikiPageContext";
import WikiSerachArea from "@/components/wiki/searchDrawer/WikiSerachArea";

const WikiSearchDrawer = () => {
const { isSearching, setIsSearching } = useContext(WikiPageContext);

const pullerBleed = 24;
return (
<SwipeableDrawer
open={isSearching}
onOpen={() => setIsSearching(true)}
onClose={() => setIsSearching(false)}
anchor="bottom"
disableSwipeToOpen
PaperProps={{
sx: {
p: 2,
borderTopLeftRadius: pullerBleed,
borderTopRightRadius: pullerBleed,
overFlow: "hidden",
},
}}
ModalProps={{
keepMounted: false,
}}
>
<Puller />
<Stack gap={2} pt={`${pullerBleed + 8}px`}>
<WikiSerachArea />
</Stack>
</SwipeableDrawer>
);
};

export default WikiSearchDrawer;

const Puller = styled(Box)(() => ({
width: 56,
height: 4,
backgroundColor: "#F6EAFB",
borderRadius: 3,
position: "absolute",
top: 8,
left: "calc(50% - 28px)",
}));
59 changes: 59 additions & 0 deletions client/src/components/wiki/searchDrawer/WikiSerachArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useEffect, useRef, useState } from "react";
import useDebounce from "@/hooks/useDebounce";
import InputSearchIcon from "@/assets/icons/InputSearchIcon.svg";
import { Stack, TextField } from "@mui/material";
import useGetAlcoholListQuery from "@/queries/alcohol/useGetAlcoholListQuery";
import AlcoholList from "@/components/wiki/AlcoholList";
import AlcoholListSkeleton from "../AlcoholListSkeleton";
import SearchHistory from "@/components/SearchHistory";
import { ALCOHOL_SEARCH_HISTORY } from "@/const/localstorageKey";

const WikiSerachArea = () => {
const [searchKeyword, setSearchKeyword] = useState("");
const debouncedValue = useDebounce(searchKeyword, 300);
const { data: alcohols, isSuccess } = useGetAlcoholListQuery(debouncedValue);
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
inputRef.current?.focus();
}, []);

return (
<>
<TextField
label="검색어를 입력해주세요"
autoFocus
autoComplete="off"
value={searchKeyword}
inputRef={inputRef}
onChange={({ target }) => setSearchKeyword(target.value)}
InputProps={{
endAdornment: <InputSearchIcon />,
sx: {
borderRadius: "12px",
},
}}
/>
<Stack gap={1} height={"232px"}>
{searchKeyword ? (
// 입력중인 경우
<>
{isSuccess ? (
<AlcoholList data={alcohols.list} />
) : (
<AlcoholListSkeleton />
)}
</>
) : (
// 입력이 없는경우 검색기록 표출
<SearchHistory
onClick={() => console.log("눌림")}
storageKey={ALCOHOL_SEARCH_HISTORY}
/>
)}
</Stack>
</>
);
};

export default WikiSerachArea;
13 changes: 13 additions & 0 deletions client/src/store/wiki/WikiPageContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Dispatch, SetStateAction, createContext } from "react";

interface WikiPageContextInterface {
isSearching: boolean;
setIsSearching: Dispatch<SetStateAction<boolean>>;
}

const WikiPageContext = createContext<WikiPageContextInterface>({
isSearching: true,
setIsSearching: () => {},
});

export default WikiPageContext;

0 comments on commit 3859f00

Please sign in to comment.