uilint-react 0.1.35 → 0.1.38

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.
@@ -0,0 +1,911 @@
1
+ "use client";
2
+ import {
3
+ groupBySourceFile,
4
+ useUILintContext,
5
+ useUILintStore
6
+ } from "./chunk-PU6XPNPN.js";
7
+
8
+ // src/components/ui-lint/UILintToolbar.tsx
9
+ import { useState, useRef, useEffect, useCallback } from "react";
10
+ import { createPortal } from "react-dom";
11
+
12
+ // src/components/ui-lint/toolbar-styles.ts
13
+ var STYLES = {
14
+ // Backgrounds
15
+ bg: "rgba(17, 24, 39, 0.92)",
16
+ bgHover: "rgba(31, 41, 55, 0.95)",
17
+ bgSegment: "rgba(31, 41, 55, 0.6)",
18
+ bgSegmentHover: "rgba(55, 65, 81, 0.8)",
19
+ bgPopover: "rgba(17, 24, 39, 0.96)",
20
+ // Borders
21
+ border: "rgba(75, 85, 99, 0.5)",
22
+ borderLight: "rgba(107, 114, 128, 0.3)",
23
+ divider: "rgba(75, 85, 99, 0.4)",
24
+ // Text
25
+ text: "#F9FAFB",
26
+ textMuted: "#9CA3AF",
27
+ textDim: "#6B7280",
28
+ // Accent colors
29
+ accent: "#3B82F6",
30
+ accentHover: "#2563EB",
31
+ accentLight: "rgba(59, 130, 246, 0.15)",
32
+ // Status colors
33
+ success: "#10B981",
34
+ successLight: "rgba(16, 185, 129, 0.15)",
35
+ warning: "#F59E0B",
36
+ warningLight: "rgba(245, 158, 11, 0.15)",
37
+ error: "#EF4444",
38
+ errorLight: "rgba(239, 68, 68, 0.15)",
39
+ // Badge colors
40
+ badgeBg: "#3B82F6",
41
+ badgeText: "#FFFFFF",
42
+ // Effects
43
+ shadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
44
+ shadowLg: "0 12px 48px rgba(0, 0, 0, 0.5)",
45
+ blur: "blur(12px)",
46
+ // Typography
47
+ font: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
48
+ fontMono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
49
+ // Sizing
50
+ pillHeight: "40px",
51
+ pillRadius: "20px",
52
+ popoverRadius: "12px",
53
+ buttonRadius: "8px",
54
+ // Transitions
55
+ transition: "all 0.15s ease-out",
56
+ transitionFast: "all 0.1s ease-out"
57
+ };
58
+ function getStatusColor(issueCount) {
59
+ if (issueCount === 0) return STYLES.success;
60
+ if (issueCount <= 2) return STYLES.warning;
61
+ return STYLES.error;
62
+ }
63
+
64
+ // src/components/ui-lint/toolbar-icons.tsx
65
+ import { jsx, jsxs } from "react/jsx-runtime";
66
+ function PauseIcon() {
67
+ return /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: [
68
+ /* @__PURE__ */ jsx("rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", fill: "currentColor" }),
69
+ /* @__PURE__ */ jsx("rect", { x: "14", y: "4", width: "4", height: "16", rx: "1", fill: "currentColor" })
70
+ ] });
71
+ }
72
+ function PlayIcon() {
73
+ return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7L8 5z", fill: "currentColor" }) });
74
+ }
75
+ function StopIcon() {
76
+ return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1", fill: "currentColor" }) });
77
+ }
78
+ function EllipsisIcon() {
79
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: [
80
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "2", fill: "currentColor" }),
81
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2", fill: "currentColor" }),
82
+ /* @__PURE__ */ jsx("circle", { cx: "19", cy: "12", r: "2", fill: "currentColor" })
83
+ ] });
84
+ }
85
+ function MagnifyingGlassIcon() {
86
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: [
87
+ /* @__PURE__ */ jsx(
88
+ "circle",
89
+ {
90
+ cx: "11",
91
+ cy: "11",
92
+ r: "7",
93
+ stroke: "currentColor",
94
+ strokeWidth: "2",
95
+ strokeLinecap: "round"
96
+ }
97
+ ),
98
+ /* @__PURE__ */ jsx(
99
+ "path",
100
+ {
101
+ d: "M21 21l-4.35-4.35",
102
+ stroke: "currentColor",
103
+ strokeWidth: "2",
104
+ strokeLinecap: "round"
105
+ }
106
+ )
107
+ ] });
108
+ }
109
+ function CheckCircleIcon() {
110
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: [
111
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }),
112
+ /* @__PURE__ */ jsx(
113
+ "path",
114
+ {
115
+ d: "M8 12l3 3 5-6",
116
+ stroke: "currentColor",
117
+ strokeWidth: "2",
118
+ strokeLinecap: "round",
119
+ strokeLinejoin: "round"
120
+ }
121
+ )
122
+ ] });
123
+ }
124
+ function SpinnerIcon() {
125
+ return /* @__PURE__ */ jsxs(
126
+ "svg",
127
+ {
128
+ width: "14",
129
+ height: "14",
130
+ viewBox: "0 0 24 24",
131
+ fill: "none",
132
+ style: {
133
+ animation: "uilint-spin 1s linear infinite"
134
+ },
135
+ children: [
136
+ /* @__PURE__ */ jsx(
137
+ "path",
138
+ {
139
+ d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83",
140
+ stroke: "currentColor",
141
+ strokeWidth: "2",
142
+ strokeLinecap: "round",
143
+ opacity: "0.3"
144
+ }
145
+ ),
146
+ /* @__PURE__ */ jsx(
147
+ "path",
148
+ {
149
+ d: "M12 2v4",
150
+ stroke: "currentColor",
151
+ strokeWidth: "2",
152
+ strokeLinecap: "round"
153
+ }
154
+ )
155
+ ]
156
+ }
157
+ );
158
+ }
159
+
160
+ // src/components/ui-lint/SettingsPopover.tsx
161
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
162
+ function SettingsPopover({ settings }) {
163
+ const wsConnected = useUILintStore((s) => s.wsConnected);
164
+ const wsUrl = useUILintStore((s) => s.wsUrl);
165
+ const connectWebSocket = useUILintStore(
166
+ (s) => s.connectWebSocket
167
+ );
168
+ const disconnectWebSocket = useUILintStore(
169
+ (s) => s.disconnectWebSocket
170
+ );
171
+ return /* @__PURE__ */ jsxs2(
172
+ "div",
173
+ {
174
+ style: {
175
+ position: "absolute",
176
+ bottom: "100%",
177
+ right: 0,
178
+ marginBottom: "8px",
179
+ width: "260px",
180
+ padding: "14px",
181
+ borderRadius: STYLES.popoverRadius,
182
+ border: `1px solid ${STYLES.border}`,
183
+ backgroundColor: STYLES.bgPopover,
184
+ backdropFilter: STYLES.blur,
185
+ WebkitBackdropFilter: STYLES.blur,
186
+ boxShadow: STYLES.shadowLg,
187
+ animation: "uilint-fade-in 0.15s ease-out"
188
+ },
189
+ children: [
190
+ /* @__PURE__ */ jsx2(
191
+ "div",
192
+ {
193
+ style: {
194
+ fontSize: "12px",
195
+ fontWeight: 600,
196
+ color: STYLES.text,
197
+ marginBottom: "12px"
198
+ },
199
+ children: "UILint Settings"
200
+ }
201
+ ),
202
+ /* @__PURE__ */ jsxs2(
203
+ "div",
204
+ {
205
+ style: {
206
+ padding: "10px 12px",
207
+ borderRadius: STYLES.buttonRadius,
208
+ border: `1px solid ${STYLES.border}`,
209
+ backgroundColor: STYLES.bgSegment
210
+ },
211
+ children: [
212
+ /* @__PURE__ */ jsx2(
213
+ "div",
214
+ {
215
+ style: {
216
+ display: "flex",
217
+ alignItems: "center",
218
+ justifyContent: "space-between",
219
+ marginBottom: "8px"
220
+ },
221
+ children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
222
+ /* @__PURE__ */ jsx2(
223
+ "div",
224
+ {
225
+ style: {
226
+ width: "8px",
227
+ height: "8px",
228
+ borderRadius: "50%",
229
+ backgroundColor: wsConnected ? STYLES.success : STYLES.error,
230
+ boxShadow: wsConnected ? `0 0 8px ${STYLES.success}` : `0 0 8px ${STYLES.error}`
231
+ }
232
+ }
233
+ ),
234
+ /* @__PURE__ */ jsx2(
235
+ "span",
236
+ {
237
+ style: {
238
+ fontSize: "12px",
239
+ fontWeight: 600,
240
+ color: wsConnected ? STYLES.success : STYLES.error
241
+ },
242
+ children: wsConnected ? "Connected" : "Disconnected"
243
+ }
244
+ )
245
+ ] })
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsx2(
249
+ "div",
250
+ {
251
+ style: {
252
+ fontSize: "10px",
253
+ color: STYLES.textDim,
254
+ fontFamily: STYLES.fontMono,
255
+ wordBreak: "break-all",
256
+ marginBottom: "10px"
257
+ },
258
+ children: wsUrl
259
+ }
260
+ ),
261
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "6px" }, children: [
262
+ /* @__PURE__ */ jsx2(
263
+ "button",
264
+ {
265
+ onClick: () => connectWebSocket(wsUrl),
266
+ style: {
267
+ flex: 1,
268
+ padding: "6px 10px",
269
+ borderRadius: "6px",
270
+ border: `1px solid ${STYLES.border}`,
271
+ backgroundColor: wsConnected ? "transparent" : STYLES.accent,
272
+ color: wsConnected ? STYLES.textMuted : "#FFFFFF",
273
+ fontSize: "11px",
274
+ fontWeight: 600,
275
+ cursor: "pointer",
276
+ transition: STYLES.transitionFast
277
+ },
278
+ title: "Reconnect to WebSocket server",
279
+ children: wsConnected ? "Reconnect" : "Connect"
280
+ }
281
+ ),
282
+ /* @__PURE__ */ jsx2(
283
+ "button",
284
+ {
285
+ onClick: () => disconnectWebSocket(),
286
+ disabled: !wsConnected,
287
+ style: {
288
+ padding: "6px 10px",
289
+ borderRadius: "6px",
290
+ border: `1px solid ${STYLES.border}`,
291
+ backgroundColor: "transparent",
292
+ color: wsConnected ? STYLES.textMuted : STYLES.textDim,
293
+ fontSize: "11px",
294
+ fontWeight: 600,
295
+ cursor: wsConnected ? "pointer" : "not-allowed",
296
+ opacity: wsConnected ? 1 : 0.5,
297
+ transition: STYLES.transitionFast
298
+ },
299
+ title: "Disconnect from WebSocket server",
300
+ children: "Disconnect"
301
+ }
302
+ )
303
+ ] })
304
+ ]
305
+ }
306
+ ),
307
+ /* @__PURE__ */ jsxs2(
308
+ "div",
309
+ {
310
+ style: {
311
+ marginTop: "12px",
312
+ fontSize: "11px",
313
+ color: STYLES.textMuted,
314
+ lineHeight: 1.5
315
+ },
316
+ children: [
317
+ /* @__PURE__ */ jsx2("strong", { style: { color: STYLES.text }, children: "\u2325+Click" }),
318
+ " any element to open the inspector sidebar"
319
+ ]
320
+ }
321
+ )
322
+ ]
323
+ }
324
+ );
325
+ }
326
+
327
+ // src/components/ui-lint/ScanResultsPopover.tsx
328
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
329
+ function ScanResultsPopover() {
330
+ const {
331
+ autoScanState,
332
+ pauseAutoScan,
333
+ resumeAutoScan,
334
+ stopAutoScan
335
+ } = useUILintContext();
336
+ const elementIssuesCache = useUILintStore(
337
+ (s) => s.elementIssuesCache
338
+ );
339
+ const isScanning = autoScanState.status === "scanning";
340
+ const isPaused = autoScanState.status === "paused";
341
+ const isComplete = autoScanState.status === "complete";
342
+ const sourceFiles = groupBySourceFile(autoScanState.elements);
343
+ const fileResults = sourceFiles.map((sf) => {
344
+ let issueCount = 0;
345
+ for (const el of sf.elements) {
346
+ const cached = elementIssuesCache.get(el.id);
347
+ if (cached) {
348
+ issueCount += cached.issues.length;
349
+ }
350
+ }
351
+ return {
352
+ path: sf.path,
353
+ displayName: sf.displayName,
354
+ issueCount,
355
+ elementCount: sf.elements.length
356
+ };
357
+ });
358
+ fileResults.sort((a, b) => {
359
+ if (b.issueCount !== a.issueCount) {
360
+ return b.issueCount - a.issueCount;
361
+ }
362
+ return a.displayName.localeCompare(b.displayName);
363
+ });
364
+ const totalFiles = fileResults.length;
365
+ const totalIssues = fileResults.reduce((sum, f) => sum + f.issueCount, 0);
366
+ const filesWithIssues = fileResults.filter((f) => f.issueCount > 0).length;
367
+ const progress = autoScanState.totalElements > 0 ? autoScanState.currentIndex / autoScanState.totalElements * 100 : 0;
368
+ return /* @__PURE__ */ jsxs3(
369
+ "div",
370
+ {
371
+ style: {
372
+ position: "absolute",
373
+ bottom: "100%",
374
+ right: 0,
375
+ marginBottom: "8px",
376
+ width: "280px",
377
+ maxHeight: "400px",
378
+ borderRadius: STYLES.popoverRadius,
379
+ border: `1px solid ${STYLES.border}`,
380
+ backgroundColor: STYLES.bgPopover,
381
+ backdropFilter: STYLES.blur,
382
+ WebkitBackdropFilter: STYLES.blur,
383
+ boxShadow: STYLES.shadowLg,
384
+ animation: "uilint-fade-in 0.15s ease-out",
385
+ display: "flex",
386
+ flexDirection: "column",
387
+ overflow: "hidden"
388
+ },
389
+ children: [
390
+ /* @__PURE__ */ jsxs3(
391
+ "div",
392
+ {
393
+ style: {
394
+ padding: "12px 14px",
395
+ borderBottom: `1px solid ${STYLES.border}`
396
+ },
397
+ children: [
398
+ /* @__PURE__ */ jsxs3(
399
+ "div",
400
+ {
401
+ style: {
402
+ display: "flex",
403
+ alignItems: "center",
404
+ justifyContent: "space-between"
405
+ },
406
+ children: [
407
+ /* @__PURE__ */ jsxs3(
408
+ "div",
409
+ {
410
+ style: {
411
+ fontSize: "12px",
412
+ fontWeight: 600,
413
+ color: STYLES.text
414
+ },
415
+ children: [
416
+ totalFiles,
417
+ " files \xB7 ",
418
+ totalIssues,
419
+ " issues"
420
+ ]
421
+ }
422
+ ),
423
+ (isScanning || isPaused) && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "4px" }, children: [
424
+ isScanning && /* @__PURE__ */ jsx3(
425
+ "button",
426
+ {
427
+ onClick: pauseAutoScan,
428
+ style: {
429
+ display: "flex",
430
+ alignItems: "center",
431
+ justifyContent: "center",
432
+ width: "24px",
433
+ height: "24px",
434
+ borderRadius: "6px",
435
+ border: `1px solid ${STYLES.border}`,
436
+ backgroundColor: "transparent",
437
+ color: STYLES.textMuted,
438
+ cursor: "pointer",
439
+ transition: STYLES.transitionFast
440
+ },
441
+ title: "Pause scan",
442
+ children: /* @__PURE__ */ jsx3(PauseIcon, {})
443
+ }
444
+ ),
445
+ isPaused && /* @__PURE__ */ jsx3(
446
+ "button",
447
+ {
448
+ onClick: resumeAutoScan,
449
+ style: {
450
+ display: "flex",
451
+ alignItems: "center",
452
+ justifyContent: "center",
453
+ width: "24px",
454
+ height: "24px",
455
+ borderRadius: "6px",
456
+ border: "none",
457
+ backgroundColor: STYLES.accent,
458
+ color: "#FFFFFF",
459
+ cursor: "pointer",
460
+ transition: STYLES.transitionFast
461
+ },
462
+ title: "Resume scan",
463
+ children: /* @__PURE__ */ jsx3(PlayIcon, {})
464
+ }
465
+ ),
466
+ /* @__PURE__ */ jsx3(
467
+ "button",
468
+ {
469
+ onClick: stopAutoScan,
470
+ style: {
471
+ display: "flex",
472
+ alignItems: "center",
473
+ justifyContent: "center",
474
+ width: "24px",
475
+ height: "24px",
476
+ borderRadius: "6px",
477
+ border: `1px solid ${STYLES.border}`,
478
+ backgroundColor: "transparent",
479
+ color: STYLES.textMuted,
480
+ cursor: "pointer",
481
+ transition: STYLES.transitionFast
482
+ },
483
+ title: "Stop scan",
484
+ children: /* @__PURE__ */ jsx3(StopIcon, {})
485
+ }
486
+ )
487
+ ] }),
488
+ isComplete && /* @__PURE__ */ jsx3(
489
+ "button",
490
+ {
491
+ onClick: stopAutoScan,
492
+ style: {
493
+ padding: "4px 8px",
494
+ borderRadius: "6px",
495
+ border: `1px solid ${STYLES.border}`,
496
+ backgroundColor: "transparent",
497
+ color: STYLES.textMuted,
498
+ fontSize: "10px",
499
+ fontWeight: 500,
500
+ cursor: "pointer",
501
+ transition: STYLES.transitionFast
502
+ },
503
+ title: "Clear results",
504
+ children: "Clear"
505
+ }
506
+ )
507
+ ]
508
+ }
509
+ ),
510
+ (isScanning || isPaused) && /* @__PURE__ */ jsxs3("div", { style: { marginTop: "10px" }, children: [
511
+ /* @__PURE__ */ jsxs3(
512
+ "div",
513
+ {
514
+ style: {
515
+ display: "flex",
516
+ justifyContent: "space-between",
517
+ fontSize: "10px",
518
+ color: STYLES.textMuted,
519
+ marginBottom: "4px"
520
+ },
521
+ children: [
522
+ /* @__PURE__ */ jsx3("span", { children: isPaused ? "Paused" : "Scanning..." }),
523
+ /* @__PURE__ */ jsxs3("span", { children: [
524
+ autoScanState.currentIndex,
525
+ " / ",
526
+ autoScanState.totalElements
527
+ ] })
528
+ ]
529
+ }
530
+ ),
531
+ /* @__PURE__ */ jsx3(
532
+ "div",
533
+ {
534
+ style: {
535
+ height: "3px",
536
+ backgroundColor: "rgba(75, 85, 99, 0.5)",
537
+ borderRadius: "2px",
538
+ overflow: "hidden"
539
+ },
540
+ children: /* @__PURE__ */ jsx3(
541
+ "div",
542
+ {
543
+ style: {
544
+ height: "100%",
545
+ width: `${progress}%`,
546
+ backgroundColor: isPaused ? STYLES.warning : STYLES.accent,
547
+ transition: "width 0.2s ease-out"
548
+ }
549
+ }
550
+ )
551
+ }
552
+ )
553
+ ] })
554
+ ]
555
+ }
556
+ ),
557
+ /* @__PURE__ */ jsx3(
558
+ "div",
559
+ {
560
+ style: {
561
+ flex: 1,
562
+ overflowY: "auto",
563
+ padding: "6px 0"
564
+ },
565
+ children: fileResults.length === 0 ? /* @__PURE__ */ jsx3(
566
+ "div",
567
+ {
568
+ style: {
569
+ padding: "20px 14px",
570
+ textAlign: "center",
571
+ fontSize: "11px",
572
+ color: STYLES.textMuted
573
+ },
574
+ children: isScanning ? "Scanning page elements..." : "No files scanned"
575
+ }
576
+ ) : fileResults.map((file) => /* @__PURE__ */ jsxs3(
577
+ "div",
578
+ {
579
+ style: {
580
+ display: "flex",
581
+ alignItems: "center",
582
+ justifyContent: "space-between",
583
+ padding: "8px 14px",
584
+ cursor: "default",
585
+ transition: STYLES.transitionFast
586
+ },
587
+ title: file.path,
588
+ children: [
589
+ /* @__PURE__ */ jsx3(
590
+ "span",
591
+ {
592
+ style: {
593
+ fontSize: "12px",
594
+ fontFamily: STYLES.fontMono,
595
+ color: STYLES.text,
596
+ overflow: "hidden",
597
+ textOverflow: "ellipsis",
598
+ whiteSpace: "nowrap",
599
+ flex: 1,
600
+ marginRight: "12px"
601
+ },
602
+ children: file.displayName
603
+ }
604
+ ),
605
+ /* @__PURE__ */ jsx3(
606
+ "span",
607
+ {
608
+ style: {
609
+ display: "inline-flex",
610
+ alignItems: "center",
611
+ justifyContent: "center",
612
+ minWidth: "24px",
613
+ height: "20px",
614
+ padding: "0 6px",
615
+ borderRadius: "10px",
616
+ backgroundColor: file.issueCount > 0 ? getStatusColor(file.issueCount) : "rgba(75, 85, 99, 0.4)",
617
+ color: file.issueCount > 0 ? "#FFFFFF" : STYLES.textMuted,
618
+ fontSize: "10px",
619
+ fontWeight: 700
620
+ },
621
+ children: file.issueCount
622
+ }
623
+ )
624
+ ]
625
+ },
626
+ file.path
627
+ ))
628
+ }
629
+ ),
630
+ isComplete && totalFiles > 0 && /* @__PURE__ */ jsx3(
631
+ "div",
632
+ {
633
+ style: {
634
+ padding: "10px 14px",
635
+ borderTop: `1px solid ${STYLES.border}`,
636
+ fontSize: "10px",
637
+ color: STYLES.textMuted
638
+ },
639
+ children: totalIssues === 0 ? /* @__PURE__ */ jsxs3("span", { style: { color: STYLES.success }, children: [
640
+ "\u2713 No issues found across ",
641
+ totalFiles,
642
+ " files"
643
+ ] }) : /* @__PURE__ */ jsxs3("span", { children: [
644
+ filesWithIssues,
645
+ " of ",
646
+ totalFiles,
647
+ " files have issues"
648
+ ] })
649
+ }
650
+ )
651
+ ]
652
+ }
653
+ );
654
+ }
655
+
656
+ // src/components/ui-lint/UILintToolbar.tsx
657
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
658
+ function UILintToolbar() {
659
+ const { settings, inspectedElement, autoScanState, startAutoScan } = useUILintContext();
660
+ const elementIssuesCache = useUILintStore(
661
+ (s) => s.elementIssuesCache
662
+ );
663
+ const [showSettings, setShowSettings] = useState(false);
664
+ const [showResults, setShowResults] = useState(false);
665
+ const [mounted, setMounted] = useState(false);
666
+ const toolbarRef = useRef(null);
667
+ const settingsRef = useRef(null);
668
+ const resultsRef = useRef(null);
669
+ useEffect(() => {
670
+ setMounted(true);
671
+ }, []);
672
+ useEffect(() => {
673
+ const handleClickOutside = (e) => {
674
+ const target = e.target;
675
+ if (showSettings && settingsRef.current) {
676
+ if (!settingsRef.current.contains(target) && !toolbarRef.current?.contains(target)) {
677
+ setShowSettings(false);
678
+ }
679
+ }
680
+ if (showResults && resultsRef.current) {
681
+ if (!resultsRef.current.contains(target) && !toolbarRef.current?.contains(target)) {
682
+ setShowResults(false);
683
+ }
684
+ }
685
+ };
686
+ if (showSettings || showResults) {
687
+ document.addEventListener("mousedown", handleClickOutside);
688
+ return () => document.removeEventListener("mousedown", handleClickOutside);
689
+ }
690
+ }, [showSettings, showResults]);
691
+ const handleScanClick = useCallback(() => {
692
+ if (autoScanState.status === "idle") {
693
+ startAutoScan();
694
+ setShowResults(true);
695
+ } else {
696
+ setShowResults(!showResults);
697
+ }
698
+ setShowSettings(false);
699
+ }, [autoScanState.status, startAutoScan, showResults]);
700
+ const handleSettingsClick = useCallback(() => {
701
+ setShowSettings(!showSettings);
702
+ setShowResults(false);
703
+ }, [showSettings]);
704
+ if (!mounted) return null;
705
+ if (inspectedElement) return null;
706
+ const isScanning = autoScanState.status === "scanning";
707
+ const isPaused = autoScanState.status === "paused";
708
+ const isComplete = autoScanState.status === "complete";
709
+ const hasResults = autoScanState.status !== "idle";
710
+ let totalIssues = 0;
711
+ elementIssuesCache.forEach((el) => {
712
+ totalIssues += el.issues.length;
713
+ });
714
+ const sourceFiles = hasResults ? groupBySourceFile(autoScanState.elements) : [];
715
+ const filesWithIssues = sourceFiles.filter((sf) => {
716
+ const issues = sf.elements.reduce((sum, el) => {
717
+ const cached = elementIssuesCache.get(el.id);
718
+ return sum + (cached?.issues.length || 0);
719
+ }, 0);
720
+ return issues > 0;
721
+ }).length;
722
+ const getScanButtonContent = () => {
723
+ if (isScanning) {
724
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
725
+ /* @__PURE__ */ jsx4(SpinnerIcon, {}),
726
+ /* @__PURE__ */ jsx4("span", { children: "Scanning..." })
727
+ ] });
728
+ }
729
+ if (isPaused) {
730
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
731
+ /* @__PURE__ */ jsx4(MagnifyingGlassIcon, {}),
732
+ /* @__PURE__ */ jsx4("span", { children: "Paused" })
733
+ ] });
734
+ }
735
+ if (isComplete) {
736
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
737
+ /* @__PURE__ */ jsx4(CheckCircleIcon, {}),
738
+ /* @__PURE__ */ jsx4("span", { children: totalIssues === 0 ? "All clear" : `${totalIssues} issues` })
739
+ ] });
740
+ }
741
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
742
+ /* @__PURE__ */ jsx4(MagnifyingGlassIcon, {}),
743
+ /* @__PURE__ */ jsx4("span", { children: "Scan" })
744
+ ] });
745
+ };
746
+ const content = /* @__PURE__ */ jsxs4(
747
+ "div",
748
+ {
749
+ "data-ui-lint": true,
750
+ style: {
751
+ position: "fixed",
752
+ bottom: "24px",
753
+ right: "24px",
754
+ zIndex: 99999,
755
+ fontFamily: STYLES.font
756
+ },
757
+ children: [
758
+ /* @__PURE__ */ jsx4("style", { children: `
759
+ @keyframes uilint-fade-in {
760
+ from { opacity: 0; transform: translateY(8px); }
761
+ to { opacity: 1; transform: translateY(0); }
762
+ }
763
+ @keyframes uilint-spin {
764
+ from { transform: rotate(0deg); }
765
+ to { transform: rotate(360deg); }
766
+ }
767
+ ` }),
768
+ /* @__PURE__ */ jsxs4(
769
+ "div",
770
+ {
771
+ style: {
772
+ textAlign: "center",
773
+ marginBottom: "8px",
774
+ fontSize: "11px",
775
+ color: STYLES.textDim,
776
+ letterSpacing: "0.01em"
777
+ },
778
+ children: [
779
+ /* @__PURE__ */ jsx4("span", { style: { color: STYLES.textMuted }, children: "\u2325+Click" }),
780
+ " to inspect"
781
+ ]
782
+ }
783
+ ),
784
+ /* @__PURE__ */ jsxs4(
785
+ "div",
786
+ {
787
+ ref: toolbarRef,
788
+ style: {
789
+ position: "relative",
790
+ display: "inline-flex",
791
+ alignItems: "center",
792
+ height: STYLES.pillHeight,
793
+ borderRadius: STYLES.pillRadius,
794
+ border: `1px solid ${STYLES.border}`,
795
+ backgroundColor: STYLES.bg,
796
+ backdropFilter: STYLES.blur,
797
+ WebkitBackdropFilter: STYLES.blur,
798
+ boxShadow: STYLES.shadow,
799
+ overflow: "hidden"
800
+ },
801
+ children: [
802
+ /* @__PURE__ */ jsxs4(
803
+ "button",
804
+ {
805
+ onClick: handleScanClick,
806
+ style: {
807
+ display: "flex",
808
+ alignItems: "center",
809
+ gap: "6px",
810
+ height: "100%",
811
+ padding: "0 16px",
812
+ border: "none",
813
+ backgroundColor: showResults || hasResults ? STYLES.bgSegmentHover : "transparent",
814
+ color: hasResults ? totalIssues > 0 ? STYLES.warning : STYLES.success : STYLES.text,
815
+ fontSize: "13px",
816
+ fontWeight: 500,
817
+ fontFamily: STYLES.font,
818
+ cursor: "pointer",
819
+ transition: STYLES.transition
820
+ },
821
+ onMouseEnter: (e) => {
822
+ if (!showResults && !hasResults) {
823
+ e.currentTarget.style.backgroundColor = STYLES.bgSegmentHover;
824
+ }
825
+ },
826
+ onMouseLeave: (e) => {
827
+ if (!showResults && !hasResults) {
828
+ e.currentTarget.style.backgroundColor = "transparent";
829
+ }
830
+ },
831
+ title: hasResults ? `${sourceFiles.length} files scanned, ${totalIssues} issues found` : "Scan page for style issues",
832
+ children: [
833
+ getScanButtonContent(),
834
+ isComplete && filesWithIssues > 0 && /* @__PURE__ */ jsx4(
835
+ "span",
836
+ {
837
+ style: {
838
+ display: "inline-flex",
839
+ alignItems: "center",
840
+ justifyContent: "center",
841
+ minWidth: "18px",
842
+ height: "18px",
843
+ padding: "0 5px",
844
+ borderRadius: "9px",
845
+ backgroundColor: STYLES.error,
846
+ color: STYLES.badgeText,
847
+ fontSize: "10px",
848
+ fontWeight: 700
849
+ },
850
+ children: filesWithIssues
851
+ }
852
+ )
853
+ ]
854
+ }
855
+ ),
856
+ /* @__PURE__ */ jsx4(
857
+ "div",
858
+ {
859
+ style: {
860
+ width: "1px",
861
+ height: "20px",
862
+ backgroundColor: STYLES.divider
863
+ }
864
+ }
865
+ ),
866
+ /* @__PURE__ */ jsx4(
867
+ "button",
868
+ {
869
+ onClick: handleSettingsClick,
870
+ style: {
871
+ display: "flex",
872
+ alignItems: "center",
873
+ justifyContent: "center",
874
+ height: "100%",
875
+ width: "40px",
876
+ border: "none",
877
+ backgroundColor: showSettings ? STYLES.bgSegmentHover : "transparent",
878
+ color: showSettings ? STYLES.text : STYLES.textMuted,
879
+ cursor: "pointer",
880
+ transition: STYLES.transition
881
+ },
882
+ onMouseEnter: (e) => {
883
+ if (!showSettings) {
884
+ e.currentTarget.style.backgroundColor = STYLES.bgSegmentHover;
885
+ e.currentTarget.style.color = STYLES.text;
886
+ }
887
+ },
888
+ onMouseLeave: (e) => {
889
+ if (!showSettings) {
890
+ e.currentTarget.style.backgroundColor = "transparent";
891
+ e.currentTarget.style.color = STYLES.textMuted;
892
+ }
893
+ },
894
+ title: "Settings",
895
+ children: /* @__PURE__ */ jsx4(EllipsisIcon, {})
896
+ }
897
+ )
898
+ ]
899
+ }
900
+ ),
901
+ showSettings && /* @__PURE__ */ jsx4("div", { ref: settingsRef, children: /* @__PURE__ */ jsx4(SettingsPopover, { settings }) }),
902
+ showResults && hasResults && /* @__PURE__ */ jsx4("div", { ref: resultsRef, children: /* @__PURE__ */ jsx4(ScanResultsPopover, {}) })
903
+ ]
904
+ }
905
+ );
906
+ return createPortal(content, document.body);
907
+ }
908
+
909
+ export {
910
+ UILintToolbar
911
+ };