8000 feat: add DropdownSearchBar · codeisneverodd/home@75a81ec · GitHub
[go: up one dir, main page]

Skip to content

Commit 75a81ec

Browse files
feat: add DropdownSearchBar
1 parent ff715d3 commit 75a81ec

File tree

10 files changed

+729
-533
lines changed

10 files changed

+729
-533
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,15 @@
3333
"react": "18.2.0",
3434
"react-dom": "18.2.0",
3535
"react-intersection-observer": "^9.4.3",
36+
"react-virtualized-auto-sizer": "^1.0.7",
37+
"react-window": "^1.8.8",
3638
"recoil": "^0.7.6",
3739
"typescript": "4.9.5",
3840
"zod": "^3.20.6"
3941
},
4042
"devDependencies": {
43+
"@types/react-virtualized-auto-sizer": "^1.0.1",
44+
"@types/react-window": "^1.8.5",
4145
"@typescript-eslint/eslint-plugin": "^5.53.0",
4246
"@typescript-eslint/parser": "^5.53.0",
4347
"eslint-config-airbnb": "^19.0.4",

src/lib/hooks/useColor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Prob } from "../solution-pass/hooks/useRepo";
44
export default function useColor() {
55
const bodyBg = useColorModeValue("white", "gray.800");
66
const subtleBg = useColorModeValue("gray.100", "gray.700");
7+
const alphaBg = useColorModeValue("gray.100", "whiteAlpha.200");
78
const levelColors: { [key in Prob["level"]]: string } = {
89
0: "#2189ff",
910
1: "#1bbaff",
@@ -13,7 +14,7 @@ export default function useColor() {
1314
5: "#c658e1"
1415
};
1516

16-
return { bodyBg, subtleBg, levelColors };
17+
return { bodyBg, subtleBg, alphaBg, levelColors };
1718
}
1819

1920
// --chakra-colors-chakra-body-text: var(--chakra-colors-gray-800);
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import useColor from "@/lib/hooks/useColor";
2+
import { Box, Button, Center, Flex, Text } from "@chakra-ui/react";
3+
import { ComponentProps } from "react";
4+
import AutoSizer from "react-virtualized-auto-sizer";
5+
import { FixedSizeList } from "react-window";
6+
import { Prob } from "../hooks/useRepo";
7+
import useSearch from "../hooks/useSearch";
8+
import SearhBar from "./SearchBar";
9+
10+
export default function DropdownSearhBar() {
11+
return (
12+
<Flex direction="column" w="full">
13+
<SearhBar rounded="none" />
14+
<Result />
15+
</Flex>
16+
);
17+
}
18+
19+
function Result() {
20+
const { result } = useSearch();
21+
const { alphaBg } = useColor();
22+
23+
if (result.keyword === "") return null;
24+
if (result.probs.length === 0)
25+
return (
26+
<Center w="full" h="80px" bg={alphaBg}>
27+
<Text>일치하는 문제가 없어요</Text>
28+
</Center>
29+
);
30+
31+
return (
32+
<Box h="260px">
33+
<AutoSizer>
34+
{({ height, width }) => (
35+
<FixedSizeList
36+
F438 height={height}
37+
width={width}
38+
itemSize={60}
39+
itemCount={result.probs.length}
40+
>
41+
{({ index, style }) => (
42+
<ResultRow probData={result.probs[index]} style={style} />
43+
)}
44+
</FixedSizeList>
45+
)}
46+
</AutoSizer>
47+
</Box>
48+
);
49+
}
50+
51+
function ResultRow({
52+
probData: { id, level, title },
53+
...props
54+
}: { probData: Prob } & ComponentProps<typeof Button>) {
55+
const { levelColors } = useColor();
56+
57+
return (
58+
<Button
59+
as={Flex}
60+
key={id}
61+
alignItems="center"
62+
gap="20px"
63+
w="full"
64+
h="60px"
65+
rounded="none"
66+
backdropFilter="auto"
67+
backdropBlur="3xl"
68+
{...props}
69+
>
70+
<Text
71+
textAlign="center"
72+
w="60px"
73+
fontSize="lg"
74+
color={levelColors[level]}
75+
fontWeight="bold"
76+
>
77+
{level}
78+
</Text>
79+
<Text flex="1">{title}</Text>
80+
</Button>
81+
);
82+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Button, Icon, IconButton } from "@chakra-ui/react";
2+
import { faPencil, faSearch } from "@fortawesome/free-solid-svg-icons";
3+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4+
import { ComponentProps } from "react";
5+
6+
const modeIcon: {
7+
[key in ModeToggleBtnProps["mode"]]: {
8+
icon: typeof faPencil;
9+
title: string;
10+
};
11+
} = {
12+
write: {
13+
icon: faPencil,
14+
title: "정답 추가하기"
15+
},
16+
search: {
17+
icon: faSearch,
18+
title: "정답 검색하기"
19+
}
20+
};
21+
22+
export default function ModeToggleBtn({ mode, ...props }: ModeToggleBtnProps) {
23+
return (
24+
<Button
25+
rounded="full"
26+
pos="fixed"
27+
bottom="40px"
28+
right="40px"
29+
size="lg"
30+
variant="solid"
31+
backdropFilter="auto"
32+
backdropBlur="3xl"
33+
leftIcon={<Icon as={FontAwesomeIcon} icon={modeIcon[mode].icon} />}
34+
{...props}
35+
>
36+
{modeIcon[mode].title}
37+
</Button>
38+
);
39+
}
40+
41+
type ModeToggleBtnProps = {
42+
mode: "write" | "search";
43+
} & ComponentProps<typeof IconButton>;

src/lib/solution-pass/components/SearchBar.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ import {
88
} from "@chakra-ui/react";
99
import { faSearch } from "@fortawesome/free-solid-svg-icons";
1010
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
11-
import { useState } from "react";
11+
import { ComponentProps, useState } from "react";
1212
import useSearch from "../hooks/useSearch";
1313

1414
let timeoutId: ReturnType<typeof setTimeout> | null = null;
1515
const DEBOUNCE_TIME = 100;
1616

17-
export default function SearhBar() {
17+
export default function SearhBar(props: ComponentProps<typeof Input>) {
1818
const [value, setValue] = useState("");
1919
const [isTyping, setIsTyping] = useState(false);
20-
const { search } = useSearch();
20+
const { search, result } = useSearch();
2121

2222
const handleTyping = (keyword: string) => {
2323
if (timeoutId) clearTimeout(timeoutId);
@@ -32,12 +32,13 @@ export default function SearhBar() {
3232
return (
3333
<InputGroup size="lg">
3434
<Input
35-
value={value}
35+
value={value === "" ? result.keyword : value}
3636
variant="filled"
3737
type="text"
3838
onChange={e => handleTyping(e.target.value)}
3939
autoComplete="off"
4040
placeholder="문제를 검색해주세요"
41+
{...props}
4142
/>
4243
<InputLeftElement>
4344
<Icon as={FontAwesomeIcon} icon={faSearch} />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Flex } from "@chakra-ui/react";
2+
import { ComponentProps } from "react";
3+
4+
export default function SolutionPassLayout(props: ComponentProps<typeof Flex>) {
5+
return (
6+
<Flex
7+
w="full"
8+
maxW="1000px"
9+
direction="column"
10+
pt="20px"
11+
align="center"
12+
m="auto"
13+
px="20px"
14+
gap="20px"
15+
{...props}
16+
/>
17+
);
18+
}

src/lib/solution-pass/hooks/useSearch.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { QueryStatus } from "@tanstack/react-query";
22
import { chosungIncludes, hangulIncludes } from "@toss/hangul";
3-
import { atom, useRecoilState } from "recoil";
3+
import { atom, useRecoilState, useResetRecoilState } from "recoil";
44
import useRepo, { Prob } from "./useRepo";
55

66
const searchAtom = atom<{
@@ -18,7 +18,7 @@ const searchAtom = atom<{
1818

1919
export default function useSearch() {
2020
const [result, setResult] = useRecoilState(searchAtom);
21-
21+
const reset = useResetRecoilState(searchAtom);
2222
const { repoQuery } = useRepo();
2323

2424
const search = (keyword: string) => {
@@ -43,5 +43,5 @@ export default function useSearch() {
4343
}
4444
};
4545

46-
return { result, search };
46+
return { result, search, reset };
4747
}

src/pages/solution-pass/index.tsx

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
import MainLayout from "@/lib/components/MainLayout";
2+
import ModeToggleBtn from "@/lib/solution-pass/components/ModeToggleBtn";
23
import ResultSection from "@/lib/solution-pass/components/ResultSection";
34
import SearhBar from "@/lib/solution-pass/components/SearchBar";
4-
import { Flex } from "@chakra-ui/react";
5+
import SolutionPassLayout from "@/lib/solution-pass/components/SolutionPassLayout";
6+
import { Link } from "@chakra-ui/react";
57

6-
export default function Home() {
8+
export default function SolutionPass() {
79
return (
8-
<MainLayout title="Solution Pass">
9-
<Flex
10-
w="full"
11-
maxW="1000px"
12-
direction="column"
13-
pt="20px"
14-
align="center"
15-
m="auto"
16-
gap="20px"
17-
>
10+
<MainLayout title="새로운 풀이">
11+
<SolutionPassLayout>
1812
<SearhBar />
1913
<ResultSection />
20-
</Flex>
14+
<Link href="/solution-pass/new">
15+
<ModeToggleBtn mode="search" aria-label="풀이 추가하기" />
16+
</Link>
17+
</SolutionPassLayout>
2118
</MainLayout>
2219
);
2320
}

src/pages/solution-pass/new.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import MainLayout from "@/lib/components/MainLayout";
2+
import DropdownSearhBar from "@/lib/solution-pass/components/DropdownSearchBar";
3+
import ModeToggleBtn from "@/lib/solution-pass/components/ModeToggleBtn";
4+
import SolutionPassLayout from "@/lib/solution-pass/components/SolutionPassLayout";
5+
import Link from "next/link";
6+
7+
export default function NewSolution() {
8+
return (
9+
<MainLayout title="Solution Pass">
10+
<SolutionPassLayout>
11+
<DropdownSearhBar />
12+
<Link href="/solution-pass">
13+
<ModeToggleBtn mode="search" aria-label="풀이 검색하기" />
14+
</Link>
15+
</SolutionPassLayout>
16+
</MainLayout>
17+
);
18+
}

0 commit comments

Comments
 (0)
0