@codespark/react 1.0.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.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/codemirror.d.ts +35 -0
- package/dist/codemirror.js +146 -0
- package/dist/index.css +82 -0
- package/dist/index.d.ts +712 -0
- package/dist/index.js +1753 -0
- package/dist/monaco.d.ts +36 -0
- package/dist/monaco.js +323 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 TonyL1u
|
|
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,115 @@
|
|
|
1
|
+
# @codespark/react
|
|
2
|
+
|
|
3
|
+
React components for the Codespark ecosystem - a lightweight React playground for live code editing and instant preview.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
Visit [https://codesparkjs.com/docs](https://codesparkjs.com/docs) to view the documentation.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Real-time preview with hot reloading
|
|
12
|
+
- Monaco Editor and CodeMirror support with syntax highlighting
|
|
13
|
+
- Single-file and multi-file mode
|
|
14
|
+
- Automatic dependency resolution via esm.sh
|
|
15
|
+
- TailwindCSS support in preview
|
|
16
|
+
- Customizable layout (vertical/horizontal orientation)
|
|
17
|
+
- File explorer for multi-file projects
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @codespark/react
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Basic Usage
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { Codespark } from '@codespark/react';
|
|
31
|
+
import '@codespark/react/index.css';
|
|
32
|
+
|
|
33
|
+
const code = `export default function App() {
|
|
34
|
+
return (
|
|
35
|
+
<div style={{ padding: 20 }}>
|
|
36
|
+
<h1>Hello World</h1>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}`;
|
|
40
|
+
|
|
41
|
+
export default function Demo() {
|
|
42
|
+
return <Codespark code={code} />;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Multi-file Mode
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { Codespark } from '@codespark/react';
|
|
50
|
+
|
|
51
|
+
const files = {
|
|
52
|
+
'./App.tsx': `import { Button } from './Button';
|
|
53
|
+
export default function App() {
|
|
54
|
+
return <Button>Click me</Button>;
|
|
55
|
+
}`,
|
|
56
|
+
'./Button.tsx': `export function Button({ children }) {
|
|
57
|
+
return <button className="btn">{children}</button>;
|
|
58
|
+
}`
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default function Demo() {
|
|
62
|
+
return <Codespark files={files} name="./App.tsx" />;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### With TailwindCSS
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<Codespark code={code} tailwindcss />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Horizontal Layout
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<Codespark code={code} orientation="horizontal" />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Custom Height
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<Codespark code={code} editorHeight={300} previewHeight="50%" />
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Props
|
|
85
|
+
|
|
86
|
+
| Prop | Type | Default | Description |
|
|
87
|
+
|------|------|---------|-------------|
|
|
88
|
+
| `code` | `string` | - | Source code for single-file mode |
|
|
89
|
+
| `files` | `Record<string, string>` | - | File mapping for multi-file mode |
|
|
90
|
+
| `name` | `string` | `'./App.tsx'` | Entry file path |
|
|
91
|
+
| `theme` | `string` | - | Editor theme |
|
|
92
|
+
| `framework` | `string` | `'react'` | Framework type |
|
|
93
|
+
| `showEditor` | `boolean` | `true` | Show code editor |
|
|
94
|
+
| `showPreview` | `boolean` | `true` | Show preview area |
|
|
95
|
+
| `showFileExplorer` | `boolean` | `true` | Show file explorer |
|
|
96
|
+
| `readonly` | `boolean` | `false` | Read-only mode |
|
|
97
|
+
| `editorHeight` | `string \| number` | `200` | Editor height |
|
|
98
|
+
| `previewHeight` | `string \| number` | `200` | Preview height |
|
|
99
|
+
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Layout orientation |
|
|
100
|
+
| `tailwindcss` | `boolean` | - | Enable TailwindCSS in preview |
|
|
101
|
+
| `editor` | `CodesparkEditorEngineComponents` | CodeMirror | Editor engine (Monaco/CodeMirror) |
|
|
102
|
+
|
|
103
|
+
## Exports
|
|
104
|
+
|
|
105
|
+
- `Codespark` - Main playground component
|
|
106
|
+
- `CodesparkEditor` - Standalone editor component
|
|
107
|
+
- `CodesparkPreview` - Standalone preview component
|
|
108
|
+
- `CodesparkFileExplorer` - File explorer component
|
|
109
|
+
- `CodesparkProvider` - Context provider
|
|
110
|
+
- `useWorkspace` - Hook for workspace management
|
|
111
|
+
- `EditorEngine` - Editor engine enum (Monaco/CodeMirror)
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ReactCodeMirrorProps, ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
2
|
+
import { ComponentType } from "react";
|
|
3
|
+
import * as monaco from "monaco-editor";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/editor-adapter/index.d.ts
|
|
6
|
+
declare enum EditorEngine {
|
|
7
|
+
Monaco = 0,
|
|
8
|
+
CodeMirror = 1,
|
|
9
|
+
}
|
|
10
|
+
interface EditorInstance {
|
|
11
|
+
[EditorEngine.Monaco]: monaco.editor.IStandaloneCodeEditor;
|
|
12
|
+
[EditorEngine.CodeMirror]: ReactCodeMirrorRef;
|
|
13
|
+
}
|
|
14
|
+
interface EditorAdapter<E extends EditorEngine = any> {
|
|
15
|
+
kind: E;
|
|
16
|
+
instance: EditorInstance[E];
|
|
17
|
+
getValue(): string;
|
|
18
|
+
setValue(value: string, addToHistory?: boolean): void;
|
|
19
|
+
format(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
interface EditorEngineComponent<E extends EditorEngine = any, P = object, I = unknown> {
|
|
22
|
+
kind: E;
|
|
23
|
+
Component: ComponentType<P>;
|
|
24
|
+
createAdapter: (instance: I) => EditorAdapter<E>;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/components/editor/codemirror/index.d.ts
|
|
28
|
+
interface CodeMirrorProps extends ReactCodeMirrorProps {
|
|
29
|
+
readonly id?: string;
|
|
30
|
+
onMount?: (editor: ReactCodeMirrorRef) => void;
|
|
31
|
+
fontFamily?: string;
|
|
32
|
+
}
|
|
33
|
+
declare const CodeMirror: EditorEngineComponent<EditorEngine.CodeMirror, CodeMirrorProps, ReactCodeMirrorRef>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { CodeMirror, CodeMirrorProps };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { javascript } from "@codemirror/lang-javascript";
|
|
2
|
+
import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
|
|
3
|
+
import { languages } from "@codemirror/language-data";
|
|
4
|
+
import ReactCodeMirror, { EditorView, Transaction } from "@uiw/react-codemirror";
|
|
5
|
+
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
+
import { clsx } from "clsx";
|
|
7
|
+
import { twMerge } from "tailwind-merge";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
|
|
10
|
+
//#region src/lib/editor-adapter/index.ts
|
|
11
|
+
let EditorEngine = /* @__PURE__ */ function(EditorEngine$1) {
|
|
12
|
+
EditorEngine$1[EditorEngine$1["Monaco"] = 0] = "Monaco";
|
|
13
|
+
EditorEngine$1[EditorEngine$1["CodeMirror"] = 1] = "CodeMirror";
|
|
14
|
+
return EditorEngine$1;
|
|
15
|
+
}({});
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/lib/utils.ts
|
|
19
|
+
function cn(...inputs) {
|
|
20
|
+
return twMerge(clsx(inputs));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/ui/skeleton.tsx
|
|
25
|
+
function Skeleton({ className, ...props }) {
|
|
26
|
+
return /* @__PURE__ */ jsx("div", {
|
|
27
|
+
"data-slot": "skeleton",
|
|
28
|
+
className: cn("bg-accent animate-pulse rounded-md", className),
|
|
29
|
+
...props
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/components/editor/codemirror/adapter.ts
|
|
35
|
+
var CodeMirrorEditorAdapter = class {
|
|
36
|
+
constructor(kind, instance) {
|
|
37
|
+
this.kind = kind;
|
|
38
|
+
this.instance = instance;
|
|
39
|
+
}
|
|
40
|
+
getValue() {
|
|
41
|
+
return this.instance.view?.state.doc.toString() ?? "";
|
|
42
|
+
}
|
|
43
|
+
setValue(value, addToHistory = false) {
|
|
44
|
+
const view = this.instance.view;
|
|
45
|
+
if (!view) return;
|
|
46
|
+
view.dispatch({
|
|
47
|
+
changes: {
|
|
48
|
+
from: 0,
|
|
49
|
+
to: view.state.doc.length,
|
|
50
|
+
insert: value
|
|
51
|
+
},
|
|
52
|
+
annotations: addToHistory ? void 0 : Transaction.addToHistory.of(false)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async format() {}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/components/editor/codemirror/index.tsx
|
|
60
|
+
const THEME = EditorView.theme({
|
|
61
|
+
"&": { fontSize: "14px" },
|
|
62
|
+
"&.cm-focused": { outline: "none" },
|
|
63
|
+
"&.cm-editor": { backgroundColor: "var(--background)" },
|
|
64
|
+
".cm-gutters.cm-gutters": {
|
|
65
|
+
backgroundColor: "var(--background)",
|
|
66
|
+
border: "none"
|
|
67
|
+
},
|
|
68
|
+
".cm-gutter": {
|
|
69
|
+
padding: "1px 4px 0px 12px",
|
|
70
|
+
minWidth: "36px"
|
|
71
|
+
},
|
|
72
|
+
".cm-scroller": {
|
|
73
|
+
paddingTop: "8px",
|
|
74
|
+
fontFamily: "Fira Code"
|
|
75
|
+
},
|
|
76
|
+
".cm-line": {
|
|
77
|
+
padding: "0 12px",
|
|
78
|
+
height: "24px",
|
|
79
|
+
lineHeight: "24px"
|
|
80
|
+
},
|
|
81
|
+
".cm-gutterElement.cm-gutterElement": {
|
|
82
|
+
padding: "0px",
|
|
83
|
+
lineHeight: "24px"
|
|
84
|
+
},
|
|
85
|
+
".cm-activeLine": { borderRadius: "4px" }
|
|
86
|
+
});
|
|
87
|
+
const LANGUAGE_EXTENSIONS = {
|
|
88
|
+
javascript: javascript({ jsx: true }),
|
|
89
|
+
markdown: markdown({
|
|
90
|
+
base: markdownLanguage,
|
|
91
|
+
codeLanguages: languages
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
const CodeMirror = {
|
|
95
|
+
kind: EditorEngine.CodeMirror,
|
|
96
|
+
Component: memo(function CodeMirror$1(props) {
|
|
97
|
+
const { id, basicSetup, extensions = [], width, height, fontFamily, lang, onMount, ...rest } = props;
|
|
98
|
+
const [mounted, setMounted] = useState(false);
|
|
99
|
+
const editorRef = useRef(null);
|
|
100
|
+
const allExtensions = useMemo(() => {
|
|
101
|
+
const exts = [THEME, ...extensions];
|
|
102
|
+
if (lang && LANGUAGE_EXTENSIONS[lang]) exts.unshift(LANGUAGE_EXTENSIONS[lang]);
|
|
103
|
+
else exts.unshift(LANGUAGE_EXTENSIONS.javascript);
|
|
104
|
+
if (fontFamily) exts.push(EditorView.theme({ "& .cm-scroller": { fontFamily } }));
|
|
105
|
+
return exts;
|
|
106
|
+
}, [extensions, fontFamily]);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
setMounted(true);
|
|
109
|
+
}, []);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (editorRef.current) onMount?.(editorRef.current);
|
|
112
|
+
}, [mounted]);
|
|
113
|
+
return /* @__PURE__ */ jsx("div", {
|
|
114
|
+
id,
|
|
115
|
+
style: { height },
|
|
116
|
+
children: !mounted ? /* @__PURE__ */ jsxs("div", {
|
|
117
|
+
className: "flex flex-col space-y-3 p-5",
|
|
118
|
+
style: { height },
|
|
119
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "w-full flex-1 rounded-xl" }), /* @__PURE__ */ jsxs("div", {
|
|
120
|
+
className: "space-y-3",
|
|
121
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-[80%]" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-[65%]" })]
|
|
122
|
+
})]
|
|
123
|
+
}) : /* @__PURE__ */ jsx(ReactCodeMirror, {
|
|
124
|
+
ref: editorRef,
|
|
125
|
+
width: width ?? "100%",
|
|
126
|
+
height,
|
|
127
|
+
basicSetup: typeof basicSetup === "boolean" ? basicSetup : {
|
|
128
|
+
lineNumbers: true,
|
|
129
|
+
foldGutter: false,
|
|
130
|
+
highlightActiveLine: false,
|
|
131
|
+
highlightActiveLineGutter: false,
|
|
132
|
+
...basicSetup
|
|
133
|
+
},
|
|
134
|
+
extensions: allExtensions,
|
|
135
|
+
lang,
|
|
136
|
+
...rest
|
|
137
|
+
})
|
|
138
|
+
});
|
|
139
|
+
}),
|
|
140
|
+
createAdapter: (instance) => {
|
|
141
|
+
return new CodeMirrorEditorAdapter(EditorEngine.CodeMirror, instance);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
export { CodeMirror };
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--radius: 0.625rem;
|
|
3
|
+
--background: oklch(1 0 0);
|
|
4
|
+
--foreground: oklch(0.145 0 0);
|
|
5
|
+
--card-foreground: oklch(0.145 0 0);
|
|
6
|
+
--primary: oklch(0.205 0 0);
|
|
7
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
8
|
+
--secondary: oklch(0.97 0 0);
|
|
9
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
10
|
+
--muted: oklch(0.97 0 0);
|
|
11
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
12
|
+
--accent: oklch(0.97 0 0);
|
|
13
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
14
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
15
|
+
--border: oklch(0.922 0 0);
|
|
16
|
+
--input: oklch(0.922 0 0);
|
|
17
|
+
--ring: oklch(0.708 0 0);
|
|
18
|
+
--sidebar: oklch(0.985 0 0);
|
|
19
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
20
|
+
--code-foreground: oklch(0.556 0 0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.dark {
|
|
24
|
+
--background: oklch(0.145 0 0);
|
|
25
|
+
--foreground: oklch(0.985 0 0);
|
|
26
|
+
--card-foreground: oklch(0.985 0 0);
|
|
27
|
+
--primary: oklch(0.922 0 0);
|
|
28
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
29
|
+
--secondary: oklch(0.269 0 0);
|
|
30
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
31
|
+
--muted: oklch(0.269 0 0);
|
|
32
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
33
|
+
--accent: oklch(0.269 0 0);
|
|
34
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
35
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
36
|
+
--border: oklch(1 0 0 / 10%);
|
|
37
|
+
--input: oklch(1 0 0 / 15%);
|
|
38
|
+
--ring: oklch(0.556 0 0);
|
|
39
|
+
--sidebar: oklch(0.205 0 0);
|
|
40
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
41
|
+
--code-foreground: oklch(0.708 0 0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@custom-variant dark (&:is(.dark *));
|
|
45
|
+
|
|
46
|
+
@theme inline {
|
|
47
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
48
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
49
|
+
--radius-lg: var(--radius);
|
|
50
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
51
|
+
--radius-2xl: calc(var(--radius) + 8px);
|
|
52
|
+
--radius-3xl: calc(var(--radius) + 12px);
|
|
53
|
+
--radius-4xl: calc(var(--radius) + 16px);
|
|
54
|
+
--color-background: var(--background);
|
|
55
|
+
--color-foreground: var(--foreground);
|
|
56
|
+
--color-card-foreground: var(--card-foreground);
|
|
57
|
+
--color-primary: var(--primary);
|
|
58
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
59
|
+
--color-secondary: var(--secondary);
|
|
60
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
61
|
+
--color-muted: var(--muted);
|
|
62
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
63
|
+
--color-accent: var(--accent);
|
|
64
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
65
|
+
--color-destructive: var(--destructive);
|
|
66
|
+
--color-border: var(--border);
|
|
67
|
+
--color-input: var(--input);
|
|
68
|
+
--color-ring: var(--ring);
|
|
69
|
+
--color-sidebar: var(--sidebar);
|
|
70
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
71
|
+
--color-code-foreground: var(--code-foreground);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@keyframes cube-rotate {
|
|
75
|
+
0% {
|
|
76
|
+
transform: rotateX(-25.5deg) rotateY(45deg);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
100% {
|
|
80
|
+
transform: rotateX(-25.5deg) rotateY(405deg);
|
|
81
|
+
}
|
|
82
|
+
}
|