diff --git a/src/components/pages/home/Card.tsx b/src/components/common/ToolCard.tsx similarity index 100% rename from src/components/pages/home/Card.tsx rename to src/components/common/ToolCard.tsx diff --git a/src/components/common/ToolCardGrid.tsx b/src/components/common/ToolCardGrid.tsx new file mode 100644 index 0000000..4df7429 --- /dev/null +++ b/src/components/common/ToolCardGrid.tsx @@ -0,0 +1,23 @@ +import { Grid } from "@mui/material"; +import equal from "fast-deep-equal"; +import { ComponentPropsWithoutRef, memo } from "react"; + +import ToolCard from "./ToolCard"; + +type Props = { + tools: ComponentPropsWithoutRef[]; +}; + +const StyledComponent = ({ tools }: Props) => ( + + {tools.map(({ icon, title, description, href, disabled }) => ( + + + + ))} + +); + +export const Component = memo(StyledComponent, equal); + +export default Component; diff --git a/src/components/common/index.ts b/src/components/common/index.ts index cd6937b..eeaaa34 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -3,5 +3,6 @@ import Configurations from "./Configurations"; import Main from "./Main"; import MainItem from "./MainItem"; import TextField from "./TextField"; +import ToolCardGrid from "./ToolCardGrid"; -export { CodeEditorHalfLoading, Configurations, Main, MainItem, TextField }; +export { CodeEditorHalfLoading, Configurations, Main, MainItem, TextField, ToolCardGrid }; diff --git a/src/components/layout/SearchBar.tsx b/src/components/layout/SearchBar.tsx index 046a177..2449911 100644 --- a/src/components/layout/SearchBar.tsx +++ b/src/components/layout/SearchBar.tsx @@ -56,8 +56,11 @@ const Container = () => { const onChange: Props["onChange"] = useCallback( ({ currentTarget: { value } }) => { setText(value); + + const url = value === "" ? pagesPath.$url() : pagesPath.search.$url(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises - router.push(pagesPath.$url()); + router.push(url); }, [setText, router] ); diff --git a/src/components/pages/home/Content.tsx b/src/components/pages/home/Content.tsx index 4aa631e..48fe721 100644 --- a/src/components/pages/home/Content.tsx +++ b/src/components/pages/home/Content.tsx @@ -1,160 +1,18 @@ -import { - Code, - DataObject, - DragHandle, - Filter, - Fingerprint, - Key, - Link, - Numbers, - TextIncrease, - Transform, -} from "@mui/icons-material"; -import { Grid } from "@mui/material"; -import Fuse from "fuse.js"; -import { memo } from "react"; -import { selector, useRecoilValue } from "recoil"; +import { ComponentPropsWithoutRef, memo } from "react"; -import { Main } from "@/components/common"; -import { searchTextState } from "@/components/layout/states"; -import { pagesPath } from "@/libs/$path"; +import { Main, ToolCardGrid } from "@/components/common"; +import { allTools } from "@/data/tools"; -import Card from "./Card"; +type Props = ComponentPropsWithoutRef; -type Props = { - filteredTools: typeof tools; -}; - -const tools = [ - { - icon: , - title: "Json <> Yaml Converter", - description: "Convert Json data to Yaml and vice versa", - keywords: "json yaml converter", - href: pagesPath.converters.json_yaml.$url(), - disabled: false, - }, - { - icon: , - title: "Number Base Converter", - description: "Convert numbers from one base to another", - keywords: "number base converter", - href: pagesPath.converters.number_base.$url(), - disabled: false, - }, - { - icon: , - title: "HTML Encoder / Decoder", - description: - "Encode or decode all the applicable characters to their corresponding HTML entities", - keywords: "html encoder escaper decocder unescaper", - href: pagesPath.encoders_decoders.html.$url(), - disabled: false, - }, - { - icon: , - title: "URL Encoder / Decoder", - description: - "Encode or decode all the applicable characters to their corresponding URL entities", - keywords: "url encoder escaper decocder unescaper", - href: pagesPath.$url(), - disabled: true, - }, - { - icon: , - title: "Base 64 Encoder / Decoder", - description: "Encode and decode Base64 data", - keywords: "base64 encoder decocder", - href: pagesPath.$url(), - disabled: true, - }, - { - icon: , - title: "JWT Decoder", - description: "Decode a JWT header, payload and signature", - keywords: "jwt json web token decocder", - href: pagesPath.$url(), - disabled: true, - }, - { - icon: , - title: "JSON Formatter", - description: "Indent or minify JSON data", - keywords: "json formatter", - href: pagesPath.$url(), - disabled: true, - }, - { - icon: , - title: "Hash Generator", - description: "Calculate MD5, SHA1, SHA256 and SHA512 hash from text data", - keywords: "hash generator md5 sha1 sha256 sha512", - href: pagesPath.$url(), - disabled: true, - }, - { - icon: , - title: "UUID Generator", - description: "Generate UUIDs version 1 and 4", - keywords: "guid uuid1 uuid4 generator", - href: pagesPath.$url(), - disabled: true, - }, - { - icon: , - title: "Regex Tester", - description: "Validate and test regular expressions", - keywords: "regular expression regex validator tester", - href: pagesPath.$url(), - disabled: true, - }, - { - icon: , - title: "PNG / JPEG Compressor", - description: "Lossless PNG and JPEG optimizer", - keywords: "png jpeg compressor optimizer image", - href: pagesPath.$url(), - disabled: true, - }, -]; - -const StyledComponent = ({ filteredTools }: Props) => ( +const StyledComponent = ({ tools }: Props) => (
- - {filteredTools.map(({ icon, title, description, href, disabled }) => ( - - - - ))} - +
); export const Component = memo(StyledComponent); -const filteredToolsState = selector({ - key: "filteredToolsState", - get: ({ get }) => { - const searchText = get(searchTextState).trim(); - - if (searchText === "") { - return { filteredTools: tools }; - } - - const searchWords = searchText.split(" ").map(word => ({ keywords: word })); - - const fuse = new Fuse(tools, { keys: ["keywords"], threshold: 0.5 }); - const result = fuse.search({ $and: searchWords }); - const filteredTools = result.map(({ item }) => item); - - return { filteredTools }; - }, -}); - -const Container = () => { - const { filteredTools } = useRecoilValue(filteredToolsState); - - return ; -}; +const Container = () => ; export default Container; diff --git a/src/components/pages/search/Content.tsx b/src/components/pages/search/Content.tsx new file mode 100644 index 0000000..22d438f --- /dev/null +++ b/src/components/pages/search/Content.tsx @@ -0,0 +1,45 @@ +import equal from "fast-deep-equal"; +import Fuse from "fuse.js"; +import { ComponentPropsWithoutRef, memo } from "react"; +import { selector, useRecoilValue } from "recoil"; + +import { Main, ToolCardGrid } from "@/components/common"; +import { searchTextState } from "@/components/layout/states"; +import { allTools } from "@/data/tools"; + +type Props = ComponentPropsWithoutRef; + +const StyledComponent = ({ tools }: Props) => ( +
+ +
+); + +export const Component = memo(StyledComponent, equal); + +const filteredToolsState = selector({ + key: "filteredToolsState", + get: ({ get }) => { + const searchText = get(searchTextState).trim(); + + if (searchText === "") { + return { filteredTools: allTools }; + } + + const searchWords = searchText.split(" ").map(word => ({ keywords: word })); + + const fuse = new Fuse(allTools, { keys: ["keywords"], threshold: 0.5 }); + const result = fuse.search({ $and: searchWords }); + const filteredTools = result.map(({ item }) => item); + + return { filteredTools }; + }, +}); + +const Container = () => { + const { filteredTools } = useRecoilValue(filteredToolsState); + + return ; +}; + +export default Container; diff --git a/src/components/pages/search/index.ts b/src/components/pages/search/index.ts new file mode 100644 index 0000000..ae1b8af --- /dev/null +++ b/src/components/pages/search/index.ts @@ -0,0 +1,3 @@ +import Content from "./Content"; + +export { Content }; diff --git a/src/data/tools.tsx b/src/data/tools.tsx new file mode 100644 index 0000000..a963bb6 --- /dev/null +++ b/src/data/tools.tsx @@ -0,0 +1,107 @@ +import { + Code, + DataObject, + DragHandle, + Filter, + Fingerprint, + Key, + Link, + Numbers, + TextIncrease, + Transform, +} from "@mui/icons-material"; + +import { pagesPath } from "@/libs/$path"; + +export const allTools = [ + { + icon: , + title: "Json <> Yaml Converter", + description: "Convert Json data to Yaml and vice versa", + keywords: "json yaml converter", + href: pagesPath.converters.json_yaml.$url(), + disabled: false, + }, + { + icon: , + title: "Number Base Converter", + description: "Convert numbers from one base to another", + keywords: "number base converter", + href: pagesPath.converters.number_base.$url(), + disabled: false, + }, + { + icon: , + title: "HTML Encoder / Decoder", + description: + "Encode or decode all the applicable characters to their corresponding HTML entities", + keywords: "html encoder escaper decocder unescaper", + href: pagesPath.encoders_decoders.html.$url(), + disabled: false, + }, + { + icon: , + title: "URL Encoder / Decoder", + description: + "Encode or decode all the applicable characters to their corresponding URL entities", + keywords: "url encoder escaper decocder unescaper", + href: pagesPath.$url(), + disabled: true, + }, + { + icon: , + title: "Base 64 Encoder / Decoder", + description: "Encode and decode Base64 data", + keywords: "base64 encoder decocder", + href: pagesPath.$url(), + disabled: true, + }, + { + icon: , + title: "JWT Decoder", + description: "Decode a JWT header, payload and signature", + keywords: "jwt json web token decocder", + href: pagesPath.$url(), + disabled: true, + }, + { + icon: , + title: "JSON Formatter", + description: "Indent or minify JSON data", + keywords: "json formatter", + href: pagesPath.$url(), + disabled: true, + }, + { + icon: , + title: "Hash Generator", + description: "Calculate MD5, SHA1, SHA256 and SHA512 hash from text data", + keywords: "hash generator md5 sha1 sha256 sha512", + href: pagesPath.$url(), + disabled: true, + }, + { + icon: , + title: "UUID Generator", + description: "Generate UUIDs version 1 and 4", + keywords: "guid uuid1 uuid4 generator", + href: pagesPath.$url(), + disabled: true, + }, + { + icon: , + title: "Regex Tester", + description: "Validate and test regular expressions", + keywords: "regular expression regex validator tester", + href: pagesPath.$url(), + disabled: true, + }, + { + icon: , + title: "PNG / JPEG Compressor", + description: "Lossless PNG and JPEG optimizer", + keywords: "png jpeg compressor optimizer image", + href: pagesPath.$url(), + disabled: true, + }, +]; diff --git a/src/libs/$path.ts b/src/libs/$path.ts index 40c1318..2bbcc86 100644 --- a/src/libs/$path.ts +++ b/src/libs/$path.ts @@ -21,6 +21,9 @@ export const pagesPath = { }), }, }, + search: { + $url: (url?: { hash?: string }) => ({ pathname: "/search" as const, hash: url?.hash }), + }, $url: (url?: { hash?: string }) => ({ pathname: "/" as const, hash: url?.hash }), }; diff --git a/src/pages/search.tsx b/src/pages/search.tsx new file mode 100644 index 0000000..cd2f813 --- /dev/null +++ b/src/pages/search.tsx @@ -0,0 +1,7 @@ +import type { NextPage } from "next"; + +import { Content } from "@/components/pages/search"; + +const Page: NextPage = Content; + +export default Page;