diff --git a/commitlint.config.js b/commitlint.config.js index 3bb2de2d..1fcb76ab 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -21,6 +21,7 @@ const CommitLintConfiguration = { "projects", "seo", "services", + "snippets", "static", "theme", "utils", diff --git a/content/snippets/custom-scroll-bar.md b/content/snippets/custom-scroll-bar.md new file mode 100644 index 00000000..410595f2 --- /dev/null +++ b/content/snippets/custom-scroll-bar.md @@ -0,0 +1,40 @@ +--- +title: Custom Scrollbar +description: define your own custom scroll bar +published: false +date: 2022-07-22 +stacks: + - css + - chakra-ui +--- + +## CSS + +```css +::-webkit-scrollbar { + width: 0.75rem; + height: 0.75rem; + background-color: blue; +} + +::-webkit-scrollbar-thumb { + border-radius: 20px; + background-color: gray; +} + +/** firefox **/ +html { + scrollbar-width: thin; + scrollbar-color: blue; +} +``` + +## References + +- MDN + - [https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-scrollbar#browser_compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-scrollbar#browser_compatibility) + - [https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scrollbars#browser_compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scrollbars#browser_compatibility) +- W3Schools: [https://www.w3schools.com/howto/howto_css_custom_scrollbar.asp](https://www.w3schools.com/howto/howto_css_custom_scrollbar.asp) +- [sznm.dev](http://sznm.dev) - chakra-ui implementation + - [https://github.com/sozonome/sznm.dev/commit/f967221e40c7d680eb25ca4944ede4f5def2b628](https://github.com/sozonome/sznm.dev/commit/f967221e40c7d680eb25ca4944ede4f5def2b628) + - [https://github.com/sozonome/sznm.dev/commit/76c3ce6895b6de5b0a0d4195378cf68cde269054](https://github.com/sozonome/sznm.dev/commit/76c3ce6895b6de5b0a0d4195378cf68cde269054) diff --git a/content/snippets/overflow-scroll-without-scrollbar.md b/content/snippets/overflow-scroll-without-scrollbar.md new file mode 100644 index 00000000..8de20c6c --- /dev/null +++ b/content/snippets/overflow-scroll-without-scrollbar.md @@ -0,0 +1,34 @@ +--- +title: Overflow Scroll without Scrollbar +description: for sleek overflow scroll in mobile viewport +published: false +date: 2022-07-22 +stacks: + - css + - chakra-ui +--- + +## CSS + +```css +.some-component { + overflow-x: scroll; /* or overflow-y */ +} + +.some-component::-webkit-scrollbar { + display: none; +} +``` + +## Chakra-UI + +```jsx + + ...some children + +``` + +## References + +- [https://stackoverflow.com/questions/65042380/how-to-add-webkit-scrollbar-pseudo-element-in-chakra-ui-element-react](https://stackoverflow.com/questions/65042380/how-to-add-webkit-scrollbar-pseudo-element-in-chakra-ui-element-react) +- [https://react.geist-ui.dev/en-us/components/tabs](https://react.geist-ui.dev/en-us/components/tabs) diff --git a/contentlayer.config.ts b/contentlayer.config.ts index 8809fa5f..fe3e1e33 100644 --- a/contentlayer.config.ts +++ b/contentlayer.config.ts @@ -77,9 +77,29 @@ const Project = defineDocumentType(() => ({ }, })); +const Snippet = defineDocumentType(() => ({ + name: "Snippet", + filePathPattern: "snippets/*.md", + fields: { + title: { type: "string", required: true }, + description: { type: "string", required: true }, + published: { type: "boolean" }, + date: { type: "string" }, + stacks: { type: "list", of: { type: "string" } }, + }, + computedFields: { + id: { + type: "string", + resolve: (snippet) => + // eslint-disable-next-line no-underscore-dangle + snippet._raw.sourceFileName.replace(/\.md$|\.mdx$/, ""), + }, + }, +})); + const contentLayerConfig = makeSource({ contentDirPath: "content", - documentTypes: [Blog, Project], + documentTypes: [Blog, Project, Snippet], markdown: { remarkPlugins: [remarkHtml], rehypePlugins: [rehypeRaw], diff --git a/src/lib/components/blog/renderers.tsx b/src/lib/components/blog/renderers.tsx index 975be1c6..433405db 100644 --- a/src/lib/components/blog/renderers.tsx +++ b/src/lib/components/blog/renderers.tsx @@ -55,5 +55,3 @@ export const renderers: Options["components"] = { h5: ({ children }) => {String(children)}, h6: ({ children }) => {String(children)}, }; - -export default renderers; diff --git a/src/lib/components/snippets/SnippetCard.tsx b/src/lib/components/snippets/SnippetCard.tsx new file mode 100644 index 00000000..175739f3 --- /dev/null +++ b/src/lib/components/snippets/SnippetCard.tsx @@ -0,0 +1,56 @@ +import { Flex, Grid, Heading, Text } from "@chakra-ui/react"; +import type { Snippet } from "contentlayer/generated"; +import Link from "next/link"; + +import { trackEventToUmami } from "lib/utils/trackEvent"; + +type SnippetCardProps = { + data: Snippet; +}; + +const SnippetCard = ({ data }: SnippetCardProps) => { + const handleClickSnippet = () => { + trackEventToUmami({ + eventValue: `Snippet: ${data.title}`, + eventType: "navigate", + }); + }; + + return ( + + + + {data.title} + {data.description} + + + + {data.stacks?.map((stack) => ( + + {stack} + + ))} + + + + ); +}; + +export default SnippetCard; diff --git a/src/lib/components/snippets/detail/Head.tsx b/src/lib/components/snippets/detail/Head.tsx new file mode 100644 index 00000000..60f4a965 --- /dev/null +++ b/src/lib/components/snippets/detail/Head.tsx @@ -0,0 +1,19 @@ +import { Grid, Heading, Text } from "@chakra-ui/react"; +import type { Snippet } from "contentlayer/generated"; + +type SnippetDetailHeadProps = { + data: Snippet; +}; + +const SnippetDetailHead = ({ data }: SnippetDetailHeadProps) => { + return ( + + + {data.title} + + {data.description} + + ); +}; + +export default SnippetDetailHead; diff --git a/src/lib/components/snippets/detail/Meta.tsx b/src/lib/components/snippets/detail/Meta.tsx new file mode 100644 index 00000000..961972de --- /dev/null +++ b/src/lib/components/snippets/detail/Meta.tsx @@ -0,0 +1,35 @@ +import type { Snippet } from "contentlayer/generated"; +import { NextSeo } from "next-seo"; + +import { baseUrl } from "lib/constants/baseUrl"; +import { sznmOgImage } from "lib/utils/sznmOgImage"; + +type SnippetDetailMetaProps = { + data: Snippet; +}; + +const SnippetDetailMeta = ({ data }: SnippetDetailMetaProps) => { + const ogImage = sznmOgImage({ + heading: data.title, + text: "Snippets | https://sznm.dev", + }); + const pageUrl = `${baseUrl}/snippets/${data.id}`; + + return ( + + ); +}; + +export default SnippetDetailMeta; diff --git a/src/lib/layout/Navigation.tsx b/src/lib/layout/Navigation.tsx index e76d976b..62f9cc35 100644 --- a/src/lib/layout/Navigation.tsx +++ b/src/lib/layout/Navigation.tsx @@ -1,7 +1,7 @@ import { IconButton } from "@chakra-ui/react"; import Link from "next/link"; import type { IconType } from "react-icons"; -import { FaFeatherAlt, FaHome, FaRocket, FaUser } from "react-icons/fa"; +import { FaCode, FaFeatherAlt, FaHome, FaRocket, FaUser } from "react-icons/fa"; import { trackEventToUmami } from "lib/utils/trackEvent"; @@ -26,7 +26,7 @@ const NavItem = ({ href, label, icon }: NavItemProps) => { aria-label={label} variant="ghost" flexBasis="25%" - fontSize={["2xl", "md"]} + fontSize={["xl", "md"]} padding={0} onClick={handleClickNavigation} > @@ -52,6 +52,11 @@ const navigations: NavItemProps[] = [ label: "Blog", icon: FaFeatherAlt, }, + { + href: "/snippets", + label: "Snippets", + icon: FaCode, + }, { href: "/about", label: "About", diff --git a/src/lib/pages/snippets/detail/Snippet.module.scss b/src/lib/pages/snippets/detail/Snippet.module.scss new file mode 100644 index 00000000..b4d993ea --- /dev/null +++ b/src/lib/pages/snippets/detail/Snippet.module.scss @@ -0,0 +1,26 @@ +.content { + p { + margin: 0.5rem 0 2rem; + } + a { + font-weight: 550; + text-decoration: underline; + } + img { + border-radius: 1rem; + margin: 1rem 0; + } + ul, + ol { + margin: 0 1.5rem; + li { + margin: 0.6rem 0; + } + } + code { + background-color: rgb(200, 200, 200); + border-radius: 0.25rem; + padding: 0 0.25rem; + color: black; + } +} diff --git a/src/lib/pages/snippets/detail/index.tsx b/src/lib/pages/snippets/detail/index.tsx new file mode 100644 index 00000000..9e76f197 --- /dev/null +++ b/src/lib/pages/snippets/detail/index.tsx @@ -0,0 +1,46 @@ +import { Box, Spacer, useColorModeValue } from "@chakra-ui/react"; +import type { GiscusProps } from "@giscus/react"; +import Giscus from "@giscus/react"; +import ReactMarkdown from "react-markdown"; +import rehypeRaw from "rehype-raw"; + +import { renderers } from "lib/components/blog/renderers"; +import SnippetDetailHead from "lib/components/snippets/detail/Head"; +import SnippetDetailMeta from "lib/components/snippets/detail/Meta"; + +import styles from "./Snippet.module.scss"; +import type { SnippetDetailProps } from "./types"; + +const SnippetDetail = ({ data }: SnippetDetailProps) => { + const giscusTheme: GiscusProps["theme"] = useColorModeValue("light", "dark"); + + return ( + + + + + + {data.body.raw} + + + + + + + ); +}; + +export default SnippetDetail; diff --git a/src/lib/pages/snippets/detail/loader.ts b/src/lib/pages/snippets/detail/loader.ts new file mode 100644 index 00000000..5c05f272 --- /dev/null +++ b/src/lib/pages/snippets/detail/loader.ts @@ -0,0 +1,30 @@ +import type { Snippet } from "contentlayer/generated"; +import { allSnippets } from "contentlayer/generated"; +import type { GetStaticProps } from "next"; + +import type { SnippetDetailParams, SnippetDetailProps } from "./types"; + +export const getStaticPaths = async () => { + const paths = allSnippets.map((project) => ({ + params: { + id: project.id, + }, + })); + return { + paths, + fallback: false, + }; +}; + +export const getStaticProps: GetStaticProps< + SnippetDetailProps, + SnippetDetailParams +> = async ({ params }) => { + const data = allSnippets.find( + ({ id }) => id === (params?.id as string) + ) as Snippet; + + return { + props: { data }, + }; +}; diff --git a/src/lib/pages/snippets/detail/types.ts b/src/lib/pages/snippets/detail/types.ts new file mode 100644 index 00000000..7bc8d283 --- /dev/null +++ b/src/lib/pages/snippets/detail/types.ts @@ -0,0 +1,9 @@ +import type { Snippet } from "contentlayer/generated"; + +export type SnippetDetailParams = { + id: string; +}; + +export type SnippetDetailProps = { + data: Snippet; +}; diff --git a/src/lib/pages/snippets/list/index.tsx b/src/lib/pages/snippets/list/index.tsx new file mode 100644 index 00000000..1359e9db --- /dev/null +++ b/src/lib/pages/snippets/list/index.tsx @@ -0,0 +1,38 @@ +import { Grid, Heading, Text } from "@chakra-ui/react"; + +import MotionBox from "lib/components/motion/MotionBox"; +import MotionGrid from "lib/components/motion/MotionGrid"; +import SnippetCard from "lib/components/snippets/SnippetCard"; +import { + childAnimationProps, + staggerAnimationProps, +} from "lib/constants/animation"; + +import type { SnippetListProps } from "./types"; + +const SnippetList = ({ snippets }: SnippetListProps) => { + return ( + + + Snippets + + A collection of my personal snippets I use throughout my projects + + + + + {snippets.map((item) => ( + + + + ))} + + + ); +}; + +export default SnippetList; diff --git a/src/lib/pages/snippets/list/loader.ts b/src/lib/pages/snippets/list/loader.ts new file mode 100644 index 00000000..2aa77499 --- /dev/null +++ b/src/lib/pages/snippets/list/loader.ts @@ -0,0 +1,14 @@ +import { allSnippets } from "contentlayer/generated"; +import type { GetStaticProps } from "next"; + +import type { SnippetListProps } from "./types"; + +export const getStaticProps: GetStaticProps = () => { + const snippets = allSnippets; + + return { + props: { + snippets, + }, + }; +}; diff --git a/src/lib/pages/snippets/list/types.ts b/src/lib/pages/snippets/list/types.ts new file mode 100644 index 00000000..710a8380 --- /dev/null +++ b/src/lib/pages/snippets/list/types.ts @@ -0,0 +1,5 @@ +import type { Snippet } from "contentlayer/generated"; + +export type SnippetListProps = { + snippets: Array; +}; diff --git a/src/pages/snippets/[id].ts b/src/pages/snippets/[id].ts new file mode 100644 index 00000000..deb7d7c8 --- /dev/null +++ b/src/pages/snippets/[id].ts @@ -0,0 +1,7 @@ +import SnippetDetail from "lib/pages/snippets/detail"; + +export { + getStaticPaths, + getStaticProps, +} from "lib/pages/snippets/detail/loader"; +export default SnippetDetail; diff --git a/src/pages/snippets/index.ts b/src/pages/snippets/index.ts new file mode 100644 index 00000000..ed6e4a0c --- /dev/null +++ b/src/pages/snippets/index.ts @@ -0,0 +1,4 @@ +import SnippetList from "lib/pages/snippets/list"; + +export { getStaticProps } from "lib/pages/snippets/list/loader"; +export default SnippetList;