@@ -2,16 +2,21 @@ import Link from 'next/link';
2
2
import { BlogPostDataEntry } from '@nx/nx-dev/data-access-documents/node-only' ;
3
3
import Image from 'next/image' ;
4
4
import { BlogAuthors } from './authors' ;
5
- import { ChevronLeftIcon } from '@heroicons/react/24/outline' ;
5
+ import { ChevronLeftIcon , ListBulletIcon } from '@heroicons/react/24/outline' ;
6
6
import { renderMarkdown } from '@nx/nx-dev/ui-markdoc' ;
7
7
import { EpisodePlayer } from './episode-player' ;
8
8
import { YouTube } from '@nx/nx-dev/ui-common' ;
9
+ import { FeaturedBlogs } from './featured-blogs' ;
10
+ import { MoreBlogs } from './more-blogs' ;
11
+ import { ALL_TOPICS , type Topic } from './topics' ;
12
+ import { Metrics } from '@nx/nx-dev/ui-markdoc' ;
9
13
10
14
export interface BlogDetailsProps {
11
15
post : BlogPostDataEntry ;
16
+ allPosts : BlogPostDataEntry [ ] ;
12
17
}
13
18
14
- export function BlogDetails ( { post } : BlogDetailsProps ) {
19
+ export function BlogDetails ( { post, allPosts } : BlogDetailsProps ) {
15
20
const { node } = renderMarkdown ( post . content , {
16
21
filePath : post . filePath ?? '' ,
17
22
headingClass : 'scroll-mt-20' ,
@@ -23,30 +28,48 @@ export function BlogDetails({ post }: BlogDetailsProps) {
23
28
year : 'numeric' ,
24
29
} ) ;
25
30
31
+ // Find the primary topic of the current post
32
+ const primaryTopic = ALL_TOPICS . find ( ( topic : Topic ) =>
33
+ post . tags . includes ( topic . value . toLowerCase ( ) )
34
+ ) ;
35
+
36
+ const relatedPosts = allPosts
37
+ . filter (
38
+ ( p ) =>
39
+ p . slug !== post . slug && // Exclude current post
40
+ p . tags . some ( ( tag ) => post . tags . includes ( tag ) ) // Include posts with matching tags
41
+ )
42
+ . slice ( 0 , 5 ) ;
43
+
26
44
return (
27
45
< main id = "main" role = "main" className = "w-full py-8" >
28
- < div className = "mx-auto flex max-w-3xl justify-between px-4 lg:px-0" >
29
- < Link
30
- href = "/blog"
31
- className = "flex w-20 shrink-0 items-center gap-2 text-slate-400 hover:text-slate-800 dark:text-slate-600 dark:hover:text-slate-200"
32
- prefetch = { false }
33
- >
34
- < ChevronLeftIcon className = "h-3 w-3" />
35
- Blog
36
- </ Link >
37
- < div className = "flex max-w-sm flex-1 grow items-center justify-end gap-2" >
38
- < BlogAuthors authors = { post . authors } />
39
- < span className = "text-sm text-slate-400 dark:text-slate-600" >
40
- { formattedDate }
41
- </ span >
46
+ < div className = "mx-auto max-w-screen-md" >
47
+ { /* Top navigation and author info */ }
48
+ < div className = "mx-auto flex justify-between px-4" >
49
+ < Link
50
+ href = "/blog"
51
+ className = "flex w-20 shrink-0 items-center gap-2 text-slate-400 hover:text-slate-800 dark:text-slate-600 dark:hover:text-slate-200"
52
+ prefetch = { false }
53
+ >
54
+ < ChevronLeftIcon className = "h-3 w-3" />
55
+ Blog
56
+ </ Link >
57
+ < div className = "flex max-w-sm flex-1 grow items-center justify-end gap-2" >
58
+ < BlogAuthors authors = { post . authors } />
59
+ < span className = "text-sm text-slate-400 dark:text-slate-600" >
60
+ { formattedDate }
61
+ </ span >
62
+ </ div >
42
63
</ div >
43
- </ div >
44
- < div id = "content-wrapper" >
45
- < header className = "mx-auto mb-16 mt-8 max-w-3xl px-4 lg:px-0 " >
64
+
65
+ { /* Title */ }
66
+ < header className = "mx-auto mb-16 mt-8 px-4" >
46
67
< h1 className = "text-center text-4xl font-semibold text-slate-900 dark:text-white" >
47
68
{ post . title }
48
69
</ h1 >
49
70
</ header >
71
+
72
+ { /* Media content (podcast, youtube, or image) */ }
50
73
{ post . podcastYoutubeId && post . podcastSpotifyId ? (
51
74
< div className = "mx-auto mb-16 w-full max-w-screen-md" >
52
75
< EpisodePlayer
@@ -74,17 +97,73 @@ export function BlogDetails({ post }: BlogDetailsProps) {
74
97
</ div >
75
98
)
76
99
) }
77
- < div className = "mx-auto min-w-0 max-w-3xl flex-auto px-4 pb-24 lg:px-0 lg:pb-16" >
78
- < div className = "relative" >
100
+ </ div >
101
+
102
+ { /* Main grid layout */ }
103
+ < div className = "mx-auto max-w-7xl px-4 lg:px-8" >
104
+ < div className = "relative isolate grid grid-cols-1 gap-8 xl:grid-cols-[200px_minmax(0,1fr)_200px]" >
105
+ < div className = "hidden min-h-full xl:block" >
106
+ { post . metrics && (
107
+ < div className = "sticky top-28 pr-4 pt-8" >
108
+ < Metrics metrics = { post . metrics } variant = "vertical" />
109
+ </ div >
110
+ ) }
111
+ </ div >
112
+
113
+ { /* Middle column - main content */ }
114
+ < div className = "w-full min-w-0 md:mx-auto md:max-w-screen-md" >
115
+ { post . metrics && (
116
+ < div className = "mb-8 xl:hidden" >
117
+ < Metrics metrics = { post . metrics } variant = "horizontal" />
118
+ </ div >
119
+ ) }
79
120
< div
80
121
data-document = "main"
81
- className = "prose prose-lg prose-slate dark:prose-invert w-full max-w-none 2xl:max-w-4xl "
122
+ className = "prose prose-lg prose-slate dark:prose-invert w-full max-w-none"
82
123
>
83
124
{ node }
84
125
</ div >
85
126
</ div >
127
+
128
+ { /* Right column - for future sticky content */ }
129
+ < div className = "hidden xl:block" >
130
+ < div className = "sticky top-24" >
131
+ { /* Right sidebar content can go here */ }
132
+ </ div >
133
+ </ div >
86
134
</ div >
87
135
</ div >
136
+
137
+ { /* Related Posts Section */ }
138
+ { post . tags . length > 0 && relatedPosts . length > 0 && (
139
+ < section className = "mt-24 border-b border-t border-slate-200 bg-slate-50 py-24 sm:py-32 dark:border-slate-800 dark:bg-slate-900" >
140
+ < div className = "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8" >
141
+ < div className = "mx-auto max-w-2xl lg:mx-0 lg:max-w-none" >
142
+ < h2 className = "mb-8 flex items-center gap-3 text-2xl font-semibold text-slate-900 dark:text-white" >
143
+ { primaryTopic ? (
144
+ < >
145
+ < primaryTopic . icon className = "h-7 w-7" />
146
+ More { primaryTopic . label }
147
+ </ >
148
+ ) : (
149
+ < >
150
+ < ListBulletIcon className = "h-7 w-7" />
151
+ More Articles
152
+ </ >
153
+ ) }
154
+ </ h2 >
155
+ { /* Show list view on small screens */ }
156
+ < div className = "md:hidden" >
157
+ < MoreBlogs blogs = { relatedPosts } />
158
+ </ div >
159
+ { /* Show grid view on larger screens */ }
160
+ < div className = "hidden md:block" >
161
+ < FeaturedBlogs blogs = { relatedPosts } />
162
+ </ div >
163
+ </ div >
164
+ </ div >
165
+ </ section >
166
+ ) }
88
167
</ main >
89
168
) ;
90
169
}
0 commit comments