@particle-academy/fancy-code 0.4.2 → 0.4.3

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,208 @@
1
+ # CodeEditor
2
+
3
+ A compound code editor built on a custom lightweight engine (no Monaco / CodeMirror dependency). Supports syntax highlighting, line numbers, word wrap, placeholder, read-only mode, height constraints, and swappable languages and themes.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import {
9
+ CodeEditor,
10
+ useCodeEditor,
11
+ } from "@particle-academy/fancy-code";
12
+ import "@particle-academy/fancy-code/styles.css";
13
+ ```
14
+
15
+ ## Basic Usage
16
+
17
+ ```tsx
18
+ <CodeEditor
19
+ defaultValue={`function greet(name) {\n return "Hello, " + name;\n}`}
20
+ language="javascript"
21
+ >
22
+ <CodeEditor.Toolbar />
23
+ <CodeEditor.Panel />
24
+ <CodeEditor.StatusBar />
25
+ </CodeEditor>
26
+ ```
27
+
28
+ All three sub-components are optional — omit any to render a minimal editor.
29
+
30
+ ## Props
31
+
32
+ ### CodeEditor (root)
33
+
34
+ | Prop | Type | Default | Description |
35
+ |------|------|---------|-------------|
36
+ | value | `string` | - | Controlled document text |
37
+ | defaultValue | `string` | `""` | Initial document text (uncontrolled) |
38
+ | onChange | `(value: string) => void` | - | Called on every document change |
39
+ | language | `string` | `"javascript"` | Language name or alias (see [languages](./languages.md)) |
40
+ | onLanguageChange | `(lang: string) => void` | - | Called when the toolbar language selector changes |
41
+ | theme | `string` | `"auto"` | `"light"`, `"dark"`, `"auto"`, or a registered custom theme |
42
+ | readOnly | `boolean` | `false` | Disable editing |
43
+ | lineNumbers | `boolean` | `true` | Show the gutter with line numbers |
44
+ | wordWrap | `boolean` | `false` | Wrap long lines instead of horizontal scroll |
45
+ | tabSize | `number` | `2` | Number of spaces per tab |
46
+ | placeholder | `string` | - | Shown when the document is empty |
47
+ | minHeight | `number` | - | Minimum height in px |
48
+ | maxHeight | `number` | - | Maximum height in px (scrolls beyond) |
49
+ | className | `string` | - | Additional CSS classes on the root container |
50
+
51
+ ### CodeEditor.Toolbar
52
+
53
+ Default-rendered toolbar with language selector, word-wrap toggle, and copy button. Pass `children` to replace the defaults.
54
+
55
+ | Prop | Type | Default | Description |
56
+ |------|------|---------|-------------|
57
+ | children | `ReactNode` | - | Custom toolbar content (replaces defaults) |
58
+ | className | `string` | - | Additional CSS classes |
59
+
60
+ ### CodeEditor.Toolbar.Separator
61
+
62
+ Vertical divider between toolbar groups. No props.
63
+
64
+ ### CodeEditor.Panel
65
+
66
+ The editable code surface.
67
+
68
+ | Prop | Type | Default | Description |
69
+ |------|------|---------|-------------|
70
+ | className | `string` | - | Additional CSS classes |
71
+
72
+ ### CodeEditor.StatusBar
73
+
74
+ Default footer showing cursor line/col, selection count, language name, and tab size. Pass `children` to replace.
75
+
76
+ | Prop | Type | Default | Description |
77
+ |------|------|---------|-------------|
78
+ | children | `ReactNode` | - | Custom status bar content (replaces defaults) |
79
+ | className | `string` | - | Additional CSS classes |
80
+
81
+ ## useCodeEditor Hook
82
+
83
+ Access editor state and commands from any child of `<CodeEditor>`. Typically used inside a custom toolbar.
84
+
85
+ ```tsx
86
+ import { useCodeEditor } from "@particle-academy/fancy-code";
87
+
88
+ function RunButton() {
89
+ const { getValue } = useCodeEditor();
90
+
91
+ return (
92
+ <button onClick={() => eval(getValue())}>Run</button>
93
+ );
94
+ }
95
+ ```
96
+
97
+ ### CodeEditorContextValue
98
+
99
+ | Property | Type | Description |
100
+ |----------|------|-------------|
101
+ | getValue | `() => string` | Current document text |
102
+ | getSelection | `() => string` | Currently selected text |
103
+ | setValue | `(value: string) => void` | Replace the entire document |
104
+ | replaceSelection | `(text: string) => void` | Replace the current selection |
105
+ | focus | `() => void` | Focus the editor |
106
+ | language | `string` | Current language name |
107
+ | setLanguage | `(lang: string) => void` | Change the active language |
108
+ | theme | `string` | Current theme name |
109
+ | readOnly | `boolean` | Whether editing is disabled |
110
+ | lineNumbers | `boolean` | Whether the gutter is shown |
111
+ | wordWrap | `boolean` | Whether word wrap is enabled |
112
+ | tabSize | `number` | Current tab size |
113
+ | toggleWordWrap | `() => void` | Toggle wordWrap on/off |
114
+ | toggleLineNumbers | `() => void` | Toggle lineNumbers on/off |
115
+ | copyToClipboard | `() => Promise<void>` | Copy the full document |
116
+ | cursorPosition | `{ line: number; col: number }` | Current cursor position |
117
+ | selectionLength | `number` | Length of current selection (0 if none) |
118
+ | placeholder | `string \| undefined` | Placeholder text |
119
+
120
+ ## Controlled Usage
121
+
122
+ ```tsx
123
+ const [code, setCode] = useState("");
124
+
125
+ <CodeEditor value={code} onChange={setCode} language="typescript">
126
+ <CodeEditor.Panel />
127
+ </CodeEditor>
128
+ ```
129
+
130
+ ## Read-Only Display
131
+
132
+ ```tsx
133
+ <CodeEditor defaultValue={someSnippet} language="php" readOnly>
134
+ <CodeEditor.Toolbar />
135
+ <CodeEditor.Panel />
136
+ </CodeEditor>
137
+ ```
138
+
139
+ ## Height Constraints
140
+
141
+ ```tsx
142
+ <CodeEditor defaultValue={longFile} minHeight={200} maxHeight={500}>
143
+ <CodeEditor.Toolbar />
144
+ <CodeEditor.Panel />
145
+ </CodeEditor>
146
+ ```
147
+
148
+ When content exceeds `maxHeight`, the panel becomes scrollable.
149
+
150
+ ## Custom Toolbar
151
+
152
+ ```tsx
153
+ function FormatButton() {
154
+ const { getValue, setValue } = useCodeEditor();
155
+
156
+ return (
157
+ <button onClick={() => setValue(getValue().trim())}>
158
+ Trim
159
+ </button>
160
+ );
161
+ }
162
+
163
+ <CodeEditor defaultValue=" hello ">
164
+ <CodeEditor.Toolbar>
165
+ <FormatButton />
166
+ <CodeEditor.Toolbar.Separator />
167
+ {/* Drop in your own widgets — no defaults are rendered when children are provided */}
168
+ </CodeEditor.Toolbar>
169
+ <CodeEditor.Panel />
170
+ </CodeEditor>
171
+ ```
172
+
173
+ ## IDE Layout (pair with TreeNav)
174
+
175
+ ```tsx
176
+ import { TreeNav } from "@particle-academy/react-fancy";
177
+ import { CodeEditor } from "@particle-academy/fancy-code";
178
+
179
+ <div className="flex" style={{ height: 600 }}>
180
+ <div className="w-56 shrink-0 overflow-y-auto border-r p-2">
181
+ <TreeNav nodes={fileTree} selectedId={activeFile} onSelect={openFile} />
182
+ </div>
183
+ <div className="flex-1">
184
+ <CodeEditor value={code} onChange={setCode} language={lang}>
185
+ <CodeEditor.Toolbar />
186
+ <CodeEditor.Panel />
187
+ <CodeEditor.StatusBar />
188
+ </CodeEditor>
189
+ </div>
190
+ </div>
191
+ ```
192
+
193
+ ## Data Attributes
194
+
195
+ | Attribute | Element |
196
+ |-----------|---------|
197
+ | `data-fancy-code-editor` | Root container |
198
+ | `data-fancy-code-toolbar` | Toolbar |
199
+ | `data-fancy-code-toolbar-separator` | Toolbar separator |
200
+ | `data-fancy-code-panel` | Editable surface |
201
+ | `data-fancy-code-statusbar` | Status bar |
202
+
203
+ Use these to target editor parts in your own CSS without reaching into internal classes.
204
+
205
+ ## See Also
206
+
207
+ - [Languages](./languages.md) — built-in languages and how to register custom ones
208
+ - [Themes](./themes.md) — built-in themes and custom theme registration
@@ -0,0 +1,65 @@
1
+ # Languages
2
+
3
+ The editor ships with six built-in language tokenizers. Registering new languages is a single function call.
4
+
5
+ ## Built-in Languages
6
+
7
+ | Language | Primary name | Aliases |
8
+ |----------|--------------|---------|
9
+ | JavaScript | `"javascript"` | `"js"`, `"jsx"` |
10
+ | TypeScript | `"typescript"` | `"ts"`, `"tsx"` |
11
+ | HTML | `"html"` | `"htm"` |
12
+ | PHP | `"php"` | — |
13
+ | Python | `"python"` | `"py"` |
14
+ | Go | `"go"` | `"golang"` |
15
+
16
+ All built-in languages are auto-registered on import of `@particle-academy/fancy-code`. Use them by passing the name to the `language` prop:
17
+
18
+ ```tsx
19
+ <CodeEditor language="typescript" defaultValue={tsCode}>
20
+ <CodeEditor.Panel />
21
+ </CodeEditor>
22
+ ```
23
+
24
+ ## Registering a Custom Language
25
+
26
+ ```tsx
27
+ import { registerLanguage } from "@particle-academy/fancy-code";
28
+
29
+ registerLanguage({
30
+ name: "sql",
31
+ aliases: ["mysql", "postgres"],
32
+ tokenize: (line) => {
33
+ // Return an array of { type, value } tokens for one line.
34
+ // `type` can be any theme token key (e.g., "keyword", "string", "number", "comment").
35
+ return [
36
+ { type: "keyword", value: "SELECT" },
37
+ { type: "plain", value: " * FROM users" },
38
+ ];
39
+ },
40
+ });
41
+ ```
42
+
43
+ Call `registerLanguage` once at app startup (typically in a `setup-languages.ts` file imported from `main.tsx`).
44
+
45
+ ## Changing Language at Runtime
46
+
47
+ The built-in toolbar includes a language selector. You can also drive it programmatically:
48
+
49
+ ```tsx
50
+ function LangPicker() {
51
+ const { language, setLanguage } = useCodeEditor();
52
+
53
+ return (
54
+ <select value={language} onChange={(e) => setLanguage(e.target.value)}>
55
+ <option value="javascript">JS</option>
56
+ <option value="typescript">TS</option>
57
+ <option value="python">Python</option>
58
+ </select>
59
+ );
60
+ }
61
+ ```
62
+
63
+ ## Tokenization Model
64
+
65
+ The engine tokenizes one line at a time — this keeps it fast on large files and avoids the complexity of a full AST-based highlighter. Token types map to theme keys: a theme defines colors per token type, and the engine renders accordingly. See [themes](./themes.md) for the token key list each theme must cover.
package/docs/themes.md ADDED
@@ -0,0 +1,88 @@
1
+ # Themes
2
+
3
+ Three built-in themes plus a simple registration API for custom ones.
4
+
5
+ ## Built-in Themes
6
+
7
+ | Name | Description |
8
+ |------|-------------|
9
+ | `"light"` | White background, blue/purple/green tokens |
10
+ | `"dark"` | Zinc-900 background, pastel tokens |
11
+ | `"auto"` | Follows `prefers-color-scheme` — switches between `"light"` and `"dark"` |
12
+
13
+ All three are auto-registered on import. Use by name:
14
+
15
+ ```tsx
16
+ <CodeEditor theme="dark" defaultValue={code}>
17
+ <CodeEditor.Panel />
18
+ </CodeEditor>
19
+ ```
20
+
21
+ ## Registering a Custom Theme
22
+
23
+ ```tsx
24
+ import { registerTheme } from "@particle-academy/fancy-code";
25
+
26
+ registerTheme({
27
+ name: "solarized",
28
+ background: "#fdf6e3",
29
+ foreground: "#586e75",
30
+ gutter: "#eee8d5",
31
+ activeLine: "rgba(147, 161, 161, 0.1)",
32
+ selection: "rgba(181, 137, 0, 0.2)",
33
+ tokens: {
34
+ keyword: "#859900",
35
+ string: "#2aa198",
36
+ number: "#d33682",
37
+ comment: "#93a1a1",
38
+ punctuation: "#586e75",
39
+ operator: "#cb4b16",
40
+ function: "#268bd2",
41
+ type: "#b58900",
42
+ // ...any token type your tokenizers emit
43
+ },
44
+ });
45
+ ```
46
+
47
+ Call `registerTheme` once at app startup. Then use by name:
48
+
49
+ ```tsx
50
+ <CodeEditor theme="solarized">
51
+ <CodeEditor.Panel />
52
+ </CodeEditor>
53
+ ```
54
+
55
+ ## Switching Theme at Runtime
56
+
57
+ ```tsx
58
+ function ThemeToggle() {
59
+ const { theme } = useCodeEditor();
60
+ const [dark, setDark] = useState(theme === "dark");
61
+ // drive the `theme` prop from your own state:
62
+ return (
63
+ <button onClick={() => setDark((v) => !v)}>
64
+ {dark ? "Light" : "Dark"}
65
+ </button>
66
+ );
67
+ }
68
+ ```
69
+
70
+ Since `theme` is controlled via the `CodeEditor` prop, re-render the parent with the new value to switch.
71
+
72
+ ## Token Types
73
+
74
+ Common token types emitted by built-in tokenizers — register colors for these when creating a theme:
75
+
76
+ - `keyword` — language keywords (`const`, `function`, `if`, ...)
77
+ - `string` — string literals
78
+ - `number` — numeric literals
79
+ - `comment` — single/multi-line comments
80
+ - `operator` — `+`, `===`, `=>`, etc.
81
+ - `punctuation` — braces, brackets, semicolons
82
+ - `function` — function names and calls
83
+ - `type` — type names (TS/Go)
84
+ - `tag` — HTML tags
85
+ - `attribute` — HTML attributes
86
+ - `plain` — anything uncategorized
87
+
88
+ Unregistered token types fall back to `foreground`.
package/package.json CHANGED
@@ -1,61 +1,68 @@
1
- {
2
- "name": "@particle-academy/fancy-code",
3
- "version": "0.4.2",
4
- "description": "Lightweight embedded code editor with syntax highlighting and extensible language/theme registries",
5
- "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "import": {
12
- "types": "./dist/index.d.ts",
13
- "default": "./dist/index.js"
14
- },
15
- "require": {
16
- "types": "./dist/index.d.cts",
17
- "default": "./dist/index.cjs"
18
- }
19
- },
20
- "./styles.css": "./dist/styles.css"
21
- },
22
- "files": [
23
- "dist",
24
- "README.md"
25
- ],
26
- "scripts": {
27
- "build": "tsup",
28
- "dev": "tsup --watch",
29
- "lint": "tsc --noEmit",
30
- "clean": "rm -rf dist",
31
- "prepublishOnly": "tsup"
32
- },
33
- "peerDependencies": {
34
- "react": "^18.0.0 || ^19.0.0",
35
- "react-dom": "^18.0.0 || ^19.0.0",
36
- "@particle-academy/react-fancy": "^1.5.0"
37
- },
38
- "dependencies": {},
39
- "devDependencies": {
40
- "@particle-academy/react-fancy": "workspace:*",
41
- "@types/react": "^19.0.0",
42
- "@types/react-dom": "^19.0.0",
43
- "react": "^19.0.0",
44
- "react-dom": "^19.0.0",
45
- "tsup": "^8.5.0",
46
- "typescript": "^5.8.0"
47
- },
48
- "publishConfig": {
49
- "access": "public"
50
- },
51
- "license": "MIT",
52
- "sideEffects": [
53
- "**/*.css",
54
- "src/languages/builtin.ts",
55
- "src/languages/index.ts",
56
- "src/engine/**/*.ts",
57
- "src/themes/light.ts",
58
- "src/themes/dark.ts",
59
- "src/themes/index.ts"
60
- ]
61
- }
1
+ {
2
+ "name": "@particle-academy/fancy-code",
3
+ "version": "0.4.3",
4
+ "description": "Lightweight embedded code editor with syntax highlighting and extensible language/theme registries",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Particle-Academy/fancy-code.git"
8
+ },
9
+ "homepage": "https://github.com/Particle-Academy/fancy-code#readme",
10
+ "bugs": "https://github.com/Particle-Academy/fancy-code/issues",
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "require": {
22
+ "types": "./dist/index.d.cts",
23
+ "default": "./dist/index.cjs"
24
+ }
25
+ },
26
+ "./styles.css": "./dist/styles.css"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "docs",
31
+ "README.md"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "dev": "tsup --watch",
36
+ "lint": "tsc --noEmit",
37
+ "clean": "rm -rf dist",
38
+ "prepublishOnly": "tsup"
39
+ },
40
+ "peerDependencies": {
41
+ "react": "^18.0.0 || ^19.0.0",
42
+ "react-dom": "^18.0.0 || ^19.0.0",
43
+ "@particle-academy/react-fancy": "^1.5.0"
44
+ },
45
+ "dependencies": {},
46
+ "devDependencies": {
47
+ "@particle-academy/react-fancy": "*",
48
+ "@types/react": "^19.0.0",
49
+ "@types/react-dom": "^19.0.0",
50
+ "react": "^19.0.0",
51
+ "react-dom": "^19.0.0",
52
+ "tsup": "^8.5.0",
53
+ "typescript": "^5.8.0"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "license": "MIT",
59
+ "sideEffects": [
60
+ "**/*.css",
61
+ "src/languages/builtin.ts",
62
+ "src/languages/index.ts",
63
+ "src/engine/**/*.ts",
64
+ "src/themes/light.ts",
65
+ "src/themes/dark.ts",
66
+ "src/themes/index.ts"
67
+ ]
68
+ }