@x-wave/blog 2.2.7 → 2.3.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.
@@ -0,0 +1,5 @@
1
+ interface AdvancedModeToggleProps {
2
+ hasAdvanced: boolean;
3
+ }
4
+ export declare function AdvancedModeToggle({ hasAdvanced }: AdvancedModeToggleProps): import("react/jsx-runtime").JSX.Element | null;
5
+ export {};
@@ -0,0 +1,8 @@
1
+ export interface ArticleNavigationProps {
2
+ prevSlug?: string;
3
+ prevTitle?: string;
4
+ nextSlug?: string;
5
+ nextTitle?: string;
6
+ language: string;
7
+ }
8
+ export declare function ArticleNavigation({ prevSlug, prevTitle, nextSlug, nextTitle, language, }: ArticleNavigationProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ interface BlogRootProps {
3
+ children: ReactNode;
4
+ }
5
+ /**
6
+ * BlogRoot component marks the entry point of the blog framework in the DOM.
7
+ * All blog-specific styles and functionality are scoped within this element.
8
+ */
9
+ export declare function BlogRoot({ children }: BlogRootProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1 @@
1
+ export declare function BlogSidebar(): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,5 @@
1
+ export interface BreadcrumbProps {
2
+ articleTitle: string;
3
+ language: string;
4
+ }
5
+ export declare function Breadcrumb({ articleTitle, language }: BreadcrumbProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { SupportedLanguage } from 'types';
2
+ interface ContentPageProps {
3
+ language: SupportedLanguage;
4
+ }
5
+ export declare function ContentPage({ language }: ContentPageProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,6 @@
1
+ import { ReactNode } from 'react';
2
+ interface DocumentationLayoutProps {
3
+ children: ReactNode;
4
+ }
5
+ export declare function DocumentationLayout({ children }: DocumentationLayoutProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Pre-configured Routes component for documentation pages.
3
+ * Use this inside your router to handle language-specific documentation routing.
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * import { BlogProvider, DocumentationRoutes } from '@x-wave/blog'
8
+ * import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
9
+ *
10
+ * function App() {
11
+ * return (
12
+ * <BlogProvider config={config} blog={blog}>
13
+ * <BrowserRouter>
14
+ * <Routes>
15
+ * <Route path="/:language/*" element={<DocumentationRoutes />} />
16
+ * <Route path="/" element={<Navigate to="/en/welcome" replace />} />
17
+ * </Routes>
18
+ * </BrowserRouter>
19
+ * </BlogProvider>
20
+ * )
21
+ * }
22
+ * ```
23
+ */
24
+ export declare function DocumentationRoutes(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ interface HeaderProps {
2
+ onMobileMenuToggle?: () => void;
3
+ }
4
+ export declare function Header({ onMobileMenuToggle }: HeaderProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,8 @@
1
+ export interface BlogArticle {
2
+ slug: string;
3
+ title: string;
4
+ date?: string;
5
+ author?: string;
6
+ description?: string;
7
+ }
8
+ export declare function HomePage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ export interface MetadataProps {
2
+ date?: string;
3
+ author?: string;
4
+ }
5
+ export declare function Metadata({ date, author }: MetadataProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,6 @@
1
+ interface MobileMenuProps {
2
+ isOpen: boolean;
3
+ onClose: () => void;
4
+ }
5
+ export declare function MobileMenu({ isOpen, onClose }: MobileMenuProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,17 @@
1
+ interface LanguageSelectorProps {
2
+ styles: Record<string, string>;
3
+ onLanguageChange?: () => void;
4
+ }
5
+ export declare function LanguageSelector({ styles, onLanguageChange, }: LanguageSelectorProps): import("react/jsx-runtime").JSX.Element;
6
+ interface NavigationMenuProps {
7
+ styles: Record<string, string>;
8
+ onLinkClick?: () => void;
9
+ }
10
+ export declare function NavigationMenu({ styles, onLinkClick }: NavigationMenuProps): import("react/jsx-runtime").JSX.Element;
11
+ interface NavigationContentProps {
12
+ styles: Record<string, string>;
13
+ onLinkClick?: () => void;
14
+ onLanguageChange?: () => void;
15
+ }
16
+ export declare function NavigationContent({ styles, onLinkClick, onLanguageChange, }: NavigationContentProps): import("react/jsx-runtime").JSX.Element;
17
+ export {};
@@ -0,0 +1,5 @@
1
+ interface SearchBarProps {
2
+ language: string;
3
+ }
4
+ export declare function SearchBar({ language }: SearchBarProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1 @@
1
+ export declare function Sidebar(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ interface TableOfContentsProps {
2
+ content: string;
3
+ englishContent: string;
4
+ }
5
+ export declare function TableOfContents({ content, englishContent, }: TableOfContentsProps): import("react/jsx-runtime").JSX.Element | null;
6
+ export {};
@@ -0,0 +1,12 @@
1
+ interface TagResult {
2
+ title: string;
3
+ slug: string;
4
+ }
5
+ interface TagResultsModalProps {
6
+ tag: string;
7
+ results: TagResult[];
8
+ language: string;
9
+ onClose: () => void;
10
+ }
11
+ export declare function TagResultsModal({ tag, results, language, onClose, }: TagResultsModalProps): import('react').ReactPortal;
12
+ export {};
@@ -0,0 +1,7 @@
1
+ interface TagsProps {
2
+ tags: string[];
3
+ variant?: 'default' | 'compact';
4
+ onTagClick?: (tag: string) => void;
5
+ }
6
+ export declare function Tags({ tags, variant, onTagClick }: TagsProps): import("react/jsx-runtime").JSX.Element | null;
7
+ export {};
@@ -0,0 +1,24 @@
1
+ const t = [
2
+ {
3
+ slug: "welcome"
4
+ },
5
+ {
6
+ title: "nav.helpResources",
7
+ defaultOpen: !0,
8
+ items: [
9
+ {
10
+ slug: "glossary"
11
+ },
12
+ {
13
+ slug: "faq"
14
+ }
15
+ ]
16
+ }
17
+ ], s = "Staking Docs", o = ["en", "zh", "es"], e = "https://github.com/polkadot-cloud", l = "https://staking.polkadot.cloud";
18
+ export {
19
+ s as DOCS_TITLE,
20
+ e as GITHUB_REPO_URL,
21
+ t as NAVIGATION_DATA,
22
+ l as STAKING_DASHBOARD_URL,
23
+ o as SUPPORTED_LANGUAGES
24
+ };
package/context.d.ts ADDED
@@ -0,0 +1,194 @@
1
+ import { ReactNode } from 'react';
2
+ import { LoadedContent, NavigationEntry } from 'types';
3
+ /**
4
+ * Configuration for the blog framework, provided once via `BlogProvider`
5
+ * and accessible throughout the component tree via `useBlogConfig()`.
6
+ */
7
+ export interface BlogConfig {
8
+ /** The blog/docs site title displayed in the sidebar and mobile menu. */
9
+ title: string;
10
+ /**
11
+ * Optional default description for SEO meta tags.
12
+ * Used as fallback description for articles that don't have their own description.
13
+ */
14
+ description?: string;
15
+ /**
16
+ * An optional SVG React component used as the site logo.
17
+ * Receives a `className` prop for styling.
18
+ * Example: `import CloudSvg from './cloud.svg?react'`
19
+ */
20
+ logo?: React.ComponentType<{
21
+ className?: string;
22
+ }>;
23
+ /**
24
+ * The list of supported locale codes (e.g. `['en', 'zh', 'es']`).
25
+ * These are used for language validation in routing and the language switcher.
26
+ */
27
+ supportedLanguages: readonly string[];
28
+ /**
29
+ * Optional base path for all documentation routes.
30
+ * Use this when mounting documentation under a subpath (e.g., '/blog' or '/docs').
31
+ * Default: '' (root path)
32
+ * Example: basePath: '/blog' → routes become /blog/en/welcome
33
+ */
34
+ basePath?: string;
35
+ /**
36
+ * Optional maximum width for the content area.
37
+ * Default: '80rem'
38
+ * Example: contentMaxWidth: '100rem' or contentMaxWidth: '1400px'
39
+ */
40
+ contentMaxWidth?: string;
41
+ /**
42
+ * The default route displayed at the root path (/).
43
+ * Use 'latest' to show the home page listing latest articles.
44
+ * Use any slug (e.g., 'welcome', 'getting-started') to display that article.
45
+ * Default: 'latest'
46
+ * Example: defaultRoute: 'welcome' or defaultRoute: 'latest'
47
+ */
48
+ defaultRoute?: string;
49
+ /**
50
+ * Whether to display the sidebar navigation menu.
51
+ * Default: true
52
+ * Note: This is independent of navigationData. You can have navigationData without showing a sidebar,
53
+ * or show a sidebar with empty navigationData.
54
+ */
55
+ showSideMenu?: boolean;
56
+ /**
57
+ * Whether to display next/previous article navigation at the bottom of content pages.
58
+ * Useful for guiding readers through sequential content.
59
+ * Default: false
60
+ */
61
+ showArticleNavigation?: boolean;
62
+ /**
63
+ * Optional default theme for the blog.
64
+ * If set, this will override the user's localStorage preference and system theme.
65
+ * Use this to ensure the blog matches your consuming app's theme.
66
+ * Options: 'light' | 'dark' | 'system' (default: uses localStorage or 'system')
67
+ */
68
+ defaultTheme?: 'light' | 'dark' | 'system';
69
+ /**
70
+ * Optional header configuration.
71
+ * If omitted entirely, the built-in header component will not be rendered.
72
+ * Use the exported `useTheme()` and `useSearchModal()` hooks to build custom headers.
73
+ */
74
+ header?: {
75
+ /**
76
+ * Custom navigation links displayed in the header.
77
+ * Example: "Launch App" button or external links.
78
+ */
79
+ navLinks?: HeaderLink[];
80
+ /**
81
+ * Custom dropdown menu items.
82
+ * If provided, displays a "Support" dropdown with these items.
83
+ */
84
+ dropdownItems?: HeaderDropdownItem[];
85
+ };
86
+ /**
87
+ * Optional social links displayed in the blog sidebar.
88
+ * These links appear in the "Connect" section on the home page.
89
+ * Example: Discord, GitHub, Email, etc.
90
+ */
91
+ socialLinks?: HeaderLink[];
92
+ /**
93
+ * Optional React node to render after article content as a Call-to-Action.
94
+ * Appears after the main content but before tags and article navigation.
95
+ * Example: Newsletter signup, related links, promotional content.
96
+ */
97
+ articleCTA?: React.ReactNode;
98
+ /**
99
+ * Optional custom font CSS property for article titles.
100
+ * Applied to h1 blog titles on ContentPage and title on HomePage.
101
+ * Example: 'Roboto, sans-serif' or '"Segoe UI", sans-serif'
102
+ */
103
+ articleTitleFont?: string;
104
+ }
105
+ /**
106
+ * A navigation link in the header.
107
+ */
108
+ export interface HeaderLink {
109
+ /** Link text. Can be a string, i18n key, or React element. */
110
+ label: string | React.ReactNode;
111
+ /** Link URL (supports mailto: and other protocols). */
112
+ url: string;
113
+ /** Optional icon component (receives `className` prop for styling). */
114
+ icon?: React.ComponentType<{
115
+ className?: string;
116
+ }>;
117
+ /** Whether to open in new tab. Defaults to `true` for external URLs. */
118
+ target?: '_blank' | '_self';
119
+ /** rel attribute (e.g. 'noopener noreferrer'). Auto-set if target is '_blank'. */
120
+ rel?: string;
121
+ /** Optional CSS class name for custom styling. */
122
+ className?: string;
123
+ }
124
+ /**
125
+ * A dropdown menu item in the header support dropdown.
126
+ */
127
+ export interface HeaderDropdownItem {
128
+ /** Item text. Can be a string, i18n key, or React element. */
129
+ label: string | React.ReactNode;
130
+ /** Item URL (supports mailto: and other protocols). */
131
+ url: string;
132
+ /** Optional icon component (receives `className` prop for styling). */
133
+ icon?: React.ComponentType<{
134
+ className?: string;
135
+ }>;
136
+ /** Whether to open in new tab. Defaults to `true` for external URLs. */
137
+ target?: '_blank' | '_self';
138
+ /** rel attribute (e.g. 'noopener noreferrer'). Auto-set if target is '_blank'. */
139
+ rel?: string;
140
+ }
141
+ export interface BlogContextValue {
142
+ config: BlogConfig & {
143
+ /** Dynamically built tag index from MDX frontmatter, per language */
144
+ tagIndex?: Record<string, Record<string, Array<{
145
+ slug: string;
146
+ title: string;
147
+ }>>>;
148
+ /** Optional navigation data passed to BlogProvider */
149
+ navigationData?: NavigationEntry[];
150
+ };
151
+ loadContent: (language: string, slug: string, advanced?: boolean) => Promise<LoadedContent>;
152
+ loadEnglishContent: (slug: string, advanced?: boolean) => Promise<string>;
153
+ discoverArticles: (language: string) => Promise<Array<{
154
+ slug: string;
155
+ title: string;
156
+ date?: string;
157
+ author?: string;
158
+ description?: string;
159
+ }>>;
160
+ }
161
+ /**
162
+ * Blog utilities object returned from createBlogUtils().
163
+ * Contains MDX files glob and content loaders.
164
+ */
165
+ export interface BlogUtils {
166
+ mdxFiles: Record<string, () => Promise<unknown>>;
167
+ loadContent: (language: string, slug: string, advanced?: boolean) => Promise<LoadedContent>;
168
+ loadEnglishContent: (slug: string, advanced?: boolean) => Promise<string>;
169
+ discoverArticles: (language: string) => Promise<Array<{
170
+ slug: string;
171
+ title: string;
172
+ date?: string;
173
+ author?: string;
174
+ description?: string;
175
+ }>>;
176
+ }
177
+ export interface BlogProviderProps {
178
+ children: ReactNode;
179
+ config: BlogConfig;
180
+ /**
181
+ * Blog utilities object returned from createBlogUtils().
182
+ * Contains MDX files and content loaders.
183
+ */
184
+ blog: BlogUtils;
185
+ /**
186
+ * Optional navigation structure for the docs sidebar.
187
+ * If omitted, the sidebar will not be rendered.
188
+ * Entries may be individual items or grouped sections.
189
+ * Titles should be i18n keys resolved at runtime.
190
+ */
191
+ navigationData?: NavigationEntry[];
192
+ }
193
+ export declare function BlogProvider({ children, config, blog, navigationData, }: BlogProviderProps): import("react/jsx-runtime").JSX.Element;
194
+ export declare function useBlogConfig(): BlogContextValue;
package/hooks.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Hooks and utilities for custom header implementations.
3
+ * These are the same functions used by the built-in Header component.
4
+ */
5
+ export type { Theme } from './theme';
6
+ export { useTheme } from './theme';
7
+ /**
8
+ * Hook to control the search modal.
9
+ * Returns functions to open/close the search modal programmatically.
10
+ */
11
+ export declare function useSearchModal(): {
12
+ openSearchModal: () => void;
13
+ closeSearchModal: () => void;
14
+ };
15
+ /**
16
+ * Hook to change the language.
17
+ * Returns a function that changes the i18n language and navigates to the corresponding route.
18
+ * Preserves search params and hash, and handles basePath correctly.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const changeLanguage = useLanguageChange()
23
+ * <button onClick={() => changeLanguage('es')}>Español</button>
24
+ * ```
25
+ */
26
+ export declare function useLanguageChange(): (newLanguage: string) => void;
package/index.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ export { AdvancedModeToggle } from './components/AdvancedModeToggle';
2
+ export type { ArticleNavigationProps } from './components/ArticleNavigation';
3
+ export { ArticleNavigation } from './components/ArticleNavigation';
4
+ export { BlogRoot } from './components/BlogRoot';
5
+ export { BlogSidebar } from './components/BlogSidebar';
6
+ export type { BreadcrumbProps } from './components/Breadcrumb';
7
+ export { Breadcrumb } from './components/Breadcrumb';
8
+ export { ContentPage } from './components/ContentPage';
9
+ export { DocumentationLayout } from './components/DocumentationLayout';
10
+ export { DocumentationRoutes } from './components/DocumentationRoutes';
11
+ export { Header } from './components/Header';
12
+ export { HomePage } from './components/HomePage';
13
+ export type { MetadataProps } from './components/Metadata';
14
+ export { Metadata } from './components/Metadata';
15
+ export { MobileMenu } from './components/MobileMenu';
16
+ export { LanguageSelector, NavigationContent, NavigationMenu, } from './components/NavigationContent';
17
+ export { SearchBar } from './components/SearchBar';
18
+ export { Sidebar } from './components/Sidebar';
19
+ export { TableOfContents } from './components/TableOfContents';
20
+ export { TagResultsModal } from './components/TagResultsModal';
21
+ export { Tags } from './components/Tags';
22
+ export type { BlogConfig, BlogContextValue, BlogProviderProps, BlogUtils, HeaderDropdownItem, HeaderLink, } from './context';
23
+ export { BlogProvider, useBlogConfig } from './context';
24
+ export type { Theme } from './hooks';
25
+ export { useLanguageChange, useSearchModal, useTheme } from './hooks';
26
+ export { createBlogUtils, createContentLoaders } from './loaders';
27
+ export { ThemeProvider } from './theme';
28
+ export { generateHeadingId, getAdjacentArticles, getNavigationData, } from './utils';
package/loaders.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ import { LoadedContent } from 'types';
2
+ /**
3
+ * Factory function to create content loader functions from a Vite glob import.
4
+ *
5
+ * The app must handle the `import.meta.glob` call because Vite resolves glob patterns
6
+ * relative to the file that calls them. This function handles all the generic logic:
7
+ * frontmatter parsing and content loading.
8
+ *
9
+ * @param mdxFiles - Object returned from `import.meta.glob('./docs/**\/*.mdx', { ... })`
10
+ * @returns Object with `loadMDXContent` and `loadEnglishContent` functions
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const mdxFiles = import.meta.glob('./docs/**\/*.mdx', {
15
+ * query: '?raw',
16
+ * import: 'default',
17
+ * eager: false,
18
+ * })
19
+ *
20
+ * export const { loadMDXContent, loadEnglishContent } = createContentLoaders(mdxFiles)
21
+ * ```
22
+ */
23
+ export declare function createContentLoaders(mdxFiles: Record<string, () => Promise<unknown>>): {
24
+ /**
25
+ * Load MDX content for a given language and slug.
26
+ * Automatically loads the advanced variant if `advanced` is true.
27
+ */
28
+ loadMDXContent(language: string, slug: string, advanced?: boolean): Promise<LoadedContent>;
29
+ /**
30
+ * Load English content for generating consistent heading IDs.
31
+ * All heading anchors are derived from English content for stability across translations.
32
+ */
33
+ loadEnglishContent(slug: string, advanced?: boolean): Promise<string>;
34
+ /**
35
+ * Build a tag index from all MDX files.
36
+ * Uses shared metadata cache to avoid duplicate file processing.
37
+ *
38
+ * @param language - Language code to scan (defaults to 'en')
39
+ * @returns Promise resolving to tag index mapping tag names to document info
40
+ */
41
+ buildTagIndex(language?: string): Promise<Record<string, Array<{
42
+ slug: string;
43
+ title: string;
44
+ }>>>;
45
+ /**
46
+ * Discover all articles from the file system by scanning MDX files.
47
+ * Uses shared metadata cache to avoid duplicate file processing with buildTagIndex.
48
+ *
49
+ * @param language - Language code to scan
50
+ * @returns Promise resolving to array of articles with metadata, sorted by date (newest first) then title
51
+ */
52
+ discoverArticles(language: string): Promise<Array<{
53
+ slug: string;
54
+ title: string;
55
+ date?: string;
56
+ author?: string;
57
+ description?: string;
58
+ }>>;
59
+ };
60
+ /**
61
+ * Convenience function to create all blog utilities from a Vite glob import.
62
+ * This bundles mdxFiles, content loaders, and tag indexing into a single export.
63
+ *
64
+ * @param mdxFiles - Object returned from `import.meta.glob('./docs/**\/*.mdx', { ... })`
65
+ * @returns Object with mdxFiles, loadContent, loadEnglishContent, and discoverArticles
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const mdxFiles = import.meta.glob('./docs/**\/*.mdx', {
70
+ * query: '?raw',
71
+ * import: 'default',
72
+ * eager: false,
73
+ * })
74
+ *
75
+ * export const blog = createBlogUtils(mdxFiles)
76
+ *
77
+ * // In App.tsx:
78
+ * <BlogProvider config={config} blog={blog} />
79
+ * ```
80
+ */
81
+ export declare function createBlogUtils(mdxFiles: Record<string, () => Promise<unknown>>): {
82
+ mdxFiles: Record<string, () => Promise<unknown>>;
83
+ loadContent: (language: string, slug: string, advanced?: boolean) => Promise<LoadedContent>;
84
+ loadEnglishContent: (slug: string, advanced?: boolean) => Promise<string>;
85
+ discoverArticles: (language: string) => Promise<Array<{
86
+ slug: string;
87
+ title: string;
88
+ date?: string;
89
+ author?: string;
90
+ description?: string;
91
+ }>>;
92
+ };
@@ -0,0 +1,180 @@
1
+ import e from "i18next";
2
+ import { default as c } from "i18next";
3
+ import a from "i18next-browser-languagedetector";
4
+ import { initReactI18next as t } from "react-i18next";
5
+ const s = {
6
+ en: {
7
+ translation: {
8
+ language: "English",
9
+ languageCode: "en",
10
+ nav: {
11
+ gettingStarted: "Getting Started",
12
+ helpResources: "Help Resources",
13
+ guides: "Guides",
14
+ reference: "Reference",
15
+ previous: "Previous",
16
+ next: "Next"
17
+ },
18
+ docs: {
19
+ helloWorld: "Welcome!",
20
+ gettingStarted: "Getting Started",
21
+ glossary: "Glossary",
22
+ faq: "FAQ"
23
+ },
24
+ ui: {
25
+ home: "Home",
26
+ simple: "Simple",
27
+ advanced: "Advanced",
28
+ switchLanguage: "Switch Language",
29
+ tableOfContents: "Table of Contents",
30
+ onThisPage: "On this page",
31
+ launchApp: "Launch App",
32
+ support: "Support",
33
+ discord: "Discord",
34
+ email: "Email",
35
+ theme: "Theme",
36
+ light: "Light",
37
+ dark: "Dark",
38
+ system: "System",
39
+ search: "Search",
40
+ searchPlaceholder: "Search documentation...",
41
+ searchNavigate: "to navigate",
42
+ searchSelect: "to select",
43
+ searchClose: "to close",
44
+ noSearchResults: "No results found",
45
+ tags: "Tags",
46
+ filterByTag: "Filter by tag",
47
+ tagResults: "Articles tagged with",
48
+ noTagResults: "No articles found with this tag",
49
+ lastEdited: "Last edited",
50
+ axes: "Articles",
51
+ latestPosts: "Latest Posts",
52
+ loadingArticles: "Loading articles...",
53
+ noArticlesFound: "No articles found",
54
+ by: "By",
55
+ connect: "Connect"
56
+ }
57
+ }
58
+ },
59
+ zh: {
60
+ translation: {
61
+ language: "中文",
62
+ languageCode: "zh",
63
+ nav: {
64
+ gettingStarted: "开始使用",
65
+ helpResources: "帮助资源",
66
+ guides: "指南",
67
+ reference: "参考",
68
+ previous: "上一篇",
69
+ next: "下一篇"
70
+ },
71
+ docs: {
72
+ helloWorld: "欢迎!",
73
+ gettingStarted: "开始使用",
74
+ glossary: "术语表",
75
+ faq: "常见问题"
76
+ },
77
+ ui: {
78
+ home: "首页",
79
+ simple: "简单",
80
+ advanced: "高级",
81
+ switchLanguage: "切换语言",
82
+ tableOfContents: "目录",
83
+ onThisPage: "本页内容",
84
+ launchApp: "启动应用",
85
+ support: "支持",
86
+ discord: "Discord",
87
+ email: "电子邮件",
88
+ theme: "主题",
89
+ light: "浅色",
90
+ dark: "深色",
91
+ system: "系统",
92
+ search: "搜索",
93
+ searchPlaceholder: "搜索文档...",
94
+ searchNavigate: "导航",
95
+ searchSelect: "选择",
96
+ searchClose: "关闭",
97
+ noSearchResults: "未找到结果",
98
+ tags: "标签",
99
+ filterByTag: "按标签筛选",
100
+ tagResults: "带有标签的文章",
101
+ noTagResults: "未找到带有此标签的文章",
102
+ lastEdited: "最后编辑",
103
+ articles: "文章",
104
+ loadingArticles: "正在加载文章...",
105
+ noArticlesFound: "未找到文章",
106
+ by: "作者",
107
+ connect: "连接"
108
+ }
109
+ }
110
+ },
111
+ es: {
112
+ translation: {
113
+ language: "Español",
114
+ languageCode: "es",
115
+ nav: {
116
+ gettingStarted: "Primeros Pasos",
117
+ helpResources: "Recursos de Ayuda",
118
+ guides: "Guías",
119
+ reference: "Referencia",
120
+ previous: "Anterior",
121
+ next: "Siguiente"
122
+ },
123
+ docs: {
124
+ helloWorld: "¡Bienvenido!",
125
+ gettingStarted: "Primeros Pasos",
126
+ glossary: "Glosario",
127
+ faq: "Preguntas Frecuentes"
128
+ },
129
+ ui: {
130
+ home: "Inicio",
131
+ simple: "Simple",
132
+ advanced: "Avanzado",
133
+ switchLanguage: "Cambiar Idioma",
134
+ tableOfContents: "Tabla de Contenidos",
135
+ onThisPage: "En esta página",
136
+ launchApp: "Iniciar App",
137
+ support: "Soporte",
138
+ discord: "Discord",
139
+ email: "Correo",
140
+ theme: "Tema",
141
+ light: "Claro",
142
+ dark: "Oscuro",
143
+ system: "Sistema",
144
+ search: "Buscar",
145
+ searchPlaceholder: "Buscar documentación...",
146
+ searchNavigate: "para navegar",
147
+ searchSelect: "para seleccionar",
148
+ searchClose: "para cerrar",
149
+ noSearchResults: "No se encontraron resultados",
150
+ tags: "Etiquetas",
151
+ filterByTag: "Filtrar por etiqueta",
152
+ tagResults: "Artículos etiquetados con",
153
+ noTagResults: "No se encontraron artículos con esta etiqueta",
154
+ lastEdited: "Última edición",
155
+ articles: "Artículos",
156
+ loadingArticles: "Cargando artículos...",
157
+ noArticlesFound: "No se encontraron artículos",
158
+ by: "Por",
159
+ connect: "Conectar"
160
+ }
161
+ }
162
+ }
163
+ };
164
+ e.use(a).use(t).init({
165
+ resources: s,
166
+ fallbackLng: "en",
167
+ lng: "en",
168
+ // Set 'en' as the initial language synchronously
169
+ debug: !1,
170
+ interpolation: {
171
+ escapeValue: !1
172
+ },
173
+ detection: {
174
+ order: ["path", "localStorage", "navigator"],
175
+ lookupFromPathIndex: 0
176
+ }
177
+ });
178
+ export {
179
+ c as default
180
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-wave/blog",
3
- "version": "2.2.7",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -51,6 +51,7 @@
51
51
  "@vitejs/plugin-react-swc": "^3.10.1",
52
52
  "sass": "^1.97.3",
53
53
  "vite": "^6.3.5",
54
+ "vite-plugin-dts": "^4.5.4",
54
55
  "vite-tsconfig-paths": "^5.1.4"
55
56
  }
56
57
  }
package/theme.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from 'react';
2
+ /**
3
+ * Theme preference options:
4
+ * - `'light'` - always use light theme
5
+ * - `'dark'` - always use dark theme
6
+ * - `'system'` - automatically follow the OS/browser preference
7
+ */
8
+ export type Theme = 'light' | 'dark' | 'system';
9
+ interface ThemeContextValue {
10
+ theme: Theme;
11
+ setTheme: (theme: Theme) => void;
12
+ effectiveTheme: 'light' | 'dark';
13
+ }
14
+ interface ThemeProviderProps {
15
+ children: ReactNode;
16
+ defaultTheme?: Theme;
17
+ }
18
+ export declare function ThemeProvider({ children, defaultTheme }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare const useTheme: () => ThemeContextValue;
20
+ export {};
@@ -0,0 +1,31 @@
1
+ export interface NavigationItem {
2
+ slug: string;
3
+ hasAdvanced?: boolean;
4
+ }
5
+ export interface NavigationItemWithTitle extends NavigationItem {
6
+ title: string;
7
+ }
8
+ export interface NavigationSection {
9
+ title: string;
10
+ items: NavigationItem[];
11
+ defaultOpen?: boolean;
12
+ }
13
+ export interface NavigationSectionWithTitles {
14
+ title: string;
15
+ items: NavigationItemWithTitle[];
16
+ defaultOpen?: boolean;
17
+ }
18
+ export type NavigationEntry = NavigationSection | NavigationItem;
19
+ export type NavigationEntryWithTitles = NavigationSectionWithTitles | NavigationItemWithTitle;
20
+ export interface LoadedContent {
21
+ content: string;
22
+ frontmatter: {
23
+ title?: string;
24
+ hasAdvanced?: boolean;
25
+ tags?: string[];
26
+ date?: string;
27
+ author?: string;
28
+ [key: string]: unknown;
29
+ };
30
+ }
31
+ export type SupportedLanguage = string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * URL and link manipulation utilities for the blog framework
3
+ */
4
+ /**
5
+ * Build an internal link with language prefix and optional basePath
6
+ *
7
+ * @param path - The relative path (e.g., 'welcome', 'glossary')
8
+ * @param language - The language code (e.g., 'en', 'es', 'zh')
9
+ * @param basePath - Optional base path prefix
10
+ * @returns The formatted link URL
11
+ */
12
+ export declare function buildInternalLink(path: string, language: string, basePath?: string): string;
13
+ /**
14
+ * Parse search/query parameters from a URL search string
15
+ *
16
+ * @param search - The search string (with or without leading ?)
17
+ * @returns Object with parsed advanced mode and anchor parameters
18
+ */
19
+ export declare function parseSearchParams(search: string): {
20
+ advanced: boolean;
21
+ anchor: string;
22
+ };
23
+ /**
24
+ * Smooth scroll to an element by ID
25
+ *
26
+ * @param id - The element ID to scroll to
27
+ * @param headerOffset - Vertical offset to account for sticky header (default: 80px)
28
+ */
29
+ export declare function scrollToElement(id: string, headerOffset?: number): void;
30
+ /**
31
+ * Update the current URL with an anchor parameter and optional advanced mode flag
32
+ *
33
+ * @param anchorId - The anchor ID to link to
34
+ * @param isAdvancedMode - Whether advanced mode is enabled (default: false)
35
+ */
36
+ export declare function updateUrlWithAnchor(anchorId: string, isAdvancedMode?: boolean): void;
37
+ /**
38
+ * Scroll to the top of the window
39
+ * @param behavior - Scroll behavior: 'smooth' for animated scroll, 'auto' for instant
40
+ */
41
+ export declare function scrollToTop(behavior?: 'smooth' | 'auto'): void;
package/utils.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { LoadedContent, NavigationEntry, NavigationEntryWithTitles, NavigationSection } from 'types';
2
+ export type { Theme } from './theme';
3
+ export { ThemeProvider, useTheme } from './theme';
4
+ export declare const isNavigationSection: (entry: NavigationEntry) => entry is NavigationSection;
5
+ export declare const isNavigationSectionWithTitles: (entry: NavigationEntryWithTitles) => entry is {
6
+ title: string;
7
+ items: Array<{
8
+ title: string;
9
+ slug: string;
10
+ }>;
11
+ };
12
+ export declare const getNavigationData: (navigationData: NavigationEntry[], language: string, loadContent: (language: string, slug: string, advanced?: boolean) => Promise<LoadedContent>) => Promise<NavigationEntryWithTitles[]>;
13
+ export declare const generateHeadingId: (text: string) => string;
14
+ /**
15
+ * Get the next and previous article based on chronological order.
16
+ * Articles are ordered by date (newest first), then alphabetically by title.
17
+ * Prev = newer article, Next = older article.
18
+ */
19
+ export declare const getAdjacentArticles: (currentSlug: string, articles: Array<{
20
+ slug: string;
21
+ title: string;
22
+ }>) => {
23
+ prev?: {
24
+ slug: string;
25
+ title: string;
26
+ };
27
+ next?: {
28
+ slug: string;
29
+ title: string;
30
+ };
31
+ };
package/consts/index.ts DELETED
@@ -1,9 +0,0 @@
1
- export const DOCS_TITLE = 'Staking Docs'
2
-
3
- export const SUPPORTED_LANGUAGES = ['en', 'zh', 'es'] as const
4
-
5
- export const GITHUB_REPO_URL = 'https://github.com/polkadot-cloud'
6
-
7
- export const STAKING_DASHBOARD_URL = 'https://staking.polkadot.cloud'
8
-
9
- export { NAVIGATION_DATA } from './navigation'
@@ -1,26 +0,0 @@
1
- import type { NavigationEntry } from 'types'
2
-
3
- // Navigation structure - defines the sidebar menu order and grouping
4
- // - Individual item titles are loaded from their MDX frontmatter
5
- // - Section titles are i18n keys (e.g., 'nav.helpResources') and will be translated
6
- // Optional properties:
7
- // - hasAdvanced: boolean - indicates if the page has an advanced version
8
- // - defaultOpen: boolean - for sections, set to true to have them open by default
9
- // Note: showTableOfContents is configured in each article's frontmatter, not here
10
- export const NAVIGATION_DATA: NavigationEntry[] = [
11
- {
12
- slug: 'welcome',
13
- },
14
- {
15
- title: 'nav.helpResources',
16
- defaultOpen: true,
17
- items: [
18
- {
19
- slug: 'glossary',
20
- },
21
- {
22
- slug: 'faq',
23
- },
24
- ],
25
- },
26
- ]
package/locales/index.ts DELETED
@@ -1,182 +0,0 @@
1
- import i18n from 'i18next'
2
- import LanguageDetector from 'i18next-browser-languagedetector'
3
- import { initReactI18next } from 'react-i18next'
4
-
5
- const resources = {
6
- en: {
7
- translation: {
8
- language: 'English',
9
- languageCode: 'en',
10
- nav: {
11
- gettingStarted: 'Getting Started',
12
- helpResources: 'Help Resources',
13
- guides: 'Guides',
14
- reference: 'Reference',
15
- previous: 'Previous',
16
- next: 'Next',
17
- },
18
- docs: {
19
- helloWorld: 'Welcome!',
20
- gettingStarted: 'Getting Started',
21
- glossary: 'Glossary',
22
- faq: 'FAQ',
23
- },
24
- ui: {
25
- home: 'Home',
26
- simple: 'Simple',
27
- advanced: 'Advanced',
28
- switchLanguage: 'Switch Language',
29
- tableOfContents: 'Table of Contents',
30
- onThisPage: 'On this page',
31
- launchApp: 'Launch App',
32
- support: 'Support',
33
- discord: 'Discord',
34
- email: 'Email',
35
- theme: 'Theme',
36
- light: 'Light',
37
- dark: 'Dark',
38
- system: 'System',
39
- search: 'Search',
40
- searchPlaceholder: 'Search documentation...',
41
- searchNavigate: 'to navigate',
42
- searchSelect: 'to select',
43
- searchClose: 'to close',
44
- noSearchResults: 'No results found',
45
- tags: 'Tags',
46
- filterByTag: 'Filter by tag',
47
- tagResults: 'Articles tagged with',
48
- noTagResults: 'No articles found with this tag',
49
- lastEdited: 'Last edited',
50
- axes: 'Articles',
51
- latestPosts: 'Latest Posts',
52
- loadingArticles: 'Loading articles...',
53
- noArticlesFound: 'No articles found',
54
- by: 'By',
55
- connect: 'Connect',
56
- },
57
- },
58
- },
59
- zh: {
60
- translation: {
61
- language: '中文',
62
- languageCode: 'zh',
63
- nav: {
64
- gettingStarted: '开始使用',
65
- helpResources: '帮助资源',
66
- guides: '指南',
67
- reference: '参考',
68
- previous: '上一篇',
69
- next: '下一篇',
70
- },
71
- docs: {
72
- helloWorld: '欢迎!',
73
- gettingStarted: '开始使用',
74
- glossary: '术语表',
75
- faq: '常见问题',
76
- },
77
- ui: {
78
- home: '首页',
79
- simple: '简单',
80
- advanced: '高级',
81
- switchLanguage: '切换语言',
82
- tableOfContents: '目录',
83
- onThisPage: '本页内容',
84
- launchApp: '启动应用',
85
- support: '支持',
86
- discord: 'Discord',
87
- email: '电子邮件',
88
- theme: '主题',
89
- light: '浅色',
90
- dark: '深色',
91
- system: '系统',
92
- search: '搜索',
93
- searchPlaceholder: '搜索文档...',
94
- searchNavigate: '导航',
95
- searchSelect: '选择',
96
- searchClose: '关闭',
97
- noSearchResults: '未找到结果',
98
- tags: '标签',
99
- filterByTag: '按标签筛选',
100
- tagResults: '带有标签的文章',
101
- noTagResults: '未找到带有此标签的文章',
102
- lastEdited: '最后编辑',
103
- articles: '文章',
104
- loadingArticles: '正在加载文章...',
105
- noArticlesFound: '未找到文章',
106
- by: '作者',
107
- connect: '连接',
108
- },
109
- },
110
- },
111
- es: {
112
- translation: {
113
- language: 'Español',
114
- languageCode: 'es',
115
- nav: {
116
- gettingStarted: 'Primeros Pasos',
117
- helpResources: 'Recursos de Ayuda',
118
- guides: 'Guías',
119
- reference: 'Referencia',
120
- previous: 'Anterior',
121
- next: 'Siguiente',
122
- },
123
- docs: {
124
- helloWorld: '¡Bienvenido!',
125
- gettingStarted: 'Primeros Pasos',
126
- glossary: 'Glosario',
127
- faq: 'Preguntas Frecuentes',
128
- },
129
- ui: {
130
- home: 'Inicio',
131
- simple: 'Simple',
132
- advanced: 'Avanzado',
133
- switchLanguage: 'Cambiar Idioma',
134
- tableOfContents: 'Tabla de Contenidos',
135
- onThisPage: 'En esta página',
136
- launchApp: 'Iniciar App',
137
- support: 'Soporte',
138
- discord: 'Discord',
139
- email: 'Correo',
140
- theme: 'Tema',
141
- light: 'Claro',
142
- dark: 'Oscuro',
143
- system: 'Sistema',
144
- search: 'Buscar',
145
- searchPlaceholder: 'Buscar documentación...',
146
- searchNavigate: 'para navegar',
147
- searchSelect: 'para seleccionar',
148
- searchClose: 'para cerrar',
149
- noSearchResults: 'No se encontraron resultados',
150
- tags: 'Etiquetas',
151
- filterByTag: 'Filtrar por etiqueta',
152
- tagResults: 'Artículos etiquetados con',
153
- noTagResults: 'No se encontraron artículos con esta etiqueta',
154
- lastEdited: 'Última edición',
155
- articles: 'Artículos',
156
- loadingArticles: 'Cargando artículos...',
157
- noArticlesFound: 'No se encontraron artículos',
158
- by: 'Por',
159
- connect: 'Conectar',
160
- },
161
- },
162
- },
163
- }
164
-
165
- i18n
166
- .use(LanguageDetector)
167
- .use(initReactI18next)
168
- .init({
169
- resources,
170
- fallbackLng: 'en',
171
- lng: 'en', // Set 'en' as the initial language synchronously
172
- debug: false,
173
- interpolation: {
174
- escapeValue: false,
175
- },
176
- detection: {
177
- order: ['path', 'localStorage', 'navigator'],
178
- lookupFromPathIndex: 0,
179
- },
180
- })
181
-
182
- export default i18n
package/types/index.ts DELETED
@@ -1,40 +0,0 @@
1
- export interface NavigationItem {
2
- slug: string
3
- hasAdvanced?: boolean
4
- }
5
-
6
- export interface NavigationItemWithTitle extends NavigationItem {
7
- title: string
8
- }
9
-
10
- export interface NavigationSection {
11
- title: string
12
- items: NavigationItem[]
13
- defaultOpen?: boolean
14
- }
15
-
16
- export interface NavigationSectionWithTitles {
17
- title: string
18
- items: NavigationItemWithTitle[]
19
- defaultOpen?: boolean
20
- }
21
-
22
- export type NavigationEntry = NavigationSection | NavigationItem
23
-
24
- export type NavigationEntryWithTitles =
25
- | NavigationSectionWithTitles
26
- | NavigationItemWithTitle
27
-
28
- export interface LoadedContent {
29
- content: string
30
- frontmatter: {
31
- title?: string
32
- hasAdvanced?: boolean
33
- tags?: string[]
34
- date?: string
35
- author?: string
36
- [key: string]: unknown
37
- }
38
- }
39
-
40
- export type SupportedLanguage = string