@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.
Files changed (2) hide show
  1. package/README.md +409 -0
  2. 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/fancy-code",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Lightweight embedded code editor component built on CodeMirror 6",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",