Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: style blog #33

Merged
merged 10 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading