feat(blog): grouped list, pinned posts, author/date/copy-link metadata (#20)

Co-authored-by: Alice <alice@prismshadow.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-07-22 22:22:02 +08:00
1 parent 85f8b9bf1a
commit 3806e4e441
14 files changed
+314 -49

No files matched your search

@@ -1,18 +1,32 @@
/** Blog category badge: product news (brand tint) vs release notes (neutral tint). */
/**
* Blog badges: category (news = brand fill, practice = brand outline, changelog =
* neutral) + the pinned marker, all sharing the same pill shape.
*/
import { S } from "../lib/strings";
import type { BlogCategory } from "../lib/blog";
const CATEGORY_STYLES: Record<BlogCategory, string> = {
news: "border-brand-200 bg-brand-50 text-brand-700 dark:border-brand-800 dark:bg-brand-950 dark:text-brand-300",
practice:
"border-brand-200 bg-transparent text-brand-700 dark:border-brand-800 dark:text-brand-300",
changelog:
"border-gray-200 bg-gray-50 text-gray-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300",
};
export function CategoryBadge({ category }: { category: BlogCategory }) {
const isNews = category === "news";
return (
<span
className={`rounded-full border px-2 py-0.5 text-[11px] font-medium ${
isNews
? "border-brand-200 bg-brand-50 text-brand-700 dark:border-brand-800 dark:bg-brand-950 dark:text-brand-300"
: "border-gray-200 bg-gray-50 text-gray-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300"
}`}
className={`rounded-full border px-2 py-0.5 text-[11px] font-medium ${CATEGORY_STYLES[category]}`}
>
{isNews ? S.blog.news : S.blog.changelog}
{S.blog[category]}
</span>
);
}
export function PinnedBadge() {
return (
<span className="rounded-full border border-brand-200 bg-brand-50 px-2 py-0.5 text-[11px] font-medium text-brand-700 dark:border-brand-800 dark:bg-brand-950 dark:text-brand-300">
{S.blog.pinned}
</span>
);
}
@@ -339,6 +339,14 @@ export function ExternalLinkIcon(props: IconProps) {
);
}
export function LinkIcon(props: IconProps) {
return (
<Icon {...props}>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</Icon>
);
}
export function KeyIcon(props: IconProps) {
return (
<Icon {...props}>