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:
@@ -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>
|
||||||
);
|
);
|
||||||
|
@@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -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}
|
||||||
|
@@ -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"
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -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>
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
|
@@ -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;
|
||||||
|
@@ -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}
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
|
@@ -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`,
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
|
@@ -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>
|
||||||
|
@@ -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,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
|
@@ -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>
|
||||||
,{" "}
|
,{" "}
|
||||||
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user