Skip to content

Commit ffe8e51

Browse files
committed
style: update blog list styling
Also fix the layout shifts caused by nested anchor tags on AuthorAvatar
1 parent 1e3b943 commit ffe8e51

7 files changed

Lines changed: 79 additions & 63 deletions

File tree

app/components/AuthorAvatar.vue

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@ import type { ResolvedAuthor } from '#shared/schemas/blog'
44
const props = defineProps<{
55
author: ResolvedAuthor
66
size?: 'sm' | 'md' | 'lg'
7-
disableLink?: boolean
87
}>()
98
10-
const { t } = useI18n()
11-
129
const sizeClasses = computed(() => {
1310
switch (props.size ?? 'md') {
1411
case 'sm':
@@ -28,23 +25,13 @@ const initials = computed(() =>
2825
.toUpperCase()
2926
.slice(0, 2),
3027
)
31-
32-
const isLink = computed(() => !props.disableLink && props.author.profileUrl)
33-
const ariaLabel = computed(() => t('blog.author.view_profile', { name: props.author.name }))
3428
</script>
3529

3630
<template>
37-
<component
38-
:is="isLink ? 'a' : 'div'"
39-
:href="isLink ? author.profileUrl : undefined"
40-
:target="isLink ? '_blank' : undefined"
41-
:rel="isLink ? 'noopener noreferrer' : undefined"
42-
:aria-label="isLink ? ariaLabel : undefined"
31+
<div
4332
class="shrink-0 flex items-center justify-center border border-border rounded-full bg-bg-muted overflow-hidden"
44-
:class="[sizeClasses, { 'hover:border-primary transition-colors': isLink }]"
45-
:title="author.name"
33+
:class="[sizeClasses]"
4634
>
47-
<span v-if="!isLink" class="sr-only">{{ author.name }}</span>
4835
<img
4936
v-if="author.avatar"
5037
:src="author.avatar"
@@ -54,5 +41,5 @@ const ariaLabel = computed(() => t('blog.author.view_profile', { name: props.aut
5441
<span v-else class="text-fg-subtle font-mono" aria-hidden="true">
5542
{{ initials }}
5643
</span>
57-
</component>
44+
</div>
5845
</template>

app/components/AuthorList.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ const { resolvedAuthors } = useAuthorProfiles(props.authors)
2929
</div>
3030
</div>
3131

32-
<!-- Compact variant: stacked avatars -->
33-
<div v-else class="flex items-center gap-2">
34-
<div class="flex items-center gap-1">
32+
<!-- Compact variant: no avatars -->
33+
<div v-else class="flex items-center gap-2 min-w-0">
34+
<div class="flex items-center">
3535
<AuthorAvatar
36-
v-for="author in resolvedAuthors"
36+
v-for="(author, index) in resolvedAuthors"
3737
:key="author.name"
3838
:author="author"
3939
size="md"
4040
class="ring-2 ring-bg"
41+
:class="index > 0 ? '-ms-3' : ''"
4142
/>
4243
</div>
4344
<span class="text-xs text-fg-muted font-mono truncate">

app/components/BlogPostListCard.vue

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const emit = defineEmits<{
2525

2626
<template>
2727
<article
28-
class="group card-interactive relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
28+
class="group relative focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
2929
>
3030
<NuxtLink
3131
:to="`/blog/${path}`"
@@ -36,21 +36,18 @@ const emit = defineEmits<{
3636
>
3737
<!-- Text Content -->
3838
<div class="flex-1 min-w-0 text-left">
39+
<span class="text-xs text-fg-muted font-mono">{{ published }}</span>
3940
<h2
40-
class="font-mono text-base font-medium text-fg group-hover:text-primary transition-colors hover:underline"
41+
class="font-mono text-xl font-medium text-fg group-hover:text-primary transition-colors hover:underline"
4142
>
4243
{{ title }}
4344
</h2>
44-
45-
<div class="flex items-center gap-2 text-xs text-fg-muted font-mono mt-1">
46-
<AuthorList :authors="authors" />
47-
<span>•</span>
48-
<span>{{ published }}</span>
49-
</div>
50-
51-
<p v-if="excerpt" class="text-sm text-muted-foreground mt-2 line-clamp-2 no-underline">
45+
<p v-if="excerpt" class="text-fg-muted leading-relaxed line-clamp-2 no-underline">
5246
{{ excerpt }}
5347
</p>
48+
<div class="flex flex-wrap items-center gap-2 text-xs text-fg-muted font-mono">
49+
<AuthorList :authors="authors" />
50+
</div>
5451
</div>
5552

5653
<span

app/pages/blog/index.vue

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<script setup lang="ts">
2+
const router = useRouter()
3+
24
import type { BlogPostFrontmatter } from '#shared/schemas/blog'
5+
import { isModEventDivert } from '@atproto/api/dist/client/types/tools/ozone/moderation/defs'
36
47
const blogModules = import.meta.glob<BlogPostFrontmatter>('./*.md', { eager: true })
58
@@ -17,44 +20,69 @@ const placeHolder = ['atproto', 'nuxt']
1720
1821
definePageMeta({
1922
name: 'blog',
20-
// alias: ['/:path(.*)*'],
2123
})
2224
2325
useSeoMeta({
24-
title: () => $t('blog.title'),
25-
description: () => $t('blog.description'),
26+
title: () => `${$t('blog.title')} - npmx`,
27+
ogTitle: () => `${$t('blog.title')} - npmx`,
28+
twitterTitle: () => `${$t('blog.title')} - npmx`,
29+
description: () => $t('blog.meta_description'),
30+
ogDescription: () => $t('blog.meta_description'),
31+
twitterDescription: () => $t('blog.meta_description'),
2632
})
2733
</script>
2834

2935
<template>
30-
<main class="container py-8 sm:py-12 w-full">
31-
<header class="mb-8 pb-8 border-b border-border flex place-content-center text-3xl">
32-
<h1 class="">blog...</h1>
33-
</header>
34-
35-
<article v-if="posts && posts.length > 0" class="mx-30 space-y-4">
36-
<BlogPostListCard
37-
v-for="(post, idx) in posts"
38-
:key="`${post.authors.map(a => a.name).join('-')}-${post.title}`"
39-
:authors="post.authors"
40-
:title="post.title"
41-
:path="post.slug"
42-
:excerpt="post.excerpt || post.description || 'No Excerpt Available'"
43-
:topics="Array.isArray(post.tags) ? post.tags : placeHolder"
44-
:published="post.date"
45-
:index="idx"
46-
@focus="i => console.log('Hovered:', i)"
47-
/>
48-
<!-- :selected="toSuggestionIndex(unifiedSelectedIndex) === idx" -->
49-
<!-- :is-exact-match="
50-
(exactMatchType === 'org' && suggestion.type === 'org') ||
51-
(exactMatchType === 'user' && suggestion.type === 'user')
52-
" -->
53-
<!-- @focus="handleBlogPostSelect" -->
54-
<!-- </div> -->
36+
<main class="container w-full flex-1 py-12 sm:py-16 overflow-x-hidden">
37+
<article class="max-w-2xl mx-auto">
38+
<header class="mb-12">
39+
<div class="flex items-baseline justify-between gap-4 mb-4">
40+
<h1 class="font-mono text-3xl sm:text-4xl font-medium">
41+
{{ $t('blog.heading') }}
42+
</h1>
43+
<button
44+
type="button"
45+
class="inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
46+
@click="router.back()"
47+
>
48+
<span class="i-carbon:arrow-left rtl-flip w-4 h-4" aria-hidden="true" />
49+
<span class="hidden sm:inline">{{ $t('nav.back') }}</span>
50+
</button>
51+
</div>
52+
<p class="text-fg-muted text-lg">
53+
{{ $t('tagline') }}
54+
</p>
55+
</header>
56+
<article v-if="posts && posts.length > 0" class="flex flex-col gap-8">
57+
<template
58+
v-for="(post, idx) in posts"
59+
:key="`${post.authors.map(a => a.name).join('-')}-${post.title}`"
60+
>
61+
<BlogPostListCard
62+
:authors="post.authors"
63+
:title="post.title"
64+
:path="post.slug"
65+
:excerpt="post.excerpt || post.description || 'No Excerpt Available'"
66+
:topics="Array.isArray(post.tags) ? post.tags : placeHolder"
67+
:published="post.date"
68+
:index="idx"
69+
@focus="i => console.log('Hovered:', i)"
70+
/>
71+
<hr v-if="idx < posts.length - 1" class="border-border-subtle" />
72+
</template>
73+
<!-- :selected="toSuggestionIndex(unifiedSelectedIndex) === idx" -->
74+
<!-- :is-exact-match="
75+
(exactMatchType === 'org' && suggestion.type === 'org') ||
76+
(exactMatchType === 'user' && suggestion.type === 'user')
77+
" -->
78+
<!-- @focus="handleBlogPostSelect" -->
79+
<!-- </div> -->
80+
</article>
81+
82+
<isModEventDivert v-else class="text-center py-20 text-fg-subtle"
83+
>No posts found.</isModEventDivert
84+
>
5585
</article>
56-
57-
<article v-else class="text-center py-20 text-fg-subtle">No posts found.</article>
5886
</main>
5987
</template>
6088

i18n/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@
6161
},
6262
"blog": {
6363
"title": "Blog",
64-
"description": "Insights and updates from the npmx community",
64+
"heading": "blog",
65+
"meta_description": "Insights and updates from the npmx community",
6566
"author": {
6667
"view_profile": "View {name}'s profile on Bluesky"
6768
}

lunaria/files/en-GB.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@
6161
},
6262
"blog": {
6363
"title": "Blog",
64-
"description": "Insights and updates from the npmx community",
64+
"heading": "blog",
65+
"meta_description": "Insights and updates from the npmx community",
6566
"author": {
6667
"view_profile": "View {name}'s profile on Bluesky"
6768
}

lunaria/files/en-US.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@
6161
},
6262
"blog": {
6363
"title": "Blog",
64-
"description": "Insights and updates from the npmx community",
64+
"heading": "blog",
65+
"meta_description": "Insights and updates from the npmx community",
6566
"author": {
6667
"view_profile": "View {name}'s profile on Bluesky"
6768
}

0 commit comments

Comments
 (0)