mirror of
https://github.com/thiloho/thiloho.github.io.git
synced 2025-11-22 10:21:36 +01:00
Create more components to avoid duplication
This commit is contained in:
26
src/components/Button.astro
Normal file
26
src/components/Button.astro
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
25
src/components/Icon.astro
Normal 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]}
|
||||
/>
|
||||
@@ -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>
|
||||
|
||||
49
src/components/TableOfContents.astro
Normal file
49
src/components/TableOfContents.astro
Normal 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>
|
||||
28
src/components/Track.astro
Normal file
28
src/components/Track.astro
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
</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>
|
||||
<Track
|
||||
title={track.data.title}
|
||||
artist={track.data.artist}
|
||||
album={track.data.album}
|
||||
youtubeLink={track.data.youtubeLink}
|
||||
cover={track.data.cover}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user