@x-wave/blog 1.0.0 → 1.0.3

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 ADDED
@@ -0,0 +1,424 @@
1
+ # @x-wave/blog
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.
4
+
5
+ ## Features
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' // Framework 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.
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, 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
+ ```
179
+
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
+ }
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.
229
+
230
+ ### Frontmatter
231
+
232
+ Optional YAML at the top of your MDX file:
233
+
234
+ ```mdx
235
+ ---
236
+ title: Getting Started
237
+ author: Jane Doe
238
+ date: 2026-02-23
239
+ hasAdvanced: true
240
+ tags:
241
+ - tutorial
242
+ - beginner
243
+ ---
244
+
245
+ # Welcome!
246
+
247
+ Regular content here.
248
+ ```
249
+
250
+ | Field | Type | Description |
251
+ |---|---|---|
252
+ | `title` | `string` | Document title (informational, not displayed by framework) |
253
+ | `author` | `string` | Author name. Displayed below the page title with a user icon. |
254
+ | `date` | `string` | Publication or update date. Displayed below the page title with "Last edited" label and calendar icon (i18n supported). |
255
+ | `hasAdvanced` | `boolean` | Enables Simple/Advanced mode toggle. Requires a `-advanced.mdx` variant. |
256
+ | `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. |
257
+
258
+ ### Advanced mode variants
259
+
260
+ Create `welcome-advanced.mdx` alongside `welcome.mdx`:
261
+
262
+ ```
263
+ src/docs/
264
+ ├── en/
265
+ │ ├── welcome.mdx
266
+ │ └── welcome-advanced.mdx
267
+ ```
268
+
269
+ Set `hasAdvanced: true` in the simple version's frontmatter, and the framework automatically shows a toggle.
270
+
271
+ ## API reference
272
+
273
+ ### Components
274
+
275
+ All components are exported from `@x-wave/blog`:
276
+
277
+ ```ts
278
+ import {
279
+ BlogProvider,
280
+ DocumentationRoutes,
281
+ DocumentationLayout,
282
+ ContentPage,
283
+ Header,
284
+ Sidebar,
285
+ TableOfContents,
286
+ AdvancedModeToggle
287
+ } from '@x-wave/blog'
288
+ ```
289
+
290
+ | Component | Purpose |
291
+ |---|---|
292
+ | `BlogProvider` | Root context wrapper (required) |
293
+ | `DocumentationRoutes` | Pre-configured Routes for documentation pages (recommended) |
294
+ | `DocumentationLayout` | Page layout: header + sidebar + content |
295
+ | `ContentPage` | Loads and renders MDX pages |
296
+ | `Header` | Top navigation bar |
297
+ | `Sidebar` | Left navigation panel |
298
+ | `TableOfContents` | "On this page" anchor panel |
299
+ | `AdvancedModeToggle` | Simple/Advanced tab switch |
300
+
301
+ ### Hooks
302
+
303
+ ```ts
304
+ import { useTheme } from '@x-wave/blog'
305
+
306
+ const { theme, setTheme, effectiveTheme } = useTheme()
307
+ ```
308
+ Manages light/dark/system theme preference.
309
+
310
+ ### Utilities
311
+
312
+ ```ts
313
+ import { createBlogUtils } from '@x-wave/blog'
314
+
315
+ const blog = createBlogUtils(mdxFiles)
316
+ ```
317
+ Creates all blog utilities from a Vite glob import. This is the recommended approach as it bundles everything together.
318
+
319
+ Returns an object with:
320
+ - **`mdxFiles`**: The glob import (used internally for automatic tag indexing)
321
+ - **`loadContent(language, slug, advanced?)`**: Loads MDX content for a specific language and slug
322
+ - **`loadEnglishContent(slug, advanced?)`**: Loads English content for heading ID generation
323
+
324
+ Pass the entire `blog` object to BlogProvider:
325
+
326
+ ```tsx
327
+ <BlogProvider config={config} blog={blog}>
328
+ ```
329
+
330
+ **Alternative: Advanced usage**
331
+
332
+ ```ts
333
+ import { createContentLoaders } from '@x-wave/blog'
334
+
335
+ const { loadMDXContent, loadEnglishContent, buildTagIndex } = createContentLoaders(mdxFiles)
336
+ ```
337
+ For advanced use cases where you need more control over content loading and tag indexing.
338
+
339
+ ### Types
340
+
341
+ All TypeScript types are exported from `@x-wave/blog/types`:
342
+
343
+ ```ts
344
+ import type { NavigationEntry, BlogConfig, HeaderLink } from '@x-wave/blog/types'
345
+ ```
346
+
347
+ ## Customization
348
+
349
+ ### CSS variables
350
+
351
+ The framework exports SCSS variable files. Import and override them in your own stylesheets:
352
+
353
+ ```scss
354
+ // In your app.scss
355
+ @use '~@x-wave/blog/styles/_variables' as vars;
356
+
357
+ // Override the color palette
358
+ $color-primary: #007bff;
359
+ $color-background: #fafafa;
360
+
361
+ // Your custom styles here
362
+ ```
363
+
364
+ Or import directly from the framework package:
365
+
366
+ ```scss
367
+ @import 'node_modules/@x-wave/blog/dist/styles/_variables.scss';
368
+
369
+ $color-primary: #007bff;
370
+ $color-background: #fafafa;
371
+ ```
372
+
373
+ Available variables include:
374
+ - `$color-primary`, `$color-secondary`
375
+ - `$color-background`, `$color-text`
376
+ - `$spacing-xs`, `$spacing-sm`, `$spacing-md`, `$spacing-lg`, `$spacing-xl`
377
+ - `$font-family-sans`, `$font-family-mono`
378
+ - And more—see [styles/_variables.scss](packages/styles/_variables.scss)
379
+
380
+ ### Config options
381
+
382
+ **BlogConfig** properties:
383
+
384
+ ```ts
385
+ interface BlogConfig {
386
+ title: string // Site title
387
+ logo?: React.ComponentType<{ className?: string }> // Optional logo
388
+ supportedLanguages: readonly string[] // e.g. ['en', 'es', 'zh']
389
+ navigationData: NavigationEntry[] // Menu structure
390
+ basePath?: string // Base path for all routes (default: '')
391
+ header?: {
392
+ navLinks?: HeaderLink[] // Top-level nav links
393
+ dropdownItems?: HeaderDropdownItem[] // Support dropdown menu
394
+ }
395
+ }
396
+ ```
397
+
398
+ **Key properties:**
399
+
400
+ | Property | Type | Required | Description |
401
+ |---|---|---|---|
402
+ | `title` | `string` | Yes | Site title displayed in header and sidebar |
403
+ | `supportedLanguages` | `string[]` | Yes | Array of language codes (e.g., `['en', 'es', 'zh']`) |
404
+ | `navigationData` | `NavigationEntry[]` | Yes | Sidebar navigation structure |
405
+ | `basePath` | `string` | No | Base path prefix for all routes. Use when mounting docs under a subpath like `/blog` or `/docs`. Default: `''` (root) |
406
+ | `logo` | `React.ComponentType` | No | SVG component for site logo |
407
+ | `header` | `object` | No | Header configuration with nav links and dropdown items |
408
+
409
+ ## Browser support
410
+
411
+ - Chrome/Edge 90+
412
+ - Firefox 88+
413
+ - Safari 14+
414
+ - Mobile browsers (iOS Safari 14.5+, Chrome Android 90+)
415
+
416
+ ## License
417
+
418
+ See LICENSE file in this repository.
419
+
420
+ ---
421
+
422
+ ## For framework maintainers
423
+
424
+ Contributing or maintaining this framework? See [DEVELOPMENT.md](./DEVELOPMENT.md) for setup, architecture, and build system details.