@particle-academy/fancy-code 0.4.1 → 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.
package/README.md CHANGED
@@ -1,25 +1,20 @@
1
1
  # @particle-academy/fancy-code
2
2
 
3
- Lightweight embedded code editor with syntax highlighting, custom toolbar buttons, and extensible language/theme registries. Part of the `@particle-academy` component ecosystem.
3
+ Lightweight embedded code editor with syntax highlighting, custom toolbar buttons, and extensible language and theme registries. Part of the `@particle-academy` component ecosystem.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- # npm
9
8
  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
9
+ # or: pnpm add @particle-academy/fancy-code
10
+ # or: yarn add @particle-academy/fancy-code
16
11
  ```
17
12
 
18
13
  **Peer dependencies:** `react >= 18`, `react-dom >= 18`, `@particle-academy/react-fancy >= 1.5`
19
14
 
20
- ## Usage
15
+ ## Setup
21
16
 
22
- Add the `@source` directive to your main CSS file so Tailwind v4 scans the component library:
17
+ Add the package styles to your main CSS (Tailwind v4 scans via `@source`):
23
18
 
24
19
  ```css
25
20
  @import "tailwindcss";
@@ -27,22 +22,16 @@ Add the `@source` directive to your main CSS file so Tailwind v4 scans the compo
27
22
  @source "../node_modules/@particle-academy/fancy-code/dist/**/*.js";
28
23
  ```
29
24
 
30
- Then import and use:
25
+ ## Quick Start
31
26
 
32
27
  ```tsx
33
28
  import { CodeEditor } from "@particle-academy/fancy-code";
34
- import "@particle-academy/fancy-code/styles.css";
35
29
 
36
30
  function App() {
37
31
  const [code, setCode] = useState('console.log("Hello");');
38
32
 
39
33
  return (
40
- <CodeEditor
41
- value={code}
42
- onChange={setCode}
43
- language="javascript"
44
- className="rounded-xl border border-zinc-200 dark:border-zinc-700"
45
- >
34
+ <CodeEditor value={code} onChange={setCode} language="javascript">
46
35
  <CodeEditor.Toolbar />
47
36
  <CodeEditor.Panel />
48
37
  <CodeEditor.StatusBar />
@@ -51,7 +40,15 @@ function App() {
51
40
  }
52
41
  ```
53
42
 
54
- The component has no default border or rounding — add your own via `className` for standalone use, or embed directly into IDE layouts without visual conflicts.
43
+ The component ships with no default border or rounding — add your own via `className` for standalone use, or embed directly into IDE layouts without visual conflicts.
44
+
45
+ ## Documentation
46
+
47
+ | Topic | Doc |
48
+ |-------|-----|
49
+ | Full component API (props, sub-components, `useCodeEditor` hook) | [docs/CodeEditor.md](./docs/CodeEditor.md) |
50
+ | Built-in languages + registering custom ones | [docs/languages.md](./docs/languages.md) |
51
+ | Built-in themes + custom theme registration | [docs/themes.md](./docs/themes.md) |
55
52
 
56
53
  ## Commands
57
54
 
@@ -62,364 +59,14 @@ pnpm --filter @particle-academy/fancy-code lint # Type-check (tsc --noEmit)
62
59
  pnpm --filter @particle-academy/fancy-code clean # Remove dist/
63
60
  ```
64
61
 
65
- ## Component API
66
-
67
- ### Compound Components
68
-
69
- | Component | Description |
70
- |-----------|-------------|
71
- | `CodeEditor` | Root wrapper — context provider, state management |
72
- | `CodeEditor.Toolbar` | Action bar with default buttons or custom children |
73
- | `CodeEditor.Toolbar.Separator` | Vertical divider between toolbar groups |
74
- | `CodeEditor.Panel` | Code editing surface |
75
- | `CodeEditor.StatusBar` | Cursor position, language, tab size display |
76
-
77
- ### CodeEditor Props
78
-
79
- ```ts
80
- interface CodeEditorProps {
81
- children: ReactNode;
82
- className?: string;
83
- value?: string; // Controlled value
84
- defaultValue?: string; // Uncontrolled initial value
85
- onChange?: (value: string) => void;
86
- language?: string; // Language name or alias (default: "javascript")
87
- onLanguageChange?: (lang: string) => void;
88
- theme?: string; // "light" | "dark" | "auto" | custom name (default: "auto")
89
- readOnly?: boolean; // Default: false
90
- lineNumbers?: boolean; // Default: true
91
- wordWrap?: boolean; // Default: false
92
- tabSize?: number; // Default: 2
93
- placeholder?: string;
94
- minHeight?: number; // Minimum height in px
95
- maxHeight?: number; // Max height before scrolling
96
- }
97
- ```
98
-
99
- ### useCodeEditor Hook
100
-
101
- Access the editor context from custom toolbar buttons:
102
-
103
- ```tsx
104
- import { useCodeEditor } from "@particle-academy/fancy-code";
105
- import { Action } from "@particle-academy/react-fancy";
106
-
107
- function RunButton() {
108
- const { getValue } = useCodeEditor();
109
- return <Action size="xs" onClick={() => run(getValue())}>Run</Action>;
110
- }
111
-
112
- <CodeEditor value={code} onChange={setCode} language="javascript">
113
- <CodeEditor.Toolbar>
114
- <RunButton />
115
- <CodeEditor.Toolbar.Separator />
116
- </CodeEditor.Toolbar>
117
- <CodeEditor.Panel />
118
- </CodeEditor>
119
- ```
120
-
121
- **Context value:**
122
-
123
- | Method / Property | Description |
124
- |-------------------|-------------|
125
- | `getValue()` | Get current document text |
126
- | `getSelection()` | Get currently selected text |
127
- | `setValue(text)` | Replace entire document |
128
- | `replaceSelection(text)` | Replace current selection |
129
- | `focus()` | Focus the editor |
130
- | `language` | Current language name |
131
- | `setLanguage(name)` | Switch active language |
132
- | `theme` | Current theme name |
133
- | `readOnly` | Whether the editor is read-only |
134
- | `lineNumbers` | Whether line numbers are shown |
135
- | `wordWrap` | Whether word wrap is enabled |
136
- | `tabSize` | Current tab size |
137
- | `toggleWordWrap()` | Toggle word wrap on/off |
138
- | `toggleLineNumbers()` | Toggle line numbers on/off |
139
- | `copyToClipboard()` | Copy entire document to clipboard |
140
- | `cursorPosition` | `{ line, col }` — current cursor position |
141
- | `selectionLength` | Length of current selection (0 if none) |
142
-
143
- ## Examples
144
-
145
- ### Basic Editor
146
-
147
- ```tsx
148
- <CodeEditor value={code} onChange={setCode} language="typescript">
149
- <CodeEditor.Toolbar />
150
- <CodeEditor.Panel />
151
- <CodeEditor.StatusBar />
152
- </CodeEditor>
153
- ```
154
-
155
- ### Read-Only Code Display
156
-
157
- ```tsx
158
- <CodeEditor value={snippet} language="php" readOnly>
159
- <CodeEditor.Panel />
160
- </CodeEditor>
161
- ```
162
-
163
- ### Custom Toolbar
164
-
165
- ```tsx
166
- <CodeEditor value={code} onChange={setCode} language="javascript">
167
- <CodeEditor.Toolbar>
168
- <RunButton />
169
- <CodeEditor.Toolbar.Separator />
170
- <FormatButton />
171
- <CopyButton />
172
- </CodeEditor.Toolbar>
173
- <CodeEditor.Panel />
174
- <CodeEditor.StatusBar />
175
- </CodeEditor>
176
- ```
177
-
178
- ### Height Constraints
179
-
180
- ```tsx
181
- <CodeEditor value={longCode} language="javascript" maxHeight={400} minHeight={200}>
182
- <CodeEditor.Toolbar />
183
- <CodeEditor.Panel />
184
- <CodeEditor.StatusBar />
185
- </CodeEditor>
186
- ```
187
-
188
- ### Minimal (No Toolbar, No StatusBar)
189
-
190
- ```tsx
191
- <CodeEditor value={snippet} language="javascript" readOnly lineNumbers={false}>
192
- <CodeEditor.Panel />
193
- </CodeEditor>
194
- ```
195
-
196
- ## Built-in Languages
197
-
198
- | Language | Aliases |
199
- |----------|---------|
200
- | JavaScript | `js`, `javascript`, `jsx` |
201
- | TypeScript | `ts`, `typescript`, `tsx` |
202
- | HTML | `html`, `htm` |
203
- | PHP | `php` |
204
- | Python | `py`, `python` |
205
- | Go | `go`, `golang` |
206
-
207
- ## Custom Language Registration
208
-
209
- Add languages beyond the built-ins using `registerLanguage`:
210
-
211
- ```tsx
212
- import { registerLanguage } from "@particle-academy/fancy-code";
213
-
214
- registerLanguage({
215
- name: "Python",
216
- aliases: ["py", "python"],
217
- support: () => {
218
- // Return a LanguageSupport instance
219
- const { python } = require("@codemirror/lang-python");
220
- return python();
221
- },
222
- });
223
-
224
- // Lazy-loaded
225
- registerLanguage({
226
- name: "Rust",
227
- aliases: ["rs", "rust"],
228
- support: async () => {
229
- const { rust } = await import("@codemirror/lang-rust");
230
- return rust();
231
- },
232
- });
233
- ```
234
-
235
- Then use it:
236
-
237
- ```tsx
238
- <CodeEditor language="python" ... />
239
- ```
240
-
241
- ## Built-in Color Schemes
242
-
243
- | Theme | Description |
244
- |-------|-------------|
245
- | `light` | White background, blue/purple/green token colors |
246
- | `dark` | Zinc-900 background, pastel token colors |
247
- | `auto` | Automatically selects light/dark based on `prefers-color-scheme` (default) |
248
-
249
- ## Custom Theme Registration
250
-
251
- Create custom syntax highlighting color schemes using `registerTheme`:
252
-
253
- ```tsx
254
- import { registerTheme } from "@particle-academy/fancy-code";
255
-
256
- registerTheme({
257
- name: "monokai",
258
- variant: "dark",
259
- editorTheme: /* editor chrome styles (background, gutter, cursor, selection) */,
260
- highlightStyle: /* token color definitions (keywords, strings, comments, etc.) */,
261
- });
262
- ```
263
-
264
- Then use it:
265
-
266
- ```tsx
267
- <CodeEditor theme="monokai" ... />
268
- ```
269
-
270
- **Theme definition:**
271
-
272
- ```ts
273
- interface ThemeDefinition {
274
- name: string; // Unique theme name
275
- variant: "light" | "dark"; // Light or dark base
276
- editorTheme: Extension; // Editor chrome styles (gutter, cursor, selection)
277
- highlightStyle: HighlightStyle; // Syntax token color definitions
278
- }
279
- ```
280
-
281
- ## Built-in Features
282
-
283
- - Syntax highlighting with language-aware tokenization
284
- - Line numbers with active line gutter highlight
285
- - Active line highlighting
286
- - Bracket matching and auto-close
287
- - Code folding with fold gutter
288
- - Search and replace (Ctrl+F / Cmd+F)
289
- - Autocompletion
290
- - History (undo/redo)
291
- - Indent on input
292
- - Selection highlighting
293
- - Tab key indentation
294
-
295
- All features are reconfigurable at runtime — switching languages, themes, line numbers, word wrap, tab size, and read-only mode happens without recreating the editor.
296
-
297
- ## Customization
298
-
299
- All components render `data-fancy-code-*` attributes on their root elements for external CSS targeting:
300
-
301
- | Attribute | Element |
302
- |-----------|---------|
303
- | `data-fancy-code-editor` | Root wrapper |
304
- | `data-fancy-code-toolbar` | Toolbar bar |
305
- | `data-fancy-code-toolbar-separator` | Toolbar separator |
306
- | `data-fancy-code-panel` | Editing surface |
307
- | `data-fancy-code-statusbar` | Status bar |
308
-
309
- ## Architecture
310
-
311
- ### Directory Layout
312
-
313
- ```
314
- src/
315
- ├── components/
316
- │ └── CodeEditor/
317
- │ ├── CodeEditor.tsx # Root compound component + context
318
- │ ├── CodeEditor.types.ts # All prop/context types
319
- │ ├── CodeEditor.context.ts # React context + useCodeEditor hook
320
- │ ├── CodeEditorPanel.tsx # Editing surface
321
- │ ├── CodeEditorToolbar.tsx # Default toolbar + custom children
322
- │ ├── CodeEditorToolbarSeparator.tsx
323
- │ ├── CodeEditorStatusBar.tsx # Cursor/language/tab display
324
- │ └── index.ts
325
- ├── hooks/
326
- │ ├── use-codemirror.ts # Core editor lifecycle
327
- │ └── use-dark-mode.ts # Reactive prefers-color-scheme
328
- ├── languages/
329
- │ ├── registry.ts # Global language registry
330
- │ ├── builtin.ts # JS, TS, HTML, PHP registrations
331
- │ ├── types.ts # LanguageDefinition type
332
- │ └── index.ts
333
- ├── themes/
334
- │ ├── registry.ts # Global theme registry
335
- │ ├── light.ts # Built-in light color scheme
336
- │ ├── dark.ts # Built-in dark color scheme
337
- │ ├── types.ts # ThemeDefinition type
338
- │ └── index.ts
339
- ├── styles.css # Base structural styles
340
- └── index.ts # Public API
341
- ```
342
-
343
- ### Public Exports
344
-
345
- ```ts
346
- // Components
347
- export { CodeEditor, useCodeEditor };
348
- export type { CodeEditorProps, CodeEditorContextValue, CodeEditorToolbarProps, CodeEditorPanelProps, CodeEditorStatusBarProps };
349
-
350
- // Language registration
351
- export { registerLanguage, getLanguage, getRegisteredLanguages };
352
- export type { LanguageDefinition };
353
-
354
- // Theme registration
355
- export { registerTheme, getTheme, getRegisteredThemes };
356
- export type { ThemeDefinition };
357
- ```
358
-
359
- ## Related: TreeNav from react-fancy
360
-
361
- For IDE-style layouts, pair `CodeEditor` with the `TreeNav` component from `@particle-academy/react-fancy`. TreeNav provides hierarchical file/folder navigation with expand/collapse, selection, and extension-based file icons.
362
-
363
- ```tsx
364
- import { TreeNav } from "@particle-academy/react-fancy";
365
- import { CodeEditor } from "@particle-academy/fancy-code";
366
-
367
- <div className="flex" style={{ height: 600 }}>
368
- <TreeNav
369
- nodes={fileTree}
370
- selectedId={activeFile}
371
- onSelect={(id, node) => openFile(id, node)}
372
- />
373
- <CodeEditor value={code} onChange={setCode} language={lang}>
374
- <CodeEditor.Toolbar />
375
- <CodeEditor.Panel />
376
- <CodeEditor.StatusBar />
377
- </CodeEditor>
378
- </div>
379
- ```
380
-
381
- See the full IDE demo at `/react-demos/ide` in the monorepo.
382
-
383
- ## Demo Pages
384
-
385
- 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:
386
-
387
- 1. Basic editor with default toolbar
388
- 2. Read-only code display
389
- 3. Custom toolbar buttons via `useCodeEditor()`
390
- 4. Language switching (JS, TS, HTML, PHP)
391
- 5. Light vs dark theme showcase
392
- 6. Word wrap and configuration
393
- 7. Minimal mode (no toolbar, no status bar)
394
-
395
- ---
396
-
397
- ## Agent Guidelines
398
-
399
- Guidelines for AI agents (Claude Code, Copilot, etc.) working on this package.
400
-
401
- ### Component Pattern
402
-
403
- - Uses the same compound component pattern as `@particle-academy/react-fancy`'s Editor: `Object.assign(Root, { Toolbar, Panel, StatusBar })`.
404
- - Context is provided via `CodeEditorContext` and consumed with `useCodeEditor()`.
405
- - All root elements have `data-fancy-code-*` attributes for external CSS targeting.
406
-
407
- ### Editor Engine
408
-
409
- - The `useCodemirror` hook manages the editor lifecycle.
410
- - Uses compartments for hot-swapping language, theme, line numbers, word wrap, tab size, read-only, placeholder, and height constraints.
411
- - External value changes are synced via `isExternalUpdate` ref to prevent onChange loops.
412
- - SSR-safe: the editor is only created inside `useEffect`.
413
-
414
- ### Extension System
62
+ ## At a Glance
415
63
 
416
- - Languages and themes use the same global registry pattern as react-fancy's `registerExtension`.
417
- - Built-in languages/themes are registered via side-effect imports in `languages/builtin.ts` and `themes/light.ts`/`themes/dark.ts`.
418
- - The `sideEffects` field in `package.json` lists these files to prevent tree-shaking.
64
+ - **5 public exports** — `CodeEditor` (+ `Toolbar`, `Toolbar.Separator`, `Panel`, `StatusBar`) and the `useCodeEditor` hook
65
+ - **6 built-in languages** — JavaScript, TypeScript, HTML, PHP, Python, Go
66
+ - **3 built-in themes** — `light`, `dark`, `auto` (follows `prefers-color-scheme`)
67
+ - **Zero third-party dependencies** — custom engine, no Monaco / CodeMirror / Shiki
68
+ - **IDE-ready** — pairs with `TreeNav` from `@particle-academy/react-fancy` for full IDE layouts; see [docs/CodeEditor.md](./docs/CodeEditor.md) for an example.
419
69
 
420
- ### Build
70
+ ## License
421
71
 
422
- - tsup handles the build — ESM, CJS, and `.d.ts` generation.
423
- - `react`, `react-dom`, and `@particle-academy/react-fancy` are external dependencies.
424
- - All editor engine packages are **bundled** (not external) so consumers don't need to install them.
425
- - After any change, verify with `npm run build` from the monorepo root.
72
+ MIT
package/dist/index.cjs CHANGED
@@ -85,7 +85,7 @@ function CodeEditorPanel({ className }) {
85
85
  children: lineNumberElements
86
86
  }
87
87
  ),
88
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative min-w-0 flex-1", children: [
88
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: reactFancy.cn("relative flex-1", wordWrap && "min-w-0"), children: [
89
89
  /* @__PURE__ */ jsxRuntime.jsx(
90
90
  "pre",
91
91
  {
@@ -110,7 +110,10 @@ function CodeEditorPanel({ className }) {
110
110
  "textarea",
111
111
  {
112
112
  ref: textareaRef,
113
- className: "relative m-0 block w-full resize-none border-none bg-transparent p-2.5 text-[13px] leading-[1.5] text-transparent outline-none",
113
+ className: reactFancy.cn(
114
+ "relative m-0 block resize-none border-none bg-transparent p-2.5 text-[13px] leading-[1.5] text-transparent outline-none",
115
+ wordWrap ? "w-full" : "min-w-full"
116
+ ),
114
117
  style: {
115
118
  caretColor: themeColors.cursorColor,
116
119
  minHeight: _minHeight ? _minHeight - 40 : 80,
@@ -1295,6 +1298,7 @@ function useEditorEngine({
1295
1298
  language,
1296
1299
  theme,
1297
1300
  readOnly,
1301
+ wordWrap,
1298
1302
  tabSize,
1299
1303
  onCursorChange
1300
1304
  }) {
@@ -1333,7 +1337,13 @@ function useEditorEngine({
1333
1337
  if (!ta) return;
1334
1338
  ta.style.height = "auto";
1335
1339
  ta.style.height = ta.scrollHeight + "px";
1336
- }, []);
1340
+ if (!wordWrap) {
1341
+ ta.style.width = "0";
1342
+ ta.style.width = ta.scrollWidth + "px";
1343
+ } else {
1344
+ ta.style.width = "";
1345
+ }
1346
+ }, [wordWrap]);
1337
1347
  react.useEffect(() => {
1338
1348
  const ta = textareaRef.current;
1339
1349
  if (!ta) return;
@@ -1521,6 +1531,7 @@ function CodeEditorRoot({
1521
1531
  language: currentLanguage,
1522
1532
  theme: resolvedTheme,
1523
1533
  readOnly,
1534
+ wordWrap: isWordWrap,
1524
1535
  tabSize: tabSizeProp,
1525
1536
  onCursorChange: ({ line, col, selectionLength: sel }) => {
1526
1537
  setCursorPosition({ line, col });