polen 0.9.0-next.4 → 0.9.0-next.6
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/build/api/singletons/markdown/markdown.d.ts.map +1 -1
- package/build/api/singletons/markdown/markdown.js +33 -11
- package/build/api/singletons/markdown/markdown.js.map +1 -1
- package/build/api/vite/plugins/pages.d.ts.map +1 -1
- package/build/api/vite/plugins/pages.js +17 -0
- package/build/api/vite/plugins/pages.js.map +1 -1
- package/build/lib/shiki/index.d.ts +2 -0
- package/build/lib/shiki/index.d.ts.map +1 -0
- package/build/lib/shiki/index.js +2 -0
- package/build/lib/shiki/index.js.map +1 -0
- package/build/lib/shiki/shiki.d.ts +26 -0
- package/build/lib/shiki/shiki.d.ts.map +1 -0
- package/build/lib/shiki/shiki.js +105 -0
- package/build/lib/shiki/shiki.js.map +1 -0
- package/build/template/components/CodeBlock.d.ts +17 -0
- package/build/template/components/CodeBlock.d.ts.map +1 -0
- package/build/template/components/CodeBlock.jsx +42 -0
- package/build/template/components/CodeBlock.jsx.map +1 -0
- package/build/template/components/sidebar/Sidebar.d.ts +5 -3
- package/build/template/components/sidebar/Sidebar.d.ts.map +1 -1
- package/build/template/components/sidebar/Sidebar.jsx +3 -3
- package/build/template/components/sidebar/Sidebar.jsx.map +1 -1
- package/build/template/entry.client.jsx +1 -0
- package/build/template/entry.client.jsx.map +1 -1
- package/build/template/routes/root.d.ts.map +1 -1
- package/build/template/routes/root.jsx +56 -29
- package/build/template/routes/root.jsx.map +1 -1
- package/package.json +9 -1
- package/src/api/singletons/markdown/markdown.test.ts +89 -0
- package/src/api/singletons/markdown/markdown.ts +35 -13
- package/src/api/vite/plugins/pages.ts +17 -0
- package/src/lib/shiki/index.ts +1 -0
- package/src/lib/shiki/shiki.test.ts +107 -0
- package/src/lib/shiki/shiki.ts +161 -0
- package/src/template/components/CodeBlock.tsx +73 -0
- package/src/template/components/sidebar/Sidebar.tsx +7 -5
- package/src/template/entry.client.tsx +1 -0
- package/src/template/routes/root.tsx +90 -46
- package/src/template/styles/code-block.css +186 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
2
|
+
import { parse } from './markdown.js'
|
3
|
+
|
4
|
+
describe(`markdown parser with syntax highlighting`, () => {
|
5
|
+
test(`parse highlights code blocks`, async () => {
|
6
|
+
const markdown = `
|
7
|
+
# Hello
|
8
|
+
|
9
|
+
\`\`\`javascript
|
10
|
+
const x = 42
|
11
|
+
console.log(x)
|
12
|
+
\`\`\`
|
13
|
+
`
|
14
|
+
const result = await parse(markdown)
|
15
|
+
|
16
|
+
expect(result).toContain(`<h1>Hello</h1>`)
|
17
|
+
expect(result).toContain(`<pre`)
|
18
|
+
expect(result).toContain(`shiki`)
|
19
|
+
expect(result).toContain(`console`)
|
20
|
+
expect(result).toContain(`42`)
|
21
|
+
})
|
22
|
+
|
23
|
+
// Note: parseSync cannot be used with async rehype plugins like Shiki
|
24
|
+
// This is a known limitation - syntax highlighting requires async processing
|
25
|
+
|
26
|
+
test(`parse supports GraphQL syntax`, async () => {
|
27
|
+
const markdown = `
|
28
|
+
\`\`\`graphql
|
29
|
+
type Query {
|
30
|
+
user(id: ID!): User
|
31
|
+
}
|
32
|
+
\`\`\`
|
33
|
+
`
|
34
|
+
const result = await parse(markdown)
|
35
|
+
|
36
|
+
expect(result).toContain(`type`)
|
37
|
+
expect(result).toContain(`Query`)
|
38
|
+
// Check that both ID and ! are present (they may be in separate spans)
|
39
|
+
expect(result).toContain(`> ID<`)
|
40
|
+
expect(result).toContain(`>!</`)
|
41
|
+
})
|
42
|
+
|
43
|
+
test(`parse handles inline code`, async () => {
|
44
|
+
const markdown = `This is \`inline code\` in a sentence.`
|
45
|
+
const result = await parse(markdown)
|
46
|
+
|
47
|
+
expect(result).toContain(`<code>inline code</code>`)
|
48
|
+
})
|
49
|
+
|
50
|
+
test(`parse supports GitHub Flavored Markdown`, async () => {
|
51
|
+
const markdown = `
|
52
|
+
| Column 1 | Column 2 |
|
53
|
+
|----------|----------|
|
54
|
+
| Cell 1 | Cell 2 |
|
55
|
+
|
56
|
+
- [x] Task 1
|
57
|
+
- [ ] Task 2
|
58
|
+
`
|
59
|
+
const result = await parse(markdown)
|
60
|
+
|
61
|
+
expect(result).toContain(`<table>`)
|
62
|
+
expect(result).toContain(`<input`)
|
63
|
+
expect(result).toContain(`checked`)
|
64
|
+
})
|
65
|
+
|
66
|
+
test(`parse handles code blocks without language`, async () => {
|
67
|
+
const markdown = `
|
68
|
+
\`\`\`
|
69
|
+
plain text without language
|
70
|
+
\`\`\`
|
71
|
+
`
|
72
|
+
const result = await parse(markdown)
|
73
|
+
|
74
|
+
expect(result).toContain(`<pre`)
|
75
|
+
expect(result).toContain(`plain text without language`)
|
76
|
+
})
|
77
|
+
|
78
|
+
test(`parse preserves theme CSS variables`, async () => {
|
79
|
+
const markdown = `
|
80
|
+
\`\`\`javascript
|
81
|
+
const theme = "light"
|
82
|
+
\`\`\`
|
83
|
+
`
|
84
|
+
const result = await parse(markdown)
|
85
|
+
|
86
|
+
expect(result).toContain(`--shiki-light`)
|
87
|
+
expect(result).toContain(`--shiki-dark`)
|
88
|
+
})
|
89
|
+
})
|
@@ -1,21 +1,43 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
import
|
1
|
+
import { unified } from 'unified'
|
2
|
+
import remarkParse from 'remark-parse'
|
3
|
+
import remarkGfm from 'remark-gfm'
|
4
|
+
import remarkRehype from 'remark-rehype'
|
5
|
+
import rehypeShiki from '@shikijs/rehype'
|
6
|
+
import rehypeStringify from 'rehype-stringify'
|
4
7
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
.use(
|
9
|
-
.
|
8
|
+
// Create a processor with Shiki for syntax highlighting (async only)
|
9
|
+
const createProcessorWithShiki = () => {
|
10
|
+
return unified()
|
11
|
+
.use(remarkParse)
|
12
|
+
.use(remarkGfm)
|
13
|
+
.use(remarkRehype)
|
14
|
+
.use(rehypeShiki, {
|
15
|
+
themes: {
|
16
|
+
light: `github-light`,
|
17
|
+
dark: `tokyo-night`,
|
18
|
+
},
|
19
|
+
defaultColor: false,
|
20
|
+
cssVariablePrefix: `--shiki-`,
|
21
|
+
})
|
22
|
+
.use(rehypeStringify)
|
23
|
+
}
|
24
|
+
|
25
|
+
// Create a processor without syntax highlighting for sync processing
|
26
|
+
const createProcessorSync = () => {
|
27
|
+
return unified()
|
28
|
+
.use(remarkParse)
|
29
|
+
.use(remarkGfm)
|
30
|
+
.use(remarkRehype)
|
31
|
+
.use(rehypeStringify)
|
32
|
+
}
|
10
33
|
|
34
|
+
export const parse = async (content: string): Promise<string> => {
|
35
|
+
const result = await createProcessorWithShiki().process(content)
|
11
36
|
return String(result)
|
12
37
|
}
|
13
38
|
|
14
39
|
export const parseSync = (content: string): string => {
|
15
|
-
|
16
|
-
|
17
|
-
.use(RemarkHtml)
|
18
|
-
.processSync(content)
|
19
|
-
|
40
|
+
// Note: Syntax highlighting is not available in sync mode due to @shikijs/rehype being async-only
|
41
|
+
const result = createProcessorSync().processSync(content)
|
20
42
|
return String(result)
|
21
43
|
}
|
@@ -10,6 +10,7 @@ import { superjson } from '#singletons/superjson'
|
|
10
10
|
import mdx from '@mdx-js/rollup'
|
11
11
|
import { Path, Str } from '@wollybeard/kit'
|
12
12
|
import remarkGfm from 'remark-gfm'
|
13
|
+
import rehypeShiki from '@shikijs/rehype'
|
13
14
|
|
14
15
|
const _debug = debug.sub(`vite-plugin-pages`)
|
15
16
|
|
@@ -123,6 +124,22 @@ export const Pages = ({
|
|
123
124
|
...mdx({
|
124
125
|
jsxImportSource: `polen/react`,
|
125
126
|
remarkPlugins: [remarkGfm],
|
127
|
+
rehypePlugins: [
|
128
|
+
[
|
129
|
+
rehypeShiki,
|
130
|
+
{
|
131
|
+
themes: {
|
132
|
+
light: `github-light`,
|
133
|
+
dark: `tokyo-night`,
|
134
|
+
},
|
135
|
+
defaultColor: false,
|
136
|
+
cssVariablePrefix: `--shiki-`,
|
137
|
+
transformers: [
|
138
|
+
// Line numbers will be handled via CSS
|
139
|
+
],
|
140
|
+
},
|
141
|
+
],
|
142
|
+
],
|
126
143
|
}),
|
127
144
|
},
|
128
145
|
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './shiki.ts'
|
@@ -0,0 +1,107 @@
|
|
1
|
+
import { expect, test } from 'vitest'
|
2
|
+
import { getHighlighter, highlightCode } from './shiki.js'
|
3
|
+
|
4
|
+
test(`getHighlighter returns singleton instance`, async () => {
|
5
|
+
const highlighter1 = await getHighlighter()
|
6
|
+
const highlighter2 = await getHighlighter()
|
7
|
+
|
8
|
+
expect(highlighter1).toBe(highlighter2)
|
9
|
+
})
|
10
|
+
|
11
|
+
test(`highlightCode generates HTML with syntax highlighting`, async () => {
|
12
|
+
const code = `const hello = "world"`
|
13
|
+
const result = await highlightCode({
|
14
|
+
code,
|
15
|
+
lang: `javascript`,
|
16
|
+
theme: `light`,
|
17
|
+
})
|
18
|
+
|
19
|
+
expect(result).toContain(`<pre`)
|
20
|
+
expect(result).toContain(`shiki`)
|
21
|
+
expect(result).toContain(`hello`)
|
22
|
+
expect(result).toContain(`world`)
|
23
|
+
})
|
24
|
+
|
25
|
+
test(`highlightCode supports TypeScript`, async () => {
|
26
|
+
const code = `interface User { name: string }`
|
27
|
+
const result = await highlightCode({
|
28
|
+
code,
|
29
|
+
lang: `typescript`,
|
30
|
+
theme: `light`,
|
31
|
+
})
|
32
|
+
|
33
|
+
expect(result).toContain(`interface`)
|
34
|
+
expect(result).toContain(`User`)
|
35
|
+
expect(result).toContain(`string`)
|
36
|
+
})
|
37
|
+
|
38
|
+
test(`highlightCode supports GraphQL`, async () => {
|
39
|
+
const code = `type Query { user: User }`
|
40
|
+
const result = await highlightCode({
|
41
|
+
code,
|
42
|
+
lang: `graphql`,
|
43
|
+
theme: `light`,
|
44
|
+
})
|
45
|
+
|
46
|
+
expect(result).toContain(`type`)
|
47
|
+
expect(result).toContain(`Query`)
|
48
|
+
expect(result).toContain(`User`)
|
49
|
+
})
|
50
|
+
|
51
|
+
test(`highlightCode handles unknown language gracefully`, async () => {
|
52
|
+
const code = `some random text`
|
53
|
+
// Use 'text' as fallback for unknown languages
|
54
|
+
const result = await highlightCode({
|
55
|
+
code,
|
56
|
+
lang: `text`,
|
57
|
+
theme: `light`,
|
58
|
+
})
|
59
|
+
|
60
|
+
// Should still return highlighted HTML
|
61
|
+
expect(result).toContain(`<pre`)
|
62
|
+
expect(result).toContain(`some random text`)
|
63
|
+
})
|
64
|
+
|
65
|
+
test(`highlightCode applies line numbers when requested`, async () => {
|
66
|
+
const code = `line 1\nline 2\nline 3`
|
67
|
+
const result = await highlightCode({
|
68
|
+
code,
|
69
|
+
lang: `text`,
|
70
|
+
theme: `light`,
|
71
|
+
showLineNumbers: true,
|
72
|
+
})
|
73
|
+
|
74
|
+
expect(result).toContain(`data-line-numbers="true"`)
|
75
|
+
})
|
76
|
+
|
77
|
+
test(`highlightCode applies line highlighting`, async () => {
|
78
|
+
const code = `line 1\nline 2\nline 3`
|
79
|
+
const result = await highlightCode({
|
80
|
+
code,
|
81
|
+
lang: `text`,
|
82
|
+
theme: `light`,
|
83
|
+
highlightLines: [2],
|
84
|
+
})
|
85
|
+
|
86
|
+
expect(result).toContain(`data-highlighted="true"`)
|
87
|
+
})
|
88
|
+
|
89
|
+
test(`highlightCode supports both light and dark themes`, async () => {
|
90
|
+
const code = `const x = 1`
|
91
|
+
|
92
|
+
const lightResult = await highlightCode({
|
93
|
+
code,
|
94
|
+
lang: `javascript`,
|
95
|
+
theme: `light`,
|
96
|
+
})
|
97
|
+
|
98
|
+
const darkResult = await highlightCode({
|
99
|
+
code,
|
100
|
+
lang: `javascript`,
|
101
|
+
theme: `dark`,
|
102
|
+
})
|
103
|
+
|
104
|
+
// Both should contain theme CSS variables
|
105
|
+
expect(lightResult).toContain(`--shiki-light`)
|
106
|
+
expect(darkResult).toContain(`--shiki-dark`)
|
107
|
+
})
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import { createHighlighter, type Highlighter, type BundledTheme, type BundledLanguage } from 'shiki'
|
2
|
+
import {
|
3
|
+
transformerNotationHighlight,
|
4
|
+
transformerNotationDiff,
|
5
|
+
transformerNotationFocus,
|
6
|
+
transformerRenderWhitespace,
|
7
|
+
} from '@shikijs/transformers'
|
8
|
+
|
9
|
+
export interface ShikiOptions {
|
10
|
+
themes?: {
|
11
|
+
light: BundledTheme
|
12
|
+
dark: BundledTheme
|
13
|
+
}
|
14
|
+
langs?: BundledLanguage[]
|
15
|
+
defaultTheme?: 'light' | 'dark'
|
16
|
+
}
|
17
|
+
|
18
|
+
const DEFAULT_THEMES = {
|
19
|
+
light: `github-light` as BundledTheme,
|
20
|
+
dark: `tokyo-night` as BundledTheme,
|
21
|
+
}
|
22
|
+
|
23
|
+
const DEFAULT_LANGS: BundledLanguage[] = [
|
24
|
+
`typescript`,
|
25
|
+
`javascript`,
|
26
|
+
`jsx`,
|
27
|
+
`tsx`,
|
28
|
+
`graphql`,
|
29
|
+
`json`,
|
30
|
+
`yaml`,
|
31
|
+
`markdown`,
|
32
|
+
`bash`,
|
33
|
+
`shell`,
|
34
|
+
`css`,
|
35
|
+
`html`,
|
36
|
+
`sql`,
|
37
|
+
`python`,
|
38
|
+
`rust`,
|
39
|
+
`go`,
|
40
|
+
`java`,
|
41
|
+
`csharp`,
|
42
|
+
`php`,
|
43
|
+
`ruby`,
|
44
|
+
`swift`,
|
45
|
+
`kotlin`,
|
46
|
+
`scala`,
|
47
|
+
`r`,
|
48
|
+
`matlab`,
|
49
|
+
`latex`,
|
50
|
+
`dockerfile`,
|
51
|
+
`makefile`,
|
52
|
+
`nginx`,
|
53
|
+
`apache`,
|
54
|
+
`xml`,
|
55
|
+
`toml`,
|
56
|
+
`ini`,
|
57
|
+
`diff`,
|
58
|
+
]
|
59
|
+
|
60
|
+
// Singleton highlighter instance
|
61
|
+
let highlighterInstance: Highlighter | null = null
|
62
|
+
let highlighterPromise: Promise<Highlighter> | null = null
|
63
|
+
|
64
|
+
export async function getHighlighter(options: ShikiOptions = {}): Promise<Highlighter> {
|
65
|
+
if (highlighterInstance) {
|
66
|
+
return highlighterInstance
|
67
|
+
}
|
68
|
+
|
69
|
+
if (!highlighterPromise) {
|
70
|
+
const themes = options.themes || DEFAULT_THEMES
|
71
|
+
const langs = options.langs || DEFAULT_LANGS
|
72
|
+
|
73
|
+
highlighterPromise = createHighlighter({
|
74
|
+
themes: [themes.light, themes.dark],
|
75
|
+
langs,
|
76
|
+
}).then(highlighter => {
|
77
|
+
highlighterInstance = highlighter
|
78
|
+
return highlighter
|
79
|
+
})
|
80
|
+
}
|
81
|
+
|
82
|
+
return highlighterPromise
|
83
|
+
}
|
84
|
+
|
85
|
+
export interface CodeHighlightOptions {
|
86
|
+
code: string
|
87
|
+
lang?: string
|
88
|
+
theme?: 'light' | 'dark'
|
89
|
+
showLineNumbers?: boolean
|
90
|
+
highlightLines?: number[]
|
91
|
+
diffLines?: { add: number[], remove: number[] }
|
92
|
+
focusLines?: number[]
|
93
|
+
showInvisibles?: boolean
|
94
|
+
}
|
95
|
+
|
96
|
+
export async function highlightCode({
|
97
|
+
code,
|
98
|
+
lang = `text`,
|
99
|
+
theme = `light`,
|
100
|
+
showLineNumbers = false,
|
101
|
+
highlightLines = [],
|
102
|
+
diffLines,
|
103
|
+
focusLines = [],
|
104
|
+
showInvisibles = false,
|
105
|
+
}: CodeHighlightOptions): Promise<string> {
|
106
|
+
const highlighter = await getHighlighter()
|
107
|
+
|
108
|
+
const themes = {
|
109
|
+
light: DEFAULT_THEMES.light,
|
110
|
+
dark: DEFAULT_THEMES.dark,
|
111
|
+
}
|
112
|
+
|
113
|
+
const transformers = []
|
114
|
+
|
115
|
+
// Add line numbers transformer if needed
|
116
|
+
if (showLineNumbers) {
|
117
|
+
// Custom line numbers will be handled in CSS
|
118
|
+
transformers.push({
|
119
|
+
name: `line-numbers`,
|
120
|
+
pre(node: any) {
|
121
|
+
node.properties[`data-line-numbers`] = `true`
|
122
|
+
}
|
123
|
+
})
|
124
|
+
}
|
125
|
+
|
126
|
+
// Add highlight transformer
|
127
|
+
if (highlightLines.length > 0) {
|
128
|
+
transformers.push({
|
129
|
+
name: `highlight-lines`,
|
130
|
+
line(node: any, line: number) {
|
131
|
+
if (highlightLines.includes(line)) {
|
132
|
+
node.properties[`data-highlighted`] = `true`
|
133
|
+
}
|
134
|
+
}
|
135
|
+
})
|
136
|
+
}
|
137
|
+
|
138
|
+
// Add standard transformers
|
139
|
+
transformers.push(
|
140
|
+
transformerNotationHighlight(),
|
141
|
+
transformerNotationDiff(),
|
142
|
+
transformerNotationFocus(),
|
143
|
+
)
|
144
|
+
|
145
|
+
if (showInvisibles) {
|
146
|
+
transformers.push(transformerRenderWhitespace())
|
147
|
+
}
|
148
|
+
|
149
|
+
// Generate HTML with CSS variables for theme switching
|
150
|
+
const html = highlighter.codeToHtml(code, {
|
151
|
+
lang,
|
152
|
+
themes,
|
153
|
+
defaultColor: false,
|
154
|
+
transformers,
|
155
|
+
})
|
156
|
+
|
157
|
+
return html
|
158
|
+
}
|
159
|
+
|
160
|
+
// Re-export types
|
161
|
+
export type { Highlighter, BundledTheme, BundledLanguage } from 'shiki'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import { highlightCode } from '#lib/shiki/index'
|
2
|
+
import React, { useEffect, useState } from 'react'
|
3
|
+
|
4
|
+
interface CodeBlockProps {
|
5
|
+
children: string
|
6
|
+
language?: string
|
7
|
+
className?: string
|
8
|
+
showLineNumbers?: boolean
|
9
|
+
highlightLines?: number[]
|
10
|
+
diffLines?: { add: number[]; remove: number[] }
|
11
|
+
focusLines?: number[]
|
12
|
+
showInvisibles?: boolean
|
13
|
+
}
|
14
|
+
|
15
|
+
export const CodeBlock: React.FC<CodeBlockProps> = ({
|
16
|
+
children,
|
17
|
+
language = `text`,
|
18
|
+
className = ``,
|
19
|
+
showLineNumbers = false,
|
20
|
+
highlightLines = [],
|
21
|
+
diffLines,
|
22
|
+
focusLines = [],
|
23
|
+
showInvisibles = false,
|
24
|
+
}) => {
|
25
|
+
const [html, setHtml] = useState<string>(``)
|
26
|
+
const [isLoading, setIsLoading] = useState(true)
|
27
|
+
|
28
|
+
// TODO: Implement proper theme detection
|
29
|
+
// For now, we'll rely on CSS to handle theme switching
|
30
|
+
const theme = `light` // Default to light theme
|
31
|
+
|
32
|
+
useEffect(() => {
|
33
|
+
const renderCode = async () => {
|
34
|
+
try {
|
35
|
+
const output = await highlightCode({
|
36
|
+
code: children,
|
37
|
+
lang: language,
|
38
|
+
theme,
|
39
|
+
showLineNumbers,
|
40
|
+
highlightLines,
|
41
|
+
diffLines,
|
42
|
+
focusLines,
|
43
|
+
showInvisibles,
|
44
|
+
})
|
45
|
+
|
46
|
+
setHtml(output)
|
47
|
+
} catch (error) {
|
48
|
+
console.error(`Failed to highlight code:`, error)
|
49
|
+
// Fallback to plain text
|
50
|
+
setHtml(`<pre><code>${children}</code></pre>`)
|
51
|
+
} finally {
|
52
|
+
setIsLoading(false)
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
renderCode()
|
57
|
+
}, [children, language, showLineNumbers, highlightLines, diffLines, focusLines, showInvisibles])
|
58
|
+
|
59
|
+
if (isLoading) {
|
60
|
+
return (
|
61
|
+
<pre className={className}>
|
62
|
+
<code>{children}</code>
|
63
|
+
</pre>
|
64
|
+
)
|
65
|
+
}
|
66
|
+
|
67
|
+
return (
|
68
|
+
<div
|
69
|
+
className={`code-block ${className}`}
|
70
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
71
|
+
/>
|
72
|
+
)
|
73
|
+
}
|
@@ -1,17 +1,19 @@
|
|
1
1
|
import type { FileRouter } from '#lib/file-router/index'
|
2
2
|
import { Box } from '@radix-ui/themes'
|
3
|
+
import type { BoxOwnProps, LayoutProps, MarginProps } from '@radix-ui/themes/props'
|
3
4
|
import { Items } from './SidebarItem.tsx'
|
4
5
|
|
5
|
-
interface SidebarProps {
|
6
|
-
|
6
|
+
interface SidebarProps extends LayoutProps, MarginProps, BoxOwnProps {
|
7
|
+
data: FileRouter.Sidebar.Item[]
|
8
|
+
style?: React.CSSProperties
|
7
9
|
}
|
8
10
|
|
9
|
-
export const Sidebar = ({
|
11
|
+
export const Sidebar = ({ data, ...props }: SidebarProps) => {
|
10
12
|
return (
|
11
13
|
<Box
|
12
14
|
data-testid='sidebar'
|
13
15
|
role='Sidebar'
|
14
|
-
|
16
|
+
{...props}
|
15
17
|
>
|
16
18
|
<style>
|
17
19
|
{`
|
@@ -20,7 +22,7 @@ export const Sidebar = ({ items }: SidebarProps) => {
|
|
20
22
|
}
|
21
23
|
`}
|
22
24
|
</style>
|
23
|
-
<Items items={
|
25
|
+
<Items items={data} />
|
24
26
|
</Box>
|
25
27
|
)
|
26
28
|
}
|
@@ -2,6 +2,7 @@
|
|
2
2
|
// But then, we won't get it from the client manifest. But we could get it from the server manifest. Should we do that?
|
3
3
|
// But then, that wouldn't work for SPA. Does that matter? Just put a conditional here e.g. if (import.meta.env.PROD) ...?
|
4
4
|
import '@radix-ui/themes/styles.css'
|
5
|
+
// import './styles/code-block.css' // TODO: Handle CSS in build process
|
5
6
|
import { ReactDomClient } from '#dep/react-dom-client/index'
|
6
7
|
import { StrictMode } from 'react'
|
7
8
|
import { createBrowserRouter, RouterProvider } from 'react-router'
|