proto-plugin 0.1.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.
Files changed (209) hide show
  1. package/AGENTS.md +350 -0
  2. package/README.md +279 -0
  3. package/components.json +18 -0
  4. package/package.json +73 -0
  5. package/scripts/download-mobbin-reference-images.mjs +112 -0
  6. package/scripts/lib/host-config.example.sh +48 -0
  7. package/scripts/lib/host-config.sh +25 -0
  8. package/scripts/lib/resolve-host-root.mjs +18 -0
  9. package/scripts/lib/source-path.sh +49 -0
  10. package/scripts/link-source-db.sh +128 -0
  11. package/scripts/link-source.sh +40 -0
  12. package/scripts/publish-package.sh +147 -0
  13. package/scripts/share-prototype.sh +279 -0
  14. package/scripts/sync-from-source.sh +74 -0
  15. package/scripts/verify-prototype-component-ids.mjs +151 -0
  16. package/scripts/verify-prototype-preview-states.mjs +135 -0
  17. package/src/app.ts +4 -0
  18. package/src/components/platform-ui/code-block.tsx +21 -0
  19. package/src/components/platform-ui/controls-panel-options.tsx +71 -0
  20. package/src/components/platform-ui/controls-panel-select.tsx +438 -0
  21. package/src/components/platform-ui/empty-state.tsx +15 -0
  22. package/src/components/platform-ui/icon-button.tsx +18 -0
  23. package/src/components/platform-ui/panel-select.tsx +123 -0
  24. package/src/components/platform-ui/panel.tsx +97 -0
  25. package/src/components/platform-ui/review-dropdown-menu.tsx +35 -0
  26. package/src/components/platform-ui/sidebar.tsx +293 -0
  27. package/src/components/platform-ui/toolbar-icon-button.tsx +34 -0
  28. package/src/components/platform-ui/toolbar.tsx +37 -0
  29. package/src/components/prototype-gallery-client.tsx +57 -0
  30. package/src/components/prototype-provider.tsx +20 -0
  31. package/src/components/prototypes/mini-pill-label.tsx +37 -0
  32. package/src/components/prototypes/prototype-change-log-panel.tsx +207 -0
  33. package/src/components/prototypes/prototype-comment-provider.tsx +61 -0
  34. package/src/components/prototypes/prototype-comment-toolbar.tsx +65 -0
  35. package/src/components/prototypes/prototype-control.tsx +31 -0
  36. package/src/components/prototypes/prototype-controls.tsx +553 -0
  37. package/src/components/prototypes/prototype-create-exploration-modal.tsx +117 -0
  38. package/src/components/prototypes/prototype-create-state-modal.tsx +117 -0
  39. package/src/components/prototypes/prototype-default-state-map-page.tsx +66 -0
  40. package/src/components/prototypes/prototype-design-brief-modal.tsx +128 -0
  41. package/src/components/prototypes/prototype-design-brief-panel.tsx +525 -0
  42. package/src/components/prototypes/prototype-design-context-panel.tsx +65 -0
  43. package/src/components/prototypes/prototype-floating-pill.module.scss +1341 -0
  44. package/src/components/prototypes/prototype-mobbin-gallery.tsx +330 -0
  45. package/src/components/prototypes/prototype-reference-docs.tsx +222 -0
  46. package/src/components/prototypes/prototype-review-chrome.module.scss +630 -0
  47. package/src/components/prototypes/prototype-review-chrome.tsx +1241 -0
  48. package/src/components/prototypes/prototype-review-dropdown-menu-item.tsx +1 -0
  49. package/src/components/prototypes/prototype-review-dropdown-menu-separator.tsx +1 -0
  50. package/src/components/prototypes/prototype-review-panel-menu-item.tsx +1 -0
  51. package/src/components/prototypes/prototype-review-panel-menu-separator.tsx +1 -0
  52. package/src/components/prototypes/prototype-review-panel-select.tsx +4 -0
  53. package/src/components/prototypes/prototype-review-sidebar.tsx +167 -0
  54. package/src/components/prototypes/prototype-share-link-restore.tsx +75 -0
  55. package/src/components/prototypes/prototype-share-mode.tsx +206 -0
  56. package/src/components/prototypes/prototype-spec-panel-content.tsx +538 -0
  57. package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +336 -0
  58. package/src/components/prototypes/prototype-state-canvas-overlay.tsx +1023 -0
  59. package/src/components/prototypes/prototype-state-canvas.tsx +38 -0
  60. package/src/components/prototypes/prototype-state-screenshot-preview.tsx +47 -0
  61. package/src/components/prototypes/prototype-target.tsx +120 -0
  62. package/src/components/prototypes/prototype-tweak-highlight.tsx +132 -0
  63. package/src/components/prototypes/prototype-tweaks.tsx +48 -0
  64. package/src/components/prototypes/prototype-variant-explorer.tsx +347 -0
  65. package/src/components/prototypes/prototype-variant-rationale-panel.tsx +49 -0
  66. package/src/components/prototypes/prototype-variant-tabs.tsx +207 -0
  67. package/src/components/prototypes/prototype-vercel-preview-modal.tsx +120 -0
  68. package/src/components/prototypes/state-map-annotation-tooltip.module.scss +89 -0
  69. package/src/components/prototypes/state-map-annotation-tooltip.tsx +137 -0
  70. package/src/components/prototypes/state-map-wireframe-highlight.module.scss +25 -0
  71. package/src/components/prototypes/state-map-wireframe-highlight.tsx +82 -0
  72. package/src/components/shell/prototype-gallery-shell.tsx +79 -0
  73. package/src/components/shell/prototype-screenshot-capture.tsx +45 -0
  74. package/src/components/shell/prototype-shell.module.scss +194 -0
  75. package/src/components/shell/prototype-shell.tsx +160 -0
  76. package/src/components/shell/prototype-state-screenshot-capture.tsx +68 -0
  77. package/src/components/ui/button.tsx +58 -0
  78. package/src/components/ui/dialog.module.scss +81 -0
  79. package/src/components/ui/dialog.tsx +127 -0
  80. package/src/components/ui/dropdown-menu.tsx +200 -0
  81. package/src/components/ui/input.tsx +22 -0
  82. package/src/components/ui/label.tsx +26 -0
  83. package/src/components/ui/option-select.tsx +81 -0
  84. package/src/components/ui/scroll-area.tsx +48 -0
  85. package/src/components/ui/separator.tsx +31 -0
  86. package/src/components/ui/slider.tsx +28 -0
  87. package/src/components/ui/switch.tsx +29 -0
  88. package/src/components/ui/tabs.tsx +55 -0
  89. package/src/components/ui/toggle-group.tsx +80 -0
  90. package/src/components/ui/tooltip.tsx +32 -0
  91. package/src/config/create-prototype-gallery-page.tsx +65 -0
  92. package/src/config/create-prototype-host-app.tsx +121 -0
  93. package/src/config/create-prototype-page.tsx +41 -0
  94. package/src/config/create-prototype-site-layout.tsx +28 -0
  95. package/src/config/define-prototype-config.ts +7 -0
  96. package/src/config/prototype-icon.tsx +25 -0
  97. package/src/config/prototype-site-layout-client.tsx +35 -0
  98. package/src/config/with-prototype.ts +20 -0
  99. package/src/index.ts +303 -0
  100. package/src/lib/pr-split/build-pr-agent-prompt.ts +68 -0
  101. package/src/lib/pr-split/build-pr-split-prototype-url.ts +12 -0
  102. package/src/lib/pr-split/pr-split-highlight.ts +6 -0
  103. package/src/lib/pr-split/pr-split-types.ts +58 -0
  104. package/src/lib/pr-split/resolve-source-preview-url.ts +45 -0
  105. package/src/lib/pr-split/use-pr-vercel-previews.ts +69 -0
  106. package/src/lib/prototype-comments/core/annotation-channel.ts +41 -0
  107. package/src/lib/prototype-comments/core/annotation-status.ts +60 -0
  108. package/src/lib/prototype-comments/core/annotation-target.ts +567 -0
  109. package/src/lib/prototype-comments/core/capture-theme.ts +41 -0
  110. package/src/lib/prototype-comments/core/capture.ts +11 -0
  111. package/src/lib/prototype-comments/core/comment-capture-blocked.ts +47 -0
  112. package/src/lib/prototype-comments/core/comment-threads.ts +52 -0
  113. package/src/lib/prototype-comments/core/element-identification.ts +615 -0
  114. package/src/lib/prototype-comments/core/export.ts +35 -0
  115. package/src/lib/prototype-comments/core/freeze-animations.ts +266 -0
  116. package/src/lib/prototype-comments/core/identify-element.ts +76 -0
  117. package/src/lib/prototype-comments/core/normalize-viewport.ts +35 -0
  118. package/src/lib/prototype-comments/core/react-detection.ts +704 -0
  119. package/src/lib/prototype-comments/core/restore.ts +7 -0
  120. package/src/lib/prototype-comments/core/screenshot.ts +428 -0
  121. package/src/lib/prototype-comments/core/source-location.ts +906 -0
  122. package/src/lib/prototype-comments/core/storage.ts +80 -0
  123. package/src/lib/prototype-comments/core/types.ts +79 -0
  124. package/src/lib/prototype-comments/core/validation.ts +17 -0
  125. package/src/lib/prototype-comments/hooks/useLayoutShift.ts +61 -0
  126. package/src/lib/prototype-comments/index.ts +58 -0
  127. package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +1398 -0
  128. package/src/lib/prototype-comments/react/CommentProvider.tsx +298 -0
  129. package/src/lib/prototype-comments/scss.d.ts +10 -0
  130. package/src/lib/prototype-comments/ui/CommentAnnotationOverlay.tsx +253 -0
  131. package/src/lib/prototype-comments/ui/CommentDetailPage.tsx +56 -0
  132. package/src/lib/prototype-comments/ui/CommentHeaderCount.tsx +28 -0
  133. package/src/lib/prototype-comments/ui/CommentThread.tsx +331 -0
  134. package/src/lib/prototype-comments/ui/CommentsGrid.tsx +780 -0
  135. package/src/lib/prototype-comments/ui/CommentsListPage.tsx +25 -0
  136. package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +77 -0
  137. package/src/lib/prototype-comments/ui/annotation-marker/index.tsx +200 -0
  138. package/src/lib/prototype-comments/ui/annotation-marker/styles.module.scss +194 -0
  139. package/src/lib/prototype-comments/ui/annotation-popup/index.tsx +335 -0
  140. package/src/lib/prototype-comments/ui/annotation-popup/styles.module.scss +469 -0
  141. package/src/lib/prototype-comments/ui/capture-styles.module.scss +2225 -0
  142. package/src/lib/prototype-comments/ui/comment-highlight.ts +48 -0
  143. package/src/lib/prototype-comments/ui/comment-thread.module.scss +203 -0
  144. package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +253 -0
  145. package/src/lib/prototype-comments/ui/comments-sidebar.module.scss +123 -0
  146. package/src/lib/prototype-comments/ui/icon-transitions.module.scss +51 -0
  147. package/src/lib/prototype-comments/ui/icons.tsx +1086 -0
  148. package/src/lib/prototype-comments/ui/is-comment-detail-path.ts +7 -0
  149. package/src/lib/prototypes/capture-prototype-viewport.ts +32 -0
  150. package/src/lib/prototypes/changelog-meta-context.tsx +162 -0
  151. package/src/lib/prototypes/changelog-meta-storage.ts +62 -0
  152. package/src/lib/prototypes/changelog-meta.ts +95 -0
  153. package/src/lib/prototypes/create-prototype-registry.ts +74 -0
  154. package/src/lib/prototypes/create-state-screenshot-thumbnail.ts +46 -0
  155. package/src/lib/prototypes/design-exploration-default-storage.ts +68 -0
  156. package/src/lib/prototypes/design-exploration-default.ts +51 -0
  157. package/src/lib/prototypes/design-exploration-types.ts +203 -0
  158. package/src/lib/prototypes/prototype-comment-registry.tsx +145 -0
  159. package/src/lib/prototypes/prototype-comment-review-bridge.tsx +40 -0
  160. package/src/lib/prototypes/prototype-comment-review-state.ts +57 -0
  161. package/src/lib/prototypes/prototype-component-registry.ts +40 -0
  162. package/src/lib/prototypes/prototype-config-types.ts +34 -0
  163. package/src/lib/prototypes/prototype-preview-state-registry.ts +443 -0
  164. package/src/lib/prototypes/prototype-preview-state-types.ts +89 -0
  165. package/src/lib/prototypes/prototype-review-context.tsx +905 -0
  166. package/src/lib/prototypes/prototype-review-url.ts +134 -0
  167. package/src/lib/prototypes/prototype-share-link.ts +175 -0
  168. package/src/lib/prototypes/prototype-state-canvas-constants.ts +118 -0
  169. package/src/lib/prototypes/prototype-state-canvas-types.ts +159 -0
  170. package/src/lib/prototypes/prototype-state-screenshot.ts +39 -0
  171. package/src/lib/prototypes/prototype-tweak-registry.ts +96 -0
  172. package/src/lib/prototypes/prototype-tweak-types.ts +31 -0
  173. package/src/lib/prototypes/reference-docs.ts +137 -0
  174. package/src/lib/prototypes/screenshot-manifest.ts +33 -0
  175. package/src/lib/prototypes/screenshot-src.ts +7 -0
  176. package/src/lib/prototypes/share-command.ts +8 -0
  177. package/src/lib/prototypes/state-map-annotation.ts +70 -0
  178. package/src/lib/prototypes/use-design-exploration-default.ts +114 -0
  179. package/src/lib/prototypes/use-persisted-local-state.ts +140 -0
  180. package/src/lib/prototypes/use-prototype-comments.ts +63 -0
  181. package/src/lib/prototypes/use-prototype-mobile-viewport-frame.ts +46 -0
  182. package/src/lib/prototypes/use-prototype-state-screenshot-src.ts +71 -0
  183. package/src/lib/prototypes/use-prototype-tool-theme.tsx +115 -0
  184. package/src/lib/prototypes/use-prototype-viewport-frame.ts +34 -0
  185. package/src/lib/prototypes/use-register-prototype-reference-docs.ts +16 -0
  186. package/src/lib/prototypes/use-snap-corner-position.ts +417 -0
  187. package/src/lib/prototypes/use-toolbar-hover-panel.ts +98 -0
  188. package/src/lib/prototypes/variant-set-lucide-icon.tsx +55 -0
  189. package/src/lib/tool-portal.ts +33 -0
  190. package/src/lib/use-copy-to-clipboard.ts +81 -0
  191. package/src/lib/utils.ts +6 -0
  192. package/src/lib/vercel-preview/parse-vercel-github-comment.ts +81 -0
  193. package/src/server/create-host-api-route.ts +100 -0
  194. package/src/server/create-prototype-api-routes.ts +428 -0
  195. package/src/server/gallery/create-gallery-api.ts +56 -0
  196. package/src/server/gallery/folders-route.ts +156 -0
  197. package/src/server/gallery/gallery-env.ts +39 -0
  198. package/src/server/gallery/gallery-route.ts +150 -0
  199. package/src/server/gallery/gallery-types.ts +15 -0
  200. package/src/server/gallery/image-route.ts +59 -0
  201. package/src/server/gallery/item-route.ts +92 -0
  202. package/src/server/pr-vercel-preview-route.ts +162 -0
  203. package/src/server/redis/client.ts +55 -0
  204. package/src/server/redis/prototype-changelog-meta.ts +31 -0
  205. package/src/server/redis/prototype-comments.ts +92 -0
  206. package/src/server/redis/prototype-design-exploration-default.ts +32 -0
  207. package/src/server.ts +51 -0
  208. package/src/styles/globals.css +624 -0
  209. package/tsconfig.json +19 -0
@@ -0,0 +1,335 @@
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect, useCallback, forwardRef, useImperativeHandle } from "react";
4
+ import styles from "./styles.module.scss";
5
+ import { originalSetTimeout } from "../../core/freeze-animations";
6
+ import { IconTrash } from "../icons";
7
+
8
+ // =============================================================================
9
+ // Helpers
10
+ // =============================================================================
11
+
12
+ /** Focus an element while temporarily blocking focus-trap libraries (e.g. Radix
13
+ * FocusScope) from reclaiming focus via focusin/focusout handlers. */
14
+ function focusBypassingTraps(el: HTMLElement | null) {
15
+ if (!el) return;
16
+ const trap = (e: Event) => e.stopImmediatePropagation();
17
+ document.addEventListener("focusin", trap, true);
18
+ document.addEventListener("focusout", trap, true);
19
+ try {
20
+ el.focus();
21
+ } finally {
22
+ document.removeEventListener("focusin", trap, true);
23
+ document.removeEventListener("focusout", trap, true);
24
+ }
25
+ }
26
+
27
+ // =============================================================================
28
+ // Types
29
+ // =============================================================================
30
+
31
+ export interface AnnotationPopupCSSProps {
32
+ /** Element name to display in header */
33
+ element: string;
34
+ /** Optional timestamp display (e.g., "@ 1.23s" for animation feedback) */
35
+ timestamp?: string;
36
+ /** Optional selected/highlighted text */
37
+ selectedText?: string;
38
+ /** Placeholder text for the textarea */
39
+ placeholder?: string;
40
+ /** Initial value for textarea (for edit mode) */
41
+ initialValue?: string;
42
+ /** Label for submit button (default: "Add") */
43
+ submitLabel?: string;
44
+ /** Called when annotation is submitted with text */
45
+ onSubmit: (text: string) => void;
46
+ /** Called when popup is cancelled/dismissed */
47
+ onCancel: () => void;
48
+ /** Called when delete button is clicked (only shown if provided) */
49
+ onDelete?: () => void;
50
+ /** Read-only preview mode — shows comment text with a single Close button */
51
+ readOnly?: boolean;
52
+ /** Position styles (left, top) */
53
+ style?: React.CSSProperties;
54
+ /** Custom color for submit button and textarea focus (hex) */
55
+ accentColor?: string;
56
+ /** External exit state (parent controls exit animation) */
57
+ isExiting?: boolean;
58
+ /** Light mode styling */
59
+ lightMode?: boolean;
60
+ /** Show the element/component path header (default: true) */
61
+ showElementPath?: boolean;
62
+ /** Computed styles for the selected element */
63
+ computedStyles?: Record<string, string>;
64
+ }
65
+
66
+ export interface AnnotationPopupCSSHandle {
67
+ /** Shake the popup (e.g., when user clicks outside) */
68
+ shake: () => void;
69
+ }
70
+
71
+ // =============================================================================
72
+ // Component
73
+ // =============================================================================
74
+
75
+ export const AnnotationPopupCSS = forwardRef<AnnotationPopupCSSHandle, AnnotationPopupCSSProps>(
76
+ function AnnotationPopupCSS(
77
+ {
78
+ element,
79
+ timestamp,
80
+ selectedText,
81
+ placeholder = "What should change?",
82
+ initialValue = "",
83
+ submitLabel = "Add",
84
+ onSubmit,
85
+ onCancel,
86
+ onDelete,
87
+ readOnly = false,
88
+ style,
89
+ accentColor = "#3c82f7",
90
+ isExiting = false,
91
+ lightMode = false,
92
+ showElementPath = true,
93
+ computedStyles,
94
+ },
95
+ ref
96
+ ) {
97
+ const [text, setText] = useState(initialValue);
98
+ const [isShaking, setIsShaking] = useState(false);
99
+
100
+ useEffect(() => {
101
+ setText(initialValue);
102
+ }, [initialValue]);
103
+ const [animState, setAnimState] = useState<"initial" | "enter" | "entered" | "exit">("initial");
104
+ const [isFocused, setIsFocused] = useState(false);
105
+ const [isStylesExpanded, setIsStylesExpanded] = useState(false); // Computed styles accordion state
106
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
107
+ const popupRef = useRef<HTMLDivElement>(null);
108
+ const cancelTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
109
+ const shakeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
110
+
111
+ // Sync with parent exit state
112
+ useEffect(() => {
113
+ if (isExiting && animState !== "exit") {
114
+ setAnimState("exit");
115
+ }
116
+ }, [isExiting, animState]);
117
+
118
+ // Animate in on mount and focus textarea
119
+ useEffect(() => {
120
+ // Start enter animation (use originalSetTimeout to bypass freeze patch)
121
+ originalSetTimeout(() => {
122
+ setAnimState("enter");
123
+ }, 0);
124
+ // Transition to entered state after animation completes
125
+ const enterTimer = originalSetTimeout(() => {
126
+ setAnimState("entered");
127
+ }, 200); // Match animation duration
128
+ const focusTimer = originalSetTimeout(() => {
129
+ if (readOnly) return;
130
+ const textarea = textareaRef.current;
131
+ if (textarea) {
132
+ focusBypassingTraps(textarea);
133
+ textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
134
+ textarea.scrollTop = textarea.scrollHeight;
135
+ }
136
+ }, 50);
137
+ return () => {
138
+ clearTimeout(enterTimer);
139
+ clearTimeout(focusTimer);
140
+ if (cancelTimerRef.current) clearTimeout(cancelTimerRef.current);
141
+ if (shakeTimerRef.current) clearTimeout(shakeTimerRef.current);
142
+ };
143
+ }, [readOnly]);
144
+
145
+ // Shake animation
146
+ const shake = useCallback(() => {
147
+ if (shakeTimerRef.current) clearTimeout(shakeTimerRef.current);
148
+ setIsShaking(true);
149
+ shakeTimerRef.current = originalSetTimeout(() => {
150
+ setIsShaking(false);
151
+ focusBypassingTraps(textareaRef.current);
152
+ }, 250);
153
+ }, []);
154
+
155
+ // Expose shake to parent via ref
156
+ useImperativeHandle(ref, () => ({
157
+ shake,
158
+ }), [shake]);
159
+
160
+ // Handle cancel with exit animation
161
+ const handleCancel = useCallback(() => {
162
+ setAnimState("exit");
163
+ cancelTimerRef.current = originalSetTimeout(() => {
164
+ onCancel();
165
+ }, 150); // Match exit animation duration
166
+ }, [onCancel]);
167
+
168
+ // Handle submit
169
+ const handleSubmit = useCallback(() => {
170
+ if (!text.trim()) return;
171
+ onSubmit(text.trim());
172
+ }, [text, onSubmit]);
173
+
174
+ // Handle keyboard
175
+ const handleKeyDown = useCallback(
176
+ (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
177
+ e.stopPropagation();
178
+ if (e.nativeEvent.isComposing) return;
179
+ if (!readOnly && e.key === "Enter" && !e.shiftKey) {
180
+ e.preventDefault();
181
+ handleSubmit();
182
+ }
183
+ if (e.key === "Escape") {
184
+ handleCancel();
185
+ }
186
+ },
187
+ [readOnly, handleSubmit, handleCancel]
188
+ );
189
+
190
+ const popupClassName = [
191
+ styles.popup,
192
+ lightMode ? styles.light : "",
193
+ animState === "enter" ? styles.enter : "",
194
+ animState === "entered" ? styles.entered : "",
195
+ animState === "exit" ? styles.exit : "",
196
+ isShaking ? styles.shake : "",
197
+ ].filter(Boolean).join(" ");
198
+
199
+ return (
200
+ <div
201
+ ref={popupRef}
202
+ className={popupClassName}
203
+ data-annotation-popup
204
+ style={style}
205
+ onClick={(e) => e.stopPropagation()}
206
+ >
207
+ {!readOnly && showElementPath && (
208
+ <>
209
+ <div className={styles.header}>
210
+ {computedStyles && Object.keys(computedStyles).length > 0 ? (
211
+ <button
212
+ className={styles.headerToggle}
213
+ onClick={() => {
214
+ const wasExpanded = isStylesExpanded;
215
+ setIsStylesExpanded(!isStylesExpanded);
216
+ if (wasExpanded) {
217
+ // Refocus textarea when closing
218
+ originalSetTimeout(() => focusBypassingTraps(textareaRef.current), 0);
219
+ }
220
+ }}
221
+ type="button"
222
+ >
223
+ <svg
224
+ className={`${styles.chevron} ${isStylesExpanded ? styles.expanded : ""}`}
225
+ width="14"
226
+ height="14"
227
+ viewBox="0 0 14 14"
228
+ fill="none"
229
+ xmlns="http://www.w3.org/2000/svg"
230
+ >
231
+ <path
232
+ d="M5.5 10.25L9 7.25L5.75 4"
233
+ stroke="currentColor"
234
+ strokeWidth="1.5"
235
+ strokeLinecap="round"
236
+ strokeLinejoin="round"
237
+ />
238
+ </svg>
239
+ <span className={styles.element}>{element}</span>
240
+ </button>
241
+ ) : (
242
+ <span className={styles.element}>{element}</span>
243
+ )}
244
+ {timestamp && <span className={styles.timestamp}>{timestamp}</span>}
245
+ </div>
246
+
247
+ {/* Collapsible computed styles section - uses grid-template-rows for smooth animation */}
248
+ {computedStyles && Object.keys(computedStyles).length > 0 && (
249
+ <div className={`${styles.stylesWrapper} ${isStylesExpanded ? styles.expanded : ""}`}>
250
+ <div className={styles.stylesInner}>
251
+ <div className={styles.stylesBlock}>
252
+ {Object.entries(computedStyles).map(([key, value]) => (
253
+ <div key={key} className={styles.styleLine}>
254
+ <span className={styles.styleProperty}>
255
+ {key.replace(/([A-Z])/g, "-$1").toLowerCase()}
256
+ </span>
257
+ : <span className={styles.styleValue}>{value}</span>;
258
+ </div>
259
+ ))}
260
+ </div>
261
+ </div>
262
+ </div>
263
+ )}
264
+ </>
265
+ )}
266
+
267
+ {selectedText && (
268
+ <div className={styles.quote}>
269
+ &ldquo;{selectedText.slice(0, 80)}
270
+ {selectedText.length > 80 ? "..." : ""}&rdquo;
271
+ </div>
272
+ )}
273
+
274
+ {readOnly ? (
275
+ <p className={styles.commentText}>{text}</p>
276
+ ) : (
277
+ <textarea
278
+ ref={textareaRef}
279
+ className={styles.textarea}
280
+ style={{ borderColor: isFocused ? accentColor : undefined }}
281
+ placeholder={placeholder}
282
+ value={text}
283
+ onChange={(e) => setText(e.target.value)}
284
+ onFocus={() => setIsFocused(true)}
285
+ onBlur={() => setIsFocused(false)}
286
+ rows={2}
287
+ onKeyDown={handleKeyDown}
288
+ />
289
+ )}
290
+
291
+ <div className={styles.actions}>
292
+ {readOnly ? (
293
+ <button className={styles.cancel} onClick={handleCancel} type="button">
294
+ Close
295
+ </button>
296
+ ) : (
297
+ <>
298
+ {onDelete && (
299
+ <div className={styles.deleteWrapper}>
300
+ <button className={styles.deleteButton} onClick={onDelete} type="button">
301
+ <IconTrash size={22} />
302
+ </button>
303
+ </div>
304
+ )}
305
+ <button className={styles.cancel} onClick={handleCancel} type="button">
306
+ Cancel
307
+ </button>
308
+ <button
309
+ className={styles.submit}
310
+ style={
311
+ lightMode
312
+ ? {
313
+ color: text.trim() ? accentColor : undefined,
314
+ opacity: text.trim() ? 1 : 0.4,
315
+ }
316
+ : {
317
+ backgroundColor: accentColor,
318
+ opacity: text.trim() ? 1 : 0.4,
319
+ }
320
+ }
321
+ onClick={handleSubmit}
322
+ disabled={!text.trim()}
323
+ type="button"
324
+ >
325
+ {submitLabel}
326
+ </button>
327
+ </>
328
+ )}
329
+ </div>
330
+ </div>
331
+ );
332
+ }
333
+ );
334
+
335
+ export default AnnotationPopupCSS;