Skip to content

Commit 602c786

Browse files
committed
Prepare components for first blog article
1 parent 7d6d280 commit 602c786

File tree

15 files changed

+267
-69
lines changed

15 files changed

+267
-69
lines changed

src/assets/state/articles/article.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import { StaticImageData } from "next/image";
21
import { ReactNode } from "react";
2+
import { StaticImageData } from "next/image";
3+
34
import { IMember } from "../team";
5+
import { ITag } from "./tags";
46

57
type IArticle = {
68
title: string;
79
thumbnail: StaticImageData,
810
thumbnailAlt: string;
911

10-
tags: string[];
12+
tags: ITag[];
1113

1214
/**
1315
* A high level overview of the article E.g. Technology
@@ -16,8 +18,6 @@ type IArticle = {
1618

1719
slug: string;
1820

19-
20-
2121
author: IMember;
2222

2323
teaser: string;

src/assets/state/articles/tags.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
1+
export interface ITag {
2+
name: string,
3+
hidden?: boolean;
4+
}
5+
16
export default {
2-
"news": "News",
3-
"commitRocket": "Commit Rocket",
4-
"git": "Git"
5-
} satisfies Record<string, string>;
7+
"news": {
8+
name: "News",
9+
},
10+
"commitRocket": {
11+
name: "Commit Rocket"
12+
},
13+
"git": {
14+
name: "Git"
15+
},
16+
"intro": {
17+
name: "Introduction"
18+
},
19+
20+
} satisfies Record<string, ITag>;

src/components/content/List.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { cva, VariantProps as GetVariantProps } from "class-variance-authority";
2+
import React, { DetailedHTMLProps, ForwardedRef, forwardRef, HTMLAttributes, useMemo } from "react";
3+
import { twMerge } from "tailwind-merge";
4+
5+
export const style = cva("pl-5 flex flex-col", {
6+
variants: {
7+
numbered: {
8+
true: "list-decimal",
9+
false: "list-disc"
10+
}
11+
},
12+
defaultVariants: {
13+
numbered: false
14+
}
15+
});
16+
17+
export type TagProps = DetailedHTMLProps<HTMLAttributes<HTMLUListElement | HTMLOListElement>, HTMLUListElement | HTMLOListElement>;
18+
export type VariantProps = GetVariantProps<typeof style>;
19+
export type BaseProps = {};
20+
21+
export type ListProps = BaseProps & TagProps & VariantProps;
22+
23+
const List = forwardRef((
24+
{ numbered, className, ...props }: Omit<ListProps, "ref">,
25+
ref: ForwardedRef<HTMLUListElement | HTMLOListElement>
26+
) => {
27+
28+
const ListTag = numbered ? "ol" : "ul";
29+
30+
const computedClassName = useMemo(
31+
() => twMerge(style({ numbered }), className),
32+
[className]
33+
);
34+
35+
return (
36+
<ListTag
37+
//@ts-ignore
38+
ref={ref}
39+
className={computedClassName}
40+
{...props}
41+
/>
42+
);
43+
});
44+
45+
46+
const children = {
47+
Item: forwardRef((props: DetailedHTMLProps<HTMLAttributes<HTMLLIElement>, HTMLLIElement>, ref: ForwardedRef<HTMLLIElement>) => {
48+
return <li ref={ref} {...props} />;
49+
})
50+
};
51+
52+
export type IArticleList = typeof List & typeof children;
53+
54+
export default Object.assign({}, List, children) as IArticleList;

src/components/layout/Heading.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,22 @@ export const H4 = makeHeading(4, "text-2xl font-semibold md:text-3xl");
3232
export const H5 = makeHeading(5, "text-xl font-semibold md:text-2xl");
3333
export const H6 = makeHeading(6, "text-lg font-semibold md:text-xl");
3434

35-
export default { H1, H2, H3, H4, H5, H6 };
35+
export default {
36+
/** Styled `h1` */
37+
H1,
38+
39+
/** Styled `h2` */
40+
H2,
41+
42+
/** Styled `h3` */
43+
H3,
44+
45+
/** Styled `h4` */
46+
H4,
47+
48+
/** Styled `h5` */
49+
H5,
50+
51+
/** Styled `h6` */
52+
H6
53+
};

src/components/navigation/Link.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const style = cva("transition-colors gap-2", {
2020
}
2121
},
2222
defaultVariants: {
23+
color: "primary",
2324
underline: false
2425
}
2526
});
@@ -28,13 +29,14 @@ export type VariantProps = GetVariantProps<typeof style>;
2829

2930
type LinkProps = {
3031
underline?: boolean;
32+
nofollow?: boolean;
3133
external?: boolean;
32-
} & RequiredKeys<VariantProps, "color">
34+
} & VariantProps
3335
& Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof InternalLinkProps>
3436
& InternalLinkProps
3537
& React.RefAttributes<HTMLAnchorElement>;
3638

37-
const Link = ({ className, color, underline, children, external, hrefLang = "en", rel, ...props }: LinkProps) => {
39+
const Link = ({ className, color, underline, children, external, nofollow, hrefLang = "en", rel, ...props }: LinkProps) => {
3840

3941
const computedClassName = useMemo(
4042
() => twMerge(style({ color, underline }), className),
@@ -45,13 +47,13 @@ const Link = ({ className, color, underline, children, external, hrefLang = "en"
4547
<InternalLink
4648
className={computedClassName}
4749
hrefLang={hrefLang}
48-
rel={`${external ? "external" : ""} ${rel ?? ""}`}
50+
rel={`${external ? "external opener" : ""} ${nofollow ? "nofollow" : ""} ${rel ?? ""}`.trim()}
4951
target={external ? "_blank" : undefined}
5052
{...props}
5153
>
5254
{children}
5355
{external && <ArrowTopRightOnSquareIcon
54-
className="inline w-4 h-4"
56+
className="inline-block w-[1em] h-[1em]"
5557
/>}
5658
</InternalLink>
5759
);

src/components/pages/blog/ArticleBrief.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,20 @@ const ArticleBrief = ({ title, thumbnail, thumbnailAlt, readtime, teaser, author
6767
/>
6868
</NextLink>
6969
<div className="flex flex-col flex-1 gap-4 p-4 pt-0">
70-
{tags.length > 0 && <div className="flex flex-wrap gap-1">
71-
{tags.map((tag, i) => (
72-
<LinkButton
73-
key={i}
74-
href={makeTagUrl(tag)}
75-
color="secondary"
76-
className="px-2 py-1 text-xs font-semibold border"
77-
prefetch={false}
78-
>
79-
{tag}
80-
</LinkButton>
70+
{tags.length > 0 && <ul className="flex flex-wrap gap-1">
71+
{tags.map(({ name, hidden }, i) => (
72+
!hidden && <li key={i}>
73+
<LinkButton
74+
href={makeTagUrl(name)}
75+
color="secondary"
76+
className="px-2 py-1 text-xs font-semibold border"
77+
prefetch={false}
78+
>
79+
{name}
80+
</LinkButton>
81+
</li>
8182
))}
82-
</div>}
83+
</ul>}
8384
<Link href={url} color="fill-contrast" className="block text-2xl font-bold">
8485
{title}
8586
</Link>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import List from "@/components/content/List";
2+
import Heading from "@/components/layout/Heading";
3+
import Link from "@/components/navigation/Link";
4+
5+
import ArticleTableOfContent from "./ArticleTableOfContent";
6+
import React, { useMemo } from "react";
7+
import { twMerge } from "tailwind-merge";
8+
const A = {
9+
/** Wrapped `ol`/`ul` */
10+
Ls: List,
11+
/** Wrapped `li` */
12+
Li: List.Item,
13+
/** Table Of Content for the Article */
14+
TOC: ArticleTableOfContent,
15+
16+
/** `A` tag wrapped with custom styling */
17+
Link,
18+
19+
/**
20+
* A container `div` that inherits from the parent container.
21+
*
22+
* Usually used for containing related blocks of texts to style them in a consisten way.
23+
*/
24+
Container: ({ className, ...props }: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>) => {
25+
const computedClassName = useMemo(
26+
() => twMerge("flex flex-col gap-8", className),
27+
[className]
28+
);
29+
30+
return <div className={computedClassName} {...props} />;
31+
},
32+
33+
...Heading,
34+
};
35+
36+
export default A;

src/components/pages/blog/post/ArticleContext.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import { createContext, useContext } from "react";
33

44
export const ArticleContext = createContext<ArticleContext>({ ready: false });
55

6+
export interface Heading {
7+
text: string;
8+
level: number;
9+
id: string;
10+
}
11+
612
export type ArticleContext = ({
713
ready: true;
8-
headings: {
9-
text: string;
10-
level: number;
11-
id: string;
12-
}[];
14+
headings: Heading[];
1315
path: string;
1416
} & IArticle) | {
1517
ready: false;

src/components/pages/blog/post/ArticleMeta.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const ArticleMeta = ({ author, readtime, created, updated }: ArticleMetaProps) =
2525
return (
2626
<div className="flex flex-wrap items-center justify-center gap-4">
2727
<AuthorTag
28-
className="flex items-center gap-2 group/author"
28+
className="flex items-center gap-2 group/writer"
2929
//@ts-ignore
3030
href={author.links.length > 0 ? author.links[0].href : undefined}
3131
//@ts-ignore
@@ -34,16 +34,17 @@ const ArticleMeta = ({ author, readtime, created, updated }: ArticleMetaProps) =
3434
>
3535
<div className="overflow-hidden rounded-full">
3636
<img
37-
className="object-contain w-8 h-8 rounded-full aspect-square group-hover/author:scale-110 motion-safe:transition-all"
37+
aria-hidden
38+
className="object-contain w-8 h-8 rounded-full aspect-square group-hover/writer:scale-110 motion-safe:transition-all"
3839
src={author.image.src}
3940
width={author.image.width}
4041
height={author.image.height}
4142
alt="A picture of the author"
4243
/>
4344
</div>
4445
<div className="flex flex-col">
45-
<div>{author.fullName}</div>
46-
<div className="text-sm">{author.title}</div>
46+
<p aria-label="article-author" itemProp="author">{author.fullName}</p>
47+
<p className="text-sm" aria-hidden>{author.title}</p>
4748
</div>
4849
</AuthorTag>
4950
{DotSeparator}

0 commit comments

Comments
 (0)