Create more components to avoid duplication

This commit is contained in:
thiloho
2025-04-29 03:07:35 +02:00
parent 70c2afcb00
commit f6a5e2518f
8 changed files with 151 additions and 124 deletions

View File

@@ -0,0 +1,26 @@
---
interface Props {
href?: string;
variant?: "text" | "icon";
title?: string;
class?: string;
}
const { href, variant = "text", title, class: className = "" } = Astro.props;
const baseClasses =
"border-transparent inline-block border-b-2 p-2 cursor-pointer hover:border-neutral-300 hover:bg-neutral-100 hover:dark:border-neutral-600 hover:dark:bg-neutral-700";
const classes = `${baseClasses} ${variant === "icon" && href ? "inline-grid place-content-center" : ""} ${className}`;
---
{
href ? (
<a href={href} class={classes} title={title}>
<slot />
</a>
) : (
<button class={classes} title={title}>
<slot />
</button>
)
}

View File

@@ -1,5 +1,6 @@
---
import Date from "./Date.astro";
import Icon from "./Icon.astro";
interface Props {
title: string;
@@ -29,15 +30,7 @@ const { title, pubDate, modDate, slug } = Astro.props;
href={`https://github.com/thiloho/thiloho.github.io/edit/main/src/content/blog/${slug}/index.md`}
class="inline-flex items-center gap-1 text-blue-800 hover:no-underline dark:text-blue-300"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5"
>
<path d="m5.433 13.917 1.262-3.155A4 4 0 0 1 7.58 9.42l6.92-6.918a2.121 2.121 0 0 1 3 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 0 1-.65-.65Z" />
<path d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0 0 10 3H4.75A2.75 2.75 0 0 0 2 5.75v9.5A2.75 2.75 0 0 0 4.75 18h9.5A2.75 2.75 0 0 0 17 15.25V10a.75.75 0 0 0-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5Z" />
</svg>
<Icon name="edit" />
Edit this page
</a>
</hgroup>

25
src/components/Icon.astro Normal file
View File

@@ -0,0 +1,25 @@
---
interface Props {
name: "edit" | "rss" | "sun" | "moon" | "book";
class?: string;
}
const { name, class: className = "" } = Astro.props;
const icons = {
edit: '<path d="m5.433 13.917 1.262-3.155A4 4 0 0 1 7.58 9.42l6.92-6.918a2.121 2.121 0 0 1 3 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 0 1-.65-.65Z" /><path d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0 0 10 3H4.75A2.75 2.75 0 0 0 2 5.75v9.5A2.75 2.75 0 0 0 4.75 18h9.5A2.75 2.75 0 0 0 17 15.25V10a.75.75 0 0 0-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5Z" />',
rss: '<path d="M3.75 3a.75.75 0 0 0-.75.75v.5c0 .414.336.75.75.75H4c6.075 0 11 4.925 11 11v.25c0 .414.336.75.75.75h.5a.75.75 0 0 0 .75-.75V16C17 8.82 11.18 3 4 3h-.25Z" /><path d="M3 8.75A.75.75 0 0 1 3.75 8H4a8 8 0 0 1 8 8v.25a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1-.75-.75V16a6 6 0 0 0-6-6h-.25A.75.75 0 0 1 3 9.25v-.5ZM7 15a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z" />',
sun: '<path d="M10 2a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 2ZM10 15a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 15ZM10 7a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM15.657 5.404a.75.75 0 1 0-1.06-1.06l-1.061 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM6.464 14.596a.75.75 0 1 0-1.06-1.06l-1.06 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM18 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 18 10ZM5 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 5 10ZM14.596 15.657a.75.75 0 0 0 1.06-1.06l-1.06-1.061a.75.75 0 1 0-1.06 1.06l1.06 1.06ZM5.404 6.464a.75.75 0 0 0 1.06-1.06l-1.06-1.06a.75.75 0 1 0-1.061 1.06l1.06 1.06Z" />',
moon: '<path fill-rule="evenodd" d="M7.455 2.004a.75.75 0 0 1 .26.77 7 7 0 0 0 9.958 7.967.75.75 0 0 1 1.067.853A8.5 8.5 0 1 1 6.647 1.921a.75.75 0 0 1 .808.083Z" clip-rule="evenodd" />',
book: '<path d="M10.75 16.82A7.462 7.462 0 0 1 15 15.5c.71 0 1.396.098 2.046.282A.75.75 0 0 0 18 15.06v-11a.75.75 0 0 0-.546-.721A9.006 9.006 0 0 0 15 3a8.963 8.963 0 0 0-4.25 1.065V16.82ZM9.25 4.065A8.963 8.963 0 0 0 5 3c-.85 0-1.673.118-2.454.339A.75.75 0 0 0 2 4.06v11a.75.75 0 0 0 .954.721A7.506 7.506 0 0 1 5 15.5c1.579 0 3.042.487 4.25 1.32V4.065Z" />',
};
---
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class={`size-5 ${className}`}
aria-hidden="true"
set:html={icons[name]}
/>

View File

@@ -1,5 +1,7 @@
---
import Logo from "../content/TH.svg";
import Icon from "./Icon.astro";
import Button from "./Button.astro";
const routes = ["blog", "tracks"];
---
@@ -14,66 +16,21 @@ const routes = ["blog", "tracks"];
<div class="flex">
{
routes.map((route) => (
<a
class="inline-block border-b-2 border-transparent p-2 hover:border-neutral-300 hover:bg-neutral-100 hover:dark:border-neutral-600 hover:dark:bg-neutral-700"
href={`/${route}`}
>
<Button href={`/${route}`}>
{route
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ")}
</a>
</Button>
))
}
<button
class="theme-toggle cursor-pointer border-b-2 border-transparent p-2 hover:border-neutral-300 hover:bg-neutral-100 hover:dark:border-neutral-600 hover:dark:bg-neutral-700"
title="Toggle dark mode"
>
<!-- Moon -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5 dark:hidden"
>
<path
fill-rule="evenodd"
d="M7.455 2.004a.75.75 0 0 1 .26.77 7 7 0 0 0 9.958 7.967.75.75 0 0 1 1.067.853A8.5 8.5 0 1 1 6.647 1.921a.75.75 0 0 1 .808.083Z"
clip-rule="evenodd"></path>
</svg>
<!-- Sun -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="hidden size-5 dark:block"
>
<path
d="M10 2a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 2ZM10 15a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 15ZM10 7a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM15.657 5.404a.75.75 0 1 0-1.06-1.06l-1.061 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM6.464 14.596a.75.75 0 1 0-1.06-1.06l-1.06 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM18 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 18 10ZM5 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 5 10ZM14.596 15.657a.75.75 0 0 0 1.06-1.06l-1.06-1.061a.75.75 0 1 0-1.06 1.06l1.06 1.06ZM5.404 6.464a.75.75 0 0 0 1.06-1.06l-1.06-1.06a.75.75 0 1 0-1.061 1.06l1.06 1.06Z"
></path>
</svg>
</button>
<a
class="inline-grid cursor-pointer place-content-center border-b-2 border-transparent p-2 hover:border-neutral-300 hover:bg-neutral-100 hover:dark:border-neutral-600 hover:dark:bg-neutral-700"
title="RSS feed"
href="/rss.xml"
>
<!-- RSS -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5"
>
<path
d="M3.75 3a.75.75 0 0 0-.75.75v.5c0 .414.336.75.75.75H4c6.075 0 11 4.925 11 11v.25c0 .414.336.75.75.75h.5a.75.75 0 0 0 .75-.75V16C17 8.82 11.18 3 4 3h-.25Z"
></path>
<path
d="M3 8.75A.75.75 0 0 1 3.75 8H4a8 8 0 0 1 8 8v.25a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1-.75-.75V16a6 6 0 0 0-6-6h-.25A.75.75 0 0 1 3 9.25v-.5ZM7 15a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"
></path>
</svg>
</a>
<Button variant="icon" title="Toggle dark mode" class="theme-toggle">
<Icon name="moon" class="dark:hidden" />
<Icon name="sun" class="hidden dark:block" />
</Button>
<Button variant="icon" href="/rss.xml" title="RSS feed">
<Icon name="rss" />
</Button>
</div>
</div>
</nav>

View File

@@ -0,0 +1,49 @@
---
import Icon from "./Icon.astro";
import type { MarkdownHeading } from "astro";
interface Props {
headings: MarkdownHeading[];
}
const { headings } = Astro.props;
---
<details
class="toc sticky top-10 z-10 -translate-y-px border border-neutral-300 bg-white lg:top-2 dark:border-neutral-600 dark:bg-neutral-800"
>
<summary
title="Table of contents"
class="mx-auto flex w-fit cursor-pointer list-none border-b-2 border-transparent p-2 hover:border-neutral-300 hover:bg-neutral-100 hover:dark:border-neutral-600 hover:dark:bg-neutral-700"
>
<Icon name="book" />
</summary>
<div class="not-prose max-h-[calc(100vh-8rem)] overflow-y-scroll p-2">
<p class="text-center">
<strong class="text-sm">Table of Contents</strong>
</p>
<ul>
{
headings.length ? (
headings
.filter(({ depth }) => depth === 2)
.map((heading) => (
<li>
<a
class="block px-2 py-1 text-center text-blue-800 hover:underline dark:text-blue-300"
href={`#${heading.slug}`}
aria-labelledby={`Section: ${heading.slug}`}
>
{heading.text}
</a>
</li>
))
) : (
<p class="text-center text-red-800 dark:text-red-300">
No headings to generate entries for yet
</p>
)
}
</ul>
</div>
</details>

View File

@@ -0,0 +1,28 @@
---
import { Image } from "astro:assets";
interface Props {
title: string;
artist: string;
album: string;
youtubeLink: string;
cover: any;
}
const { title, artist, album, youtubeLink, cover } = Astro.props;
---
<figure>
<a href={youtubeLink}>
<Image
src={cover}
alt={`Cover for the song '${title}' by artist(s) '${artist}'`}
class="border border-neutral-400 duration-300 hover:scale-105 dark:border-neutral-500"
/>
</a>
<figcaption class="flex flex-col p-4 text-center">
<p class="text-lg font-bold">{title}</p>
<p>{artist}</p>
<p class="text-sm">{album}</p>
</figcaption>
</figure>

View File

@@ -1,6 +1,7 @@
---
import PageLayout from "../../layouts/PageLayout.astro";
import { getCollection, getEntry, render } from "astro:content";
import TableOfContents from "../../components/TableOfContents.astro";
export const getStaticPaths = async () => {
const allArticles = await getCollection("blog");
@@ -27,53 +28,7 @@ const { Content, headings } = await render(article);
modDate={article.data.modDate}
{slug}
>
<details
class="toc sticky top-10 z-10 -translate-y-px border border-neutral-300 bg-white lg:top-2 dark:border-neutral-600 dark:bg-neutral-800"
>
<summary
title="Table of contents"
class="mx-auto flex w-fit cursor-pointer list-none border-b-2 border-transparent p-2 hover:border-neutral-300 hover:bg-neutral-100 hover:dark:border-neutral-600 hover:dark:bg-neutral-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5"
>
<path
d="M10.75 16.82A7.462 7.462 0 0 1 15 15.5c.71 0 1.396.098 2.046.282A.75.75 0 0 0 18 15.06v-11a.75.75 0 0 0-.546-.721A9.006 9.006 0 0 0 15 3a8.963 8.963 0 0 0-4.25 1.065V16.82ZM9.25 4.065A8.963 8.963 0 0 0 5 3c-.85 0-1.673.118-2.454.339A.75.75 0 0 0 2 4.06v11a.75.75 0 0 0 .954.721A7.506 7.506 0 0 1 5 15.5c1.579 0 3.042.487 4.25 1.32V4.065Z"
></path>
</svg>
</summary>
<div class="not-prose max-h-[calc(100vh-8rem)] overflow-y-scroll p-2">
<p class="text-center">
<strong class="text-sm">Table of Contents</strong>
</p>
<ul>
{
headings.length ? (
headings
.filter(({ depth }) => depth === 2)
.map((heading) => (
<li>
<a
class="block px-2 py-1 text-center text-blue-800 hover:underline dark:text-blue-300"
href={`#${heading.slug}`}
aria-labelledby={`Section: ${heading.slug}`}
>
{heading.text}
</a>
</li>
))
) : (
<p class="text-center text-red-800 dark:text-red-300">
No headings to generate entries for yet
</p>
)
}
</ul>
</div>
</details>
<TableOfContents {headings} />
<Content />
</PageLayout>

View File

@@ -2,6 +2,7 @@
import PageLayout from "../layouts/PageLayout.astro";
import { Image } from "astro:assets";
import { getCollection } from "astro:content";
import Track from "../components/Track.astro";
const tracks = await getCollection("tracks");
---
@@ -20,20 +21,13 @@ const tracks = await getCollection("tracks");
>
{
tracks.map((track) => (
<figure>
<a href={track.data.youtubeLink}>
<Image
src={track.data.cover}
alt={`Cover for the song '${track.data.title}' by artist(s) '${track.data.artist}'`}
class="border border-neutral-400 duration-300 hover:scale-105 dark:border-neutral-500"
<Track
title={track.data.title}
artist={track.data.artist}
album={track.data.album}
youtubeLink={track.data.youtubeLink}
cover={track.data.cover}
/>
</a>
<figcaption class="flex flex-col p-4 text-center">
<p class="text-lg font-bold">{track.data.title}</p>
<p>{track.data.artist}</p>
<p class="text-sm">{track.data.album}</p>
</figcaption>
</figure>
))
}
</div>