@san-siva/blogkit-md 0.1.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,9 @@
1
+ {
2
+ "printWidth": 80,
3
+ "tabWidth": 2,
4
+ "singleQuote": true,
5
+ "trailingComma": "es5",
6
+ "useTabs": true,
7
+ "arrowParens": "avoid",
8
+ "semi": true
9
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Santhosh Siva
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # blogkit-md
2
+
3
+ A Next.js tool that converts standard markdown files into rendered blog posts for [`@san-siva/blogkit`](https://blogkit.santhoshsiva.dev).
4
+
5
+ > Note: [This](https://blogkit-md.santhoshsiva.dev) blog post is translated from the `README.md` file in the [`blogkit-md`](https://github.com/san-siva/blogkit-md) repository.
6
+
7
+ ## Getting started
8
+
9
+ ```bash
10
+ git clone https://github.com/san-siva/blogkit-md.git
11
+ npm install
12
+ ```
13
+
14
+ ### Dev server
15
+
16
+ ```bash
17
+ npm run dev # uses data/test.md by default
18
+ npm run dev -- --file=data/my-post.md # specify a markdown file
19
+ ```
20
+
21
+ The dev server watches the markdown file for changes and auto-reloads the browser via HMR.
22
+
23
+ ### Build
24
+
25
+ ```bash
26
+ npm run build
27
+ npm run start
28
+ ```
29
+
30
+ ### Lint
31
+
32
+ ```bash
33
+ npm run lint # check
34
+ npm run fix # auto-fix
35
+ ```
36
+
37
+ ## Using BlogPost in your own Next.js app
38
+
39
+ `blogkit-md` exposes a `BlogPost` server component you can drop into any Next.js project.
40
+
41
+ ### Install
42
+
43
+ ```bash
44
+ npm install @san-siva/blogkit-md
45
+ ```
46
+
47
+ Or as a local package:
48
+
49
+ ```json
50
+ "@san-siva/blogkit-md": "file:../blogkit-md"
51
+ ```
52
+
53
+ Add it to `transpilePackages` in your `next.config.ts`:
54
+
55
+ ```ts
56
+ const nextConfig = {
57
+ transpilePackages: ['@san-siva/blogkit-md'],
58
+ };
59
+ ```
60
+
61
+ ### Usage
62
+
63
+ ```tsx
64
+ import { BlogPost } from '@san-siva/blogkit-md';
65
+
66
+ export default function Page() {
67
+ return (
68
+ <BlogPost
69
+ filePath="content/my-post.md"
70
+ jsonLd={{
71
+ '@context': 'https://schema.org',
72
+ '@type': 'BlogPosting',
73
+ headline: 'My Post',
74
+ description: 'Post description',
75
+ datePublished: '2026-01-01',
76
+ author: { '@type': 'Person', name: 'Your Name' },
77
+ }}
78
+ />
79
+ );
80
+ }
81
+ ```
82
+
83
+ ### Props
84
+
85
+ | Prop | Type | Required | Description |
86
+ | :--------- | :------------------- | :------: | :--------------------------------------------------------------------------- |
87
+ | `filePath` | `string` | Yes | Path to the markdown file. Relative paths are resolved from `process.cwd()`. |
88
+ | `jsonLd` | `WithContext<Thing>` | No | Optional JSON-LD schema passed to `<Blog>` for structured data / SEO. |
89
+
90
+ ## Supported markdown features
91
+
92
+ | Feature | Syntax |
93
+ | --------------- | ---------------------------------- |
94
+ | Headings | `# H1` `## H2` `### H3` `#### H4` |
95
+ | Paragraph | Plain text |
96
+ | Hard line break | Two spaces at end of line |
97
+ | Bold | `**bold**` |
98
+ | Italic | `_italic_` |
99
+ | Inline code | `` `code` `` |
100
+ | Link | `[text](url)` |
101
+ | Image | `![alt](url)` |
102
+ | Ordered list | `1. item` |
103
+ | Unordered list | `- item` |
104
+ | Table | GFM table syntax |
105
+ | Code block | ` ```lang ` |
106
+ | Mermaid diagram | ` ```mermaid ` |
107
+ | Thematic break | `---` |
108
+ | Blockquote | `> text` — renders as info callout |
109
+
110
+ ## Philosophy
111
+
112
+ ### Not Your Average Markdown Viewer
113
+
114
+ If you're looking for a strictly standard, 1:1 markdown renderer, `blogkit-md` might not be what you expect.
115
+
116
+ <mark>Instead of building just another plain document viewer, intentional design liberties have been taken to render markdown as **beautiful, engaging blog posts**.</mark>
117
+
118
+ Documentation shouldn't be a wall of boring text. The goal of this tool is to make reading technical docs, articles, and guides an exciting and visually pleasing experience.
119
+
120
+ ### Key Differences
121
+
122
+ | | blogkit-md | Plain markdown renderer |
123
+ | -------------- | -------------------------------------------------------- | ----------------------- |
124
+ | **Output** | Styled blog post | Raw document |
125
+ | **Typography** | Optimized for long-form reading | Unstyled |
126
+ | **Ecosystem** | Built for [Blogkit](https://github.com/san-siva/blogkit) | Generic |
127
+
128
+ ## Architecture
129
+
130
+ The markdown file is parsed into an AST using `remark-parse` + `remark-gfm`, then transformed into React components from `@san-siva/blogkit`.
131
+
132
+ ```mermaid
133
+ flowchart LR
134
+ A[Markdown file] --> B[Parse AST]
135
+ B --> C[Group sections]
136
+ C --> D[Render to React]
137
+ D --> E[Blog page]
138
+ ```
139
+
140
+ ## How Markdown Translates to Blog Sections
141
+
142
+ ### Headings as Layout Triggers
143
+
144
+ In `blogkit-md`, headings aren't just for changing font sizes—**they are the architectural blueprint for your post**. Each heading level acts as a layout trigger, directly controlling how `BlogSection` components are generated, nested, or promoted.
145
+
146
+ | Markdown | Layout Behavior |
147
+ | :----------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
148
+ | `# H1` | **Page Title.** Sets the main article title. Does not generate a structural section block. |
149
+ | `## H2` | **Main Section.** Creates a new, top-level `BlogSection`. |
150
+ | `### H3` | **Subsection.** Nests cleanly within the currently active `H2` section. |
151
+ | `#### H4` | **Section Break.** Renders as a bold line, but acts as a layout trigger: it forces the _next_ `H3` to break out and become a brand-new, top-level section. |
152
+ | `##### H5` & `###### H6` | **Inline Emphasis.** Renders as a bold line within the current section or subsection without altering the page layout. |
153
+
154
+ > Standard content—such as paragraphs, lists, and code blocks—automatically flows into the most recently opened section or subsection.
155
+
156
+ ### Special Layout Rules
157
+
158
+ Because `blogkit-md` is optimized for blog readability, it includes smart fallbacks to ensure your layout looks great even in edge cases:
159
+
160
+ | Rule | Behavior |
161
+ | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
162
+ | Strictly Deepening Hierarchy | Headings within a section must always go deeper (e.g., `H2 → H3 → H4`). If the hierarchy reverses—like an `H3` appearing after an `H4`—the nesting breaks, and the `H3` is promoted to a brand-new top-level section. |
163
+ | `H1` loses structural significance if not at the top | If an `H1` appears anywhere other than the very top of the document, it does not create a page title. Instead, it is treated as stylized text and rendered as a section break. |
164
+ | Intro section | Any text written before the first heading—or directly beneath the `# H1` page title—is automatically grouped into an untitled, top-level BlogSection |
165
+
166
+ ### Visualizing the Structure
167
+
168
+ Let's put those layout rules into practice. Here is how a standard markdown document translates into a blog layout:
169
+
170
+ ```markdown
171
+ # My Awesome Blog Post
172
+
173
+ This text becomes the Preamble (an untitled, top-level section).
174
+
175
+ ## The Setup
176
+
177
+ Some content goes here.
178
+
179
+ ### Prerequisites
180
+
181
+ Nested content belongs here.
182
+
183
+ ## The Execution
184
+
185
+ #### Note on performance:
186
+
187
+ ### The Results
188
+ ```
189
+
190
+ Here is how the parser breaks the above document down into isolated React components:
191
+
192
+ ##### Intro section
193
+
194
+ ```markdown
195
+ This text becomes an introductory, untitled section.
196
+ ```
197
+
198
+ ##### Section 1
199
+
200
+ ```markdown
201
+ ## The Setup
202
+
203
+ Some content goes here.
204
+
205
+ #### Prerequisites
206
+
207
+ Nested content belongs here.
208
+ ```
209
+
210
+ ##### Section 2
211
+
212
+ ```markdown
213
+ ## The Execution
214
+
215
+ #### Note on performance:
216
+ ```
217
+
218
+ ##### Section 3
219
+
220
+ ```markdown
221
+ ## The Results
222
+ ```
223
+
224
+ ## Want more customization?
225
+
226
+ `blogkit-md` is just one piece of the puzzle. If you want to customize the underlying React components, tweak the UI, or take full control over your blog's layout, dive into the official [Blogkit documentation](https://blogkit.santhoshsiva.dev/).
227
+
228
+ ## License
229
+
230
+ `blogkit-md` is open source software licensed under the [MIT license](https://github.com/san-siva/blogkit-md/blob/main/LICENSE).
231
+ Contributions are welcome!
232
+
233
+ ## About
234
+
235
+ - **Author:** [Santhosh Siva](https://www.santhoshsiva.dev)
236
+ - **License:** [MIT](https://github.com/san-siva/blogkit-md/blob/main/LICENSE)
package/app/layout.tsx ADDED
@@ -0,0 +1,44 @@
1
+ import type { Metadata } from 'next';
2
+ import { JetBrains_Mono, Montserrat, Rubik } from 'next/font/google';
3
+
4
+ import '@san-siva/stylekit/styles/globals.scss';
5
+ import '@san-siva/blogkit/styles.css';
6
+ import styles from '@san-siva/stylekit/styles/index.module.scss';
7
+
8
+ const montserrat = Montserrat({
9
+ subsets: ['latin'] as const,
10
+ weight: ['400', '500', '600', '700', '800'] as const,
11
+ style: ['normal', 'italic'] as const,
12
+ variable: '--font-montserrat',
13
+ });
14
+
15
+ const rubik = Rubik({
16
+ subsets: ['latin'] as const,
17
+ weight: ['300', '400', '500', '600', '700', '800', '900'] as const,
18
+ style: ['normal', 'italic'] as const,
19
+ variable: '--font-rubik',
20
+ });
21
+
22
+ const jetbrainsMono = JetBrains_Mono({
23
+ subsets: ['latin'] as const,
24
+ weight: ['400', '500', '600', '700'] as const,
25
+ style: ['normal', 'italic'] as const,
26
+ variable: '--font-jetbrains-mono',
27
+ });
28
+
29
+ export const metadata: Metadata = {
30
+ title: 'Blogkit MD',
31
+ description: 'Markdown to JSX blog post renderer',
32
+ };
33
+
34
+ const RootLayout = ({ children }: { children: React.ReactNode }) => (
35
+ <html lang="en">
36
+ <body
37
+ className={`${montserrat.variable} ${rubik.variable} ${jetbrainsMono.variable}`}
38
+ >
39
+ <div className={`${styles.page}`}>{children}</div>
40
+ </body>
41
+ </html>
42
+ );
43
+
44
+ export default RootLayout;
package/app/page.tsx ADDED
@@ -0,0 +1,27 @@
1
+ import { Blog, BlogHeader, Callout } from '@san-siva/blogkit';
2
+
3
+ import { readMarkdownFile } from '@/hooks/readMarkdownFile';
4
+ import { MarkdownSections } from '@/utils/renderMarkdown';
5
+
6
+ const Page = async () => {
7
+ const result = await readMarkdownFile(process.env.MARKDOWN_FILE);
8
+
9
+ if (!result.success) {
10
+ return (
11
+ <Blog>
12
+ <Callout type="warning">{result.error}</Callout>
13
+ </Blog>
14
+ );
15
+ }
16
+
17
+ const { rendered } = result;
18
+
19
+ return (
20
+ <Blog>
21
+ {rendered.pageTitle && <BlogHeader title={[rendered.pageTitle]} desc={[]} />}
22
+ <MarkdownSections rendered={rendered} />
23
+ </Blog>
24
+ );
25
+ };
26
+
27
+ export default Page;
@@ -0,0 +1,54 @@
1
+ import { Blog, BlogHeader, Callout } from '@san-siva/blogkit';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import type { Thing, WithContext } from 'schema-dts';
5
+
6
+ import { parseMarkdown } from '../utils/parseMarkdown';
7
+ import { MarkdownSections, renderMarkdownAst } from '../utils/renderMarkdown';
8
+
9
+ type BlogPostProperties = {
10
+ filePath: string;
11
+ jsonLd?: WithContext<Thing>;
12
+ };
13
+
14
+ const BlogPost = async ({ filePath, jsonLd }: BlogPostProperties) => {
15
+ const absolutePath = path.isAbsolute(filePath)
16
+ ? filePath
17
+ : path.join(process.cwd(), filePath);
18
+
19
+ let content: string;
20
+ try {
21
+ content = await readFile(absolutePath, 'utf8');
22
+ } catch {
23
+ return (
24
+ <Blog>
25
+ <Callout type="warning">
26
+ Could not read file: &quot;{filePath}&quot;. Make sure the path is
27
+ correct and the file exists.
28
+ </Callout>
29
+ </Blog>
30
+ );
31
+ }
32
+
33
+ if (!content.trim()) {
34
+ return (
35
+ <Blog>
36
+ <Callout type="warning">File &quot;{filePath}&quot; is empty.</Callout>
37
+ </Blog>
38
+ );
39
+ }
40
+
41
+ const ast = parseMarkdown(content);
42
+ const rendered = renderMarkdownAst(ast);
43
+
44
+ return (
45
+ <Blog jsonLd={jsonLd}>
46
+ {rendered.pageTitle && (
47
+ <BlogHeader title={[rendered.pageTitle]} desc={[]} />
48
+ )}
49
+ <MarkdownSections rendered={rendered} />
50
+ </Blog>
51
+ );
52
+ };
53
+
54
+ export default BlogPost;
@@ -0,0 +1,29 @@
1
+ import tseslint, { configs as tseslintConfigs } from 'typescript-eslint';
2
+
3
+ import {
4
+ defaultExtends,
5
+ defaultPlugins,
6
+ defaultRules,
7
+ defaultSettings,
8
+ languageOptions,
9
+ testFiles,
10
+ } from './utilities';
11
+
12
+ export default tseslint.config(
13
+ {
14
+ ignores: ['node_modules/**', 'dist/**', '.next/**', 'next-env.d.ts'],
15
+ },
16
+ {
17
+ files: ['**/*.ts', '**/*.js'],
18
+ ignores: testFiles,
19
+ plugins: defaultPlugins,
20
+ extends: defaultExtends,
21
+ rules: defaultRules,
22
+ settings: defaultSettings,
23
+ languageOptions,
24
+ },
25
+ {
26
+ files: ['eslint.config.ts', 'eslint/**/*.ts'],
27
+ extends: [tseslintConfigs.disableTypeChecked],
28
+ }
29
+ );
@@ -0,0 +1,20 @@
1
+ import tseslint from 'typescript-eslint';
2
+
3
+ import {
4
+ languageOptions,
5
+ reactExtends,
6
+ reactPlugins,
7
+ reactRules,
8
+ reactSettings,
9
+ testFiles,
10
+ } from './utilities';
11
+
12
+ export default tseslint.config({
13
+ files: ['**/*.tsx', '**/*.jsx'],
14
+ ignores: testFiles,
15
+ plugins: reactPlugins,
16
+ extends: reactExtends,
17
+ rules: reactRules,
18
+ settings: reactSettings,
19
+ languageOptions,
20
+ });
@@ -0,0 +1,236 @@
1
+ import eslint from '@eslint/js';
2
+ import type { Linter } from 'eslint';
3
+ import eslintConfigPrettier from 'eslint-config-prettier';
4
+ import importPlugin from 'eslint-plugin-import';
5
+ import jsxA11y from 'eslint-plugin-jsx-a11y';
6
+ import { configs as noAwaitInPromiseConfigs } from 'eslint-plugin-no-await-in-promise';
7
+ import react from 'eslint-plugin-react';
8
+ import reactHooks from 'eslint-plugin-react-hooks';
9
+ import simpleImportSort from 'eslint-plugin-simple-import-sort';
10
+ import eslintPluginUnicorn from 'eslint-plugin-unicorn';
11
+ import { configs as tseslintConfigs } from 'typescript-eslint';
12
+
13
+ export const defaultExtends = [
14
+ eslint.configs.recommended,
15
+ eslintPluginUnicorn.configs.recommended,
16
+ eslintConfigPrettier,
17
+ noAwaitInPromiseConfigs.recommended,
18
+ ...tseslintConfigs.recommended,
19
+ importPlugin.flatConfigs.recommended,
20
+ importPlugin.flatConfigs.typescript,
21
+ ];
22
+
23
+ export const defaultPlugins = {
24
+ 'simple-import-sort': simpleImportSort,
25
+ };
26
+
27
+ export const tsRules: Linter.RulesRecord = {
28
+ '@typescript-eslint/no-explicit-any': 1,
29
+ '@typescript-eslint/no-floating-promises': 2,
30
+ '@typescript-eslint/no-unused-vars': 1,
31
+ '@typescript-eslint/naming-convention': [
32
+ 1,
33
+ {
34
+ selector: 'import',
35
+ format: ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE'],
36
+ },
37
+ {
38
+ selector: 'enumMember',
39
+ format: ['PascalCase'],
40
+ },
41
+ {
42
+ selector: 'default',
43
+ format: ['camelCase'],
44
+ },
45
+ {
46
+ selector: 'variable',
47
+ format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
48
+ },
49
+ {
50
+ selector: 'variable',
51
+ modifiers: ['destructured'],
52
+ format: ['camelCase', 'snake_case', 'UPPER_CASE', 'PascalCase'],
53
+ },
54
+ {
55
+ selector: 'function',
56
+ format: ['camelCase', 'PascalCase'],
57
+ },
58
+ {
59
+ selector: 'parameter',
60
+ format: [],
61
+ },
62
+ {
63
+ selector: 'memberLike',
64
+ modifiers: ['private'],
65
+ format: ['camelCase'],
66
+ leadingUnderscore: 'require',
67
+ },
68
+ {
69
+ selector: 'typeLike',
70
+ format: ['PascalCase'],
71
+ },
72
+ { selector: 'property', format: [] },
73
+ {
74
+ selector: 'method',
75
+ format: ['PascalCase', 'camelCase', 'UPPER_CASE'],
76
+ leadingUnderscore: 'allowDouble',
77
+ },
78
+ ],
79
+ '@typescript-eslint/no-use-before-define': 2,
80
+ '@typescript-eslint/no-unused-expressions': [
81
+ 'error',
82
+ {
83
+ allowShortCircuit: true,
84
+ allowTernary: true,
85
+ allowTaggedTemplates: true,
86
+ },
87
+ ],
88
+ };
89
+
90
+ export const defaultRules: Linter.RulesRecord = {
91
+ ...tsRules,
92
+ 'unicorn/filename-case': [
93
+ 'error',
94
+ {
95
+ cases: {
96
+ camelCase: true,
97
+ kebabCase: true,
98
+ pascalCase: true,
99
+ },
100
+ },
101
+ ],
102
+ 'unicorn/prevent-abbreviations': [1, { allowList: { dev: true } }],
103
+ 'unicorn/no-array-reduce': [1],
104
+ 'unicorn/catch-error-name': [0],
105
+ 'unicorn/no-for-loop': [1],
106
+ 'unicorn/no-null': [0],
107
+ 'unicorn/prefer-string-raw': [1],
108
+ 'unicorn/prefer-string-slice': [1],
109
+ 'unicorn/no-negated-condition': [1],
110
+ 'unicorn/no-array-for-each': [0],
111
+ 'unicorn/no-array-callback-reference': [0],
112
+ 'unicorn/no-object-as-default-parameter': [1],
113
+ 'unicorn/prefer-math-min-max': [1],
114
+ 'unicorn/no-process-exit': [1],
115
+ 'unicorn/prefer-top-level-await': [1],
116
+ 'unicorn/prefer-number-properties': [1],
117
+ 'unicorn/consistent-function-scoping': [1],
118
+ 'arrow-body-style': [1, 'as-needed'],
119
+ camelcase: 0,
120
+ 'default-param-last': 0,
121
+ 'import/order': 0,
122
+ 'simple-import-sort/imports': [
123
+ 1,
124
+ {
125
+ groups: [
126
+ ['^react$', '^react-.*'],
127
+ ['next', 'next-.*'],
128
+ [String.raw`^@?\w`],
129
+ [String.raw`^\.\.(?!/?$)`, String.raw`^\.\./?$`],
130
+ [
131
+ String.raw`^\./(?=.*/)(?!/?$)`,
132
+ String.raw`^\.(?!/?$)`,
133
+ String.raw`^\./?$`,
134
+ ],
135
+ [String.raw`^.+\.s?css$`],
136
+ ],
137
+ },
138
+ ],
139
+ 'simple-import-sort/exports': 1,
140
+ 'import/no-extraneous-dependencies': 0,
141
+ 'import/extensions': 0,
142
+ 'import/no-cycle': [2, { ignoreExternal: true }],
143
+ 'import/no-unresolved': 0,
144
+ 'import/prefer-default-export': 0,
145
+ 'max-len': 0,
146
+ 'no-console': 0,
147
+ 'no-delete-var': 2,
148
+ 'no-shadow': 0,
149
+ 'no-underscore-dangle': [2, { allow: ['_id'] }],
150
+ 'no-unused-expressions': 2,
151
+ 'no-unused-labels': 2,
152
+ 'no-use-before-define': 0,
153
+ semi: 1,
154
+ quotes: [
155
+ 1,
156
+ 'single',
157
+ {
158
+ avoidEscape: true,
159
+ allowTemplateLiterals: true,
160
+ },
161
+ ],
162
+ };
163
+
164
+ export const languageOptions = {
165
+ ecmaVersion: 2022 as const,
166
+ parserOptions: {
167
+ projectService: true,
168
+ defaultProject: '../tsconfig.json',
169
+ tsconfigRootDir: import.meta.dirname,
170
+ },
171
+ };
172
+
173
+ export const testFiles = [
174
+ '**/*.test.ts',
175
+ '**/*.test.tsx',
176
+ '**/*.test.js',
177
+ '**/*.test.jsx',
178
+ '**/*.spec.ts',
179
+ '**/*.spec.tsx',
180
+ '**/*.spec.js',
181
+ '**/*.spec.jsx',
182
+ ];
183
+
184
+ export const defaultSettings = {
185
+ 'import/resolver': {
186
+ typescript: {
187
+ project: '../tsconfig.json',
188
+ },
189
+ },
190
+ };
191
+
192
+ export const reactPlugins = {
193
+ ...defaultPlugins,
194
+ react,
195
+ 'react-hooks': reactHooks,
196
+ };
197
+
198
+ export const reactSettings = {
199
+ ...defaultSettings,
200
+ react: {
201
+ version: 'detect',
202
+ pragma: 'React',
203
+ fragment: 'Fragment',
204
+ },
205
+ };
206
+
207
+ export const reactRules: Linter.RulesRecord = {
208
+ ...defaultRules,
209
+ 'react/jsx-uses-react': 0,
210
+ 'react/react-in-jsx-scope': 0,
211
+ 'react-hooks/rules-of-hooks': 2,
212
+ 'react-hooks/exhaustive-deps': 1,
213
+ 'eol-last': ['error', 'always'],
214
+ 'jsx-a11y/alt-text': 0,
215
+ 'jsx-a11y/click-events-have-key-events': 0,
216
+ 'jsx-a11y/control-has-associated-label': 0,
217
+ 'jsx-a11y/no-noninteractive-element-interactions': 0,
218
+ 'jsx-a11y/no-static-element-interactions': 0,
219
+ 'jsx-a11y/label-has-associated-control': 0,
220
+ 'react/jsx-filename-extension': [1, { extensions: ['.tsx'] }],
221
+ 'react/jsx-props-no-spreading': 0,
222
+ 'react/no-unstable-nested-components': ['error', { allowAsProps: true }],
223
+ 'react/prop-types': 0,
224
+ 'react/require-default-props': 0,
225
+ 'react/function-component-definition': [
226
+ 2,
227
+ { namedComponents: 'arrow-function' },
228
+ ],
229
+ 'react/no-unknown-property': 1,
230
+ };
231
+
232
+ export const reactExtends = [
233
+ react.configs.flat.recommended,
234
+ jsxA11y.flatConfigs?.recommended,
235
+ ...defaultExtends,
236
+ ];
@@ -0,0 +1,8 @@
1
+ import common from './eslint/common.config';
2
+ import react from './eslint/react.config';
3
+
4
+ export default [
5
+ { ignores: ['website/**'] },
6
+ ...common,
7
+ ...react,
8
+ ];