Skip to content

Commit 13de7bc

Browse files
committed
feat: scaffold docs-site with Nextra v4, Sandpack playground
- Nextra v4.6.1 + Next.js 16 + React 19 docs site in docs-site/ - app/[[...mdxPath]]/page.tsx with correct Nextra v4 page pattern - mdx-components.tsx merges nextra-theme-docs + custom Playground - components/Playground.tsx: Sandpack with network toggle (testnet/local) - content/: home, quickstart, 11 package pages, devkit CLI, 3 playground examples - pnpm-workspace.yaml: adds docs-site to monorepo - Build verified: 22/22 static pages generated cleanly Run: pnpm --filter cfxdevkit-docs dev (port 3100)
1 parent 6c77692 commit 13de7bc

35 files changed

Lines changed: 6715 additions & 182 deletions

‎docs-site/.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.next/
2+
out/
3+
node_modules/
4+
*.tsbuildinfo
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { generateStaticParamsFor, importPage } from 'nextra/pages'
2+
import { useMDXComponents as getMDXComponents } from '../../mdx-components'
3+
4+
export const generateStaticParams = generateStaticParamsFor('mdxPath')
5+
6+
export async function generateMetadata(props: {
7+
params: Promise<{ mdxPath: string[] }>
8+
}) {
9+
const params = await props.params
10+
const { metadata } = await importPage(params.mdxPath)
11+
return metadata
12+
}
13+
14+
const Wrapper = getMDXComponents().wrapper
15+
16+
export default async function Page(props: {
17+
params: Promise<{ mdxPath: string[] }>
18+
}) {
19+
const params = await props.params
20+
const { default: MDXContent, toc, metadata } = await importPage(params.mdxPath)
21+
return (
22+
<Wrapper toc={toc} metadata={metadata}>
23+
<MDXContent {...props} params={params} />
24+
</Wrapper>
25+
)
26+
}

‎docs-site/app/layout.tsx‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Footer, Layout, Navbar } from 'nextra-theme-docs'
2+
import { Head } from 'nextra/components'
3+
import { getPageMap } from 'nextra/page-map'
4+
import 'nextra-theme-docs/style.css'
5+
import type { Metadata } from 'next'
6+
7+
export const metadata: Metadata = {
8+
title: {
9+
template: '%s — cfxdevkit',
10+
default: 'cfxdevkit — Conflux TypeScript SDK',
11+
},
12+
description:
13+
'TypeScript SDK and local dev tools for building on Conflux — dual-space (Core + eSpace), React components, automation engine, and more.',
14+
metadataBase: new URL('https://cfxdevkit.org'),
15+
openGraph: {
16+
title: 'cfxdevkit — Conflux TypeScript SDK',
17+
description:
18+
'TypeScript SDK and local dev tools for building on Conflux.',
19+
url: 'https://cfxdevkit.org',
20+
siteName: 'cfxdevkit',
21+
type: 'website',
22+
},
23+
twitter: {
24+
card: 'summary_large_image',
25+
title: 'cfxdevkit — Conflux TypeScript SDK',
26+
},
27+
}
28+
29+
const navbar = (
30+
<Navbar
31+
logo={
32+
<span style={{ fontWeight: 700, fontSize: '1.1rem', letterSpacing: '-0.02em' }}>
33+
cfxdevkit
34+
</span>
35+
}
36+
projectLink="https://github.com/cfxdevkit/conflux-devkit"
37+
/>
38+
)
39+
40+
const footer = (
41+
<Footer>
42+
<span>
43+
Apache 2.0 {new Date().getFullYear()} ©{' '}
44+
<a href="https://github.com/cfxdevkit" target="_blank" rel="noreferrer">
45+
cfxdevkit
46+
</a>
47+
</span>
48+
</Footer>
49+
)
50+
51+
export default async function RootLayout({
52+
children,
53+
}: {
54+
children: React.ReactNode
55+
}) {
56+
return (
57+
<html lang="en" dir="ltr" suppressHydrationWarning>
58+
<Head />
59+
<body>
60+
<Layout
61+
navbar={navbar}
62+
footer={footer}
63+
pageMap={await getPageMap()}
64+
docsRepositoryBase="https://github.com/cfxdevkit/conflux-devkit/tree/main/docs-site"
65+
editLink="Edit this page on GitHub"
66+
feedback={{ content: 'Question? Give us feedback' }}
67+
sidebar={{ defaultMenuCollapseLevel: 1 }}
68+
>
69+
{children}
70+
</Layout>
71+
</body>
72+
</html>
73+
)
74+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import {
5+
SandpackProvider,
6+
SandpackLayout,
7+
SandpackCodeEditor,
8+
SandpackPreview,
9+
SandpackConsole,
10+
useActiveCode,
11+
useSandpack,
12+
} from '@codesandbox/sandpack-react'
13+
// sandpackDark is bundled with sandpack-react
14+
const sandpackDark = 'dark' as const
15+
16+
// Conflux network configs
17+
const NETWORKS = {
18+
testnet: {
19+
label: 'Testnet',
20+
chainId: 71,
21+
rpcUrl: 'https://evmtestnet.confluxrpc.com',
22+
blockExplorer: 'https://evmtestnet.confluxscan.io',
23+
},
24+
local: {
25+
label: 'Local (localhost:8545)',
26+
chainId: 2030,
27+
rpcUrl: 'http://localhost:8545',
28+
blockExplorer: '',
29+
},
30+
} as const
31+
32+
type Network = keyof typeof NETWORKS
33+
34+
interface PlaygroundProps {
35+
/** The active code file name, e.g. "index.ts" */
36+
file?: string
37+
/** Record of filename → code content */
38+
files?: Record<string, string>
39+
/** Template: "vanilla-ts" | "nextjs" | "node" */
40+
template?: 'vanilla-ts' | 'node'
41+
/** Show console instead of preview */
42+
showConsole?: boolean
43+
/** Extra npm dependencies beyond the defaults */
44+
extraDeps?: Record<string, string>
45+
}
46+
47+
const DEFAULT_DEPS: Record<string, string> = {
48+
viem: '^2.0.0',
49+
'@cfxdevkit/core': 'latest',
50+
'@cfxdevkit/contracts': 'latest',
51+
}
52+
53+
function NetworkToggle({
54+
network,
55+
onChange,
56+
}: {
57+
network: Network
58+
onChange: (n: Network) => void
59+
}) {
60+
return (
61+
<div
62+
style={{
63+
display: 'flex',
64+
alignItems: 'center',
65+
gap: '8px',
66+
padding: '6px 12px',
67+
borderBottom: '1px solid var(--sp-colors-surface2)',
68+
background: 'var(--sp-colors-surface1)',
69+
fontSize: '12px',
70+
fontFamily: 'var(--sp-font-mono)',
71+
}}
72+
>
73+
<span style={{ color: 'var(--sp-colors-fg-inactive)' }}>network:</span>
74+
{(Object.keys(NETWORKS) as Network[]).map((key) => (
75+
<button
76+
key={key}
77+
onClick={() => onChange(key)}
78+
style={{
79+
padding: '2px 10px',
80+
borderRadius: '4px',
81+
border: 'none',
82+
cursor: 'pointer',
83+
fontFamily: 'inherit',
84+
fontSize: '11px',
85+
background:
86+
network === key
87+
? 'var(--sp-colors-accent)'
88+
: 'var(--sp-colors-surface2)',
89+
color:
90+
network === key
91+
? '#fff'
92+
: 'var(--sp-colors-fg-inactive)',
93+
}}
94+
>
95+
{NETWORKS[key].label}
96+
</button>
97+
))}
98+
<span
99+
style={{
100+
marginLeft: 'auto',
101+
color: 'var(--sp-colors-fg-inactive)',
102+
fontSize: '10px',
103+
}}
104+
>
105+
chainId: {NETWORKS[network].chainId} · {NETWORKS[network].rpcUrl}
106+
</span>
107+
</div>
108+
)
109+
}
110+
111+
export function Playground({
112+
files = {},
113+
template = 'vanilla-ts',
114+
showConsole = true,
115+
extraDeps = {},
116+
file = 'index.ts',
117+
}: PlaygroundProps) {
118+
const [network, setNetwork] = useState<Network>('testnet')
119+
120+
const networkConfig = NETWORKS[network]
121+
122+
// Inject a network-config helper that examples can import
123+
const networkFile = `// Auto-generated — re-renders when you toggle network above
124+
export const NETWORK = {
125+
chainId: ${networkConfig.chainId},
126+
rpcUrl: '${networkConfig.rpcUrl}',
127+
blockExplorer: '${networkConfig.blockExplorer}',
128+
} as const
129+
`
130+
131+
const mergedFiles = {
132+
'/network-config.ts': { code: networkFile, readOnly: true },
133+
...Object.fromEntries(
134+
Object.entries(files).map(([name, code]) => [
135+
name.startsWith('/') ? name : `/${name}`,
136+
{ code },
137+
])
138+
),
139+
}
140+
141+
return (
142+
<div style={{ margin: '1.5rem 0', borderRadius: '8px', overflow: 'hidden' }}>
143+
<SandpackProvider
144+
key={network} // remount on network change to reset execution
145+
template={template}
146+
theme={sandpackDark}
147+
files={mergedFiles}
148+
options={{
149+
activeFile: file.startsWith('/') ? file : `/${file}`,
150+
visibleFiles: Object.keys(mergedFiles),
151+
recompileMode: 'delayed',
152+
recompileDelay: 500,
153+
}}
154+
customSetup={{
155+
dependencies: {
156+
...DEFAULT_DEPS,
157+
...extraDeps,
158+
},
159+
}}
160+
>
161+
<NetworkToggle network={network} onChange={setNetwork} />
162+
<SandpackLayout>
163+
<SandpackCodeEditor
164+
showLineNumbers
165+
showInlineErrors
166+
wrapContent
167+
style={{ height: 400 }}
168+
/>
169+
{showConsole ? (
170+
<SandpackConsole style={{ height: 400 }} />
171+
) : (
172+
<SandpackPreview style={{ height: 400 }} />
173+
)}
174+
</SandpackLayout>
175+
</SandpackProvider>
176+
</div>
177+
)
178+
}

‎docs-site/content/_meta.js‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export default {
2+
index: {
3+
title: 'Home',
4+
type: 'page',
5+
display: 'hidden',
6+
},
7+
quickstart: {
8+
title: 'Quick Start',
9+
type: 'page',
10+
},
11+
packages: {
12+
title: 'Packages',
13+
type: 'page',
14+
},
15+
devkit: {
16+
title: 'Devkit CLI',
17+
type: 'page',
18+
},
19+
examples: {
20+
title: 'Playground',
21+
type: 'page',
22+
},
23+
}

‎docs-site/content/devkit/_meta.js‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
index: 'Overview',
3+
'api-reference': 'API Reference',
4+
}

0 commit comments

Comments
 (0)