feat: polishing
This commit is contained in:
parent
77bf1bbdf4
commit
0b430e5d43
21 changed files with 235 additions and 144 deletions
|
@ -35,8 +35,8 @@ export default defineConfig({
|
|||
rehypePrettyCode,
|
||||
{
|
||||
theme: {
|
||||
light: 'vitesse-light',
|
||||
dark: 'vitesse-dark',
|
||||
light: 'github-light-high-contrast',
|
||||
dark: 'github-dark-high-contrast',
|
||||
},
|
||||
transformers: [
|
||||
transformerNotationDiff(),
|
||||
|
|
|
@ -16,7 +16,7 @@ const { entry } = Astro.props as {
|
|||
|
||||
const formattedDate = formatDate(entry.data.date)
|
||||
const readTime = readingTime(entry.body)
|
||||
const authors = await parseAuthors(entry.data.author ?? [])
|
||||
const authors = await parseAuthors(entry.data.authors ?? [])
|
||||
---
|
||||
|
||||
<div
|
||||
|
|
|
@ -7,6 +7,7 @@ type Props = {
|
|||
class?: string
|
||||
underline?: boolean
|
||||
'data-heading'?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
|
@ -19,7 +19,7 @@ const { prevPost, nextPost } = Astro.props
|
|||
>
|
||||
<div class="mr-2 flex-shrink-0">
|
||||
<ArrowLeft
|
||||
className="h-4 w-4 transition-transform group-hover:-translate-x-1"
|
||||
className="size-4 transition-transform group-hover:-translate-x-1"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-start overflow-hidden">
|
||||
|
@ -46,7 +46,7 @@ const { prevPost, nextPost } = Astro.props
|
|||
</div>
|
||||
<div class="ml-2 flex-shrink-0">
|
||||
<ArrowRight
|
||||
className="h-4 w-4 transition-transform group-hover:translate-x-1"
|
||||
className="size-4 transition-transform group-hover:translate-x-1"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
|
|
|
@ -1,37 +1,59 @@
|
|||
---
|
||||
import { Twitter, Github, Linkedin, Mail, Rss } from 'lucide-react'
|
||||
import { SITE } from '@consts'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
import Link from '@components/Link.astro'
|
||||
---
|
||||
|
||||
<ul class="not-prose flex flex-wrap gap-2" role="list">
|
||||
<li>
|
||||
<a href="#" class="inline-block" aria-label="Twitter" title="Twitter">
|
||||
<Twitter />
|
||||
</a>
|
||||
<Link
|
||||
href="#"
|
||||
aria-label="Twitter"
|
||||
title="Twitter"
|
||||
class={buttonVariants({ variant: 'outline', size: 'icon' })}
|
||||
>
|
||||
<Twitter className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="inline-block" aria-label="GitHub" title="GitHub">
|
||||
<Github />
|
||||
</a>
|
||||
<Link
|
||||
href="#"
|
||||
aria-label="GitHub"
|
||||
title="GitHub"
|
||||
class={buttonVariants({ variant: 'outline', size: 'icon' })}
|
||||
>
|
||||
<Github className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="inline-block" aria-label="LinkedIn" title="LinkedIn">
|
||||
<Linkedin />
|
||||
</a>
|
||||
<Link
|
||||
href="#"
|
||||
aria-label="LinkedIn"
|
||||
title="LinkedIn"
|
||||
class={buttonVariants({ variant: 'outline', size: 'icon' })}
|
||||
>
|
||||
<Linkedin className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="inline-block" aria-label="Email" title="Email">
|
||||
<Mail />
|
||||
</a>
|
||||
<Link
|
||||
href="#"
|
||||
aria-label="Email"
|
||||
title="Email"
|
||||
class={buttonVariants({ variant: 'outline', size: 'icon' })}
|
||||
>
|
||||
<Mail className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
<Link
|
||||
href={`${SITE.SITEURL}/rss.xml`}
|
||||
class="inline-block"
|
||||
aria-label="RSS feed"
|
||||
title="RSS feed"
|
||||
class={buttonVariants({ variant: 'outline', size: 'icon' })}
|
||||
>
|
||||
<Rss />
|
||||
</a>
|
||||
<Rss className="size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -31,7 +31,7 @@ export interface BadgeProps
|
|||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props}>
|
||||
<Hash className="mr-0.5 h-3 w-3" />
|
||||
<Hash className="size-3 -translate-x-0.5" />
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -98,7 +98,7 @@ const BreadcrumbEllipsis = ({
|
|||
className={cn('flex h-9 w-9 items-center justify-center', className)}
|
||||
{...props}
|
||||
>
|
||||
<DotsHorizontalIcon className="h-4 w-4" />
|
||||
<DotsHorizontalIcon className="size-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
)
|
||||
|
|
|
@ -36,7 +36,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||
<ChevronRightIcon className="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
|
@ -110,7 +110,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
|
@ -133,7 +133,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<DotFilledIcon className="h-4 w-4 fill-current" />
|
||||
<DotFilledIcon className="size-4 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Moon, Sun } from 'lucide-react'
|
||||
import { Laptop, Moon, Sun } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
@ -30,21 +30,29 @@ export function ModeToggle() {
|
|||
return (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="group"
|
||||
title="Toggle theme"
|
||||
>
|
||||
<Sun className="size-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute size-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setThemeState('theme-light')}>
|
||||
Light
|
||||
<Sun className="mr-2 size-4" />
|
||||
<span>Light</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setThemeState('dark')}>
|
||||
Dark
|
||||
<Moon className="mr-2 size-4" />
|
||||
<span>Dark</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setThemeState('system')}>
|
||||
System
|
||||
<Laptop className="mr-2 size-4" />
|
||||
<span>System</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
@ -4,7 +4,7 @@ description: 'This a dummy post written in the year 2023.'
|
|||
date: '2023-01-01'
|
||||
tags: ['dummy', 'placeholder']
|
||||
image: '/1200x630.png'
|
||||
author: ['enscribe']
|
||||
authors: ['enscribe']
|
||||
---
|
||||
|
||||
This is a dummy post written in the year 2023.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
title: '2024 Post'
|
||||
description: 'This a dummy post written in the year 2024.'
|
||||
description: 'This a dummy post written in the year 2024 (with multiple authors).'
|
||||
date: '2024-01-01'
|
||||
tags: ['dummy', 'placeholder']
|
||||
image: '/1200x630.png'
|
||||
author: ['enscribe']
|
||||
authors: ['enscribe', 'jktrn']
|
||||
---
|
||||
|
||||
This is a dummy post written in the year 2024.
|
||||
|
|
|
@ -4,7 +4,7 @@ description: 'There should not be a single reason why you would need a command p
|
|||
date: '2024-07-25'
|
||||
tags: ['webdev', 'opinion']
|
||||
image: '/1200x630.png'
|
||||
author: ['enscribe', 'jktrn']
|
||||
authors: ['enscribe']
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
@ -15,7 +15,7 @@ I have a lot of opinions about what makes a great blogging template. As a cumula
|
|||
|
||||
astro-erudite is written in Astro, a framework hyperoptimized for static content such as blogs. Aesthetically, it is also designed to be as boring as possible while still maintaining maximum functionality, as to allow for the freedom of the developer (or the designer they hire) to make their blog uniquely their own. Within the codebase of this template I've included many nuances that, in my opinion (and there will be many, many opinions here), make the developer experience significantly more pleasant. I've also _excluded_ many features that, frankly, you don't need.
|
||||
|
||||
### Welcoming some DX features
|
||||
## Welcoming some DX features
|
||||
|
||||
This is a non-exhaustive list of features I believe are essential for a frictionless developer experience:
|
||||
|
||||
|
@ -48,7 +48,7 @@ This is a non-exhaustive list of features I believe are essential for a friction
|
|||
|
||||
- The `cn(){:ts}` function is a utility function which combines [clsx](https://www.npmjs.com/package/clsx) and [tailwind-merge](https://www.npmjs.com/package/tailwind-merge), two packages which allow painless conditional class addition and concatenation:
|
||||
|
||||
```tsx
|
||||
```tsx title="src/lib/utils.ts" caption="A utility function for class name concatenation" showLineNumbers
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
|
@ -59,7 +59,7 @@ This is a non-exhaustive list of features I believe are essential for a friction
|
|||
|
||||
This needs to be in every single template. This is an example of it being used in my `<Link>{:tsx}` component:
|
||||
|
||||
```astro showLineNumbers title="src/components/Link.astro" caption="A custom Link component with tailwind-merge and clsx" {10-14}
|
||||
```astro showLineNumbers title="src/components/Link.astro" caption="A custom Link component with tailwind-merge and clsx" {17-21}
|
||||
---
|
||||
import { cn } from '@lib/utils'
|
||||
|
||||
|
@ -95,7 +95,7 @@ This is a non-exhaustive list of features I believe are essential for a friction
|
|||
|
||||
Awesome!
|
||||
|
||||
### Welcoming some UX features
|
||||
## Welcoming some UX features
|
||||
|
||||
Within the blog itself (as in the layout, appearance, and navigation) are features that I believe are essential for a great user experience:
|
||||
|
||||
|
@ -103,9 +103,11 @@ Within the blog itself (as in the layout, appearance, and navigation) are featur
|
|||
- Theme selectors should be self-explanatory. I've added one on the top right of the header, which is also `sticky` and not `absolute` such that it doesn't ignore the document flow (and thus you won't have to add `mt-20` to the top of every single page).
|
||||
- The table of contents of a post shouldn't be reduced to a `<details closed>{:html}` at the start of a blog post on desktop. You'd need to go to the top of the page to navigate through items. I've added a sticky `TableOfContents` component which always hangs out around the unused left side margin of a blog post. I'll still use a collapsible `<details>{:html}` element for the table of contents on mobile though since obviously a table of contents on the side is unfeasible for small screens.
|
||||
- Every page, except the homepage, will have a `<Breadcrumb>{:tsx}` component which shows you your current location in the site hierarchy. I don't see these often in blog templates even though they are so amazing for both discoverability (SEO and crawling) and user experience (the user always knows how "deep" they are in the site).
|
||||
- You can specify a post author via frontmatter. If this post author's slug is found within the `Authors` collection, then it will render particular info from that author's frontmatter file, `[author-name].md` (e.g. avatar, link to profile).
|
||||
- You can specify multiple post authors via frontmatter. If this post author's slug is found within the `Authors` collection, then it will render particular info from that author's frontmatter file, `[author-name].md` (e.g. avatar, link to profile). For example, the previous post (2024 Post) has two authors: "enscribe" and "jktrn", where "enscribe" is the only author with a custom avatar since "jktrn" is unregistered.
|
||||
- Each author will have their own page, which lists all of their posts. If you're the only author throughout the entire blog then you can simply disregard all aspects regarding both inserting authors and the `Authors` collection.
|
||||
- Each tag will also have their own page, which lists all of the posts under that tag!
|
||||
|
||||
### Foregoing some slop
|
||||
## Foregoing some slop
|
||||
|
||||
- Goodbye, [ESLint](https://eslint.org/)! There have been so many occasions where I've had to deal with blogging templates with in-built pre-commit hooks which enforce contrived and arbitrary linting rules that, frankly, I couldn't be bothered with. Obviously, linting is awesome for ensuring consistency and best practice, but that's with shared and large codebases. You're dealing with, at most, your mediocre MDX blog posts and some interior fetching. It's just not worth the hypertension.
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ const blog = defineCollection({
|
|||
draft: z.boolean().optional(),
|
||||
image: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
author: z.array(z.string()).optional(),
|
||||
authors: z.array(z.string()).optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import { cn } from '@lib/utils'
|
|||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/"
|
||||
><HomeIcon className="h-4 w-4" /></BreadcrumbLink
|
||||
><HomeIcon className="size-4" /></BreadcrumbLink
|
||||
>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
|
@ -37,7 +37,7 @@ import { cn } from '@lib/utils'
|
|||
>
|
||||
<div class="max-w-md">
|
||||
<h1 class="mb-4 text-3xl font-bold">404: Page not found</h1>
|
||||
<p class="prose dark:prose-invert">
|
||||
<p class="prose prose-neutral dark:prose-invert">
|
||||
Oops! The page you're looking for doesn't exist.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,7 @@ import { HomeIcon } from 'lucide-react'
|
|||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/"
|
||||
><HomeIcon className="h-4 w-4" /></BreadcrumbLink
|
||||
><HomeIcon className="size-4" /></BreadcrumbLink
|
||||
>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
|
@ -33,7 +33,7 @@ import { HomeIcon } from 'lucide-react'
|
|||
<section>
|
||||
<div class="min-w-full">
|
||||
<h1 class="mb-4 text-3xl font-bold">Some more about us</h1>
|
||||
<p class="prose dark:prose-invert">
|
||||
<p class="prose prose-neutral dark:prose-invert">
|
||||
{SITE.TITLE} is an opinionated, no-frills static blogging template built
|
||||
with Astro.
|
||||
</p>
|
||||
|
|
|
@ -32,12 +32,7 @@ const { author } = Astro.props
|
|||
const allPosts = await getCollection('blog')
|
||||
const authorPosts = allPosts
|
||||
.filter((post) => {
|
||||
if (typeof post.data.author === 'string') {
|
||||
return post.data.author === author.data.name && !post.data.draft
|
||||
} else if (post.data.author && 'slug' in post.data.author) {
|
||||
return post.data.author.slug === author.slug && !post.data.draft
|
||||
}
|
||||
return false
|
||||
return post.data.authors && post.data.authors.includes(author.slug)
|
||||
})
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
|
||||
---
|
||||
|
@ -51,7 +46,7 @@ const authorPosts = allPosts
|
|||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/"
|
||||
><HomeIcon className="h-4 w-4" /></BreadcrumbLink
|
||||
><HomeIcon className="size-4" /></BreadcrumbLink
|
||||
>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
|
|
|
@ -22,7 +22,7 @@ const authors = await getCollection('authors')
|
|||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/"
|
||||
><HomeIcon className="h-4 w-4" /></BreadcrumbLink
|
||||
><HomeIcon className="size-4" /></BreadcrumbLink
|
||||
>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
import { type CollectionEntry, getCollection, getEntry } from 'astro:content'
|
||||
import { type CollectionEntry, getCollection } from 'astro:content'
|
||||
import Layout from '@layouts/Layout.astro'
|
||||
import Container from '@components/Container.astro'
|
||||
import { formatDate, readingTime } from '@lib/utils'
|
||||
|
@ -59,7 +59,7 @@ const prevPost = getPrevPost(currentPostSlug)
|
|||
const post = Astro.props
|
||||
const { Content, headings } = await post.render()
|
||||
|
||||
const authors = await parseAuthors(post.data.author ?? [])
|
||||
const authors = await parseAuthors(post.data.authors ?? [])
|
||||
---
|
||||
|
||||
<Layout title={post.data.title} description={post.data.description}>
|
||||
|
@ -68,7 +68,7 @@ const authors = await parseAuthors(post.data.author ?? [])
|
|||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/"
|
||||
><HomeIcon className="h-4 w-4" /></BreadcrumbLink
|
||||
><HomeIcon className="size-4" /></BreadcrumbLink
|
||||
>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
|
@ -147,7 +147,7 @@ const authors = await parseAuthors(post.data.author ?? [])
|
|||
href={`/tags/${tag}`}
|
||||
class={badgeVariants({ variant: 'secondary' })}
|
||||
>
|
||||
<Hash className="mr-0.5 h-3 w-3" />
|
||||
<Hash className="-translate-x-0.5 size-3" />
|
||||
{tag}
|
||||
</a>
|
||||
))
|
||||
|
@ -162,7 +162,7 @@ const authors = await parseAuthors(post.data.author ?? [])
|
|||
|
||||
{headings.length > 0 && <TableOfContents headings={headings} />}
|
||||
|
||||
<article class="prose max-w-none dark:prose-invert">
|
||||
<article class="prose prose-neutral max-w-none dark:prose-invert">
|
||||
<Content />
|
||||
</article>
|
||||
|
||||
|
@ -185,13 +185,21 @@ const authors = await parseAuthors(post.data.author ?? [])
|
|||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const scrollToTopButton = document.getElementById('scroll-to-top')
|
||||
if (scrollToTopButton) {
|
||||
const footer = document.querySelector('footer')
|
||||
|
||||
if (scrollToTopButton && footer) {
|
||||
scrollToTopButton.addEventListener('click', () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
})
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
scrollToTopButton.classList.toggle('hidden', window.scrollY <= 300)
|
||||
const footerRect = footer.getBoundingClientRect()
|
||||
const isFooterVisible = footerRect.top <= window.innerHeight
|
||||
|
||||
scrollToTopButton.classList.toggle(
|
||||
'hidden',
|
||||
window.scrollY <= 300 || isFooterVisible,
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -39,7 +39,7 @@ const years = Object.keys(posts).sort((a, b) => parseInt(b) - parseInt(a))
|
|||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/"
|
||||
><HomeIcon className="h-4 w-4" /></BreadcrumbLink
|
||||
><HomeIcon className="size-4" /></BreadcrumbLink
|
||||
>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
|
|
|
@ -2,7 +2,17 @@
|
|||
import { getCollection } from 'astro:content'
|
||||
import Layout from '@layouts/Layout.astro'
|
||||
import Container from '@components/Container.astro'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import Link from '@components/Link.astro'
|
||||
import { badgeVariants } from '@components/ui/badge'
|
||||
import { Hash, HomeIcon } from 'lucide-react'
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb'
|
||||
|
||||
const blog = (await getCollection('blog')).filter((post) => !post.data.draft)
|
||||
|
||||
|
@ -11,20 +21,38 @@ const tags = blog
|
|||
.filter((tag, index, self) => self.indexOf(tag) === index)
|
||||
---
|
||||
|
||||
<Layout title="Tags" description="Tags">
|
||||
<Layout title="Tags" description="A list of all tags used in blog posts">
|
||||
<Container>
|
||||
<div class="space-y-10">
|
||||
<h1 class="text-3xl font-semibold">Tags</h1>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{
|
||||
tags.map((tag) => (
|
||||
<a href={`/tags/${tag}`}>
|
||||
<Badge variant="secondary" className="hover:bg-secondary/80">
|
||||
#{tag}
|
||||
</Badge>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
<div class="space-y-6">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="/"
|
||||
><HomeIcon className="size-4" /></BreadcrumbLink
|
||||
>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Tags</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<h1 class="text-3xl font-semibold">Tags</h1>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{
|
||||
tags.map((tag) => (
|
||||
<Link
|
||||
href={`/tags/${tag}`}
|
||||
class={badgeVariants({ variant: 'secondary' })}
|
||||
>
|
||||
<Hash className="-translate-x-0.5 size-3" />
|
||||
{tag}
|
||||
</Link>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
|
|
@ -64,84 +64,111 @@
|
|||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
@apply !border-border;
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
html {
|
||||
color-scheme: light;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
&.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
@apply bg-transparent;
|
||||
::-webkit-scrollbar-corner {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
code[data-theme*=' '] span {
|
||||
color: var(--shiki-light);
|
||||
}
|
||||
|
||||
.dark code[data-theme*=' '] span {
|
||||
color: var(--shiki-dark);
|
||||
}
|
||||
|
||||
pre {
|
||||
@apply max-h-[600px] overflow-auto rounded-xl border bg-secondary/20 py-4 text-sm leading-loose;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
counter-reset: line;
|
||||
@apply whitespace-pre-wrap;
|
||||
}
|
||||
|
||||
code[data-line-numbers] {
|
||||
counter-reset: line;
|
||||
}
|
||||
|
||||
code[data-line-numbers] > [data-line]::before {
|
||||
counter-increment: line;
|
||||
content: counter(line);
|
||||
@apply mr-4 inline-block w-4 text-right text-muted-foreground;
|
||||
}
|
||||
|
||||
pre > code > span[data-line] {
|
||||
@apply px-4;
|
||||
}
|
||||
|
||||
[data-highlighted-line] {
|
||||
@apply !bg-foreground/10;
|
||||
}
|
||||
|
||||
[data-highlighted-chars] > span {
|
||||
@apply rounded-md border !border-muted-foreground/40 !bg-muted p-1 font-semibold;
|
||||
}
|
||||
|
||||
[data-rehype-pretty-code-title] {
|
||||
@apply rounded-t-xl border-x border-t px-4 py-2 text-sm font-medium !text-foreground;
|
||||
}
|
||||
|
||||
[data-rehype-pretty-code-title] + pre {
|
||||
@apply mt-0 rounded-t-none;
|
||||
}
|
||||
|
||||
.diff.add {
|
||||
@apply bg-additive/15;
|
||||
}
|
||||
|
||||
.diff.remove {
|
||||
@apply bg-destructive/15;
|
||||
}
|
||||
|
||||
article :not(pre) > code {
|
||||
@apply relative rounded bg-muted/50 px-[0.3rem] py-[0.2rem] font-mono text-sm font-medium;
|
||||
}
|
||||
|
||||
article {
|
||||
@apply prose-headings:scroll-mt-20;
|
||||
|
||||
/* Removes background from <mark> elements */
|
||||
mark {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
/* Blanket syntax highlighting */
|
||||
code[data-theme*=' '] {
|
||||
span {
|
||||
color: var(--shiki-light);
|
||||
}
|
||||
|
||||
.dark & span {
|
||||
color: var(--shiki-dark);
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code {
|
||||
@apply relative rounded bg-muted/50 px-[0.3rem] py-[0.2rem] font-mono text-sm font-medium;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
figure[data-rehype-pretty-code-figure] {
|
||||
@apply relative;
|
||||
|
||||
/* Code block titles */
|
||||
[data-rehype-pretty-code-title] {
|
||||
@apply rounded-t-xl border-x border-t px-4 py-2 text-sm font-medium !text-foreground;
|
||||
|
||||
/* Remove top margin from code block if a title is present */
|
||||
& + pre {
|
||||
@apply mt-0 rounded-t-none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code block styles */
|
||||
pre {
|
||||
@apply static max-h-[600px] overflow-auto rounded-xl border bg-secondary/20 py-4 text-sm leading-loose;
|
||||
|
||||
/* Code block content */
|
||||
> code {
|
||||
@apply whitespace-pre-wrap;
|
||||
counter-reset: line;
|
||||
|
||||
/* For code blocks with line numbers */
|
||||
&[data-line-numbers] {
|
||||
> [data-line]::before {
|
||||
counter-increment: line;
|
||||
content: counter(line);
|
||||
@apply mr-4 inline-block w-4 text-right text-muted-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* For each line in the code block */
|
||||
> [data-line] {
|
||||
@apply px-4;
|
||||
}
|
||||
|
||||
/* Highlighted lines */
|
||||
[data-highlighted-line] {
|
||||
@apply bg-foreground/10;
|
||||
}
|
||||
|
||||
/* Highlighted characters */
|
||||
[data-highlighted-chars] > span {
|
||||
@apply bg-muted-foreground/40 py-[7px];
|
||||
}
|
||||
|
||||
/* Diff lines */
|
||||
.diff {
|
||||
&.add {
|
||||
@apply bg-additive/15;
|
||||
}
|
||||
&.remove {
|
||||
@apply bg-destructive/15;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy button */
|
||||
> button:has(> span) {
|
||||
@apply right-0.5 top-[3px] m-0 size-8 rounded-md bg-transparent p-1 backdrop-blur-none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue