@useclickly/react 0.2.0 → 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/README.md CHANGED
@@ -1,15 +1,19 @@
1
1
  # @useclickly/react
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@useclickly/react.svg?color=0ea5e9)](https://www.npmjs.com/package/@useclickly/react)
4
+ [![license](https://img.shields.io/npm/l/@useclickly/react.svg?color=10b981)](../../LICENSE)
5
+
3
6
  > Click-to-annotate dev toolbar for React. Generates prompts your AI coding agent can actually act on.
4
7
 
5
- Drop `<Clickly />` into your React app (dev only). A small floating action button appears in the bottom-right. Open it, click any element, write a comment — copy a perfectly-formatted markdown prompt into Claude Code / Cursor / Codex.
8
+ Drop `<Clickly />` into your React app (dev only). A small floating action button appears in the bottom-right corner. Open it, click any element, write a comment — copy a perfectly-formatted markdown prompt into **Claude Code**, **Cursor**, **Codex**, or any MCP-aware tool.
6
9
 
7
10
  ## Install
8
11
 
9
12
  ```bash
10
13
  npm install -D @useclickly/react
11
- # or: pnpm add -D @useclickly/react
12
- # or: yarn add -D @useclickly/react
14
+ # or:
15
+ pnpm add -D @useclickly/react
16
+ yarn add -D @useclickly/react
13
17
  ```
14
18
 
15
19
  ## Use
@@ -27,20 +31,30 @@ export default function App() {
27
31
  }
28
32
  ```
29
33
 
30
- That's it.
34
+ That's it. The component returns `null` on the server (safe under SSR / Next App Router) and ships with `"use client"` already baked into the bundle.
35
+
36
+ ## What it does
37
+
38
+ | Mode | How |
39
+ |---|---|
40
+ | **Single** | Click any element → popup with comment box |
41
+ | **Multi-select** | Press `2` or shift-click to pin elements, hit **Annotate (N)** when done |
42
+ | **Area drag** | Press `3`, drag a rectangle, every intersecting element gets pinned |
43
+ | **Persistent pins** | Annotated elements stay numbered on the page across page interactions |
44
+ | **Auto-copy** | Submitting an annotation copies markdown to clipboard (toggleable) |
45
+ | **Settings** | Output detail tier, React component info, marker colour — all in the gear menu |
46
+
47
+ ## What gets captured
31
48
 
32
- ## Features
49
+ For every selected element, Clickly automatically collects:
33
50
 
34
- - **Click any element** annotation popup with comment box
35
- - **Multi-select** shift-click to pin elements, hit `Annotate (N)` when done
36
- - **Area drag** press `3`, drag a rectangle, every intersecting element gets pinned
37
- - **Persistent numbered pins** on every annotated element
38
- - **CSS selectors + DOM paths + computed styles** captured automatically
39
- - **Auto-copy markdown** to clipboard on every Add
40
- - **AFS 1.1 schema** wire-compatible with Agentation, parseable by any MCP-aware agent
41
- - **Crosshair cursor + text-select disabled** while inspecting
42
- - **Shadow DOM** for zero CSS leakage in either direction
43
- - **`"use client"` baked in** — Next.js App Router just works
51
+ - Short + full CSS selector (`section.hero > button.cta`)
52
+ - Computed styles (curated by detail tier)
53
+ - Bounding box + position (handles `fixed` / `sticky`)
54
+ - ARIA / `role` / `tabindex` / `title`
55
+ - Visible nearby text (truncated)
56
+ - **React component tree** (`App > Dashboard > Button`) via Fiber walk
57
+ - **Source file + line** (`src/Button.tsx:42`) on Vite/Next instant grep target
44
58
 
45
59
  ## Keyboard shortcuts
46
60
 
@@ -53,17 +67,51 @@ That's it.
53
67
  | `C` | Copy all annotations as markdown |
54
68
  | `X` | Clear all annotations |
55
69
 
70
+ ## Props
71
+
72
+ All optional; `<Clickly />` works with zero config.
73
+
74
+ ```ts
75
+ interface ClicklyProps {
76
+ className?: string;
77
+ endpoint?: string; // MCP server URL — see @useclickly/mcp-server
78
+ sessionId?: string;
79
+ onAnnotationAdd?: (id: string) => void;
80
+ onAnnotationDelete?: (id: string) => void;
81
+ onAnnotationsClear?: () => void;
82
+ onCopy?: (markdown: string) => void;
83
+ onSessionCreated?: (sessionId: string) => void;
84
+ }
85
+ ```
86
+
87
+ ## Output
88
+
89
+ ```md
90
+ ## Annotation #1
91
+ **Element:** `button.cta.primary`
92
+ **Path:** `body > main > section.hero > button.cta.primary`
93
+ **Position:** 120px, 480px (200×48px)
94
+ **Source:** `src/components/Hero.tsx:42`
95
+ **React:** App > LandingPage > HeroSection > CTAButton
96
+ **Feedback:** Button is cut off on mobile viewport
97
+ ```
98
+
99
+ Configurable via the settings popover: `compact` (one-liner) → `standard` → `detailed` → `forensic` (full computed styles).
100
+
101
+ The shape is **[AFS 1.1](https://agentation.dev/schema/annotation.v1.1.json)**-compatible — every MCP-aware agent can parse it.
102
+
56
103
  ## Requirements
57
104
 
58
105
  - React 18+ or 19+
59
106
  - Browser environment (no SSR; component returns `null` server-side)
60
107
  - Desktop only — touch / mobile is not optimised
108
+ - **Not for React Native** — see the main [README](https://github.com/useclickly/clickly#react-native)
109
+
110
+ ## Advanced
61
111
 
62
- ## Links
112
+ For framework-agnostic engine access — building your own UI on top of Clickly's selection + metadata — see [`@useclickly/core`](https://www.npmjs.com/package/@useclickly/core).
63
113
 
64
- - Main repo and docs: [github.com/clickly/clickly](https://github.com/clickly/clickly)
65
- - AFS 1.1 schema: see `@useclickly/core`
66
- - MCP server (for agent integration): see `@useclickly/mcp-server`
114
+ For agent integration over MCP — your agent reading/responding to annotations directly — see [`@useclickly/mcp-server`](https://www.npmjs.com/package/@useclickly/mcp-server).
67
115
 
68
116
  ## License
69
117
 
package/dist/index.cjs CHANGED
@@ -148,11 +148,18 @@ function annotationsToMarkdown(annotations, detail = "standard") {
148
148
  function formatOne(a, index, detail) {
149
149
  const lines = [];
150
150
  lines.push(`## Annotation #${index}`);
151
- lines.push(`**Element:** \`${a.element}${a.cssClasses ? "." + a.cssClasses.split(" ").join(".") : ""}\``);
151
+ lines.push(
152
+ `**Element:** \`${a.element}${a.cssClasses ? "." + a.cssClasses.split(" ").join(".") : ""}\``
153
+ );
152
154
  lines.push(`**Path:** \`${a.elementPath}\``);
153
155
  if (detail !== "compact" && a.boundingBox) {
154
156
  const b = a.boundingBox;
155
- lines.push(`**Position:** ${Math.round(b.x)}px, ${Math.round(b.y)}px (${Math.round(b.width)}\xD7${Math.round(b.height)}px)`);
157
+ lines.push(
158
+ `**Position:** ${Math.round(b.x)}px, ${Math.round(b.y)}px (${Math.round(b.width)}\xD7${Math.round(b.height)}px)`
159
+ );
160
+ }
161
+ if (detail !== "compact" && a.sourceFile) {
162
+ lines.push(`**Source:** \`${a.sourceFile}:${a.sourceLine ?? "?"}\``);
156
163
  }
157
164
  if (detail === "detailed" || detail === "forensic") {
158
165
  if (a.reactComponents) lines.push(`**React:** ${a.reactComponents}`);
@@ -351,13 +358,25 @@ function Toolbar({ engine, onCollapse }) {
351
358
  style: { left: position.x, top: position.y, width: TOOLBAR_SIZE.width },
352
359
  "aria-label": "Clickly toolbar",
353
360
  children: [
354
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grip", ...handleProps, title: "Drag to move", children: /* @__PURE__ */ jsxRuntime.jsx(IconGrip, {}) }),
361
+ /* @__PURE__ */ jsxRuntime.jsx(
362
+ "div",
363
+ {
364
+ className: "grip",
365
+ ...handleProps,
366
+ title: "Drag to move",
367
+ role: "separator",
368
+ "aria-orientation": "vertical",
369
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconGrip, {})
370
+ }
371
+ ),
355
372
  /* @__PURE__ */ jsxRuntime.jsx(
356
373
  "button",
357
374
  {
358
375
  className: `clickly-btn icon-only${currentMode === "single" ? " is-active" : ""}`,
359
376
  onClick: () => setMode("single"),
360
377
  title: "Single (1)",
378
+ "aria-label": "Single-element selection mode",
379
+ "aria-pressed": currentMode === "single",
361
380
  children: /* @__PURE__ */ jsxRuntime.jsx(IconCursor, {})
362
381
  }
363
382
  ),
@@ -367,6 +386,8 @@ function Toolbar({ engine, onCollapse }) {
367
386
  className: `clickly-btn icon-only${currentMode === "multi" ? " is-active" : ""}`,
368
387
  onClick: () => setMode("multi"),
369
388
  title: "Multi-select (2 / shift-click)",
389
+ "aria-label": "Multi-element selection mode",
390
+ "aria-pressed": currentMode === "multi",
370
391
  children: /* @__PURE__ */ jsxRuntime.jsx(IconLayers, {})
371
392
  }
372
393
  ),
@@ -376,6 +397,8 @@ function Toolbar({ engine, onCollapse }) {
376
397
  className: `clickly-btn icon-only${currentMode === "area" ? " is-active" : ""}`,
377
398
  onClick: () => setMode("area"),
378
399
  title: "Area drag (3)",
400
+ "aria-label": "Area drag-selection mode",
401
+ "aria-pressed": currentMode === "area",
379
402
  children: /* @__PURE__ */ jsxRuntime.jsx(IconSquare, {})
380
403
  }
381
404
  ),
@@ -424,6 +447,7 @@ function Toolbar({ engine, onCollapse }) {
424
447
  className: "clickly-btn icon-only",
425
448
  onClick: onCopy,
426
449
  title: "Copy markdown (C)",
450
+ "aria-label": "Copy all annotations as markdown",
427
451
  disabled: annotations.length === 0,
428
452
  children: /* @__PURE__ */ jsxRuntime.jsx(IconCopy, {})
429
453
  }
@@ -434,6 +458,7 @@ function Toolbar({ engine, onCollapse }) {
434
458
  className: "clickly-btn icon-only",
435
459
  onClick: onClear,
436
460
  title: "Clear (X)",
461
+ "aria-label": "Clear all annotations",
437
462
  disabled: annotations.length === 0,
438
463
  children: /* @__PURE__ */ jsxRuntime.jsx(IconTrash, {})
439
464
  }
@@ -445,6 +470,8 @@ function Toolbar({ engine, onCollapse }) {
445
470
  className: "clickly-btn icon-only",
446
471
  onClick: () => setShowSettings((v) => !v),
447
472
  title: "Settings",
473
+ "aria-label": "Open settings",
474
+ "aria-expanded": showSettings,
448
475
  children: /* @__PURE__ */ jsxRuntime.jsx(IconSettings, {})
449
476
  }
450
477
  ),
@@ -454,6 +481,7 @@ function Toolbar({ engine, onCollapse }) {
454
481
  className: "clickly-btn icon-only",
455
482
  onClick: onCollapse,
456
483
  title: "Collapse (Esc)",
484
+ "aria-label": "Collapse Clickly toolbar",
457
485
  children: /* @__PURE__ */ jsxRuntime.jsx(IconClose, {})
458
486
  }
459
487
  ),
@@ -527,8 +555,9 @@ function AnnotationPopup({ engine }) {
527
555
  if (elements.length === 0) return;
528
556
  const sharedComment = comment.trim() || "(no comment)";
529
557
  const isMulti = sel.kind !== "single";
558
+ const showReact = useSettings.getState().showReactComponents;
530
559
  for (const el of elements) {
531
- const md = core.collectMetadata(el, { detail: outputDetail });
560
+ const md = core.collectMetadata(el, { detail: outputDetail, includeReact: showReact });
532
561
  const annotation = {
533
562
  id: "ann_" + nanoid.nanoid(8),
534
563
  comment: sharedComment,
@@ -546,6 +575,10 @@ function AnnotationPopup({ engine }) {
546
575
  nearbyText: md.nearbyText || void 0,
547
576
  selectedText: md.selectedText || void 0,
548
577
  isFixed: md.isFixed || void 0,
578
+ reactComponents: md.reactComponents || void 0,
579
+ sourceFile: md.sourceFile || void 0,
580
+ sourceLine: md.sourceLine || void 0,
581
+ sourceColumn: md.sourceColumn || void 0,
549
582
  isMultiSelect: isMulti,
550
583
  kind: "feedback",
551
584
  status: "pending"
@@ -586,6 +619,7 @@ function AnnotationPopup({ engine }) {
586
619
  ref: taRef,
587
620
  value: comment,
588
621
  placeholder: "Describe the issue or change\u2026 (\u2318/Ctrl + Enter to submit)",
622
+ "aria-label": "Annotation comment",
589
623
  onChange: (e) => setComment(e.target.value),
590
624
  onKeyDown: onKey
591
625
  }