@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 +67 -19
- package/dist/index.cjs +38 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +38 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
# @useclickly/react
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@useclickly/react)
|
|
4
|
+
[](../../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
|
|
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:
|
|
12
|
-
|
|
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
|
-
|
|
49
|
+
For every selected element, Clickly automatically collects:
|
|
33
50
|
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|