@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 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
+ }