@particle-academy/fancy-code 0.1.0 → 0.1.1
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/README.md +409 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# @particle-academy/fancy-code
|
|
2
|
+
|
|
3
|
+
Lightweight embedded code editor built on CodeMirror 6. Compound component API with custom toolbar buttons, syntax highlighting color schemes, and extensible language/theme registries.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# npm
|
|
9
|
+
npm install @particle-academy/fancy-code
|
|
10
|
+
|
|
11
|
+
# pnpm
|
|
12
|
+
pnpm add @particle-academy/fancy-code
|
|
13
|
+
|
|
14
|
+
# yarn
|
|
15
|
+
yarn add @particle-academy/fancy-code
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Peer dependencies:** `react >= 18`, `react-dom >= 18`, `@particle-academy/react-fancy >= 1.5`
|
|
19
|
+
|
|
20
|
+
**Bundled dependencies:** CodeMirror 6 (state, view, commands, language, search, autocomplete), language packages (JavaScript, TypeScript, HTML, PHP), `@lezer/highlight`
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Add the `@source` directive to your main CSS file so Tailwind v4 scans the component library:
|
|
25
|
+
|
|
26
|
+
```css
|
|
27
|
+
@import "tailwindcss";
|
|
28
|
+
@import "@particle-academy/fancy-code/styles.css";
|
|
29
|
+
@source "../node_modules/@particle-academy/fancy-code/dist/**/*.js";
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then import and use:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { CodeEditor } from "@particle-academy/fancy-code";
|
|
36
|
+
import "@particle-academy/fancy-code/styles.css";
|
|
37
|
+
|
|
38
|
+
function App() {
|
|
39
|
+
const [code, setCode] = useState('console.log("Hello");');
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<CodeEditor value={code} onChange={setCode} language="javascript">
|
|
43
|
+
<CodeEditor.Toolbar />
|
|
44
|
+
<CodeEditor.Panel />
|
|
45
|
+
<CodeEditor.StatusBar />
|
|
46
|
+
</CodeEditor>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Commands
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm --filter @particle-academy/fancy-code build # Build with tsup (ESM + CJS + DTS)
|
|
55
|
+
pnpm --filter @particle-academy/fancy-code dev # Watch mode
|
|
56
|
+
pnpm --filter @particle-academy/fancy-code lint # Type-check (tsc --noEmit)
|
|
57
|
+
pnpm --filter @particle-academy/fancy-code clean # Remove dist/
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Component API
|
|
61
|
+
|
|
62
|
+
### Compound Components
|
|
63
|
+
|
|
64
|
+
| Component | Description |
|
|
65
|
+
|-----------|-------------|
|
|
66
|
+
| `CodeEditor` | Root wrapper — context provider, state management |
|
|
67
|
+
| `CodeEditor.Toolbar` | Action bar with default buttons or custom children |
|
|
68
|
+
| `CodeEditor.Toolbar.Separator` | Vertical divider between toolbar groups |
|
|
69
|
+
| `CodeEditor.Panel` | CodeMirror editing surface |
|
|
70
|
+
| `CodeEditor.StatusBar` | Cursor position, language, tab size display |
|
|
71
|
+
|
|
72
|
+
### CodeEditor Props
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
interface CodeEditorProps {
|
|
76
|
+
children: ReactNode;
|
|
77
|
+
className?: string;
|
|
78
|
+
value?: string; // Controlled value
|
|
79
|
+
defaultValue?: string; // Uncontrolled initial value
|
|
80
|
+
onChange?: (value: string) => void;
|
|
81
|
+
language?: string; // Language name or alias (default: "javascript")
|
|
82
|
+
onLanguageChange?: (lang: string) => void;
|
|
83
|
+
theme?: string; // "light" | "dark" | "auto" | custom name (default: "auto")
|
|
84
|
+
readOnly?: boolean; // Default: false
|
|
85
|
+
lineNumbers?: boolean; // Default: true
|
|
86
|
+
wordWrap?: boolean; // Default: false
|
|
87
|
+
tabSize?: number; // Default: 2
|
|
88
|
+
placeholder?: string;
|
|
89
|
+
minHeight?: number; // Minimum height in px
|
|
90
|
+
maxHeight?: number; // Max height before scrolling
|
|
91
|
+
extensions?: Extension[]; // Additional CodeMirror extensions
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### useCodeEditor Hook
|
|
96
|
+
|
|
97
|
+
Access the editor context from custom toolbar buttons:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { useCodeEditor } from "@particle-academy/fancy-code";
|
|
101
|
+
import { Action } from "@particle-academy/react-fancy";
|
|
102
|
+
|
|
103
|
+
function RunButton() {
|
|
104
|
+
const { getValue } = useCodeEditor();
|
|
105
|
+
return <Action size="xs" onClick={() => eval(getValue())}>Run</Action>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
<CodeEditor value={code} onChange={setCode} language="javascript">
|
|
109
|
+
<CodeEditor.Toolbar>
|
|
110
|
+
<RunButton />
|
|
111
|
+
<CodeEditor.Toolbar.Separator />
|
|
112
|
+
</CodeEditor.Toolbar>
|
|
113
|
+
<CodeEditor.Panel />
|
|
114
|
+
</CodeEditor>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Context value:**
|
|
118
|
+
|
|
119
|
+
| Method / Property | Description |
|
|
120
|
+
|-------------------|-------------|
|
|
121
|
+
| `view` | The CodeMirror `EditorView` instance (null before mount) |
|
|
122
|
+
| `getValue()` | Get current document text |
|
|
123
|
+
| `getSelection()` | Get currently selected text |
|
|
124
|
+
| `setValue(text)` | Replace entire document |
|
|
125
|
+
| `replaceSelection(text)` | Replace current selection |
|
|
126
|
+
| `focus()` | Focus the editor |
|
|
127
|
+
| `language` | Current language name |
|
|
128
|
+
| `setLanguage(name)` | Switch active language |
|
|
129
|
+
| `theme` | Current theme name |
|
|
130
|
+
| `readOnly` | Whether the editor is read-only |
|
|
131
|
+
| `lineNumbers` | Whether line numbers are shown |
|
|
132
|
+
| `wordWrap` | Whether word wrap is enabled |
|
|
133
|
+
| `tabSize` | Current tab size |
|
|
134
|
+
| `toggleWordWrap()` | Toggle word wrap on/off |
|
|
135
|
+
| `toggleLineNumbers()` | Toggle line numbers on/off |
|
|
136
|
+
| `copyToClipboard()` | Copy entire document to clipboard |
|
|
137
|
+
| `cursorPosition` | `{ line, col }` — current cursor position |
|
|
138
|
+
| `selectionLength` | Length of current selection (0 if none) |
|
|
139
|
+
|
|
140
|
+
## Examples
|
|
141
|
+
|
|
142
|
+
### Basic Editor
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
<CodeEditor value={code} onChange={setCode} language="typescript">
|
|
146
|
+
<CodeEditor.Toolbar />
|
|
147
|
+
<CodeEditor.Panel />
|
|
148
|
+
<CodeEditor.StatusBar />
|
|
149
|
+
</CodeEditor>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Read-Only Code Display
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<CodeEditor value={snippet} language="php" readOnly>
|
|
156
|
+
<CodeEditor.Panel />
|
|
157
|
+
</CodeEditor>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Custom Toolbar
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
<CodeEditor value={code} onChange={setCode} language="javascript">
|
|
164
|
+
<CodeEditor.Toolbar>
|
|
165
|
+
<RunButton />
|
|
166
|
+
<CodeEditor.Toolbar.Separator />
|
|
167
|
+
<FormatButton />
|
|
168
|
+
<CopyButton />
|
|
169
|
+
</CodeEditor.Toolbar>
|
|
170
|
+
<CodeEditor.Panel />
|
|
171
|
+
<CodeEditor.StatusBar />
|
|
172
|
+
</CodeEditor>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Height Constraints
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<CodeEditor value={longCode} language="javascript" maxHeight={400} minHeight={200}>
|
|
179
|
+
<CodeEditor.Toolbar />
|
|
180
|
+
<CodeEditor.Panel />
|
|
181
|
+
<CodeEditor.StatusBar />
|
|
182
|
+
</CodeEditor>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Minimal (No Toolbar, No StatusBar)
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
<CodeEditor value={snippet} language="javascript" readOnly lineNumbers={false}>
|
|
189
|
+
<CodeEditor.Panel />
|
|
190
|
+
</CodeEditor>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Built-in Languages
|
|
194
|
+
|
|
195
|
+
| Language | Aliases |
|
|
196
|
+
|----------|---------|
|
|
197
|
+
| JavaScript | `js`, `javascript`, `jsx` |
|
|
198
|
+
| TypeScript | `ts`, `typescript`, `tsx` |
|
|
199
|
+
| HTML | `html`, `htm` |
|
|
200
|
+
| PHP | `php` |
|
|
201
|
+
|
|
202
|
+
## Custom Language Registration
|
|
203
|
+
|
|
204
|
+
Add languages beyond the four built-ins using `registerLanguage`:
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
import { registerLanguage } from "@particle-academy/fancy-code";
|
|
208
|
+
import { python } from "@codemirror/lang-python";
|
|
209
|
+
|
|
210
|
+
// Synchronous (package already installed)
|
|
211
|
+
registerLanguage({
|
|
212
|
+
name: "Python",
|
|
213
|
+
aliases: ["py", "python"],
|
|
214
|
+
support: () => python(),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Lazy-loaded
|
|
218
|
+
registerLanguage({
|
|
219
|
+
name: "Rust",
|
|
220
|
+
aliases: ["rs", "rust"],
|
|
221
|
+
support: async () => {
|
|
222
|
+
const { rust } = await import("@codemirror/lang-rust");
|
|
223
|
+
return rust();
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Then use it:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
<CodeEditor language="python" ... />
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Built-in Color Schemes
|
|
235
|
+
|
|
236
|
+
| Theme | Description |
|
|
237
|
+
|-------|-------------|
|
|
238
|
+
| `light` | White background, blue/purple/green token colors |
|
|
239
|
+
| `dark` | Zinc-900 background, pastel token colors |
|
|
240
|
+
| `auto` | Automatically selects light/dark based on `prefers-color-scheme` (default) |
|
|
241
|
+
|
|
242
|
+
## Custom Theme Registration
|
|
243
|
+
|
|
244
|
+
Create custom syntax highlighting color schemes using `registerTheme`:
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
import { registerTheme } from "@particle-academy/fancy-code";
|
|
248
|
+
import { EditorView } from "@codemirror/view";
|
|
249
|
+
import { HighlightStyle } from "@codemirror/language";
|
|
250
|
+
import { tags } from "@lezer/highlight";
|
|
251
|
+
|
|
252
|
+
registerTheme({
|
|
253
|
+
name: "monokai",
|
|
254
|
+
variant: "dark",
|
|
255
|
+
editorTheme: EditorView.theme({
|
|
256
|
+
"&": { backgroundColor: "#272822", color: "#f8f8f2" },
|
|
257
|
+
".cm-gutters": { backgroundColor: "#272822", color: "#75715e" },
|
|
258
|
+
".cm-activeLine": { backgroundColor: "#3e3d32" },
|
|
259
|
+
// ... other editor chrome styles
|
|
260
|
+
}, { dark: true }),
|
|
261
|
+
highlightStyle: HighlightStyle.define([
|
|
262
|
+
{ tag: tags.keyword, color: "#f92672" },
|
|
263
|
+
{ tag: tags.string, color: "#e6db74" },
|
|
264
|
+
{ tag: tags.function(tags.variableName), color: "#a6e22e" },
|
|
265
|
+
{ tag: tags.number, color: "#ae81ff" },
|
|
266
|
+
{ tag: tags.comment, color: "#75715e", fontStyle: "italic" },
|
|
267
|
+
// ... other token styles
|
|
268
|
+
]),
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Then use it:
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
<CodeEditor theme="monokai" ... />
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Theme definition:**
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
interface ThemeDefinition {
|
|
282
|
+
name: string; // Unique theme name
|
|
283
|
+
variant: "light" | "dark"; // For EditorView.darkTheme
|
|
284
|
+
editorTheme: Extension; // EditorView.theme({...}) — gutter, cursor, selection
|
|
285
|
+
highlightStyle: HighlightStyle; // HighlightStyle.define([...]) — token colors
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Built-in Features
|
|
290
|
+
|
|
291
|
+
The editor includes these CodeMirror extensions out of the box:
|
|
292
|
+
|
|
293
|
+
- Syntax highlighting with language-aware tokenization
|
|
294
|
+
- Line numbers with active line gutter highlight
|
|
295
|
+
- Active line highlighting
|
|
296
|
+
- Bracket matching and auto-close
|
|
297
|
+
- Code folding with fold gutter
|
|
298
|
+
- Search and replace (Ctrl+F / Cmd+F)
|
|
299
|
+
- Autocompletion
|
|
300
|
+
- History (undo/redo)
|
|
301
|
+
- Indent on input
|
|
302
|
+
- Selection highlighting
|
|
303
|
+
- Tab key indentation
|
|
304
|
+
|
|
305
|
+
All features are reconfigurable at runtime via compartments — switching languages, themes, line numbers, word wrap, tab size, and read-only mode happens without recreating the editor.
|
|
306
|
+
|
|
307
|
+
## Architecture
|
|
308
|
+
|
|
309
|
+
### Directory Layout
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
src/
|
|
313
|
+
├── components/
|
|
314
|
+
│ └── CodeEditor/
|
|
315
|
+
│ ├── CodeEditor.tsx # Root compound component + context
|
|
316
|
+
│ ├── CodeEditor.types.ts # All prop/context types
|
|
317
|
+
│ ├── CodeEditor.context.ts # React context + useCodeEditor hook
|
|
318
|
+
│ ├── CodeEditorPanel.tsx # CodeMirror mount point
|
|
319
|
+
│ ├── CodeEditorToolbar.tsx # Default toolbar + custom children
|
|
320
|
+
│ ├── CodeEditorToolbarSeparator.tsx
|
|
321
|
+
│ ├── CodeEditorStatusBar.tsx # Cursor/language/tab display
|
|
322
|
+
│ └── index.ts
|
|
323
|
+
├── hooks/
|
|
324
|
+
│ ├── use-codemirror.ts # Core CM lifecycle + compartments
|
|
325
|
+
│ └── use-dark-mode.ts # Reactive prefers-color-scheme
|
|
326
|
+
├── languages/
|
|
327
|
+
│ ├── registry.ts # Global language registry
|
|
328
|
+
│ ├── builtin.ts # JS, TS, HTML, PHP registrations
|
|
329
|
+
│ ├── types.ts # LanguageDefinition type
|
|
330
|
+
│ └── index.ts
|
|
331
|
+
├── themes/
|
|
332
|
+
│ ├── registry.ts # Global theme registry
|
|
333
|
+
│ ├── light.ts # Built-in light color scheme
|
|
334
|
+
│ ├── dark.ts # Built-in dark color scheme
|
|
335
|
+
│ ├── types.ts # ThemeDefinition type
|
|
336
|
+
│ └── index.ts
|
|
337
|
+
├── styles.css # Base CodeMirror structural styles
|
|
338
|
+
└── index.ts # Public API
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Data Attributes
|
|
342
|
+
|
|
343
|
+
| Attribute | Element |
|
|
344
|
+
|-----------|---------|
|
|
345
|
+
| `data-fancy-code-editor` | Root wrapper |
|
|
346
|
+
| `data-fancy-code-toolbar` | Toolbar bar |
|
|
347
|
+
| `data-fancy-code-toolbar-separator` | Toolbar separator |
|
|
348
|
+
| `data-fancy-code-panel` | CodeMirror mount point |
|
|
349
|
+
| `data-fancy-code-statusbar` | Status bar |
|
|
350
|
+
|
|
351
|
+
### Public Exports
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
// Components
|
|
355
|
+
export { CodeEditor, useCodeEditor };
|
|
356
|
+
export type { CodeEditorProps, CodeEditorContextValue, CodeEditorToolbarProps, CodeEditorPanelProps, CodeEditorStatusBarProps };
|
|
357
|
+
|
|
358
|
+
// Language registration
|
|
359
|
+
export { registerLanguage, getLanguage, getRegisteredLanguages };
|
|
360
|
+
export type { LanguageDefinition };
|
|
361
|
+
|
|
362
|
+
// Theme registration
|
|
363
|
+
export { registerTheme, getTheme, getRegisteredThemes };
|
|
364
|
+
export type { ThemeDefinition };
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Demo Pages
|
|
368
|
+
|
|
369
|
+
The demo page lives in the monorepo at `resources/js/react-demos/pages/CodeEditorDemo.tsx` and is accessible at `/react-demos/code-editor`. It demonstrates:
|
|
370
|
+
|
|
371
|
+
1. Basic editor with default toolbar
|
|
372
|
+
2. Read-only code display
|
|
373
|
+
3. Custom toolbar buttons via `useCodeEditor()`
|
|
374
|
+
4. Language switching (JS, TS, HTML, PHP)
|
|
375
|
+
5. Light vs dark theme showcase
|
|
376
|
+
6. Word wrap and configuration
|
|
377
|
+
7. Minimal mode (no toolbar, no status bar)
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Agent Guidelines
|
|
382
|
+
|
|
383
|
+
Guidelines for AI agents (Claude Code, Copilot, etc.) working on this package.
|
|
384
|
+
|
|
385
|
+
### Component Pattern
|
|
386
|
+
|
|
387
|
+
- Uses the same compound component pattern as `@particle-academy/react-fancy`'s Editor: `Object.assign(Root, { Toolbar, Panel, StatusBar })`.
|
|
388
|
+
- Context is provided via `CodeEditorContext` and consumed with `useCodeEditor()`.
|
|
389
|
+
- All root elements have `data-fancy-code-*` attributes for external CSS targeting.
|
|
390
|
+
|
|
391
|
+
### CodeMirror Integration
|
|
392
|
+
|
|
393
|
+
- The `useCodemirror` hook manages the full `EditorView` lifecycle.
|
|
394
|
+
- Uses `Compartment` from `@codemirror/state` for hot-swapping language, theme, line numbers, word wrap, tab size, read-only, placeholder, and height constraints.
|
|
395
|
+
- External value changes are synced via `isExternalUpdate` ref to prevent onChange loops.
|
|
396
|
+
- SSR-safe: EditorView is only created inside `useEffect`.
|
|
397
|
+
|
|
398
|
+
### Extension System
|
|
399
|
+
|
|
400
|
+
- Languages and themes use the same global registry pattern as react-fancy's `registerExtension`.
|
|
401
|
+
- Built-in languages/themes are registered via side-effect imports in `languages/builtin.ts` and `themes/light.ts`/`themes/dark.ts`.
|
|
402
|
+
- The `sideEffects` field in `package.json` lists these files to prevent tree-shaking.
|
|
403
|
+
|
|
404
|
+
### Build
|
|
405
|
+
|
|
406
|
+
- tsup handles the build — ESM, CJS, and `.d.ts` generation.
|
|
407
|
+
- `react`, `react-dom`, and `@particle-academy/react-fancy` are external dependencies.
|
|
408
|
+
- All CodeMirror packages are **bundled** (not external) so consumers don't need to install them.
|
|
409
|
+
- After any change, verify with `npm run build` from the monorepo root.
|