@x-wave/blog 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +496 -63
- package/index.js +1194 -1270
- package/package.json +7 -4
- package/styles/index.css +2 -2
- package/vite-config/blog-discovery.d.ts +15 -0
- package/vite-config/blog-discovery.d.ts.map +1 -0
- package/vite-config/blog-discovery.js +131 -0
- package/vite-config/blog-discovery.js.map +1 -0
- package/vite-config/index.d.ts +5 -0
- package/vite-config/index.d.ts.map +1 -0
- package/vite-config/index.js +9 -0
- package/vite-config/index.js.map +1 -0
- package/vite-config/meta-tags.d.ts +28 -0
- package/vite-config/meta-tags.d.ts.map +1 -0
- package/vite-config/meta-tags.js +129 -0
- package/vite-config/meta-tags.js.map +1 -0
- package/vite-config/setup-ssg.d.ts +41 -0
- package/vite-config/setup-ssg.d.ts.map +1 -0
- package/vite-config/setup-ssg.js +84 -0
- package/vite-config/setup-ssg.js.map +1 -0
- package/vite-config/static-gen-plugin.d.ts +19 -0
- package/vite-config/static-gen-plugin.d.ts.map +1 -0
- package/vite-config/static-gen-plugin.js +56 -0
- package/vite-config/static-gen-plugin.js.map +1 -0
- package/vite-config/types.d.ts +30 -0
- package/vite-config/types.d.ts.map +1 -0
- package/vite-config/types.js +5 -0
- package/vite-config/types.js.map +1 -0
package/README.md
CHANGED
|
@@ -1,54 +1,236 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @x-wave/blog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A responsive, multi-language documentation framework for React + Vite applications. Ships with TypeScript, i18n (i18next), MDX support, dark mode, and built-in navigation.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
├── styles/ # SCSS + compiled CSS
|
|
15
|
-
└── types/ # Shared TypeScript types
|
|
16
|
-
```
|
|
7
|
+
- **Multi-language support**: Ship 3+ languages with a single codebase (en, es, zh included)
|
|
8
|
+
- **MDX content**: Write docs in Markdown with React components
|
|
9
|
+
- **Dark mode**: Built-in light/dark/system theme toggle with localStorage persistence
|
|
10
|
+
- **Advanced mode**: Optional Simple/Advanced content variants for the same page
|
|
11
|
+
- **Mobile responsive**: Automatic sidebar → mobile menu on small screens
|
|
12
|
+
- **Headless**: No styling opinions—includes SCSS variables for full customization
|
|
13
|
+
- **HMR-friendly**: Vite development with Hot Module Replacement for instant feedback
|
|
17
14
|
|
|
18
|
-
##
|
|
15
|
+
## Installation
|
|
19
16
|
|
|
20
|
-
###
|
|
17
|
+
### npm
|
|
21
18
|
|
|
22
19
|
```bash
|
|
23
|
-
|
|
20
|
+
npm install @x-wave/blog
|
|
24
21
|
```
|
|
25
22
|
|
|
26
|
-
###
|
|
23
|
+
### pnpm
|
|
27
24
|
|
|
28
25
|
```bash
|
|
29
|
-
pnpm
|
|
26
|
+
pnpm add @x-wave/blog
|
|
30
27
|
```
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
### yarn
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
yarn add @x-wave/blog
|
|
33
|
+
```
|
|
33
34
|
|
|
34
|
-
##
|
|
35
|
+
## Quick setup
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
### 1. Create your app structure
|
|
37
38
|
|
|
38
39
|
```
|
|
39
|
-
|
|
40
|
-
├──
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
├──
|
|
45
|
-
└──
|
|
40
|
+
src/
|
|
41
|
+
├── App.tsx # Your app component
|
|
42
|
+
├── main.tsx # Entry point
|
|
43
|
+
├── navigation.ts # Site navigation definition
|
|
44
|
+
├── utils.ts # Content loaders
|
|
45
|
+
├── logo.svg # Optional: your logo
|
|
46
|
+
└── docs/
|
|
47
|
+
├── en/
|
|
48
|
+
│ ├── welcome.mdx
|
|
49
|
+
│ ├── glossary.mdx
|
|
50
|
+
│ └── faq.mdx
|
|
51
|
+
├── es/
|
|
52
|
+
│ ├── welcome.mdx
|
|
53
|
+
│ ├── glossary.mdx
|
|
54
|
+
│ └── faq.mdx
|
|
55
|
+
└── zh/
|
|
56
|
+
├── welcome.mdx
|
|
57
|
+
├── glossary.mdx
|
|
58
|
+
└── faq.mdx
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. Set up i18n and styles
|
|
62
|
+
|
|
63
|
+
Import the i18n setup and framework styles in your app entry point:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// src/main.tsx
|
|
67
|
+
import '@x-wave/blog/locales' // Initialises i18next with en, es, zh
|
|
68
|
+
import '@x-wave/blog/styles' // Compiled CSS with variables and component styles (required)
|
|
69
|
+
import { createRoot } from 'react-dom/client'
|
|
70
|
+
import App from './App'
|
|
71
|
+
|
|
72
|
+
createRoot(document.getElementById('root')!).render(<App />)
|
|
46
73
|
```
|
|
47
74
|
|
|
48
|
-
|
|
75
|
+
> **The styles import is required** for the UI components and layout to render correctly. This imports a single compiled CSS file (`index.css`) that contains all framework styles, CSS variables, and component styles.
|
|
76
|
+
|
|
77
|
+
**Add custom translations:**
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// src/main.tsx
|
|
81
|
+
import '@x-wave/blog/locales'
|
|
82
|
+
import i18next from 'i18next'
|
|
83
|
+
|
|
84
|
+
// Add French translations
|
|
85
|
+
i18next.addResourceBundle('fr', 'translation', {
|
|
86
|
+
language: 'Français',
|
|
87
|
+
'ui.simple': 'Simple',
|
|
88
|
+
'ui.advanced': 'Avancé',
|
|
89
|
+
// ... other keys
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Define your navigation
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// src/navigation.ts
|
|
97
|
+
import type { NavigationEntry } from '@x-wave/blog/types'
|
|
98
|
+
|
|
99
|
+
export const NAVIGATION_DATA: NavigationEntry[] = [
|
|
100
|
+
{
|
|
101
|
+
title: 'docs.welcome',
|
|
102
|
+
slug: 'welcome',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: 'Help',
|
|
106
|
+
defaultOpen: true,
|
|
107
|
+
items: [
|
|
108
|
+
{
|
|
109
|
+
title: 'docs.glossary',
|
|
110
|
+
slug: 'glossary',
|
|
111
|
+
showTableOfContents: true,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
title: 'docs.faq',
|
|
115
|
+
slug: 'faq',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 4. Create content loaders
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// src/utils.ts
|
|
126
|
+
import { createBlogUtils } from '@x-wave/blog'
|
|
127
|
+
|
|
128
|
+
// Vite glob import – resolved relative to this file
|
|
129
|
+
const mdxFiles = import.meta.glob('./docs/**/*.mdx', {
|
|
130
|
+
query: '?raw',
|
|
131
|
+
import: 'default',
|
|
132
|
+
eager: false,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Export all blog utilities in a single object
|
|
136
|
+
export const blog = createBlogUtils(mdxFiles)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 5. Wrap your app with BlogProvider
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
// src/App.tsx
|
|
143
|
+
import { BlogProvider, DocumentationRoutes } from '@x-wave/blog'
|
|
144
|
+
import { Navigate, Route, BrowserRouter as Router, Routes } from 'react-router-dom'
|
|
145
|
+
import { blog } from './utils'
|
|
146
|
+
import { NAVIGATION_DATA } from './navigation'
|
|
147
|
+
|
|
148
|
+
const SUPPORTED_LANGUAGES = ['en', 'es', 'zh'] as const
|
|
149
|
+
|
|
150
|
+
export default function App() {
|
|
151
|
+
return (
|
|
152
|
+
<BlogProvider
|
|
153
|
+
config={{
|
|
154
|
+
title: 'My Documentation',
|
|
155
|
+
supportedLanguages: SUPPORTED_LANGUAGES,
|
|
156
|
+
navigationData: NAVIGATION_DATA,
|
|
157
|
+
header: {
|
|
158
|
+
navLinks: [
|
|
159
|
+
{
|
|
160
|
+
label: 'Visit Site',
|
|
161
|
+
url: 'https://example.com',
|
|
162
|
+
target: '_blank',
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
}}
|
|
167
|
+
blog={blog}
|
|
168
|
+
>
|
|
169
|
+
<Router>
|
|
170
|
+
<Routes>
|
|
171
|
+
<Route path="/:language/*" element={<DocumentationRoutes />} />
|
|
172
|
+
<Route path="/" element={<Navigate to="/en/welcome" replace />} />
|
|
173
|
+
</Routes>
|
|
174
|
+
</Router>
|
|
175
|
+
</BlogProvider>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
> **Note**: BrowserRouter requires server configuration to fallback to `index.html` for all routes.
|
|
181
|
+
|
|
182
|
+
### Using a base path
|
|
183
|
+
|
|
184
|
+
To mount documentation under a subpath (e.g., `/blog` or `/docs`), set the `basePath` config option:
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
export default function App() {
|
|
188
|
+
return (
|
|
189
|
+
<BlogProvider
|
|
190
|
+
config={{
|
|
191
|
+
title: 'My Documentation',
|
|
192
|
+
basePath: '/blog', // All routes will be prefixed with /blog
|
|
193
|
+
supportedLanguages: SUPPORTED_LANGUAGES,
|
|
194
|
+
navigationData: NAVIGATION_DATA,
|
|
195
|
+
}}
|
|
196
|
+
blog={blog}
|
|
197
|
+
>
|
|
198
|
+
<Router>
|
|
199
|
+
<Routes>
|
|
200
|
+
{/* Routes now mounted under /blog */}
|
|
201
|
+
<Route path="/blog/:language/*" element={<DocumentationRoutes />} />
|
|
202
|
+
<Route path="/blog" element={<Navigate to="/blog/en/welcome" replace />} />
|
|
203
|
+
|
|
204
|
+
{/* Your other app routes */}
|
|
205
|
+
<Route path="/" element={<HomePage />} />
|
|
206
|
+
</Routes>
|
|
207
|
+
</Router>
|
|
208
|
+
</BlogProvider>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
This ensures internal navigation (search, tags, language switching) uses the correct base path.
|
|
214
|
+
|
|
215
|
+
## Writing content
|
|
216
|
+
|
|
217
|
+
### File naming
|
|
218
|
+
|
|
219
|
+
Place your MDX files in language-specific directories:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
src/docs/
|
|
223
|
+
├── en/welcome.mdx
|
|
224
|
+
├── es/welcome.mdx
|
|
225
|
+
└── zh/welcome.mdx
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
File names must match the `slug` field in your navigation definition.
|
|
49
229
|
|
|
50
230
|
### Frontmatter
|
|
51
231
|
|
|
232
|
+
Optional YAML at the top of your MDX file:
|
|
233
|
+
|
|
52
234
|
```mdx
|
|
53
235
|
---
|
|
54
236
|
title: Getting Started
|
|
@@ -61,68 +243,319 @@ tags:
|
|
|
61
243
|
---
|
|
62
244
|
|
|
63
245
|
# Welcome!
|
|
64
|
-
```
|
|
65
246
|
|
|
66
|
-
|
|
247
|
+
Regular content here.
|
|
248
|
+
```
|
|
67
249
|
|
|
68
250
|
| Field | Type | Description |
|
|
69
251
|
|---|---|---|
|
|
70
|
-
| `title` | `string` | Document title (informational, not displayed
|
|
71
|
-
| `description` | `string` | SEO description
|
|
72
|
-
| `author` | `string` | Author
|
|
73
|
-
| `date` | `string` |
|
|
74
|
-
| `keywords` | `string[] \| string` | Optional SEO keywords
|
|
75
|
-
| `hasAdvanced` | `boolean` | Enables Simple/Advanced toggle
|
|
76
|
-
| `tags` | `string[]` |
|
|
252
|
+
| `title` | `string` | Document title (informational, not displayed by framework) |
|
|
253
|
+
| `description` | `string` | Optional SEO description for the article. Used in the page `<meta name="description">` tag. Falls back to site-level description if not provided. |
|
|
254
|
+
| `author` | `string` | Author name. Displayed below the page title with a user icon. |
|
|
255
|
+
| `date` | `string` | Publication or update date. Displayed below the page title with "Last edited" label and calendar icon (i18n supported). |
|
|
256
|
+
| `keywords` | `string[] \| string` | Optional array of keywords or comma-separated string for SEO. Inserted into the page `<meta name="keywords">` tag. |
|
|
257
|
+
| `hasAdvanced` | `boolean` | Enables Simple/Advanced mode toggle. Requires a `-advanced.mdx` variant. |
|
|
258
|
+
| `tags` | `string[]` | Array of tag strings for categorizing content. Tags are automatically indexed by the framework when you pass `mdxFiles` to BlogProvider. Tags are clickable and show search results. |
|
|
77
259
|
|
|
78
260
|
### Advanced mode variants
|
|
79
261
|
|
|
80
|
-
Create
|
|
262
|
+
Create `welcome-advanced.mdx` alongside `welcome.mdx`:
|
|
81
263
|
|
|
82
264
|
```
|
|
83
|
-
|
|
84
|
-
├──
|
|
85
|
-
|
|
265
|
+
src/docs/
|
|
266
|
+
├── en/
|
|
267
|
+
│ ├── welcome.mdx
|
|
268
|
+
│ └── welcome-advanced.mdx
|
|
86
269
|
```
|
|
87
270
|
|
|
88
|
-
Set `hasAdvanced: true` in the simple version frontmatter.
|
|
271
|
+
Set `hasAdvanced: true` in the simple version's frontmatter, and the framework automatically shows a toggle.
|
|
272
|
+
|
|
273
|
+
## API reference
|
|
89
274
|
|
|
90
|
-
|
|
275
|
+
### Components
|
|
91
276
|
|
|
92
|
-
|
|
277
|
+
All components are exported from `@x-wave/blog`:
|
|
93
278
|
|
|
94
|
-
|
|
279
|
+
```ts
|
|
280
|
+
import {
|
|
281
|
+
BlogProvider,
|
|
282
|
+
DocumentationRoutes,
|
|
283
|
+
DocumentationLayout,
|
|
284
|
+
ContentPage,
|
|
285
|
+
HomePage,
|
|
286
|
+
Header,
|
|
287
|
+
Sidebar,
|
|
288
|
+
TableOfContents,
|
|
289
|
+
AdvancedModeToggle,
|
|
290
|
+
Metadata
|
|
291
|
+
} from '@x-wave/blog'
|
|
292
|
+
```
|
|
95
293
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
294
|
+
| Component | Purpose |
|
|
295
|
+
|---|---|
|
|
296
|
+
| `BlogProvider` | Root context wrapper (required) |
|
|
297
|
+
| `DocumentationRoutes` | Pre-configured Routes for documentation pages (recommended) |
|
|
298
|
+
| `DocumentationLayout` | Page layout: header + sidebar + content |
|
|
299
|
+
| `ContentPage` | Loads and renders MDX pages |
|
|
300
|
+
| `HomePage` | Lists latest articles with metadata (configurable via `defaultRoute`) |
|
|
301
|
+
| `Header` | Top navigation bar |
|
|
302
|
+
| `Sidebar` | Left navigation panel |
|
|
303
|
+
| `TableOfContents` | "On this page" anchor panel |
|
|
304
|
+
| `AdvancedModeToggle` | Simple/Advanced tab switch |
|
|
305
|
+
| `Metadata` | Displays author and publication date with icons (used in HomePage and ContentPage) |
|
|
306
|
+
|
|
307
|
+
### Hooks
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { useTheme } from '@x-wave/blog'
|
|
311
|
+
|
|
312
|
+
const { theme, setTheme, effectiveTheme } = useTheme()
|
|
313
|
+
```
|
|
314
|
+
Manages light/dark/system theme preference.
|
|
315
|
+
|
|
316
|
+
### Utilities
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
import { createBlogUtils } from '@x-wave/blog'
|
|
320
|
+
|
|
321
|
+
const blog = createBlogUtils(mdxFiles)
|
|
322
|
+
```
|
|
323
|
+
Creates all blog utilities from a Vite glob import. This is the recommended approach as it bundles everything together.
|
|
324
|
+
|
|
325
|
+
Returns an object with:
|
|
326
|
+
- **`mdxFiles`**: The glob import (used internally for automatic tag indexing)
|
|
327
|
+
- **`loadContent(language, slug, advanced?)`**: Loads MDX content for a specific language and slug
|
|
328
|
+
- **`loadEnglishContent(slug, advanced?)`**: Loads English content for heading ID generation
|
|
329
|
+
|
|
330
|
+
Pass the entire `blog` object to BlogProvider:
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
<BlogProvider config={config} blog={blog}>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Alternative: Advanced usage**
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
import { createContentLoaders } from '@x-wave/blog'
|
|
340
|
+
|
|
341
|
+
const { loadMDXContent, loadEnglishContent, buildTagIndex } = createContentLoaders(mdxFiles)
|
|
342
|
+
```
|
|
343
|
+
For advanced use cases where you need more control over content loading and tag indexing.
|
|
344
|
+
|
|
345
|
+
### Types
|
|
346
|
+
|
|
347
|
+
All TypeScript types are exported from `@x-wave/blog/types`:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import type { NavigationEntry, BlogConfig, HeaderLink } from '@x-wave/blog/types'
|
|
351
|
+
```
|
|
99
352
|
|
|
100
353
|
## Customization
|
|
101
354
|
|
|
102
|
-
###
|
|
355
|
+
### CSS variables
|
|
356
|
+
|
|
357
|
+
The framework uses CSS custom properties (`--xw-*` prefix) for theming. These are automatically injected when the framework initializes, but you can override them:
|
|
358
|
+
|
|
359
|
+
```css
|
|
360
|
+
/* In your app.css */
|
|
361
|
+
.xw-blog-root {
|
|
362
|
+
--xw-primary: #007bff;
|
|
363
|
+
--xw-background: #fafafa;
|
|
364
|
+
--xw-foreground: #1a1a1a;
|
|
365
|
+
/* ... other variables */
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Or in JavaScript:
|
|
370
|
+
|
|
371
|
+
```javascript
|
|
372
|
+
const blogRoot = document.querySelector('.xw-blog-root')
|
|
373
|
+
blogRoot.style.setProperty('--xw-primary', '#007bff')
|
|
374
|
+
blogRoot.style.setProperty('--xw-background', '#fafafa')
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Available CSS variables include:
|
|
378
|
+
- `--xw-primary`, `--xw-secondary`
|
|
379
|
+
- `--xw-background`, `--xw-foreground`
|
|
380
|
+
- `--xw-card`, `--xw-card-foreground`
|
|
381
|
+
- `--xw-muted`, `--xw-muted-foreground`
|
|
382
|
+
- `--xw-accent`, `--xw-accent-foreground`
|
|
383
|
+
- `--xw-border`, `--xw-ring`
|
|
384
|
+
- And more—defined in `packages/styles/main.scss`
|
|
385
|
+
|
|
386
|
+
All CSS variables are scoped to `.xw-blog-root` for isolation and to prevent conflicts with your application styles.
|
|
387
|
+
|
|
388
|
+
### Config options
|
|
389
|
+
|
|
390
|
+
**BlogConfig** properties:
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
interface BlogConfig {
|
|
394
|
+
title: string // Site title
|
|
395
|
+
description?: string // Optional default description for SEO (used as fallback if article has no description)
|
|
396
|
+
logo?: React.ComponentType<{ className?: string }> // Optional logo
|
|
397
|
+
supportedLanguages: readonly string[] // e.g. ['en', 'es', 'zh']
|
|
398
|
+
navigationData: NavigationEntry[] // Menu structure
|
|
399
|
+
basePath?: string // Base path for all routes (default: '')
|
|
400
|
+
contentMaxWidth?: string // Max width for content area (default: '80rem')
|
|
401
|
+
defaultRoute?: string // Default route: 'latest' (home page) or article slug (default: 'latest')
|
|
402
|
+
header?: { // Optional: omit to hide built-in header
|
|
403
|
+
navLinks?: HeaderLink[] // Top-level nav links
|
|
404
|
+
dropdownItems?: HeaderDropdownItem[] // Support dropdown menu
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Key properties:**
|
|
410
|
+
|
|
411
|
+
- **`header`**: Optional. If omitted entirely, the built-in header component will not be rendered. Use this when you want to implement a custom header.
|
|
412
|
+
- **`description`**: Optional. Default SEO description used in the page `<meta name="description">` tag. Articles can override this with their own `description` in frontmatter.
|
|
413
|
+
- **`contentMaxWidth`**: Optional. Set a custom maximum width for the main content area. Example: `'100rem'` or `'1400px'`. Default: `'80rem'`.
|
|
414
|
+
- **`defaultRoute`**: Optional. Controls which page displays at the root path (`/`):
|
|
415
|
+
- Set to `'latest'` (default) to show the HomePage listing the latest articles
|
|
416
|
+
- Set to any article slug (e.g., `'welcome'`, `'getting-started'`) to redirect the root path to that article
|
|
417
|
+
|
|
418
|
+
### Home page feature
|
|
103
419
|
|
|
104
|
-
The
|
|
420
|
+
The framework includes an optional home/latest posts page that lists articles sorted by publish date. Configure it with the `defaultRoute` option:
|
|
105
421
|
|
|
106
|
-
|
|
422
|
+
```tsx
|
|
423
|
+
// Show home page at root path (lists latest articles)
|
|
424
|
+
config: {
|
|
425
|
+
defaultRoute: 'latest' // or omit (defaults to 'latest')
|
|
426
|
+
}
|
|
107
427
|
|
|
108
|
-
|
|
428
|
+
// Or redirect root path to a specific article
|
|
429
|
+
config: {
|
|
430
|
+
defaultRoute: 'welcome' // Root path goes to /welcome article
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
The home page displays:
|
|
435
|
+
- **Title**: "Latest Posts" (i18n translated)
|
|
436
|
+
- **Header metadata**: Author and date from the most recent article
|
|
437
|
+
- **Article cards**: Recent articles with title, description, author, and date
|
|
438
|
+
- **Sorting**: Articles sorted by date (newest first), limited to 50 articles
|
|
439
|
+
|
|
440
|
+
Article metadata comes from MDX frontmatter:
|
|
441
|
+
```mdx
|
|
442
|
+
---
|
|
443
|
+
title: Getting Started
|
|
444
|
+
author: Jane Doe
|
|
445
|
+
date: 2026-02-23
|
|
446
|
+
description: Learn the basics in 5 minutes
|
|
447
|
+
keywords: [getting started, tutorial, basics]
|
|
448
|
+
---
|
|
449
|
+
```
|
|
109
450
|
|
|
110
|
-
### Custom
|
|
451
|
+
### Custom headers
|
|
452
|
+
|
|
453
|
+
If you need a custom header instead of the built-in one, simply omit the `header` config and use the provided hooks:
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
import { useTheme, useSearchModal } from '@x-wave/blog'
|
|
457
|
+
import { Moon, Sun, MagnifyingGlass } from '@phosphor-icons/react'
|
|
458
|
+
|
|
459
|
+
function CustomHeader() {
|
|
460
|
+
const { theme, setTheme } = useTheme()
|
|
461
|
+
const { openSearchModal } = useSearchModal()
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<header>
|
|
465
|
+
<h1>My Custom Header</h1>
|
|
466
|
+
|
|
467
|
+
{/* Theme toggle button */}
|
|
468
|
+
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
|
|
469
|
+
{theme === 'dark' ? <Sun /> : <Moon />}
|
|
470
|
+
</button>
|
|
471
|
+
|
|
472
|
+
{/* Search button */}
|
|
473
|
+
<button onClick={openSearchModal}>
|
|
474
|
+
<MagnifyingGlass />
|
|
475
|
+
</button>
|
|
476
|
+
</header>
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function App() {
|
|
481
|
+
return (
|
|
482
|
+
<BlogProvider
|
|
483
|
+
config={{
|
|
484
|
+
title: 'My Docs',
|
|
485
|
+
supportedLanguages: ['en'],
|
|
486
|
+
navigationData: NAVIGATION_DATA,
|
|
487
|
+
// No header config = no built-in header
|
|
488
|
+
}}
|
|
489
|
+
blog={blog}
|
|
490
|
+
>
|
|
491
|
+
<CustomHeader />
|
|
492
|
+
<Router>
|
|
493
|
+
<Routes>
|
|
494
|
+
<Route path="/:language/*" element={<DocumentationRoutes />} />
|
|
495
|
+
</Routes>
|
|
496
|
+
</Router>
|
|
497
|
+
</BlogProvider>
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
```
|
|
111
501
|
|
|
112
|
-
|
|
502
|
+
### Reusable components
|
|
113
503
|
|
|
114
|
-
|
|
504
|
+
#### Metadata component
|
|
115
505
|
|
|
116
|
-
|
|
506
|
+
The `Metadata` component displays article metadata (author and publication date) with icons and i18n support. It's used throughout the framework (HomePage and ContentPage) but is also exported for your own use:
|
|
117
507
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
508
|
+
```tsx
|
|
509
|
+
import { Metadata } from '@x-wave/blog'
|
|
510
|
+
|
|
511
|
+
function MyComponent() {
|
|
512
|
+
return (
|
|
513
|
+
<Metadata
|
|
514
|
+
author="Jane Doe"
|
|
515
|
+
date="2026-02-23"
|
|
516
|
+
/>
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
The component:
|
|
522
|
+
- Displays author with a user icon
|
|
523
|
+
- Displays date with a calendar icon and "Last edited" label
|
|
524
|
+
- Automatically formats the date using the user's locale
|
|
525
|
+
- Returns `null` if both author and date are missing
|
|
526
|
+
- Uses i18n for the "Last edited" label and date formatting
|
|
527
|
+
|
|
528
|
+
**Available hooks:**
|
|
529
|
+
|
|
530
|
+
- **`useTheme()`**: Returns `{ theme, setTheme, effectiveTheme }` for managing light/dark/system theme
|
|
531
|
+
- **`useSearchModal()`**: Returns `{ openSearchModal, closeSearchModal }` for controlling the search modal
|
|
532
|
+
|
|
533
|
+
These are the exact functions used by the built-in header, so you get the same functionality with full customization.
|
|
534
|
+
|
|
535
|
+
**Key properties (continued):**
|
|
536
|
+
|
|
537
|
+
| Property | Type | Required | Description |
|
|
538
|
+
|---|---|---|---|
|
|
539
|
+
| `title` | `string` | Yes | Site title displayed in header and sidebar |
|
|
540
|
+
| `supportedLanguages` | `string[]` | Yes | Array of language codes (e.g., `['en', 'es', 'zh']`) |
|
|
541
|
+
| `navigationData` | `NavigationEntry[]` | Yes | Sidebar navigation structure |
|
|
542
|
+
| `basePath` | `string` | No | Base path prefix for all routes. Use when mounting docs under a subpath like `/blog` or `/docs`. Default: `''` (root) |
|
|
543
|
+
| `logo` | `React.ComponentType` | No | SVG component for site logo |
|
|
544
|
+
| `header` | `object` | No | Header configuration with nav links and dropdown items |
|
|
545
|
+
|
|
546
|
+
## Browser support
|
|
547
|
+
|
|
548
|
+
- Chrome/Edge 90+
|
|
549
|
+
- Firefox 88+
|
|
550
|
+
- Safari 14+
|
|
551
|
+
- Mobile browsers (iOS Safari 14.5+, Chrome Android 90+)
|
|
121
552
|
|
|
122
553
|
## License
|
|
123
554
|
|
|
124
|
-
See
|
|
555
|
+
See LICENSE file in this repository.
|
|
556
|
+
|
|
557
|
+
---
|
|
125
558
|
|
|
126
|
-
##
|
|
559
|
+
## For framework maintainers
|
|
127
560
|
|
|
128
|
-
See [DEVELOPMENT.md](DEVELOPMENT.md) for build system details
|
|
561
|
+
Contributing or maintaining this framework? See [DEVELOPMENT.md](./DEVELOPMENT.md) for setup, architecture, and build system details.
|