mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-07-21 19:01:17 -04:00
properly merge multiple class names
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
/* all code */
|
/* all code */
|
||||||
.code {
|
.code {
|
||||||
font-size: 0.925em;
|
font-size: 0.925em;
|
||||||
|
tab-size: 2;
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
border: 1px solid var(--kinda-light);
|
border: 1px solid var(--kinda-light);
|
||||||
}
|
}
|
||||||
@@ -16,13 +17,26 @@
|
|||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block .copy_btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0.65em;
|
||||||
|
color: var(--medium-dark);
|
||||||
|
background-color: var(--background-inner);
|
||||||
|
border: 1px solid var(--kinda-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.block .copy_btn:hover {
|
||||||
|
color: var(--link);
|
||||||
|
}
|
||||||
|
|
||||||
/* the following sub-classes MUST be global -- the highlight rehype plugin isn't aware of this file */
|
/* the following sub-classes MUST be global -- the highlight rehype plugin isn't aware of this file */
|
||||||
|
|
||||||
.block :global(.code-highlight) {
|
.block :global(.code-highlight) {
|
||||||
display: block;
|
display: block;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
tab-size: 2;
|
|
||||||
color: var(--code-text);
|
color: var(--code-text);
|
||||||
background-color: var(--code-background);
|
background-color: var(--code-background);
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import classNames from "classnames";
|
||||||
import CopyButton from "../CopyButton/CopyButton";
|
import CopyButton from "../CopyButton/CopyButton";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
@@ -13,15 +14,15 @@ const CodeBlock = (props: Props) => {
|
|||||||
// 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} />
|
<CopyButton source={props.children} className={styles.copy_btn} />
|
||||||
<code {...props} className={`${styles.code} ${props.className}`}>
|
<code {...props} className={classNames(styles.code, props.className)}>
|
||||||
{props.children}
|
{props.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={`${styles.code} ${styles.inline}`}>{props.children}</code>;
|
return <code className={classNames(styles.code, styles.inline)}>{props.children}</code>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -160,7 +160,7 @@ const ContactForm = () => {
|
|||||||
<span>Sending...</span>
|
<span>Sending...</span>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<SendIcon className={`icon ${styles.send_icon}`} /> <span>Send</span>
|
<SendIcon className={classNames("icon", styles.send_icon)} /> <span>Send</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
@@ -1,19 +1,8 @@
|
|||||||
.copy {
|
.copy {
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 0.65em;
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: var(--medium-dark);
|
|
||||||
background-color: var(--background-inner);
|
|
||||||
border: 1px solid var(--kinda-light);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy:hover {
|
|
||||||
color: var(--link);
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
color: var(--success) !important;
|
color: var(--success) !important;
|
||||||
}
|
}
|
||||||
|
@@ -11,9 +11,10 @@ const cx = classNames.bind(styles);
|
|||||||
type Props = {
|
type Props = {
|
||||||
source: ReactNode;
|
source: ReactNode;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CopyButton = ({ source, timeout = 2000 }: Props) => {
|
const CopyButton = ({ source, timeout = 2000, className }: Props) => {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
const handleCopy = (e) => {
|
const handleCopy = (e) => {
|
||||||
@@ -43,7 +44,7 @@ const CopyButton = ({ source, timeout = 2000 }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={cx({ copy: true, success: !!copied })}
|
className={cx({ copy: true, success: !!copied }, className)}
|
||||||
title="Copy to clipboard"
|
title="Copy to clipboard"
|
||||||
aria-label="Copy to clipboard"
|
aria-label="Copy to clipboard"
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import classNames from "classnames";
|
||||||
import type { HTMLAttributes } from "react";
|
import type { HTMLAttributes } from "react";
|
||||||
|
|
||||||
import styles from "./Heading.module.css";
|
import styles from "./Heading.module.css";
|
||||||
@@ -8,7 +9,7 @@ type Props = HTMLAttributes<HTMLHeadingElement> & {
|
|||||||
|
|
||||||
const Heading = ({ as: Component, children, ...rest }: Props) => {
|
const Heading = ({ as: Component, children, ...rest }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Component className={`${styles.heading} ${styles[Component] || ""}`} {...rest}>
|
<Component className={classNames(styles.heading, styles[Component])} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</Component>
|
</Component>
|
||||||
);
|
);
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import classNames from "classnames";
|
||||||
import ThemeToggle from "../ThemeToggle/ThemeToggle";
|
import ThemeToggle from "../ThemeToggle/ThemeToggle";
|
||||||
import { HomeIcon, NotesIcon, ProjectsIcon, ContactIcon } from "../Icons";
|
import { HomeIcon, NotesIcon, ProjectsIcon, ContactIcon } from "../Icons";
|
||||||
|
|
||||||
@@ -7,22 +8,22 @@ import styles from "./Menu.module.css";
|
|||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
icon: <HomeIcon className={`icon ${styles.icon}`} />,
|
icon: <HomeIcon className={classNames("icon", styles.icon)} />,
|
||||||
text: "Home",
|
text: "Home",
|
||||||
href: "/",
|
href: "/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <NotesIcon className={`icon ${styles.icon}`} />,
|
icon: <NotesIcon className={classNames("icon", styles.icon)} />,
|
||||||
text: "Notes",
|
text: "Notes",
|
||||||
href: "/notes/",
|
href: "/notes/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <ProjectsIcon className={`icon ${styles.icon}`} />,
|
icon: <ProjectsIcon className={classNames("icon", styles.icon)} />,
|
||||||
text: "Projects",
|
text: "Projects",
|
||||||
href: "/projects/",
|
href: "/projects/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <ContactIcon className={`icon ${styles.icon}`} />,
|
icon: <ContactIcon className={classNames("icon", styles.icon)} />,
|
||||||
text: "Contact",
|
text: "Contact",
|
||||||
href: "/contact/",
|
href: "/contact/",
|
||||||
},
|
},
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import classNames from "classnames";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import HitCounter from "../HitCounter/HitCounter";
|
import HitCounter from "../HitCounter/HitCounter";
|
||||||
import { DateIcon, TagIcon, EditIcon, ViewsIcon } from "../Icons";
|
import { DateIcon, TagIcon, EditIcon, ViewsIcon } from "../Icons";
|
||||||
@@ -13,7 +14,7 @@ const NoteMeta = ({ slug, date, title, tags = [] }: Props) => (
|
|||||||
<div className={styles.meta}>
|
<div className={styles.meta}>
|
||||||
<div className={styles.date}>
|
<div className={styles.date}>
|
||||||
<span>
|
<span>
|
||||||
<DateIcon className={`icon ${styles.icon}`} />
|
<DateIcon className={classNames("icon", styles.icon)} />
|
||||||
</span>
|
</span>
|
||||||
<span title={format(new Date(date), "PPppp")}>
|
<span title={format(new Date(date), "PPppp")}>
|
||||||
<Link href={`/notes/${slug}/`}>
|
<Link href={`/notes/${slug}/`}>
|
||||||
@@ -25,7 +26,7 @@ const NoteMeta = ({ slug, date, title, tags = [] }: Props) => (
|
|||||||
{tags.length > 0 && (
|
{tags.length > 0 && (
|
||||||
<div className={styles.tags}>
|
<div className={styles.tags}>
|
||||||
<span>
|
<span>
|
||||||
<TagIcon className={`icon ${styles.icon}`} />
|
<TagIcon className={classNames("icon", styles.icon)} />
|
||||||
</span>
|
</span>
|
||||||
{tags.map((tag) => (
|
{tags.map((tag) => (
|
||||||
<span key={tag} className={styles.tag}>
|
<span key={tag} className={styles.tag}>
|
||||||
@@ -37,7 +38,7 @@ const NoteMeta = ({ slug, date, title, tags = [] }: Props) => (
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
<EditIcon className={`icon ${styles.icon}`} />
|
<EditIcon className={classNames("icon", styles.icon)} />
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<a
|
<a
|
||||||
@@ -53,7 +54,7 @@ const NoteMeta = ({ slug, date, title, tags = [] }: Props) => (
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
<ViewsIcon className={`icon ${styles.icon}`} />
|
<ViewsIcon className={classNames("icon", styles.icon)} />
|
||||||
</span>
|
</span>
|
||||||
<HitCounter slug={`notes/${slug}`} />
|
<HitCounter slug={`notes/${slug}`} />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
import { useEffect, useState, memo } from "react";
|
import { useEffect, useState, memo } from "react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
import styles from "./ThemeToggle.module.css";
|
import styles from "./ThemeToggle.module.css";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
// modified from Twemoji lightbulb:
|
// modified from Twemoji lightbulb:
|
||||||
const BulbIcon = ({ on = false, className = "" }) => (
|
const BulbIcon = ({ on = false, className }) => (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" className={className}>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" className={className}>
|
||||||
<g fill="none">
|
<g fill="none">
|
||||||
<path
|
<path
|
||||||
@@ -31,13 +36,13 @@ const BulbIcon = ({ on = false, className = "" }) => (
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ThemeToggle = ({ className = "" }) => {
|
const ThemeToggle = ({ className }: Props) => {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const { resolvedTheme, setTheme } = useTheme();
|
const { resolvedTheme, setTheme } = useTheme();
|
||||||
|
|
||||||
// render a dummy bulb until we're fully mounted and self-aware
|
// render a dummy bulb until we're fully mounted and self-aware
|
||||||
useEffect(() => setMounted(true), []);
|
useEffect(() => setMounted(true), []);
|
||||||
if (!mounted) return <BulbIcon on={false} className={`icon ${className}`} />;
|
if (!mounted) return <BulbIcon on={false} className={classNames("icon", className)} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -46,7 +51,7 @@ const ThemeToggle = ({ className = "" }) => {
|
|||||||
title={resolvedTheme === "light" ? "Toggle Dark Mode" : "Toggle Light Mode"}
|
title={resolvedTheme === "light" ? "Toggle Dark Mode" : "Toggle Light Mode"}
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
>
|
>
|
||||||
<BulbIcon on={resolvedTheme === "light"} className={`icon ${className}`} />
|
<BulbIcon on={resolvedTheme === "light"} className={classNames("icon", className)} />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -27,9 +27,9 @@ const Video = ({ webm, mp4, thumbnail, subs, autoplay }: Props) => {
|
|||||||
attributes: {
|
attributes: {
|
||||||
controlsList: "nodownload",
|
controlsList: "nodownload",
|
||||||
preload: "metadata",
|
preload: "metadata",
|
||||||
autoPlay: autoplay,
|
autoPlay: !!autoplay,
|
||||||
muted: autoplay,
|
muted: !!autoplay,
|
||||||
loop: autoplay,
|
loop: !!autoplay,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user