@x-wave/blog 1.1.3 → 2.0.0
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 +63 -546
- package/consts/index.ts +0 -2
- package/index.js +1268 -1205
- package/package.json +4 -6
- package/styles/index.css +2 -2
- package/vite/index.js +0 -143
package/README.md
CHANGED
|
@@ -1,285 +1,54 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Staking Docs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Multi-language documentation site built with Next.js App Router and MDX. This repo contains both the framework packages and a reference app used for local development and previews.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Repository structure
|
|
6
6
|
|
|
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
|
|
14
|
-
|
|
15
|
-
## Installation
|
|
16
|
-
|
|
17
|
-
### npm
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm install @x-wave/blog
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### pnpm
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
pnpm add @x-wave/blog
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### yarn
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
yarn add @x-wave/blog
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Quick setup
|
|
36
|
-
|
|
37
|
-
### 1. Create your app structure
|
|
38
|
-
|
|
39
|
-
```
|
|
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 />)
|
|
73
|
-
```
|
|
74
|
-
|
|
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
7
|
```
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
]
|
|
8
|
+
packages/
|
|
9
|
+
├── app/ # Next.js reference app
|
|
10
|
+
├── blog/ # Build orchestration package
|
|
11
|
+
├── ui/ # UI components and client logic
|
|
12
|
+
├── locales/ # i18n resources (i18next)
|
|
13
|
+
├── consts/ # Navigation config + shared constants
|
|
14
|
+
├── styles/ # SCSS + compiled CSS
|
|
15
|
+
└── types/ # Shared TypeScript types
|
|
120
16
|
```
|
|
121
17
|
|
|
122
|
-
|
|
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
|
-
})
|
|
18
|
+
## Quick start
|
|
134
19
|
|
|
135
|
-
|
|
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, HashRouter 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
|
-
siteUrl: 'https://docs.example.com',
|
|
156
|
-
supportedLanguages: SUPPORTED_LANGUAGES,
|
|
157
|
-
navigationData: NAVIGATION_DATA,
|
|
158
|
-
header: {
|
|
159
|
-
navLinks: [
|
|
160
|
-
{
|
|
161
|
-
label: 'Visit Site',
|
|
162
|
-
url: 'https://example.com',
|
|
163
|
-
target: '_blank',
|
|
164
|
-
},
|
|
165
|
-
],
|
|
166
|
-
},
|
|
167
|
-
}}
|
|
168
|
-
blog={blog}
|
|
169
|
-
>
|
|
170
|
-
<Router>
|
|
171
|
-
<Routes>
|
|
172
|
-
<Route path="/:language/*" element={<DocumentationRoutes />} />
|
|
173
|
-
<Route path="/" element={<Navigate to="/en/welcome" replace />} />
|
|
174
|
-
</Routes>
|
|
175
|
-
</Router>
|
|
176
|
-
</BlogProvider>
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
```
|
|
20
|
+
### 1. Install dependencies
|
|
180
21
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
### Using a base path
|
|
184
|
-
|
|
185
|
-
To mount documentation under a subpath (e.g., `/blog` or `/docs`), set the `basePath` config option:
|
|
186
|
-
|
|
187
|
-
```tsx
|
|
188
|
-
export default function App() {
|
|
189
|
-
return (
|
|
190
|
-
<BlogProvider
|
|
191
|
-
config={{
|
|
192
|
-
title: 'My Documentation',
|
|
193
|
-
basePath: '/blog', // All routes will be prefixed with /blog
|
|
194
|
-
supportedLanguages: SUPPORTED_LANGUAGES,
|
|
195
|
-
navigationData: NAVIGATION_DATA,
|
|
196
|
-
}}
|
|
197
|
-
blog={blog}
|
|
198
|
-
>
|
|
199
|
-
<Router>
|
|
200
|
-
<Routes>
|
|
201
|
-
{/* Routes now mounted under /blog */}
|
|
202
|
-
<Route path="/blog/:language/*" element={<DocumentationRoutes />} />
|
|
203
|
-
<Route path="/blog" element={<Navigate to="/blog/en/welcome" replace />} />
|
|
204
|
-
|
|
205
|
-
{/* Your other app routes */}
|
|
206
|
-
<Route path="/" element={<HomePage />} />
|
|
207
|
-
</Routes>
|
|
208
|
-
</Router>
|
|
209
|
-
</BlogProvider>
|
|
210
|
-
)
|
|
211
|
-
}
|
|
22
|
+
```bash
|
|
23
|
+
pnpm install
|
|
212
24
|
```
|
|
213
25
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
### Vite config helper (with SSG + basePath)
|
|
217
|
-
|
|
218
|
-
The framework exports a reusable Vite config helper so consumers can plug in prerendering without re-implementing build logic.
|
|
219
|
-
|
|
220
|
-
```ts
|
|
221
|
-
// vite.config.ts
|
|
222
|
-
import { createBlogViteConfig } from '@x-wave/blog/vite'
|
|
223
|
-
|
|
224
|
-
export default createBlogViteConfig({
|
|
225
|
-
basePath: process.env.VITE_BASE_PATH || '/',
|
|
226
|
-
docsDir: 'src/docs',
|
|
227
|
-
renderTarget: '#root',
|
|
228
|
-
renderAfterTime: 1200,
|
|
229
|
-
})
|
|
230
|
-
```
|
|
26
|
+
### 2. Run the Next.js app
|
|
231
27
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
```ts
|
|
235
|
-
// vite.config.ts
|
|
236
|
-
import { createBlogViteConfig } from '@x-wave/blog/vite'
|
|
237
|
-
|
|
238
|
-
export default createBlogViteConfig({
|
|
239
|
-
basePath: process.env.VITE_BASE_PATH || '/',
|
|
240
|
-
docsDir: 'src/docs',
|
|
241
|
-
renderTarget: '#root',
|
|
242
|
-
renderAfterTime: 1200,
|
|
243
|
-
baseConfig: {
|
|
244
|
-
// Your custom Vite config
|
|
245
|
-
resolve: { /* ... */ },
|
|
246
|
-
server: { /* ... */ },
|
|
247
|
-
// etc.
|
|
248
|
-
},
|
|
249
|
-
})
|
|
28
|
+
```bash
|
|
29
|
+
pnpm dev
|
|
250
30
|
```
|
|
251
31
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
- `basePath`: Public base path for deployed assets/routes (e.g. `/`, `/docs`, `/multilingual-documentation`)
|
|
255
|
-
- `docsDir`: Relative path to language MDX folders (default: `src/docs`)
|
|
256
|
-
- `renderTarget`: Selector where prerendered HTML is injected (default: `#root`)
|
|
257
|
-
- `renderAfterTime`: Delay in ms before snapshotting pages in Puppeteer
|
|
258
|
-
- `additionalPrerenderRoutes`: Extra non-MDX routes to prerender
|
|
259
|
-
- `enableTypeChecker`: Enable/disable `vite-plugin-checker`
|
|
260
|
-
- `baseConfig`: Optional. Extend the default Vite config with your own settings. The blog config intelligently merges with your base config (plugins, CSS, and optimizeDeps are combined)
|
|
261
|
-
|
|
262
|
-
For local preview at root, keep `VITE_BASE_PATH=/` (default). For subpath deploys, set `VITE_BASE_PATH` in your build environment.
|
|
263
|
-
|
|
264
|
-
## Writing content
|
|
32
|
+
The app runs from [packages/app](packages/app). The default route is `/en/welcome`.
|
|
265
33
|
|
|
266
|
-
|
|
34
|
+
## Content structure
|
|
267
35
|
|
|
268
|
-
|
|
36
|
+
MDX files live under the app package:
|
|
269
37
|
|
|
270
38
|
```
|
|
271
|
-
|
|
272
|
-
├── en/
|
|
273
|
-
├──
|
|
274
|
-
|
|
39
|
+
packages/app/docs/
|
|
40
|
+
├── en/
|
|
41
|
+
│ ├── welcome.mdx
|
|
42
|
+
│ ├── glossary.mdx
|
|
43
|
+
│ └── faq.mdx
|
|
44
|
+
├── es/
|
|
45
|
+
└── zh/
|
|
275
46
|
```
|
|
276
47
|
|
|
277
|
-
File names must match the `slug`
|
|
48
|
+
File names must match the `slug` in the navigation config.
|
|
278
49
|
|
|
279
50
|
### Frontmatter
|
|
280
51
|
|
|
281
|
-
Optional YAML at the top of your MDX file:
|
|
282
|
-
|
|
283
52
|
```mdx
|
|
284
53
|
---
|
|
285
54
|
title: Getting Started
|
|
@@ -292,320 +61,68 @@ tags:
|
|
|
292
61
|
---
|
|
293
62
|
|
|
294
63
|
# Welcome!
|
|
295
|
-
|
|
296
|
-
Regular content here.
|
|
297
64
|
```
|
|
298
65
|
|
|
66
|
+
Supported fields:
|
|
67
|
+
|
|
299
68
|
| Field | Type | Description |
|
|
300
69
|
|---|---|---|
|
|
301
|
-
| `title` | `string` | Document title (informational, not displayed
|
|
302
|
-
| `description` | `string` |
|
|
303
|
-
| `author` | `string` | Author
|
|
304
|
-
| `date` | `string` |
|
|
305
|
-
| `keywords` | `string[] \| string` | Optional
|
|
306
|
-
| `hasAdvanced` | `boolean` | Enables Simple/Advanced
|
|
307
|
-
| `tags` | `string[]` |
|
|
70
|
+
| `title` | `string` | Document title (informational, not displayed as the main heading) |
|
|
71
|
+
| `description` | `string` | SEO description override (used for `<meta name="description">`) |
|
|
72
|
+
| `author` | `string` | Author display under the title |
|
|
73
|
+
| `date` | `string` | Date shown under the title (i18n-aware label) |
|
|
74
|
+
| `keywords` | `string[] \| string` | Optional SEO keywords list |
|
|
75
|
+
| `hasAdvanced` | `boolean` | Enables Simple/Advanced toggle and `-advanced.mdx` variant |
|
|
76
|
+
| `tags` | `string[]` | Tag labels used by the tag index modal |
|
|
308
77
|
|
|
309
78
|
### Advanced mode variants
|
|
310
79
|
|
|
311
|
-
Create
|
|
80
|
+
Create a paired file that ends with `-advanced.mdx`:
|
|
312
81
|
|
|
313
82
|
```
|
|
314
|
-
|
|
315
|
-
├──
|
|
316
|
-
|
|
317
|
-
│ └── welcome-advanced.mdx
|
|
83
|
+
packages/app/docs/en/
|
|
84
|
+
├── welcome.mdx
|
|
85
|
+
└── welcome-advanced.mdx
|
|
318
86
|
```
|
|
319
87
|
|
|
320
|
-
Set `hasAdvanced: true` in the simple version
|
|
88
|
+
Set `hasAdvanced: true` in the simple version frontmatter.
|
|
321
89
|
|
|
322
|
-
##
|
|
90
|
+
## Navigation configuration
|
|
323
91
|
|
|
324
|
-
|
|
92
|
+
Navigation lives in [packages/consts/src/navigation.ts](packages/consts/src/navigation.ts) and is passed into `BlogProvider` via the app config. Each entry can specify `showTableOfContents` per item.
|
|
325
93
|
|
|
326
|
-
|
|
94
|
+
## Next.js implementation notes
|
|
327
95
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
DocumentationRoutes,
|
|
332
|
-
DocumentationLayout,
|
|
333
|
-
ContentPage,
|
|
334
|
-
HomePage,
|
|
335
|
-
Header,
|
|
336
|
-
Sidebar,
|
|
337
|
-
TableOfContents,
|
|
338
|
-
AdvancedModeToggle,
|
|
339
|
-
Metadata
|
|
340
|
-
} from '@x-wave/blog'
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
| Component | Purpose |
|
|
344
|
-
|---|---|
|
|
345
|
-
| `BlogProvider` | Root context wrapper (required) |
|
|
346
|
-
| `DocumentationRoutes` | Pre-configured Routes for documentation pages (recommended) |
|
|
347
|
-
| `DocumentationLayout` | Page layout: header + sidebar + content |
|
|
348
|
-
| `ContentPage` | Loads and renders MDX pages |
|
|
349
|
-
| `HomePage` | Lists latest articles with metadata (configurable via `defaultRoute`) |
|
|
350
|
-
| `Header` | Top navigation bar |
|
|
351
|
-
| `Sidebar` | Left navigation panel |
|
|
352
|
-
| `TableOfContents` | "On this page" anchor panel |
|
|
353
|
-
| `AdvancedModeToggle` | Simple/Advanced tab switch |
|
|
354
|
-
| `Metadata` | Displays author and publication date with icons (used in HomePage and ContentPage) |
|
|
355
|
-
|
|
356
|
-
### Hooks
|
|
357
|
-
|
|
358
|
-
```ts
|
|
359
|
-
import { useTheme } from '@x-wave/blog'
|
|
360
|
-
|
|
361
|
-
const { theme, setTheme, effectiveTheme } = useTheme()
|
|
362
|
-
```
|
|
363
|
-
Manages light/dark/system theme preference.
|
|
364
|
-
|
|
365
|
-
### Utilities
|
|
366
|
-
|
|
367
|
-
```ts
|
|
368
|
-
import { createBlogUtils } from '@x-wave/blog'
|
|
369
|
-
|
|
370
|
-
const blog = createBlogUtils(mdxFiles)
|
|
371
|
-
```
|
|
372
|
-
Creates all blog utilities from a Vite glob import. This is the recommended approach as it bundles everything together.
|
|
373
|
-
|
|
374
|
-
Returns an object with:
|
|
375
|
-
- **`mdxFiles`**: The glob import (used internally for automatic tag indexing)
|
|
376
|
-
- **`loadContent(language, slug, advanced?)`**: Loads MDX content for a specific language and slug
|
|
377
|
-
- **`loadEnglishContent(slug, advanced?)`**: Loads English content for heading ID generation
|
|
378
|
-
|
|
379
|
-
Pass the entire `blog` object to BlogProvider:
|
|
380
|
-
|
|
381
|
-
```tsx
|
|
382
|
-
<BlogProvider config={config} blog={blog}>
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
**Alternative: Advanced usage**
|
|
386
|
-
|
|
387
|
-
```ts
|
|
388
|
-
import { createContentLoaders } from '@x-wave/blog'
|
|
389
|
-
|
|
390
|
-
const { loadMDXContent, loadEnglishContent, buildTagIndex } = createContentLoaders(mdxFiles)
|
|
391
|
-
```
|
|
392
|
-
For advanced use cases where you need more control over content loading and tag indexing.
|
|
393
|
-
|
|
394
|
-
### Types
|
|
395
|
-
|
|
396
|
-
All TypeScript types are exported from `@x-wave/blog/types`:
|
|
397
|
-
|
|
398
|
-
```ts
|
|
399
|
-
import type { NavigationEntry, BlogConfig, HeaderLink } from '@x-wave/blog/types'
|
|
400
|
-
```
|
|
96
|
+
- **Static params**: `generateStaticParams()` in [packages/app/app/[language]/[slug]/page.tsx](packages/app/app/%5Blanguage%5D/%5Bslug%5D/page.tsx)
|
|
97
|
+
- **Server-side content**: `readArticle()` and `readEnglishContent()` in [packages/app/lib/content.ts](packages/app/lib/content.ts)
|
|
98
|
+
- **Client render**: `ContentPage` in [packages/ui/src/components/ContentPage/index.tsx](packages/ui/src/components/ContentPage/index.tsx)
|
|
401
99
|
|
|
402
100
|
## Customization
|
|
403
101
|
|
|
404
|
-
###
|
|
102
|
+
### Styles
|
|
405
103
|
|
|
406
|
-
The
|
|
104
|
+
The compiled CSS is exported from `@x-wave/blog/styles`. In the Next.js app, it is imported once in [packages/app/app/globals.scss](packages/app/app/globals.scss). Override CSS variables on `.xw-blog-root` to theme the UI.
|
|
407
105
|
|
|
408
|
-
|
|
409
|
-
/* In your app.css */
|
|
410
|
-
.xw-blog-root {
|
|
411
|
-
--xw-primary: #007bff;
|
|
412
|
-
--xw-background: #fafafa;
|
|
413
|
-
--xw-foreground: #1a1a1a;
|
|
414
|
-
/* ... other variables */
|
|
415
|
-
}
|
|
416
|
-
```
|
|
106
|
+
### Base path
|
|
417
107
|
|
|
418
|
-
|
|
108
|
+
If you mount the docs under a subpath, set `basePath` in the BlogProvider config. This ensures internal links and language switching include the prefix.
|
|
419
109
|
|
|
420
|
-
|
|
421
|
-
const blogRoot = document.querySelector('.xw-blog-root')
|
|
422
|
-
blogRoot.style.setProperty('--xw-primary', '#007bff')
|
|
423
|
-
blogRoot.style.setProperty('--xw-background', '#fafafa')
|
|
424
|
-
```
|
|
110
|
+
### Custom header
|
|
425
111
|
|
|
426
|
-
|
|
427
|
-
- `--xw-primary`, `--xw-secondary`
|
|
428
|
-
- `--xw-background`, `--xw-foreground`
|
|
429
|
-
- `--xw-card`, `--xw-card-foreground`
|
|
430
|
-
- `--xw-muted`, `--xw-muted-foreground`
|
|
431
|
-
- `--xw-accent`, `--xw-accent-foreground`
|
|
432
|
-
- `--xw-border`, `--xw-ring`
|
|
433
|
-
- And more—defined in `packages/styles/main.scss`
|
|
434
|
-
|
|
435
|
-
All CSS variables are scoped to `.xw-blog-root` for isolation and to prevent conflicts with your application styles.
|
|
436
|
-
|
|
437
|
-
### Config options
|
|
438
|
-
|
|
439
|
-
**BlogConfig** properties:
|
|
440
|
-
|
|
441
|
-
```ts
|
|
442
|
-
interface BlogConfig {
|
|
443
|
-
title: string // Site title
|
|
444
|
-
description?: string // Optional default description for SEO (used as fallback if article has no description)
|
|
445
|
-
logo?: React.ComponentType<{ className?: string }> // Optional logo
|
|
446
|
-
supportedLanguages: readonly string[] // e.g. ['en', 'es', 'zh']
|
|
447
|
-
navigationData: NavigationEntry[] // Menu structure
|
|
448
|
-
basePath?: string // Base path for all routes (default: '')
|
|
449
|
-
contentMaxWidth?: string // Max width for content area (default: '80rem')
|
|
450
|
-
defaultRoute?: string // Default route: 'latest' (home page) or article slug (default: 'latest')
|
|
451
|
-
header?: { // Optional: omit to hide built-in header
|
|
452
|
-
navLinks?: HeaderLink[] // Top-level nav links
|
|
453
|
-
dropdownItems?: HeaderDropdownItem[] // Support dropdown menu
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
```
|
|
112
|
+
Omit the `header` config to hide the built-in header and use the hooks provided by the UI package (`useTheme`, `useSearchModal`).
|
|
457
113
|
|
|
458
|
-
|
|
114
|
+
## Scripts
|
|
459
115
|
|
|
460
|
-
|
|
461
|
-
- **`description`**: Optional. Default SEO description used in the page `<meta name="description">` tag. Articles can override this with their own `description` in frontmatter.
|
|
462
|
-
- **`contentMaxWidth`**: Optional. Set a custom maximum width for the main content area. Example: `'100rem'` or `'1400px'`. Default: `'80rem'`.
|
|
463
|
-
- **`defaultRoute`**: Optional. Controls which page displays at the root path (`/`):
|
|
464
|
-
- Set to `'latest'` (default) to show the HomePage listing the latest articles
|
|
465
|
-
- Set to any article slug (e.g., `'welcome'`, `'getting-started'`) to redirect the root path to that article
|
|
116
|
+
From repo root:
|
|
466
117
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
```tsx
|
|
472
|
-
// Show home page at root path (lists latest articles)
|
|
473
|
-
config: {
|
|
474
|
-
defaultRoute: 'latest' // or omit (defaults to 'latest')
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Or redirect root path to a specific article
|
|
478
|
-
config: {
|
|
479
|
-
defaultRoute: 'welcome' // Root path goes to /welcome article
|
|
480
|
-
}
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
The home page displays:
|
|
484
|
-
- **Title**: "Latest Posts" (i18n translated)
|
|
485
|
-
- **Header metadata**: Author and date from the most recent article
|
|
486
|
-
- **Article cards**: Recent articles with title, description, author, and date
|
|
487
|
-
- **Sorting**: Articles sorted by date (newest first), limited to 50 articles
|
|
488
|
-
|
|
489
|
-
Article metadata comes from MDX frontmatter:
|
|
490
|
-
```mdx
|
|
491
|
-
---
|
|
492
|
-
title: Getting Started
|
|
493
|
-
author: Jane Doe
|
|
494
|
-
date: 2026-02-23
|
|
495
|
-
description: Learn the basics in 5 minutes
|
|
496
|
-
keywords: [getting started, tutorial, basics]
|
|
497
|
-
---
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
### Custom headers
|
|
501
|
-
|
|
502
|
-
If you need a custom header instead of the built-in one, simply omit the `header` config and use the provided hooks:
|
|
503
|
-
|
|
504
|
-
```tsx
|
|
505
|
-
import { useTheme, useSearchModal } from '@x-wave/blog'
|
|
506
|
-
import { Moon, Sun, MagnifyingGlass } from '@phosphor-icons/react'
|
|
507
|
-
|
|
508
|
-
function CustomHeader() {
|
|
509
|
-
const { theme, setTheme } = useTheme()
|
|
510
|
-
const { openSearchModal } = useSearchModal()
|
|
511
|
-
|
|
512
|
-
return (
|
|
513
|
-
<header>
|
|
514
|
-
<h1>My Custom Header</h1>
|
|
515
|
-
|
|
516
|
-
{/* Theme toggle button */}
|
|
517
|
-
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
|
|
518
|
-
{theme === 'dark' ? <Sun /> : <Moon />}
|
|
519
|
-
</button>
|
|
520
|
-
|
|
521
|
-
{/* Search button */}
|
|
522
|
-
<button onClick={openSearchModal}>
|
|
523
|
-
<MagnifyingGlass />
|
|
524
|
-
</button>
|
|
525
|
-
</header>
|
|
526
|
-
)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function App() {
|
|
530
|
-
return (
|
|
531
|
-
<BlogProvider
|
|
532
|
-
config={{
|
|
533
|
-
title: 'My Docs',
|
|
534
|
-
supportedLanguages: ['en'],
|
|
535
|
-
navigationData: NAVIGATION_DATA,
|
|
536
|
-
// No header config = no built-in header
|
|
537
|
-
}}
|
|
538
|
-
blog={blog}
|
|
539
|
-
>
|
|
540
|
-
<CustomHeader />
|
|
541
|
-
<Router>
|
|
542
|
-
<Routes>
|
|
543
|
-
<Route path="/:language/*" element={<DocumentationRoutes />} />
|
|
544
|
-
</Routes>
|
|
545
|
-
</Router>
|
|
546
|
-
</BlogProvider>
|
|
547
|
-
)
|
|
548
|
-
}
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### Reusable components
|
|
552
|
-
|
|
553
|
-
#### Metadata component
|
|
554
|
-
|
|
555
|
-
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:
|
|
556
|
-
|
|
557
|
-
```tsx
|
|
558
|
-
import { Metadata } from '@x-wave/blog'
|
|
559
|
-
|
|
560
|
-
function MyComponent() {
|
|
561
|
-
return (
|
|
562
|
-
<Metadata
|
|
563
|
-
author="Jane Doe"
|
|
564
|
-
date="2026-02-23"
|
|
565
|
-
/>
|
|
566
|
-
)
|
|
567
|
-
}
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
The component:
|
|
571
|
-
- Displays author with a user icon
|
|
572
|
-
- Displays date with a calendar icon and "Last edited" label
|
|
573
|
-
- Automatically formats the date using the user's locale
|
|
574
|
-
- Returns `null` if both author and date are missing
|
|
575
|
-
- Uses i18n for the "Last edited" label and date formatting
|
|
576
|
-
|
|
577
|
-
**Available hooks:**
|
|
578
|
-
|
|
579
|
-
- **`useTheme()`**: Returns `{ theme, setTheme, effectiveTheme }` for managing light/dark/system theme
|
|
580
|
-
- **`useSearchModal()`**: Returns `{ openSearchModal, closeSearchModal }` for controlling the search modal
|
|
581
|
-
|
|
582
|
-
These are the exact functions used by the built-in header, so you get the same functionality with full customization.
|
|
583
|
-
|
|
584
|
-
**Key properties (continued):**
|
|
585
|
-
|
|
586
|
-
| Property | Type | Required | Description |
|
|
587
|
-
|---|---|---|---|
|
|
588
|
-
| `title` | `string` | Yes | Site title displayed in header and sidebar |
|
|
589
|
-
| `siteUrl` | `string` | Yes | Full canonical origin used for absolute SEO URLs (e.g., `https://docs.example.com`) |
|
|
590
|
-
| `supportedLanguages` | `string[]` | Yes | Array of language codes (e.g., `['en', 'es', 'zh']`) |
|
|
591
|
-
| `navigationData` | `NavigationEntry[]` | Yes | Sidebar navigation structure |
|
|
592
|
-
| `basePath` | `string` | No | Base path prefix for all routes. Use when mounting docs under a subpath like `/blog` or `/docs`. Default: `''` (root) |
|
|
593
|
-
| `logo` | `React.ComponentType` | No | SVG component for site logo |
|
|
594
|
-
| `header` | `object` | No | Header configuration with nav links and dropdown items |
|
|
595
|
-
|
|
596
|
-
## Browser support
|
|
597
|
-
|
|
598
|
-
- Chrome/Edge 90+
|
|
599
|
-
- Firefox 88+
|
|
600
|
-
- Safari 14+
|
|
601
|
-
- Mobile browsers (iOS Safari 14.5+, Chrome Android 90+)
|
|
118
|
+
- `pnpm dev`: Starts the Next.js app and blog watcher
|
|
119
|
+
- `pnpm build`: Builds all packages and the app
|
|
120
|
+
- `pnpm build:blog`: Builds the blog package output
|
|
602
121
|
|
|
603
122
|
## License
|
|
604
123
|
|
|
605
|
-
See LICENSE
|
|
606
|
-
|
|
607
|
-
---
|
|
124
|
+
See [LICENSE](LICENSE).
|
|
608
125
|
|
|
609
|
-
##
|
|
126
|
+
## Maintainers
|
|
610
127
|
|
|
611
|
-
|
|
128
|
+
See [DEVELOPMENT.md](DEVELOPMENT.md) for build system details and contributor notes.
|
package/consts/index.ts
CHANGED