1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-09-17 04:25:33 -04:00

all components should accept additional classnames

This commit is contained in:
2022-01-20 12:06:05 -05:00
parent 2162e9d563
commit 7e37adabc1
22 changed files with 150 additions and 86 deletions

View File

@@ -1,11 +1,12 @@
import classNames from "classnames";
import type { BlockquoteHTMLAttributes } from "react"; import type { BlockquoteHTMLAttributes } from "react";
import styles from "./Blockquote.module.css"; import styles from "./Blockquote.module.css";
type Props = BlockquoteHTMLAttributes<HTMLElement>; type Props = BlockquoteHTMLAttributes<HTMLElement>;
const Blockquote = ({ children, ...rest }: Props) => ( const Blockquote = ({ children, className, ...rest }: Props) => (
<blockquote className={styles.blockquote} {...rest}> <blockquote className={classNames(styles.blockquote, className)} {...rest}>
{children} {children}
</blockquote> </blockquote>
); );

View File

@@ -5,24 +5,28 @@ import type { ReactNode } from "react";
import styles from "./CodeBlock.module.css"; import styles from "./CodeBlock.module.css";
type Props = { type Props = {
className?: string;
children: ReactNode; children: ReactNode;
className?: string;
}; };
const CodeBlock = (props: Props) => { const CodeBlock = ({ children, className, ...rest }: Props) => {
if (props.className?.split(" ").includes("code-highlight")) { if (className?.split(" ").includes("code-highlight")) {
// full multi-line code blocks with prism highlighting and copy-to-clipboard button // full multi-line code blocks with prism highlighting and copy-to-clipboard button
return ( return (
<div className={styles.block}> <div className={styles.block}>
<CopyButton source={props.children} className={styles.copy_btn} /> <CopyButton source={children} className={styles.copy_btn} />
<code {...props} className={classNames(styles.code, props.className)}> <code className={classNames(styles.code, className)} {...rest}>
{props.children} {children}
</code> </code>
</div> </div>
); );
} else { } else {
// inline code in paragraphs, headings, etc. (not highlighted) // inline code in paragraphs, headings, etc. (not highlighted)
return <code className={classNames(styles.code, styles.inline)}>{props.children}</code>; return (
<code className={classNames(styles.code, styles.inline, className)} {...rest}>
{children}
</code>
);
} }
}; };

View File

@@ -1,6 +1,7 @@
import { memo } from "react"; import { memo } from "react";
import Link from "next/link"; import Link from "next/link";
import css from "styled-jsx/css"; import css from "styled-jsx/css";
import classNames from "classnames";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
type Props = { type Props = {
@@ -10,6 +11,7 @@ type Props = {
darkColor: string; darkColor: string;
title?: string; title?: string;
external?: boolean; external?: boolean;
className?: string;
}; };
const getFancyLinkStyles = ({ lightColor, darkColor }: Partial<Props>) => { const getFancyLinkStyles = ({ lightColor, darkColor }: Partial<Props>) => {
@@ -39,23 +41,33 @@ const getFancyLinkStyles = ({ lightColor, darkColor }: Partial<Props>) => {
`; `;
}; };
const ColorfulLink = ({ href, title, lightColor, darkColor, external = false, children }: Props) => { const ColorfulLink = ({
const { className, styles } = getFancyLinkStyles({ lightColor, darkColor }); href,
title,
lightColor,
darkColor,
external = false,
className,
children,
...rest
}: Props) => {
const { className: underlineClassName, styles: underlineStyles } = getFancyLinkStyles({ lightColor, darkColor });
return ( return (
<> <>
<Link href={href} passHref={true} prefetch={false}> <Link href={href} passHref={true} prefetch={false}>
<a <a
className={className} className={classNames(underlineClassName, className)}
title={title} title={title}
target={external ? "_blank" : undefined} target={external ? "_blank" : undefined}
rel={external ? "noopener noreferrer" : undefined} rel={external ? "noopener noreferrer" : undefined}
{...rest}
> >
{children} {children}
</a> </a>
</Link> </Link>
{styles} {underlineStyles}
</> </>
); );
}; };

View File

@@ -1,4 +1,5 @@
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import classNames from "classnames";
import { Giscus } from "@giscus/react"; import { Giscus } from "@giscus/react";
import { giscusConfig } from "../../lib/config"; import { giscusConfig } from "../../lib/config";
import type { GiscusProps } from "@giscus/react"; import type { GiscusProps } from "@giscus/react";
@@ -7,13 +8,14 @@ import styles from "./Comments.module.css";
type Props = { type Props = {
title: string; title: string;
className?: string;
}; };
const Comments = ({ title }: Props) => { const Comments = ({ title, className }: Props) => {
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
return ( return (
<div className={styles.wrapper}> <div className={classNames(styles.wrapper, className)}>
<Giscus <Giscus
{...(giscusConfig as GiscusProps)} {...(giscusConfig as GiscusProps)}
term={title} term={title}

View File

@@ -11,6 +11,10 @@ import type { FormikHelpers } from "formik";
import styles from "./ContactForm.module.css"; import styles from "./ContactForm.module.css";
const cx = classNames.bind(styles); const cx = classNames.bind(styles);
type Props = {
className?: string;
};
type Values = { type Values = {
name: string; name: string;
email: string; email: string;
@@ -18,7 +22,7 @@ type Values = {
"h-captcha-response": string; "h-captcha-response": string;
}; };
const ContactForm = () => { const ContactForm = ({ className }: Props) => {
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
// status/feedback: // status/feedback:
@@ -97,7 +101,7 @@ const ContactForm = () => {
}} }}
> >
{({ setFieldValue, isSubmitting, touched, errors }) => ( {({ setFieldValue, isSubmitting, touched, errors }) => (
<Form className={styles.form} name="contact"> <Form className={className} name="contact">
<Field <Field
className={cx({ input: true, missing: errors.name && touched.name })} className={cx({ input: true, missing: errors.name && touched.name })}
name="name" name="name"

View File

@@ -50,11 +50,7 @@ const CopyButton = ({ source, timeout = 2000, className }: Props) => {
onClick={handleCopy} onClick={handleCopy}
disabled={!!copied} disabled={!!copied}
> >
{copied ? ( {copied ? <CheckOcticon fill="currentColor" /> : <ClipboardOcticon fill="currentColor" />}
<CheckOcticon className="icon" fill="currentColor" />
) : (
<ClipboardOcticon className="icon" fill="currentColor" />
)}
</button> </button>
); );
}; };

View File

@@ -1,5 +1,6 @@
import Image from "../Image/Image"; import Image from "../Image/Image";
import innerText from "react-innertext"; import innerText from "react-innertext";
import classNames from "classnames";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import type { ImageProps as NextImageProps } from "next/image"; import type { ImageProps as NextImageProps } from "next/image";
@@ -8,11 +9,12 @@ import styles from "./Figure.module.css";
type Props = Omit<NextImageProps, "alt"> & { type Props = Omit<NextImageProps, "alt"> & {
children: ReactNode; // caption (can be in markdown, yay!!!) children: ReactNode; // caption (can be in markdown, yay!!!)
alt?: string; // becomes optional -- pulled from plaintext-ified caption if missing alt?: string; // becomes optional -- pulled from plaintext-ified caption if missing
className?: string;
}; };
const Figure = ({ children, alt, ...imageProps }: Props) => { const Figure = ({ children, alt, className, ...imageProps }: Props) => {
return ( return (
<figure className={styles.figure}> <figure className={classNames(styles.figure, className)}>
<Image alt={alt || innerText(children)} {...imageProps} /> <Image alt={alt || innerText(children)} {...imageProps} />
<figcaption className={styles.caption}>{children}</figcaption> <figcaption className={styles.caption}>{children}</figcaption>
</figure> </figure>

View File

@@ -5,44 +5,45 @@ import styles from "./Heading.module.css";
type Props = HTMLAttributes<HTMLHeadingElement> & { type Props = HTMLAttributes<HTMLHeadingElement> & {
as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
className?: string;
}; };
const Heading = ({ as: Component, children, ...rest }: Props) => { const Heading = ({ as: Component, children, className, ...rest }: Props) => {
return ( return (
<Component className={classNames(styles.heading, styles[Component])} {...rest}> <Component className={classNames(styles.heading, styles[Component], className)} {...rest}>
{children} {children}
</Component> </Component>
); );
}; };
// TODO: do this less manually... // TODO: do this less manually...
export const H1 = ({ children, ...props }: Props) => ( export const H1 = ({ children, ...rest }: Props) => (
<Heading as="h1" {...props}> <Heading as="h1" {...rest}>
{children} {children}
</Heading> </Heading>
); );
export const H2 = ({ children, ...props }: Props) => ( export const H2 = ({ children, ...rest }: Props) => (
<Heading as="h2" {...props}> <Heading as="h2" {...rest}>
{children} {children}
</Heading> </Heading>
); );
export const H3 = ({ children, ...props }: Props) => ( export const H3 = ({ children, ...rest }: Props) => (
<Heading as="h3" {...props}> <Heading as="h3" {...rest}>
{children} {children}
</Heading> </Heading>
); );
export const H4 = ({ children, ...props }: Props) => ( export const H4 = ({ children, ...rest }: Props) => (
<Heading as="h4" {...props}> <Heading as="h4" {...rest}>
{children} {children}
</Heading> </Heading>
); );
export const H5 = ({ children, ...props }: Props) => ( export const H5 = ({ children, ...rest }: Props) => (
<Heading as="h5" {...props}> <Heading as="h5" {...rest}>
{children} {children}
</Heading> </Heading>
); );
export const H6 = ({ children, ...props }: Props) => ( export const H6 = ({ children, ...rest }: Props) => (
<Heading as="h6" {...props}> <Heading as="h6" {...rest}>
{children} {children}
</Heading> </Heading>
); );

View File

@@ -2,7 +2,12 @@ import useSWR from "swr";
import Loading from "../Loading/Loading"; import Loading from "../Loading/Loading";
import { fetcher } from "../../lib/fetcher"; import { fetcher } from "../../lib/fetcher";
const HitCounter = ({ slug }) => { type Props = {
slug: string;
className?: string;
};
const HitCounter = ({ slug, className }: Props) => {
// start fetching repos from API immediately // start fetching repos from API immediately
const { data, error } = useSWR(`/api/hits/?slug=${encodeURIComponent(slug)}`, fetcher, { const { data, error } = useSWR(`/api/hits/?slug=${encodeURIComponent(slug)}`, fetcher, {
// avoid double (or more) counting views // avoid double (or more) counting views
@@ -21,7 +26,7 @@ const HitCounter = ({ slug }) => {
// we have data! // we have data!
return ( return (
<span title={`${data.hits.toLocaleString("en-US")} ${data.hits === 1 ? "view" : "views"}`}> <span title={`${data.hits.toLocaleString("en-US")} ${data.hits === 1 ? "view" : "views"}`} className={className}>
{data.hits.toLocaleString("en-US")} {data.hits.toLocaleString("en-US")}
</span> </span>
); );

View File

@@ -1,5 +1,11 @@
import classNames from "classnames";
import styles from "./HorizontalRule.module.css"; import styles from "./HorizontalRule.module.css";
const HorizontalRule = () => <hr className={styles.hr} />; type Props = {
className?: string;
};
const HorizontalRule = ({ className, ...rest }: Props) => <hr className={classNames(styles.hr, className)} {...rest} />;
export default HorizontalRule; export default HorizontalRule;

View File

@@ -1,3 +1,5 @@
import classNames from "classnames";
import styles from "./IFrame.module.css"; import styles from "./IFrame.module.css";
type Props = { type Props = {
@@ -7,11 +9,12 @@ type Props = {
width?: number; // defaults to 100% width?: number; // defaults to 100%
allowScripts?: boolean; allowScripts?: boolean;
noScroll?: boolean; noScroll?: boolean;
className?: string;
}; };
const IFrame = ({ src, title, height, width, allowScripts, noScroll, ...rest }: Props) => ( const IFrame = ({ src, title, height, width, allowScripts, noScroll, className, ...rest }: Props) => (
<iframe <iframe
className={styles.frame} className={classNames(styles.frame, className)}
src={src} src={src}
title={title} title={title}
sandbox={allowScripts ? "allow-same-origin allow-scripts allow-popups" : undefined} sandbox={allowScripts ? "allow-same-origin allow-scripts allow-popups" : undefined}

View File

@@ -1,11 +1,12 @@
import NextImage from "next/image"; import NextImage from "next/image";
import classNames from "classnames";
import type { ImageProps as NextImageProps } from "next/image"; import type { ImageProps as NextImageProps } from "next/image";
import styles from "./Image.module.css"; import styles from "./Image.module.css";
const Image = ({ src, width, height, alt, quality, priority }: NextImageProps) => { const Image = ({ src, width, height, alt, quality, priority, className, ...rest }: NextImageProps) => {
return ( return (
<div className={styles.wrapper}> <div className={classNames(styles.wrapper, className)}>
<NextImage <NextImage
src={(src as string).replace(/^\/public/g, "")} src={(src as string).replace(/^\/public/g, "")}
layout="intrinsic" layout="intrinsic"
@@ -15,6 +16,7 @@ const Image = ({ src, width, height, alt, quality, priority }: NextImageProps) =
quality={quality || 65} quality={quality || 65}
loading={priority ? "eager" : "lazy"} loading={priority ? "eager" : "lazy"}
priority={!!priority} priority={!!priority}
{...rest}
/> />
</div> </div>
); );

View File

@@ -1,25 +1,27 @@
import classNames from "classnames";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import styles from "./List.module.css"; import styles from "./List.module.css";
type Props = { type Props = {
children: ReactNode; children: ReactNode;
className?: string;
}; };
export const UnorderedList = ({ children, ...rest }: Props) => ( export const UnorderedList = ({ children, className, ...rest }: Props) => (
<ul className={styles.unordered} {...rest}> <ul className={classNames(styles.unordered, className)} {...rest}>
{children} {children}
</ul> </ul>
); );
export const OrderedList = ({ children, ...rest }: Props) => ( export const OrderedList = ({ children, className, ...rest }: Props) => (
<ol className={styles.ordered} {...rest}> <ol className={classNames(styles.ordered, className)} {...rest}>
{children} {children}
</ol> </ol>
); );
// TODO: this is based on good faith that the children are all `<li>`s... // TODO: this is based on good faith that the children are all `<li>`s...
export const ListItem = ({ children, ...rest }: Props) => ( export const ListItem = ({ children, className, ...rest }: Props) => (
<li className={styles.item} {...rest}> <li className={classNames(styles.item, className)} {...rest}>
{children} {children}
</li> </li>
); );

View File

@@ -1,4 +1,5 @@
import { memo } from "react"; import { memo } from "react";
import classNames from "classnames";
import styles from "./Loading.module.css"; import styles from "./Loading.module.css";
@@ -6,9 +7,10 @@ type Props = {
width: number; // of entire container, in pixels width: number; // of entire container, in pixels
boxes?: number; // total number of boxes (default: 3) boxes?: number; // total number of boxes (default: 3)
timing?: number; // staggered timing between each box's pulse, in seconds (default: 0.1s) timing?: number; // staggered timing between each box's pulse, in seconds (default: 0.1s)
className?: string;
}; };
const Loading = ({ width, boxes = 3, timing = 0.1 }: Props) => { const Loading = ({ width, boxes = 3, timing = 0.1, className }: Props) => {
// each box is just an empty div // each box is just an empty div
const divs = []; const divs = [];
@@ -30,7 +32,7 @@ const Loading = ({ width, boxes = 3, timing = 0.1 }: Props) => {
return ( return (
<div <div
className={styles.wrapper} className={classNames(styles.wrapper, className)}
style={{ style={{
width: `${width}px`, width: `${width}px`,
height: `${width / 2}px`, height: `${width / 2}px`,

View File

@@ -1,14 +1,16 @@
import classNames from "classnames";
import { OctocatOcticon } from "../Icons"; import { OctocatOcticon } from "../Icons";
import styles from "./OctocatLink.module.css"; import styles from "./OctocatLink.module.css";
type Props = { type Props = {
repo: string; repo: string;
className?: string;
}; };
const OctocatLink = (props: Props) => ( const OctocatLink = ({ repo, className }: Props) => (
<a className={styles.link} href={`https://github.com/${props.repo}`} target="_blank" rel="noopener noreferrer"> <a className={styles.link} href={`https://github.com/${repo}`} target="_blank" rel="noopener noreferrer">
<OctocatOcticon fill="currentColor" /> <OctocatOcticon fill="currentColor" className={classNames("icon", className)} />
</a> </a>
); );

View File

@@ -1,19 +1,21 @@
import { memo } from "react"; import { memo } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link"; import Link from "next/link";
import classNames from "classnames";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import styles from "./PageTitle.module.css"; import styles from "./PageTitle.module.css";
type Props = { type Props = {
children: ReactNode; children: ReactNode;
className?: string;
}; };
const PageTitle = ({ children }: Props) => { const PageTitle = ({ children, className }: Props) => {
const router = useRouter(); const router = useRouter();
return ( return (
<h1 className={styles.title}> <h1 className={classNames(styles.title, className)}>
<Link href={router.asPath}> <Link href={router.asPath}>
<a>{children}</a> <a>{children}</a>
</Link> </Link>

View File

@@ -1,49 +1,54 @@
import classNames from "classnames";
import { intlFormat, formatDistanceToNowStrict } from "date-fns"; import { intlFormat, formatDistanceToNowStrict } from "date-fns";
import { StarOcticon, ForkOcticon } from "../Icons"; import { StarOcticon, ForkOcticon } from "../Icons";
import { RepoType } from "../../types"; import { RepoType } from "../../types";
import styles from "./RepositoryCard.module.css"; import styles from "./RepositoryCard.module.css";
const RepositoryCard = (props: RepoType) => ( type Props = RepoType & {
<div className={styles.card}> className?: string;
<a className={styles.name} href={props.url} target="_blank" rel="noopener noreferrer"> };
{props.name}
const RepositoryCard = ({ name, url, description, language, stars, forks, updatedAt, className }: Props) => (
<div className={classNames(styles.card, className)}>
<a className={styles.name} href={url} target="_blank" rel="noopener noreferrer">
{name}
</a> </a>
{props.description && <p className={styles.description}>{props.description}</p>} {description && <p className={styles.description}>{description}</p>}
<div className={styles.meta}> <div className={styles.meta}>
{props.language && ( {language && (
<div className={styles.meta_item}> <div className={styles.meta_item}>
<span className={styles.language_color} style={{ backgroundColor: props.language.color }} /> <span className={styles.language_color} style={{ backgroundColor: language.color }} />
<span>{props.language.name}</span> <span>{language.name}</span>
</div> </div>
)} )}
{props.stars > 0 && ( {stars > 0 && (
<div className={styles.meta_item}> <div className={styles.meta_item}>
<a <a
href={`${props.url}/stargazers`} href={`${url}/stargazers`}
title={`${props.stars.toLocaleString("en-US")} ${props.stars === 1 ? "star" : "stars"}`} title={`${stars.toLocaleString("en-US")} ${stars === 1 ? "star" : "stars"}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<StarOcticon fill="currentColor" className={styles.octicon} /> <StarOcticon fill="currentColor" className={styles.octicon} />
<span>{props.stars.toLocaleString("en-US")}</span> <span>{stars.toLocaleString("en-US")}</span>
</a> </a>
</div> </div>
)} )}
{props.forks > 0 && ( {forks > 0 && (
<div className={styles.meta_item}> <div className={styles.meta_item}>
<a <a
href={`${props.url}/network/members`} href={`${url}/network/members`}
title={`${props.forks.toLocaleString("en-US")} ${props.forks === 1 ? "fork" : "forks"}`} title={`${forks.toLocaleString("en-US")} ${forks === 1 ? "fork" : "forks"}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<ForkOcticon fill="currentColor" className={styles.octicon} /> <ForkOcticon fill="currentColor" className={styles.octicon} />
<span>{props.forks.toLocaleString("en-US")}</span> <span>{forks.toLocaleString("en-US")}</span>
</a> </a>
</div> </div>
)} )}
@@ -51,7 +56,7 @@ const RepositoryCard = (props: RepoType) => (
<div <div
className={styles.meta_item} className={styles.meta_item}
title={intlFormat( title={intlFormat(
new Date(props.updatedAt), new Date(updatedAt),
{ {
year: "numeric", year: "numeric",
month: "short", month: "short",
@@ -65,7 +70,7 @@ const RepositoryCard = (props: RepoType) => (
} }
)} )}
> >
<span>Updated {formatDistanceToNowStrict(new Date(props.updatedAt), { addSuffix: true })}</span> <span>Updated {formatDistanceToNowStrict(new Date(updatedAt), { addSuffix: true })}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -2,14 +2,18 @@ import Tweet from "react-tweet-embed";
type Props = { type Props = {
id: string; id: string;
className?: string;
options?: object;
}; };
const TweetEmbed = (props: Props) => ( const TweetEmbed = ({ id, className, options }: Props) => (
<Tweet <Tweet
id={props.id} className={className}
id={id}
options={{ options={{
dnt: true, dnt: true,
align: "center", align: "center",
...options,
}} }}
/> />
); );

View File

@@ -1,3 +1,4 @@
import classNames from "classnames";
import ReactPlayer from "react-player/file"; import ReactPlayer from "react-player/file";
import styles from "./Video.module.css"; import styles from "./Video.module.css";
@@ -8,9 +9,10 @@ type Props = {
thumbnail?: string; thumbnail?: string;
subs?: string; subs?: string;
autoplay?: boolean; autoplay?: boolean;
className?: string;
}; };
const Video = ({ webm, mp4, thumbnail, subs, autoplay }: Props) => { const Video = ({ webm, mp4, thumbnail, subs, autoplay, className, ...rest }: Props) => {
const url = [ const url = [
webm && { webm && {
src: webm, src: webm,
@@ -53,9 +55,9 @@ const Video = ({ webm, mp4, thumbnail, subs, autoplay }: Props) => {
} }
return ( return (
<div className={styles.wrapper}> <div className={classNames(styles.wrapper, className)}>
{/* @ts-ignore */} {/* @ts-ignore */}
<ReactPlayer width="100%" height="100%" url={url} config={config} controls={!autoplay} /> <ReactPlayer width="100%" height="100%" url={url} config={config} controls={!autoplay} {...rest} />
</div> </div>
); );
}; };

View File

@@ -1,19 +1,22 @@
import classNames from "classnames";
import ReactPlayer from "react-player/youtube"; import ReactPlayer from "react-player/youtube";
import styles from "./YouTubeEmbed.module.css"; import styles from "./YouTubeEmbed.module.css";
type Props = { type Props = {
id: string; id: string;
className?: string;
}; };
const YouTubeEmbed = ({ id }: Props) => ( const YouTubeEmbed = ({ id, className, ...rest }: Props) => (
<div className={styles.wrapper}> <div className={classNames(styles.wrapper, className)}>
<ReactPlayer <ReactPlayer
width="100%" width="100%"
height="100%" height="100%"
url={`https://www.youtube-nocookie.com/watch?v=${id}`} url={`https://www.youtube-nocookie.com/watch?v=${id}`}
light={`https://i.ytimg.com/vi/${id}/hqdefault.jpg`} light={`https://i.ytimg.com/vi/${id}/hqdefault.jpg`}
controls controls
{...rest}
/> />
</div> </div>
); );

View File

@@ -259,7 +259,7 @@ const Index = () => (
</ColorfulLink>{" "} </ColorfulLink>{" "}
<sup className="monospace pgp_key"> <sup className="monospace pgp_key">
<ColorfulLink href="/pubkey.asc" title="My Public Key" lightColor="#757575" darkColor="#959595" external> <ColorfulLink href="/pubkey.asc" title="My Public Key" lightColor="#757575" darkColor="#959595" external>
<LockIcon className="icon" /> 2B0C 9CF2 51E6 9A39 <LockIcon /> 2B0C 9CF2 51E6 9A39
</ColorfulLink> </ColorfulLink>
</sup> </sup>
,{" "} ,{" "}

View File

@@ -6,7 +6,11 @@ import { ProjectsIcon } from "../components/Icons";
import type { GetStaticProps } from "next"; import type { GetStaticProps } from "next";
import { RepoType } from "../types"; import { RepoType } from "../types";
const Projects = (props: { repos: RepoType[] }) => ( type Props = {
repos: RepoType[];
};
const Projects = ({ repos }: Props) => (
<> <>
<NextSeo <NextSeo
title="Projects" title="Projects"
@@ -20,7 +24,7 @@ const Projects = (props: { repos: RepoType[] }) => (
</PageTitle> </PageTitle>
<div className="wrapper"> <div className="wrapper">
{props.repos.map((repo: RepoType) => ( {repos.map((repo: RepoType) => (
<div key={repo.name} className="card"> <div key={repo.name} className="card">
<RepositoryCard {...repo} /> <RepositoryCard {...repo} />
</div> </div>