@x-wave/blog 1.1.4 → 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.
Files changed (4) hide show
  1. package/README.md +63 -496
  2. package/index.js +1268 -1205
  3. package/package.json +4 -3
  4. package/styles/index.css +2 -2
package/README.md CHANGED
@@ -1,236 +1,54 @@
1
- # @x-wave/blog
1
+ # Staking Docs
2
2
 
3
- A responsive, multi-language documentation framework for React + Vite applications. Ships with TypeScript, i18n (i18next), MDX support, dark mode, and built-in navigation.
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
- ## Features
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
7
  ```
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 />)
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
73
16
  ```
74
17
 
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.
18
+ ## Quick start
76
19
 
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
- ```
20
+ ### 1. Install dependencies
92
21
 
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)
22
+ ```bash
23
+ pnpm install
137
24
  ```
138
25
 
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
- 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
- ```
26
+ ### 2. Run the Next.js app
179
27
 
180
- > **Router flexibility**: Swap `HashRouter` for `BrowserRouter` to use cleaner URLs without the hash (`/en/welcome` vs `/#/en/welcome`). 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
- }
28
+ ```bash
29
+ pnpm dev
211
30
  ```
212
31
 
213
- This ensures internal navigation (search, tags, language switching) uses the correct base path.
214
-
215
- ## Writing content
32
+ The app runs from [packages/app](packages/app). The default route is `/en/welcome`.
216
33
 
217
- ### File naming
34
+ ## Content structure
218
35
 
219
- Place your MDX files in language-specific directories:
36
+ MDX files live under the app package:
220
37
 
221
38
  ```
222
- src/docs/
223
- ├── en/welcome.mdx
224
- ├── es/welcome.mdx
225
- └── zh/welcome.mdx
39
+ packages/app/docs/
40
+ ├── en/
41
+ ├── welcome.mdx
42
+ │ ├── glossary.mdx
43
+ │ └── faq.mdx
44
+ ├── es/
45
+ └── zh/
226
46
  ```
227
47
 
228
- File names must match the `slug` field in your navigation definition.
48
+ File names must match the `slug` in the navigation config.
229
49
 
230
50
  ### Frontmatter
231
51
 
232
- Optional YAML at the top of your MDX file:
233
-
234
52
  ```mdx
235
53
  ---
236
54
  title: Getting Started
@@ -243,319 +61,68 @@ tags:
243
61
  ---
244
62
 
245
63
  # Welcome!
246
-
247
- Regular content here.
248
64
  ```
249
65
 
66
+ Supported fields:
67
+
250
68
  | Field | Type | Description |
251
69
  |---|---|---|
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. |
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 |
259
77
 
260
78
  ### Advanced mode variants
261
79
 
262
- Create `welcome-advanced.mdx` alongside `welcome.mdx`:
80
+ Create a paired file that ends with `-advanced.mdx`:
263
81
 
264
82
  ```
265
- src/docs/
266
- ├── en/
267
- │ ├── welcome.mdx
268
- │ └── welcome-advanced.mdx
83
+ packages/app/docs/en/
84
+ ├── welcome.mdx
85
+ └── welcome-advanced.mdx
269
86
  ```
270
87
 
271
- Set `hasAdvanced: true` in the simple version's frontmatter, and the framework automatically shows a toggle.
272
-
273
- ## API reference
88
+ Set `hasAdvanced: true` in the simple version frontmatter.
274
89
 
275
- ### Components
90
+ ## Navigation configuration
276
91
 
277
- All components are exported from `@x-wave/blog`:
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.
278
93
 
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
- ```
94
+ ## Next.js implementation notes
293
95
 
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
- ```
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)
352
99
 
353
100
  ## Customization
354
101
 
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
102
+ ### Styles
419
103
 
420
- The framework includes an optional home/latest posts page that lists articles sorted by publish date. Configure it with the `defaultRoute` option:
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.
421
105
 
422
- ```tsx
423
- // Show home page at root path (lists latest articles)
424
- config: {
425
- defaultRoute: 'latest' // or omit (defaults to 'latest')
426
- }
106
+ ### Base path
427
107
 
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
- ```
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.
450
109
 
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
- ```
110
+ ### Custom header
501
111
 
502
- ### Reusable components
112
+ Omit the `header` config to hide the built-in header and use the hooks provided by the UI package (`useTheme`, `useSearchModal`).
503
113
 
504
- #### Metadata component
114
+ ## Scripts
505
115
 
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:
116
+ From repo root:
507
117
 
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+)
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
552
121
 
553
122
  ## License
554
123
 
555
- See LICENSE file in this repository.
556
-
557
- ---
124
+ See [LICENSE](LICENSE).
558
125
 
559
- ## For framework maintainers
126
+ ## Maintainers
560
127
 
561
- Contributing or maintaining this framework? See [DEVELOPMENT.md](./DEVELOPMENT.md) for setup, architecture, and build system details.
128
+ See [DEVELOPMENT.md](DEVELOPMENT.md) for build system details and contributor notes.