Skip to content

Commit

Permalink
feat: support themes via tailwind (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
deer authored Feb 2, 2024
1 parent 88febc7 commit ab46b3d
Show file tree
Hide file tree
Showing 28 changed files with 462 additions and 152 deletions.
14 changes: 9 additions & 5 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import { BlogOptions } from "../plugin/blog.ts";
import NavigationBar from "../islands/NavigationBar.tsx";

export default function Header(
props: { options: BlogOptions; active: string },
props: { options: BlogOptions },
) {
const isHome = props.active == "/";
return (
<div class="pb-16">
<NavigationBar active={props.active} options={props.options} />
</div>
<header class="flex items-center justify-between pt-10 border-b mb-4 pb-4">
<h1>
<a href="/" class="text-3xl font-bold">
{props.options.title}
</a>
</h1>
<NavigationBar options={props.options} />
</header>
);
}
20 changes: 9 additions & 11 deletions src/components/PostList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@ export default function PostList(
props: { posts: Post[]; showExcerpt: boolean; localization: Localization },
) {
return (
<main class="max-w-screen-md px-4 mx-auto">
<div class="mt-8">
{props.posts.map((post) => (
<PostSummary
post={post}
showExcerpt={props.showExcerpt}
localization={props.localization}
/>
))}
</div>
</main>
<div class="mt-8 w-full">
{props.posts.map((post) => (
<PostSummary
post={post}
showExcerpt={props.showExcerpt}
localization={props.localization}
/>
))}
</div>
);
}
36 changes: 15 additions & 21 deletions src/components/PostSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,47 @@
import { Post } from "../utils/posts.ts";
import { Localization } from "../plugin/blog.ts";
import TagLink from "./TagLink.tsx";
import Tags from "./Tags.tsx";

export default function PostSummary(
props: { post: Post; showExcerpt: boolean; localization: Localization },
) {
const { post, showExcerpt } = props;
return (
<div class="py-4 border-t border-gray-200" id={`post:${post.slug}`}>
<div
class="py-4 border-t border-light-mutedBackground dark:border-dark-mutedBackground first:border-t-0"
id={`post:${post.slug}`}
>
<a class="sm:col-span-2" href={`/blog/${post.slug}`}>
<h3 class="text-3xl text-gray-900 font-bold">
<h3 class="text-4xl font-bold">
{post.title}
</h3>
</a>
<div>
<div class="flex items-center mb-2">
{hasNonNullContent(post.author) && (
<>
<span class="text-gray-500 mr-1">
<span class="text-light-mutedForeground dark:text-dark-mutedForeground mr-1">
{props.localization.attribution}
</span>
{reduceAuthors(post.author)}
</>
)}
<time class="ml-1 text-gray-500">
<time class="ml-1 text-light-mutedForeground dark:text-dark-mutedForeground mr-2">
{new Date(post.date).toLocaleDateString("en-us", {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
{hasNonNullContent(post.tags) && tags(post.tags)}
{hasNonNullContent(post.tags) && <Tags tags={post.tags} />}
</div>
{showExcerpt && (post.excerpt != "") && (
<div>
<div>{post.excerpt}</div>
<a class="sm:col-span-2" href={`/blog/${post.slug}`}>
<a
class="sm:col-span-2 text-light-mutedForeground dark:text-dark-mutedForeground"
href={`/blog/${post.slug}`}
>
{props.localization.continueReading}
</a>
</div>
Expand Down Expand Up @@ -63,17 +71,3 @@ function authorElement(author: string) {
const authorUrl = `/author/${author.toLowerCase().replace(/\s+/g, "-")}`;
return <a href={authorUrl}>{author}</a>;
}

function tags(tags: string[]) {
return tags.map((tag) => {
const url = `/archive/${tag}`;
return (
<a
href={url}
class="border border-gray-300 text-gray-500 py-1 px-2 rounded inline-block hover:bg-gray-300"
>
{tag}
</a>
);
});
}
11 changes: 11 additions & 0 deletions src/components/TagLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function TagLink({ tag }: { tag: string }) {
return (
<a
id={`tag-link-${tag}`}
href={`/archive/${tag}`}
class="border border-light-mutedBackground dark:border-dark-mutedBackground text-light-mutedForeground dark:text-dark-mutedForeground py-1 px-2 rounded-lg inline-block hover:bg-light-mutedBackground dark:hover:bg-dark-mutedBackground"
>
{tag}
</a>
);
}
10 changes: 10 additions & 0 deletions src/components/Tags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import TagLink from "./TagLink.tsx";

export default function Tags({ tags, id }: { tags: string[]; id?: string }) {
return (
<div class="flex space-x-2" id={id}>
{tags.some((x) => x != null) &&
tags.map((tag, index) => <TagLink tag={tag} key={index} />)}
</div>
);
}
116 changes: 54 additions & 62 deletions src/islands/NavigationBar.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,67 @@
import { BlogOptions } from "../plugin/blog.ts";
import { useSignal } from "https://esm.sh/*@preact/[email protected]";
import { useSignal } from "@preact/signals";
import ThemeToggle from "./ThemeToggle.tsx";

export default function NavigationBar(
props: { active: string; class?: string; options: BlogOptions },
props: { class?: string; options: BlogOptions },
) {
const isHome = props.active == "/";
const isOpen = useSignal(false);
return (
<header class="sticky top-0 w-full z-10">
<div class="flex justify-between items-center px-4 py-2">
<a href="/" class="text-5xl font-bold">
{props.options.title}
</a>
<label
class="lg:hidden cursor-pointer z-20"
onClick={() => isOpen.value = !isOpen.value}
<div>
<label
class="lg:hidden cursor-pointer z-10 relative"
onClick={() => isOpen.value = !isOpen.value}
>
<span
class={`${
isOpen.value
? "block h-1 w-6 bg-light-foreground dark:bg-dark-foreground transform rotate-45"
: "block h-1 w-6 bg-light-foreground dark:bg-dark-foreground"
}`}
>
</span>
<span
class={`${
isOpen.value
? "block h-1 w-6 bg-light-foreground dark:bg-dark-foreground my-1 opacity-0"
: "block h-1 w-6 bg-light-foreground dark:bg-dark-foreground my-1"
}`}
>
<span
class={`${
isOpen.value
? "block h-1 w-6 bg-black transform rotate-45"
: "block h-1 w-6 bg-black"
}`}
>
</span>
<span
class={`${
isOpen.value
? "block h-1 w-6 bg-black my-1 opacity-0"
: "block h-1 w-6 bg-black my-1"
}`}
>
</span>
<span
class={`${
isOpen.value
? "block h-1 w-6 bg-black transform -rotate-45"
: "block h-1 w-6 bg-black"
}`}
>
</span>
</label>
<nav
</span>
<span
class={`${
isOpen.value
? "w-full fixed top-0 left-0 overflow-hidden transition-max-height duration-500 lg:relative max-h-screen bg-gray-300"
: "w-full fixed top-0 left-0 overflow-hidden transition-max-height duration-500 lg:relative max-h-0 lg:max-h-full"
? "block h-1 w-6 bg-light-foreground dark:bg-dark-foreground transform -rotate-45"
: "block h-1 w-6 bg-light-foreground dark:bg-dark-foreground"
}`}
>
<ul class="flex flex-col lg:flex-row justify-center items-center gap-4 mx-4 my-6 flex-wrap">
{Object.entries(props.options.navbarItems).map(([key, value]) => (
<li key={key}>
<a
href={value}
class={`p-2 block hover:underline ${
isHome
? props.active == value
? "text-black-900 font-bold"
: "text-black-900"
: props.active == value
? "text-black-600 font-bold"
: "text-black-600"
}`}
>
{key}
</a>
</li>
))}
</ul>
</nav>
</div>
</header>
</span>
</label>
<h2 id="primary-navigation" class="sr-only">Primary Navigation</h2>
<nav
aria-labelledby="primary-navigation"
class={`${
isOpen.value
? "w-full fixed top-0 left-0 overflow-hidden transition-max-height duration-500 lg:relative max-h-screen bg-light-mutedBackground dark:bg-dark-mutedBackground"
: "w-full fixed top-0 left-0 overflow-hidden transition-max-height duration-500 lg:relative max-h-0 lg:max-h-full"
}`}
>
<ul class="flex flex-col lg:flex-row justify-center items-center gap-4 mx-0 flex-wrap">
{Object.entries(props.options.navbarItems).map(([key, value]) => (
<li key={key}>
<a
href={value}
class="p-2 hover:underline aria-[current='page']:font-bold"
>
{key}
</a>
</li>
))}
<li>
<ThemeToggle />
</li>
</ul>
</nav>
</div>
);
}
61 changes: 61 additions & 0 deletions src/islands/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useSignal } from "@preact/signals";

const sun = (
<svg
class="w-6 h-6"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fill-rule="evenodd"
d="M13 3a1 1 0 1 0-2 0v2a1 1 0 1 0 2 0V3ZM6.3 5A1 1 0 0 0 5 6.2l1.4 1.5a1 1 0 0 0 1.5-1.5L6.3 5Zm12.8 1.3A1 1 0 0 0 17.7 5l-1.5 1.4a1 1 0 0 0 1.5 1.5L19 6.3ZM12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10Zm-9 4a1 1 0 1 0 0 2h2a1 1 0 1 0 0-2H3Zm16 0a1 1 0 1 0 0 2h2a1 1 0 1 0 0-2h-2ZM7.8 17.7a1 1 0 1 0-1.5-1.5L5 17.7A1 1 0 1 0 6.3 19l1.5-1.4Zm9.9-1.5a1 1 0 0 0-1.5 1.5l1.5 1.4a1 1 0 0 0 1.4-1.4l-1.4-1.5ZM13 19a1 1 0 1 0-2 0v2a1 1 0 1 0 2 0v-2Z"
clip-rule="evenodd"
/>
</svg>
);

const moon = (
<svg
class="w-6 h-6"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fill-rule="evenodd"
d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"
clip-rule="evenodd"
/>
</svg>
);

export default function ThemeToggle() {
const currentTheme = localStorage.getItem("theme") ?? "light";
const isDarkMode = useSignal(currentTheme === "dark");

const toggleTheme = () => {
isDarkMode.value = !isDarkMode.value;
const theme = isDarkMode.value ? "dark" : "light";

document.documentElement.classList.toggle("dark", isDarkMode.value);
const markdownBody = document.getElementById("markdown-body");
if (markdownBody) {
markdownBody.setAttribute(
"data-color-mode",
isDarkMode.value ? "dark" : "light",
);
}

document.cookie = `theme=${theme};path=/;max-age=31536000;SameSite=Lax`;
localStorage.setItem("theme", theme);
};

return (
<button onClick={toggleTheme} class="p-2 inline-block">
{isDarkMode.value ? sun : moon}
</button>
);
}
6 changes: 5 additions & 1 deletion src/plugin/blog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,11 @@ export function blogPlugin(
}],
islands: {
baseLocation: import.meta.url,
paths: ["../islands/NavigationBar.tsx", "../islands/Disqus.tsx"],
paths: [
"../islands/NavigationBar.tsx",
"../islands/Disqus.tsx",
"../islands/ThemeToggle.tsx",
],
},
};
}
21 changes: 11 additions & 10 deletions src/routes/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { PageProps } from "../../deps.ts";
import { FreshContext } from "../../deps.ts";
import Header from "../components/Header.tsx";
import { BlogOptions } from "../plugin/blog.ts";
import { themeFromRequest } from "../utils/theme.ts";

export function AppBuilder(options: BlogOptions) {
return (props: PageProps) => {
const { Component, route } = props;
// deno-lint-ignore require-await
return async (req: Request, ctx: FreshContext) => {
const themeClass = themeFromRequest(req);

const { Component } = ctx;
return (
<html>
<html class={themeClass === "dark" ? "dark" : ""}>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<main class="max-w-screen-md px-4 pt-16 mx-auto">
<Header
options={options}
active={route}
/>
<body class="max-w-screen-lg mx-auto px-4 bg-light-background dark:bg-dark-background text-light-foreground dark:text-dark-foreground min-h-screen flex flex-col">
<Header options={options} />
<main class="flex flex-grow min-h-full">
<Component />
</main>
</body>
Expand Down
Loading

0 comments on commit ab46b3d

Please sign in to comment.