uilint-react 0.1.19 → 0.1.21
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 +50 -93
- package/dist/{InspectionPanel-6DBGEWWD.js → InspectionPanel-ZBDXQ2LU.js} +2 -2
- package/dist/LocatorOverlay-3H446RPO.js +11 -0
- package/dist/{UILintToolbar-7ZYCQC4M.js → UILintToolbar-C6HOAJA4.js} +2 -2
- package/dist/{chunk-KUFV22FO.js → chunk-3DNDKMZ4.js} +72 -2
- package/dist/{chunk-3TA6OKS6.js → chunk-CWCKS753.js} +417 -90
- package/dist/{chunk-7WYVWDRU.js → chunk-EBU7YY73.js} +53 -218
- package/dist/chunk-GUF36FGA.js +276 -0
- package/dist/index.d.ts +26 -65
- package/dist/index.js +33 -1097
- package/package.json +2 -2
- package/dist/LocatorOverlay-FQEYAMT6.js +0 -9
- package/dist/SourceOverlays-2SEINA2B.js +0 -9
- package/dist/chunk-MEP7WO7U.js +0 -210
- package/dist/chunk-OWX36QE3.js +0 -595
package/dist/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
UILintToolbar
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
SourceOverlays
|
|
7
|
-
} from "./chunk-MEP7WO7U.js";
|
|
4
|
+
} from "./chunk-GUF36FGA.js";
|
|
8
5
|
import {
|
|
9
6
|
InspectionPanel,
|
|
10
7
|
clearSourceCache,
|
|
@@ -12,8 +9,10 @@ import {
|
|
|
12
9
|
fetchSourceWithContext,
|
|
13
10
|
getCachedSource,
|
|
14
11
|
prefetchSources
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import
|
|
12
|
+
} from "./chunk-CWCKS753.js";
|
|
13
|
+
import {
|
|
14
|
+
LocatorOverlay
|
|
15
|
+
} from "./chunk-3DNDKMZ4.js";
|
|
17
16
|
import {
|
|
18
17
|
DATA_UILINT_ID,
|
|
19
18
|
DEFAULT_SETTINGS,
|
|
@@ -31,19 +30,8 @@ import {
|
|
|
31
30
|
isNodeModulesPath,
|
|
32
31
|
scanDOMForSources,
|
|
33
32
|
updateElementRects,
|
|
34
|
-
useElementScan,
|
|
35
33
|
useUILintContext
|
|
36
|
-
} from "./chunk-
|
|
37
|
-
|
|
38
|
-
// src/components/UILint.tsx
|
|
39
|
-
import {
|
|
40
|
-
createContext,
|
|
41
|
-
useContext,
|
|
42
|
-
useState as useState4,
|
|
43
|
-
useEffect as useEffect2,
|
|
44
|
-
useCallback as useCallback2,
|
|
45
|
-
useRef
|
|
46
|
-
} from "react";
|
|
34
|
+
} from "./chunk-EBU7YY73.js";
|
|
47
35
|
|
|
48
36
|
// src/consistency/snapshot.ts
|
|
49
37
|
var DATA_ELEMENTS_ATTR = "data-elements";
|
|
@@ -244,950 +232,10 @@ function getElementBySnapshotId(id) {
|
|
|
244
232
|
return document.querySelector(`[${DATA_ELEMENTS_ATTR}="${id}"]`);
|
|
245
233
|
}
|
|
246
234
|
|
|
247
|
-
// src/scanner/environment.ts
|
|
248
|
-
function isBrowser() {
|
|
249
|
-
return typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
250
|
-
}
|
|
251
|
-
function isJSDOM() {
|
|
252
|
-
if (!isBrowser()) return false;
|
|
253
|
-
const userAgent = window.navigator?.userAgent || "";
|
|
254
|
-
return userAgent.includes("jsdom");
|
|
255
|
-
}
|
|
256
|
-
function isNode() {
|
|
257
|
-
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// src/components/Overlay.tsx
|
|
261
|
-
import { useState as useState2 } from "react";
|
|
262
|
-
|
|
263
|
-
// src/components/ViolationList.tsx
|
|
264
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
265
|
-
function ViolationList() {
|
|
266
|
-
const {
|
|
267
|
-
violations,
|
|
268
|
-
selectedViolation,
|
|
269
|
-
setSelectedViolation,
|
|
270
|
-
lockedViolation,
|
|
271
|
-
setLockedViolation,
|
|
272
|
-
isScanning
|
|
273
|
-
} = useUILint();
|
|
274
|
-
if (isScanning) {
|
|
275
|
-
return /* @__PURE__ */ jsxs(
|
|
276
|
-
"div",
|
|
277
|
-
{
|
|
278
|
-
style: {
|
|
279
|
-
padding: "32px 16px",
|
|
280
|
-
textAlign: "center",
|
|
281
|
-
color: "#9CA3AF"
|
|
282
|
-
},
|
|
283
|
-
children: [
|
|
284
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: "32px", marginBottom: "8px" }, children: "\u{1F50D}" }),
|
|
285
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: "14px" }, children: "Analyzing page..." }),
|
|
286
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: "12px", marginTop: "4px", color: "#6B7280" }, children: "This may take a moment" })
|
|
287
|
-
]
|
|
288
|
-
}
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
if (violations.length === 0) {
|
|
292
|
-
return /* @__PURE__ */ jsxs(
|
|
293
|
-
"div",
|
|
294
|
-
{
|
|
295
|
-
style: {
|
|
296
|
-
padding: "32px 16px",
|
|
297
|
-
textAlign: "center",
|
|
298
|
-
color: "#9CA3AF"
|
|
299
|
-
},
|
|
300
|
-
children: [
|
|
301
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: "32px", marginBottom: "8px" }, children: "\u2728" }),
|
|
302
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: "14px" }, children: "No consistency issues found" }),
|
|
303
|
-
/* @__PURE__ */ jsx("div", { style: { fontSize: "12px", marginTop: "4px" }, children: 'Click "Scan" to analyze the page' })
|
|
304
|
-
]
|
|
305
|
-
}
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
const handleClick = (violation) => {
|
|
309
|
-
if (lockedViolation?.elementIds.join(",") === violation.elementIds.join(",")) {
|
|
310
|
-
setLockedViolation(null);
|
|
311
|
-
} else {
|
|
312
|
-
setLockedViolation(violation);
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
return /* @__PURE__ */ jsx("div", { style: { padding: "8px" }, children: violations.map((violation, index) => /* @__PURE__ */ jsx(
|
|
316
|
-
ViolationCard,
|
|
317
|
-
{
|
|
318
|
-
violation,
|
|
319
|
-
isSelected: selectedViolation?.elementIds.join(",") === violation.elementIds.join(","),
|
|
320
|
-
isLocked: lockedViolation?.elementIds.join(",") === violation.elementIds.join(","),
|
|
321
|
-
onHover: () => setSelectedViolation(violation),
|
|
322
|
-
onLeave: () => setSelectedViolation(null),
|
|
323
|
-
onClick: () => handleClick(violation)
|
|
324
|
-
},
|
|
325
|
-
`${violation.elementIds.join("-")}-${index}`
|
|
326
|
-
)) });
|
|
327
|
-
}
|
|
328
|
-
function ViolationCard({
|
|
329
|
-
violation,
|
|
330
|
-
isSelected,
|
|
331
|
-
isLocked,
|
|
332
|
-
onHover,
|
|
333
|
-
onLeave,
|
|
334
|
-
onClick
|
|
335
|
-
}) {
|
|
336
|
-
const categoryColors = {
|
|
337
|
-
spacing: "#10B981",
|
|
338
|
-
color: "#F59E0B",
|
|
339
|
-
typography: "#8B5CF6",
|
|
340
|
-
sizing: "#3B82F6",
|
|
341
|
-
borders: "#06B6D4",
|
|
342
|
-
shadows: "#6B7280"
|
|
343
|
-
};
|
|
344
|
-
const severityIcons = {
|
|
345
|
-
error: "\u2716",
|
|
346
|
-
warning: "\u26A0",
|
|
347
|
-
info: "\u2139"
|
|
348
|
-
};
|
|
349
|
-
const categoryColor = categoryColors[violation.category] || "#6B7280";
|
|
350
|
-
const severityIcon = severityIcons[violation.severity] || "\u2022";
|
|
351
|
-
const isHighlighted = isSelected || isLocked;
|
|
352
|
-
return /* @__PURE__ */ jsxs(
|
|
353
|
-
"div",
|
|
354
|
-
{
|
|
355
|
-
onMouseEnter: onHover,
|
|
356
|
-
onMouseLeave: onLeave,
|
|
357
|
-
onClick,
|
|
358
|
-
style: {
|
|
359
|
-
padding: "12px",
|
|
360
|
-
marginBottom: "8px",
|
|
361
|
-
backgroundColor: isHighlighted ? "#374151" : "#111827",
|
|
362
|
-
borderRadius: "8px",
|
|
363
|
-
border: isLocked ? "1px solid #3B82F6" : isSelected ? "1px solid #4B5563" : "1px solid transparent",
|
|
364
|
-
cursor: "pointer",
|
|
365
|
-
transition: "all 0.15s"
|
|
366
|
-
},
|
|
367
|
-
children: [
|
|
368
|
-
/* @__PURE__ */ jsxs(
|
|
369
|
-
"div",
|
|
370
|
-
{
|
|
371
|
-
style: {
|
|
372
|
-
display: "flex",
|
|
373
|
-
alignItems: "center",
|
|
374
|
-
gap: "8px",
|
|
375
|
-
marginBottom: "8px"
|
|
376
|
-
},
|
|
377
|
-
children: [
|
|
378
|
-
/* @__PURE__ */ jsx(
|
|
379
|
-
"div",
|
|
380
|
-
{
|
|
381
|
-
style: {
|
|
382
|
-
display: "inline-block",
|
|
383
|
-
padding: "2px 8px",
|
|
384
|
-
borderRadius: "4px",
|
|
385
|
-
backgroundColor: `${categoryColor}20`,
|
|
386
|
-
color: categoryColor,
|
|
387
|
-
fontSize: "11px",
|
|
388
|
-
fontWeight: "600",
|
|
389
|
-
textTransform: "uppercase"
|
|
390
|
-
},
|
|
391
|
-
children: violation.category
|
|
392
|
-
}
|
|
393
|
-
),
|
|
394
|
-
/* @__PURE__ */ jsx(
|
|
395
|
-
"span",
|
|
396
|
-
{
|
|
397
|
-
style: {
|
|
398
|
-
fontSize: "12px",
|
|
399
|
-
color: violation.severity === "error" ? "#EF4444" : violation.severity === "warning" ? "#F59E0B" : "#9CA3AF"
|
|
400
|
-
},
|
|
401
|
-
children: severityIcon
|
|
402
|
-
}
|
|
403
|
-
),
|
|
404
|
-
/* @__PURE__ */ jsxs(
|
|
405
|
-
"span",
|
|
406
|
-
{
|
|
407
|
-
style: {
|
|
408
|
-
fontSize: "11px",
|
|
409
|
-
color: "#6B7280",
|
|
410
|
-
marginLeft: "auto"
|
|
411
|
-
},
|
|
412
|
-
children: [
|
|
413
|
-
violation.elementIds.length,
|
|
414
|
-
" element",
|
|
415
|
-
violation.elementIds.length !== 1 ? "s" : ""
|
|
416
|
-
]
|
|
417
|
-
}
|
|
418
|
-
)
|
|
419
|
-
]
|
|
420
|
-
}
|
|
421
|
-
),
|
|
422
|
-
/* @__PURE__ */ jsx(
|
|
423
|
-
"div",
|
|
424
|
-
{
|
|
425
|
-
style: {
|
|
426
|
-
fontSize: "13px",
|
|
427
|
-
color: "#F3F4F6",
|
|
428
|
-
lineHeight: "1.4",
|
|
429
|
-
marginBottom: "8px"
|
|
430
|
-
},
|
|
431
|
-
children: violation.message
|
|
432
|
-
}
|
|
433
|
-
),
|
|
434
|
-
violation.details && /* @__PURE__ */ jsxs(
|
|
435
|
-
"div",
|
|
436
|
-
{
|
|
437
|
-
style: {
|
|
438
|
-
fontSize: "12px",
|
|
439
|
-
color: "#9CA3AF"
|
|
440
|
-
},
|
|
441
|
-
children: [
|
|
442
|
-
violation.details.property && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "4px" }, children: [
|
|
443
|
-
/* @__PURE__ */ jsx("span", { style: { color: "#6B7280" }, children: "Property: " }),
|
|
444
|
-
/* @__PURE__ */ jsx(
|
|
445
|
-
"code",
|
|
446
|
-
{
|
|
447
|
-
style: {
|
|
448
|
-
padding: "2px 4px",
|
|
449
|
-
backgroundColor: "#374151",
|
|
450
|
-
borderRadius: "3px",
|
|
451
|
-
fontSize: "11px"
|
|
452
|
-
},
|
|
453
|
-
children: violation.details.property
|
|
454
|
-
}
|
|
455
|
-
)
|
|
456
|
-
] }),
|
|
457
|
-
violation.details.values.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "4px" }, children: [
|
|
458
|
-
/* @__PURE__ */ jsx("span", { style: { color: "#6B7280" }, children: "Values: " }),
|
|
459
|
-
violation.details.values.map((val, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
460
|
-
/* @__PURE__ */ jsx(
|
|
461
|
-
"code",
|
|
462
|
-
{
|
|
463
|
-
style: {
|
|
464
|
-
padding: "2px 4px",
|
|
465
|
-
backgroundColor: "#374151",
|
|
466
|
-
borderRadius: "3px",
|
|
467
|
-
fontSize: "11px"
|
|
468
|
-
},
|
|
469
|
-
children: val
|
|
470
|
-
}
|
|
471
|
-
),
|
|
472
|
-
i < violation.details.values.length - 1 && /* @__PURE__ */ jsx("span", { style: { margin: "0 4px", color: "#6B7280" }, children: "vs" })
|
|
473
|
-
] }, i))
|
|
474
|
-
] })
|
|
475
|
-
]
|
|
476
|
-
}
|
|
477
|
-
),
|
|
478
|
-
violation.details.suggestion && /* @__PURE__ */ jsxs(
|
|
479
|
-
"div",
|
|
480
|
-
{
|
|
481
|
-
style: {
|
|
482
|
-
marginTop: "8px",
|
|
483
|
-
padding: "8px",
|
|
484
|
-
backgroundColor: "#1E3A5F",
|
|
485
|
-
borderRadius: "4px",
|
|
486
|
-
fontSize: "12px",
|
|
487
|
-
color: "#93C5FD"
|
|
488
|
-
},
|
|
489
|
-
children: [
|
|
490
|
-
"\u{1F4A1} ",
|
|
491
|
-
violation.details.suggestion
|
|
492
|
-
]
|
|
493
|
-
}
|
|
494
|
-
),
|
|
495
|
-
isLocked && /* @__PURE__ */ jsx(
|
|
496
|
-
"div",
|
|
497
|
-
{
|
|
498
|
-
style: {
|
|
499
|
-
marginTop: "8px",
|
|
500
|
-
fontSize: "11px",
|
|
501
|
-
color: "#3B82F6"
|
|
502
|
-
},
|
|
503
|
-
children: "\u{1F512} Click to unlock"
|
|
504
|
-
}
|
|
505
|
-
)
|
|
506
|
-
]
|
|
507
|
-
}
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// src/components/QuestionPanel.tsx
|
|
512
|
-
import { useState } from "react";
|
|
513
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
514
|
-
function QuestionPanel() {
|
|
515
|
-
const { issues } = useUILint();
|
|
516
|
-
const [answers, setAnswers] = useState({});
|
|
517
|
-
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
518
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
519
|
-
const [saveSuccess, setSaveSuccess] = useState(false);
|
|
520
|
-
const [saveError, setSaveError] = useState(null);
|
|
521
|
-
const questions = generateQuestionsFromIssues(issues);
|
|
522
|
-
if (questions.length === 0) {
|
|
523
|
-
return /* @__PURE__ */ jsxs2(
|
|
524
|
-
"div",
|
|
525
|
-
{
|
|
526
|
-
style: {
|
|
527
|
-
padding: "32px 16px",
|
|
528
|
-
textAlign: "center",
|
|
529
|
-
color: "#9CA3AF"
|
|
530
|
-
},
|
|
531
|
-
children: [
|
|
532
|
-
/* @__PURE__ */ jsx2("div", { style: { fontSize: "32px", marginBottom: "8px" }, children: "\u{1F3AF}" }),
|
|
533
|
-
/* @__PURE__ */ jsx2("div", { style: { fontSize: "14px" }, children: "No style conflicts to resolve" }),
|
|
534
|
-
/* @__PURE__ */ jsx2("div", { style: { fontSize: "12px", marginTop: "4px" }, children: "Scan the page to detect inconsistencies" })
|
|
535
|
-
]
|
|
536
|
-
}
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
const currentQuestion = questions[currentQuestionIndex];
|
|
540
|
-
const handleAnswer = (value) => {
|
|
541
|
-
setAnswers((prev) => ({
|
|
542
|
-
...prev,
|
|
543
|
-
[currentQuestion.id]: value
|
|
544
|
-
}));
|
|
545
|
-
if (currentQuestionIndex < questions.length - 1) {
|
|
546
|
-
setCurrentQuestionIndex((prev) => prev + 1);
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
const handleSaveToStyleGuide = async () => {
|
|
550
|
-
console.log("[UILint] Saving preferences:", answers);
|
|
551
|
-
setIsSaving(true);
|
|
552
|
-
setSaveSuccess(false);
|
|
553
|
-
setSaveError(null);
|
|
554
|
-
try {
|
|
555
|
-
const getResponse = await fetch("/api/uilint/styleguide");
|
|
556
|
-
const data = await getResponse.json().catch(() => ({}));
|
|
557
|
-
if (!getResponse.ok || !data?.exists || !data?.content) {
|
|
558
|
-
throw new Error(
|
|
559
|
-
data?.error || 'No style guide found. Create ".uilint/styleguide.md" at your workspace root first.'
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
const updatedContent = applyAnswersToStyleGuide(data.content, answers);
|
|
563
|
-
const postResponse = await fetch("/api/uilint/styleguide", {
|
|
564
|
-
method: "POST",
|
|
565
|
-
headers: { "Content-Type": "application/json" },
|
|
566
|
-
body: JSON.stringify({ content: updatedContent })
|
|
567
|
-
});
|
|
568
|
-
if (!postResponse.ok) {
|
|
569
|
-
const err = await postResponse.json().catch(() => ({}));
|
|
570
|
-
throw new Error(err?.error || "Failed to save style guide");
|
|
571
|
-
}
|
|
572
|
-
console.log("[UILint] Style guide saved successfully!");
|
|
573
|
-
setSaveSuccess(true);
|
|
574
|
-
setTimeout(() => {
|
|
575
|
-
setAnswers({});
|
|
576
|
-
setCurrentQuestionIndex(0);
|
|
577
|
-
setSaveSuccess(false);
|
|
578
|
-
}, 1500);
|
|
579
|
-
} catch (error) {
|
|
580
|
-
console.error("[UILint] Error saving style guide:", error);
|
|
581
|
-
setSaveError(error instanceof Error ? error.message : "Save failed");
|
|
582
|
-
} finally {
|
|
583
|
-
setIsSaving(false);
|
|
584
|
-
}
|
|
585
|
-
};
|
|
586
|
-
const isComplete = Object.keys(answers).length === questions.length;
|
|
587
|
-
return /* @__PURE__ */ jsxs2("div", { style: { padding: "16px" }, children: [
|
|
588
|
-
/* @__PURE__ */ jsxs2(
|
|
589
|
-
"div",
|
|
590
|
-
{
|
|
591
|
-
style: {
|
|
592
|
-
display: "flex",
|
|
593
|
-
justifyContent: "space-between",
|
|
594
|
-
alignItems: "center",
|
|
595
|
-
marginBottom: "16px"
|
|
596
|
-
},
|
|
597
|
-
children: [
|
|
598
|
-
/* @__PURE__ */ jsxs2("span", { style: { fontSize: "12px", color: "#9CA3AF" }, children: [
|
|
599
|
-
"Question ",
|
|
600
|
-
currentQuestionIndex + 1,
|
|
601
|
-
" of ",
|
|
602
|
-
questions.length
|
|
603
|
-
] }),
|
|
604
|
-
/* @__PURE__ */ jsx2(
|
|
605
|
-
"div",
|
|
606
|
-
{
|
|
607
|
-
style: {
|
|
608
|
-
width: "100px",
|
|
609
|
-
height: "4px",
|
|
610
|
-
backgroundColor: "#374151",
|
|
611
|
-
borderRadius: "2px",
|
|
612
|
-
overflow: "hidden"
|
|
613
|
-
},
|
|
614
|
-
children: /* @__PURE__ */ jsx2(
|
|
615
|
-
"div",
|
|
616
|
-
{
|
|
617
|
-
style: {
|
|
618
|
-
width: `${(currentQuestionIndex + 1) / questions.length * 100}%`,
|
|
619
|
-
height: "100%",
|
|
620
|
-
backgroundColor: "#3B82F6",
|
|
621
|
-
transition: "width 0.3s"
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
)
|
|
625
|
-
}
|
|
626
|
-
)
|
|
627
|
-
]
|
|
628
|
-
}
|
|
629
|
-
),
|
|
630
|
-
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: "16px" }, children: [
|
|
631
|
-
/* @__PURE__ */ jsx2(
|
|
632
|
-
"div",
|
|
633
|
-
{
|
|
634
|
-
style: {
|
|
635
|
-
fontSize: "14px",
|
|
636
|
-
fontWeight: "500",
|
|
637
|
-
color: "#F3F4F6",
|
|
638
|
-
marginBottom: "8px"
|
|
639
|
-
},
|
|
640
|
-
children: currentQuestion.question
|
|
641
|
-
}
|
|
642
|
-
),
|
|
643
|
-
currentQuestion.context && /* @__PURE__ */ jsx2(
|
|
644
|
-
"div",
|
|
645
|
-
{
|
|
646
|
-
style: {
|
|
647
|
-
fontSize: "12px",
|
|
648
|
-
color: "#9CA3AF",
|
|
649
|
-
marginBottom: "12px"
|
|
650
|
-
},
|
|
651
|
-
children: currentQuestion.context
|
|
652
|
-
}
|
|
653
|
-
)
|
|
654
|
-
] }),
|
|
655
|
-
/* @__PURE__ */ jsx2("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: currentQuestion.options.map((option) => /* @__PURE__ */ jsxs2(
|
|
656
|
-
"button",
|
|
657
|
-
{
|
|
658
|
-
onClick: () => handleAnswer(option.value),
|
|
659
|
-
style: {
|
|
660
|
-
display: "flex",
|
|
661
|
-
alignItems: "center",
|
|
662
|
-
gap: "12px",
|
|
663
|
-
padding: "12px",
|
|
664
|
-
backgroundColor: answers[currentQuestion.id] === option.value ? "#374151" : "#111827",
|
|
665
|
-
border: answers[currentQuestion.id] === option.value ? "1px solid #3B82F6" : "1px solid #374151",
|
|
666
|
-
borderRadius: "8px",
|
|
667
|
-
color: "#F3F4F6",
|
|
668
|
-
fontSize: "13px",
|
|
669
|
-
textAlign: "left",
|
|
670
|
-
cursor: "pointer",
|
|
671
|
-
transition: "all 0.15s"
|
|
672
|
-
},
|
|
673
|
-
children: [
|
|
674
|
-
option.preview && /* @__PURE__ */ jsx2(
|
|
675
|
-
"div",
|
|
676
|
-
{
|
|
677
|
-
style: {
|
|
678
|
-
width: "32px",
|
|
679
|
-
height: "32px",
|
|
680
|
-
borderRadius: "4px",
|
|
681
|
-
display: "flex",
|
|
682
|
-
alignItems: "center",
|
|
683
|
-
justifyContent: "center"
|
|
684
|
-
},
|
|
685
|
-
children: option.preview
|
|
686
|
-
}
|
|
687
|
-
),
|
|
688
|
-
/* @__PURE__ */ jsx2("span", { children: option.label })
|
|
689
|
-
]
|
|
690
|
-
},
|
|
691
|
-
option.value
|
|
692
|
-
)) }),
|
|
693
|
-
/* @__PURE__ */ jsxs2(
|
|
694
|
-
"div",
|
|
695
|
-
{
|
|
696
|
-
style: {
|
|
697
|
-
display: "flex",
|
|
698
|
-
justifyContent: "space-between",
|
|
699
|
-
marginTop: "16px"
|
|
700
|
-
},
|
|
701
|
-
children: [
|
|
702
|
-
/* @__PURE__ */ jsx2(
|
|
703
|
-
"button",
|
|
704
|
-
{
|
|
705
|
-
onClick: () => setCurrentQuestionIndex((prev) => Math.max(0, prev - 1)),
|
|
706
|
-
disabled: currentQuestionIndex === 0,
|
|
707
|
-
style: {
|
|
708
|
-
padding: "8px 16px",
|
|
709
|
-
backgroundColor: "transparent",
|
|
710
|
-
border: "1px solid #374151",
|
|
711
|
-
borderRadius: "6px",
|
|
712
|
-
color: currentQuestionIndex === 0 ? "#4B5563" : "#9CA3AF",
|
|
713
|
-
fontSize: "12px",
|
|
714
|
-
cursor: currentQuestionIndex === 0 ? "not-allowed" : "pointer"
|
|
715
|
-
},
|
|
716
|
-
children: "\u2190 Back"
|
|
717
|
-
}
|
|
718
|
-
),
|
|
719
|
-
isComplete && /* @__PURE__ */ jsx2(
|
|
720
|
-
"button",
|
|
721
|
-
{
|
|
722
|
-
onClick: handleSaveToStyleGuide,
|
|
723
|
-
disabled: isSaving,
|
|
724
|
-
style: {
|
|
725
|
-
padding: "8px 16px",
|
|
726
|
-
backgroundColor: saveSuccess ? "#059669" : isSaving ? "#6B7280" : "#10B981",
|
|
727
|
-
border: "none",
|
|
728
|
-
borderRadius: "6px",
|
|
729
|
-
color: "white",
|
|
730
|
-
fontSize: "12px",
|
|
731
|
-
fontWeight: "500",
|
|
732
|
-
cursor: isSaving ? "wait" : "pointer",
|
|
733
|
-
opacity: isSaving ? 0.8 : 1,
|
|
734
|
-
transition: "all 0.2s"
|
|
735
|
-
},
|
|
736
|
-
children: saveSuccess ? "\u2713 Saved!" : isSaving ? "Saving..." : "Save to Style Guide"
|
|
737
|
-
}
|
|
738
|
-
)
|
|
739
|
-
]
|
|
740
|
-
}
|
|
741
|
-
),
|
|
742
|
-
saveError && /* @__PURE__ */ jsx2(
|
|
743
|
-
"div",
|
|
744
|
-
{
|
|
745
|
-
style: {
|
|
746
|
-
marginTop: "12px",
|
|
747
|
-
padding: "10px",
|
|
748
|
-
borderRadius: "8px",
|
|
749
|
-
backgroundColor: "#7F1D1D",
|
|
750
|
-
border: "1px solid #EF4444",
|
|
751
|
-
color: "#FEE2E2",
|
|
752
|
-
fontSize: "12px",
|
|
753
|
-
lineHeight: 1.4
|
|
754
|
-
},
|
|
755
|
-
children: saveError
|
|
756
|
-
}
|
|
757
|
-
)
|
|
758
|
-
] });
|
|
759
|
-
}
|
|
760
|
-
function generateQuestionsFromIssues(issues) {
|
|
761
|
-
const questions = [];
|
|
762
|
-
const colorIssues = issues.filter((i) => i.type === "color");
|
|
763
|
-
if (colorIssues.length > 0) {
|
|
764
|
-
const colors = /* @__PURE__ */ new Set();
|
|
765
|
-
colorIssues.forEach((issue) => {
|
|
766
|
-
if (issue.currentValue) colors.add(issue.currentValue);
|
|
767
|
-
if (issue.expectedValue) colors.add(issue.expectedValue);
|
|
768
|
-
});
|
|
769
|
-
if (colors.size >= 2) {
|
|
770
|
-
const colorArray = Array.from(colors);
|
|
771
|
-
questions.push({
|
|
772
|
-
id: "primary-color",
|
|
773
|
-
question: "Which color should be used as the primary color?",
|
|
774
|
-
context: "Multiple similar colors were detected. Choose one for consistency.",
|
|
775
|
-
options: colorArray.slice(0, 4).map((color) => ({
|
|
776
|
-
value: color,
|
|
777
|
-
label: color,
|
|
778
|
-
preview: /* @__PURE__ */ jsx2(
|
|
779
|
-
"div",
|
|
780
|
-
{
|
|
781
|
-
style: {
|
|
782
|
-
width: "100%",
|
|
783
|
-
height: "100%",
|
|
784
|
-
backgroundColor: color,
|
|
785
|
-
borderRadius: "4px"
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
)
|
|
789
|
-
}))
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
const spacingIssues = issues.filter((i) => i.type === "spacing");
|
|
794
|
-
if (spacingIssues.length > 0) {
|
|
795
|
-
questions.push({
|
|
796
|
-
id: "spacing-scale",
|
|
797
|
-
question: "What spacing scale should be used?",
|
|
798
|
-
context: "Choose a base unit for consistent spacing throughout the UI.",
|
|
799
|
-
options: [
|
|
800
|
-
{ value: "4", label: "4px base (4, 8, 12, 16, 20, 24...)" },
|
|
801
|
-
{ value: "8", label: "8px base (8, 16, 24, 32, 40...)" },
|
|
802
|
-
{
|
|
803
|
-
value: "tailwind",
|
|
804
|
-
label: "Tailwind scale (4, 8, 12, 16, 20, 24...)"
|
|
805
|
-
}
|
|
806
|
-
]
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
const typographyIssues = issues.filter((i) => i.type === "typography");
|
|
810
|
-
if (typographyIssues.length > 0) {
|
|
811
|
-
questions.push({
|
|
812
|
-
id: "font-weights",
|
|
813
|
-
question: "Which font weights should be used?",
|
|
814
|
-
context: "Select the weights to use for consistency.",
|
|
815
|
-
options: [
|
|
816
|
-
{
|
|
817
|
-
value: "400-600-700",
|
|
818
|
-
label: "Regular (400), Semibold (600), Bold (700)"
|
|
819
|
-
},
|
|
820
|
-
{
|
|
821
|
-
value: "400-500-700",
|
|
822
|
-
label: "Regular (400), Medium (500), Bold (700)"
|
|
823
|
-
},
|
|
824
|
-
{
|
|
825
|
-
value: "300-400-600",
|
|
826
|
-
label: "Light (300), Regular (400), Semibold (600)"
|
|
827
|
-
}
|
|
828
|
-
]
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
return questions;
|
|
832
|
-
}
|
|
833
|
-
function applyAnswersToStyleGuide(existingContent, answers) {
|
|
834
|
-
let content = existingContent;
|
|
835
|
-
if (answers["primary-color"]) {
|
|
836
|
-
content = upsertBulletInSection(
|
|
837
|
-
content,
|
|
838
|
-
"Colors",
|
|
839
|
-
"Primary",
|
|
840
|
-
answers["primary-color"]
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
if (answers["font-weights"]) {
|
|
844
|
-
const weightMap = {
|
|
845
|
-
"400-600-700": "400 (Regular), 600 (Semibold), 700 (Bold)",
|
|
846
|
-
"400-500-700": "400 (Regular), 500 (Medium), 700 (Bold)",
|
|
847
|
-
"300-400-600": "300 (Light), 400 (Regular), 600 (Semibold)"
|
|
848
|
-
};
|
|
849
|
-
const value = weightMap[answers["font-weights"]] || answers["font-weights"];
|
|
850
|
-
content = upsertBulletInSection(
|
|
851
|
-
content,
|
|
852
|
-
"Typography",
|
|
853
|
-
"Font Weights",
|
|
854
|
-
value
|
|
855
|
-
);
|
|
856
|
-
}
|
|
857
|
-
if (answers["spacing-scale"]) {
|
|
858
|
-
const spacingMap = {
|
|
859
|
-
"4": "4px (4, 8, 12, 16, 20, 24, 32, 40, 48...)",
|
|
860
|
-
"8": "8px (8, 16, 24, 32, 40, 48, 56, 64...)",
|
|
861
|
-
tailwind: "Tailwind (4, 8, 12, 16, 20, 24, 32, 40, 48...)"
|
|
862
|
-
};
|
|
863
|
-
const value = spacingMap[answers["spacing-scale"]] || answers["spacing-scale"];
|
|
864
|
-
content = upsertBulletInSection(content, "Spacing", "Base unit", value);
|
|
865
|
-
}
|
|
866
|
-
return content;
|
|
867
|
-
}
|
|
868
|
-
function upsertBulletInSection(markdown, sectionName, label, value) {
|
|
869
|
-
const lines = markdown.split("\n");
|
|
870
|
-
const sectionStart = lines.findIndex(
|
|
871
|
-
(line) => line.match(new RegExp(`^##\\s+${sectionName}\\s*$`, "i"))
|
|
872
|
-
);
|
|
873
|
-
if (sectionStart === -1) {
|
|
874
|
-
throw new Error(
|
|
875
|
-
`Style guide is missing section "## ${sectionName}". Add it to your style guide and try again.`
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
let sectionEnd = lines.length;
|
|
879
|
-
for (let i = sectionStart + 1; i < lines.length; i++) {
|
|
880
|
-
if (lines[i].startsWith("## ")) {
|
|
881
|
-
sectionEnd = i;
|
|
882
|
-
break;
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
const bulletRe = new RegExp(
|
|
886
|
-
`^-\\s+\\*\\*${escapeRegExp(label)}\\*\\*:\\s+.*$`
|
|
887
|
-
);
|
|
888
|
-
const newBullet = `- **${label}**: ${value}`;
|
|
889
|
-
for (let i = sectionStart + 1; i < sectionEnd; i++) {
|
|
890
|
-
if (bulletRe.test(lines[i])) {
|
|
891
|
-
lines[i] = newBullet;
|
|
892
|
-
return lines.join("\n");
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
let insertAt = sectionStart + 1;
|
|
896
|
-
while (insertAt < sectionEnd && lines[insertAt].trim() === "") insertAt++;
|
|
897
|
-
lines.splice(insertAt, 0, newBullet);
|
|
898
|
-
return lines.join("\n");
|
|
899
|
-
}
|
|
900
|
-
function escapeRegExp(s) {
|
|
901
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
// src/components/Overlay.tsx
|
|
905
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
906
|
-
function Overlay({ position }) {
|
|
907
|
-
const [isExpanded, setIsExpanded] = useState2(false);
|
|
908
|
-
const { violations, isScanning, scan, elementCount } = useUILint();
|
|
909
|
-
const positionStyles = {
|
|
910
|
-
position: "fixed",
|
|
911
|
-
zIndex: 99999,
|
|
912
|
-
...position.includes("bottom") ? { bottom: "16px" } : { top: "16px" },
|
|
913
|
-
...position.includes("left") ? { left: "16px" } : { right: "16px" }
|
|
914
|
-
};
|
|
915
|
-
const violationCount = violations.length;
|
|
916
|
-
const hasViolations = violationCount > 0;
|
|
917
|
-
return /* @__PURE__ */ jsx3("div", { style: positionStyles, children: isExpanded ? /* @__PURE__ */ jsx3(
|
|
918
|
-
ExpandedPanel,
|
|
919
|
-
{
|
|
920
|
-
onCollapse: () => setIsExpanded(false),
|
|
921
|
-
onScan: scan,
|
|
922
|
-
isScanning,
|
|
923
|
-
elementCount
|
|
924
|
-
}
|
|
925
|
-
) : /* @__PURE__ */ jsx3(
|
|
926
|
-
CollapsedButton,
|
|
927
|
-
{
|
|
928
|
-
onClick: () => setIsExpanded(true),
|
|
929
|
-
violationCount,
|
|
930
|
-
hasViolations,
|
|
931
|
-
isScanning
|
|
932
|
-
}
|
|
933
|
-
) });
|
|
934
|
-
}
|
|
935
|
-
function CollapsedButton({
|
|
936
|
-
onClick,
|
|
937
|
-
violationCount,
|
|
938
|
-
hasViolations,
|
|
939
|
-
isScanning
|
|
940
|
-
}) {
|
|
941
|
-
return /* @__PURE__ */ jsx3(
|
|
942
|
-
"button",
|
|
943
|
-
{
|
|
944
|
-
onClick,
|
|
945
|
-
style: {
|
|
946
|
-
display: "flex",
|
|
947
|
-
alignItems: "center",
|
|
948
|
-
justifyContent: "center",
|
|
949
|
-
width: "48px",
|
|
950
|
-
height: "48px",
|
|
951
|
-
borderRadius: "50%",
|
|
952
|
-
border: "none",
|
|
953
|
-
backgroundColor: isScanning ? "#3B82F6" : hasViolations ? "#EF4444" : "#10B981",
|
|
954
|
-
color: "white",
|
|
955
|
-
cursor: "pointer",
|
|
956
|
-
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
|
957
|
-
transition: "transform 0.2s, box-shadow 0.2s",
|
|
958
|
-
fontSize: "20px",
|
|
959
|
-
fontWeight: "bold"
|
|
960
|
-
},
|
|
961
|
-
onMouseEnter: (e) => {
|
|
962
|
-
e.currentTarget.style.transform = "scale(1.1)";
|
|
963
|
-
e.currentTarget.style.boxShadow = "0 6px 16px rgba(0, 0, 0, 0.2)";
|
|
964
|
-
},
|
|
965
|
-
onMouseLeave: (e) => {
|
|
966
|
-
e.currentTarget.style.transform = "scale(1)";
|
|
967
|
-
e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.15)";
|
|
968
|
-
},
|
|
969
|
-
title: isScanning ? "Analyzing..." : `UILint: ${violationCount} issue${violationCount !== 1 ? "s" : ""} found`,
|
|
970
|
-
children: isScanning ? /* @__PURE__ */ jsx3(SpinnerIcon, {}) : hasViolations ? violationCount : "\u2713"
|
|
971
|
-
}
|
|
972
|
-
);
|
|
973
|
-
}
|
|
974
|
-
function SpinnerIcon() {
|
|
975
|
-
return /* @__PURE__ */ jsxs3(
|
|
976
|
-
"svg",
|
|
977
|
-
{
|
|
978
|
-
width: "20",
|
|
979
|
-
height: "20",
|
|
980
|
-
viewBox: "0 0 24 24",
|
|
981
|
-
fill: "none",
|
|
982
|
-
style: {
|
|
983
|
-
animation: "uilint-spin 1s linear infinite"
|
|
984
|
-
},
|
|
985
|
-
children: [
|
|
986
|
-
/* @__PURE__ */ jsx3("style", { children: `
|
|
987
|
-
@keyframes uilint-spin {
|
|
988
|
-
from { transform: rotate(0deg); }
|
|
989
|
-
to { transform: rotate(360deg); }
|
|
990
|
-
}
|
|
991
|
-
` }),
|
|
992
|
-
/* @__PURE__ */ jsx3(
|
|
993
|
-
"circle",
|
|
994
|
-
{
|
|
995
|
-
cx: "12",
|
|
996
|
-
cy: "12",
|
|
997
|
-
r: "10",
|
|
998
|
-
stroke: "currentColor",
|
|
999
|
-
strokeWidth: "3",
|
|
1000
|
-
strokeLinecap: "round",
|
|
1001
|
-
strokeDasharray: "31.4 31.4",
|
|
1002
|
-
fill: "none"
|
|
1003
|
-
}
|
|
1004
|
-
)
|
|
1005
|
-
]
|
|
1006
|
-
}
|
|
1007
|
-
);
|
|
1008
|
-
}
|
|
1009
|
-
function ExpandedPanel({
|
|
1010
|
-
onCollapse,
|
|
1011
|
-
onScan,
|
|
1012
|
-
isScanning,
|
|
1013
|
-
elementCount
|
|
1014
|
-
}) {
|
|
1015
|
-
const [activeTab, setActiveTab] = useState2(
|
|
1016
|
-
"violations"
|
|
1017
|
-
);
|
|
1018
|
-
return /* @__PURE__ */ jsxs3(
|
|
1019
|
-
"div",
|
|
1020
|
-
{
|
|
1021
|
-
style: {
|
|
1022
|
-
width: "380px",
|
|
1023
|
-
maxHeight: "500px",
|
|
1024
|
-
backgroundColor: "#1F2937",
|
|
1025
|
-
borderRadius: "12px",
|
|
1026
|
-
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",
|
|
1027
|
-
overflow: "hidden",
|
|
1028
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
1029
|
-
color: "#F9FAFB"
|
|
1030
|
-
},
|
|
1031
|
-
children: [
|
|
1032
|
-
/* @__PURE__ */ jsxs3(
|
|
1033
|
-
"div",
|
|
1034
|
-
{
|
|
1035
|
-
style: {
|
|
1036
|
-
display: "flex",
|
|
1037
|
-
alignItems: "center",
|
|
1038
|
-
justifyContent: "space-between",
|
|
1039
|
-
padding: "12px 16px",
|
|
1040
|
-
borderBottom: "1px solid #374151",
|
|
1041
|
-
backgroundColor: "#111827"
|
|
1042
|
-
},
|
|
1043
|
-
children: [
|
|
1044
|
-
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
|
|
1045
|
-
/* @__PURE__ */ jsx3("span", { style: { fontSize: "16px" }, children: "\u{1F3A8}" }),
|
|
1046
|
-
/* @__PURE__ */ jsx3("span", { style: { fontWeight: "600", fontSize: "14px" }, children: "UILint" }),
|
|
1047
|
-
elementCount > 0 && !isScanning && /* @__PURE__ */ jsxs3(
|
|
1048
|
-
"span",
|
|
1049
|
-
{
|
|
1050
|
-
style: {
|
|
1051
|
-
fontSize: "11px",
|
|
1052
|
-
color: "#6B7280",
|
|
1053
|
-
marginLeft: "4px"
|
|
1054
|
-
},
|
|
1055
|
-
children: [
|
|
1056
|
-
"(",
|
|
1057
|
-
elementCount,
|
|
1058
|
-
" elements)"
|
|
1059
|
-
]
|
|
1060
|
-
}
|
|
1061
|
-
)
|
|
1062
|
-
] }),
|
|
1063
|
-
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
|
|
1064
|
-
/* @__PURE__ */ jsxs3(
|
|
1065
|
-
"button",
|
|
1066
|
-
{
|
|
1067
|
-
onClick: onScan,
|
|
1068
|
-
disabled: isScanning,
|
|
1069
|
-
style: {
|
|
1070
|
-
padding: "6px 12px",
|
|
1071
|
-
borderRadius: "6px",
|
|
1072
|
-
border: "none",
|
|
1073
|
-
backgroundColor: "#3B82F6",
|
|
1074
|
-
color: "white",
|
|
1075
|
-
fontSize: "12px",
|
|
1076
|
-
fontWeight: "500",
|
|
1077
|
-
cursor: isScanning ? "not-allowed" : "pointer",
|
|
1078
|
-
opacity: isScanning ? 0.7 : 1,
|
|
1079
|
-
display: "flex",
|
|
1080
|
-
alignItems: "center",
|
|
1081
|
-
gap: "6px"
|
|
1082
|
-
},
|
|
1083
|
-
children: [
|
|
1084
|
-
isScanning && /* @__PURE__ */ jsx3(
|
|
1085
|
-
"svg",
|
|
1086
|
-
{
|
|
1087
|
-
width: "12",
|
|
1088
|
-
height: "12",
|
|
1089
|
-
viewBox: "0 0 24 24",
|
|
1090
|
-
fill: "none",
|
|
1091
|
-
style: {
|
|
1092
|
-
animation: "uilint-spin 1s linear infinite"
|
|
1093
|
-
},
|
|
1094
|
-
children: /* @__PURE__ */ jsx3(
|
|
1095
|
-
"circle",
|
|
1096
|
-
{
|
|
1097
|
-
cx: "12",
|
|
1098
|
-
cy: "12",
|
|
1099
|
-
r: "10",
|
|
1100
|
-
stroke: "currentColor",
|
|
1101
|
-
strokeWidth: "3",
|
|
1102
|
-
strokeLinecap: "round",
|
|
1103
|
-
strokeDasharray: "31.4 31.4",
|
|
1104
|
-
fill: "none"
|
|
1105
|
-
}
|
|
1106
|
-
)
|
|
1107
|
-
}
|
|
1108
|
-
),
|
|
1109
|
-
isScanning ? "Analyzing..." : "Scan"
|
|
1110
|
-
]
|
|
1111
|
-
}
|
|
1112
|
-
),
|
|
1113
|
-
/* @__PURE__ */ jsx3(
|
|
1114
|
-
"button",
|
|
1115
|
-
{
|
|
1116
|
-
onClick: onCollapse,
|
|
1117
|
-
style: {
|
|
1118
|
-
padding: "6px 8px",
|
|
1119
|
-
borderRadius: "6px",
|
|
1120
|
-
border: "none",
|
|
1121
|
-
backgroundColor: "transparent",
|
|
1122
|
-
color: "#9CA3AF",
|
|
1123
|
-
fontSize: "16px",
|
|
1124
|
-
cursor: "pointer"
|
|
1125
|
-
},
|
|
1126
|
-
children: "\u2715"
|
|
1127
|
-
}
|
|
1128
|
-
)
|
|
1129
|
-
] })
|
|
1130
|
-
]
|
|
1131
|
-
}
|
|
1132
|
-
),
|
|
1133
|
-
/* @__PURE__ */ jsxs3(
|
|
1134
|
-
"div",
|
|
1135
|
-
{
|
|
1136
|
-
style: {
|
|
1137
|
-
display: "flex",
|
|
1138
|
-
borderBottom: "1px solid #374151"
|
|
1139
|
-
},
|
|
1140
|
-
children: [
|
|
1141
|
-
/* @__PURE__ */ jsx3(
|
|
1142
|
-
TabButton,
|
|
1143
|
-
{
|
|
1144
|
-
active: activeTab === "violations",
|
|
1145
|
-
onClick: () => setActiveTab("violations"),
|
|
1146
|
-
children: "Violations"
|
|
1147
|
-
}
|
|
1148
|
-
),
|
|
1149
|
-
/* @__PURE__ */ jsx3(
|
|
1150
|
-
TabButton,
|
|
1151
|
-
{
|
|
1152
|
-
active: activeTab === "questions",
|
|
1153
|
-
onClick: () => setActiveTab("questions"),
|
|
1154
|
-
children: "Questions"
|
|
1155
|
-
}
|
|
1156
|
-
)
|
|
1157
|
-
]
|
|
1158
|
-
}
|
|
1159
|
-
),
|
|
1160
|
-
/* @__PURE__ */ jsx3("div", { style: { maxHeight: "380px", overflow: "auto" }, children: activeTab === "violations" ? /* @__PURE__ */ jsx3(ViolationList, {}) : /* @__PURE__ */ jsx3(QuestionPanel, {}) })
|
|
1161
|
-
]
|
|
1162
|
-
}
|
|
1163
|
-
);
|
|
1164
|
-
}
|
|
1165
|
-
function TabButton({ active, onClick, children }) {
|
|
1166
|
-
return /* @__PURE__ */ jsx3(
|
|
1167
|
-
"button",
|
|
1168
|
-
{
|
|
1169
|
-
onClick,
|
|
1170
|
-
style: {
|
|
1171
|
-
flex: 1,
|
|
1172
|
-
padding: "10px 16px",
|
|
1173
|
-
border: "none",
|
|
1174
|
-
backgroundColor: "transparent",
|
|
1175
|
-
color: active ? "#3B82F6" : "#9CA3AF",
|
|
1176
|
-
fontSize: "13px",
|
|
1177
|
-
fontWeight: "500",
|
|
1178
|
-
cursor: "pointer",
|
|
1179
|
-
borderBottom: active ? "2px solid #3B82F6" : "2px solid transparent",
|
|
1180
|
-
marginBottom: "-1px"
|
|
1181
|
-
},
|
|
1182
|
-
children
|
|
1183
|
-
}
|
|
1184
|
-
);
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
235
|
// src/consistency/highlights.tsx
|
|
1188
|
-
import { useEffect, useState
|
|
236
|
+
import { useEffect, useState, useCallback } from "react";
|
|
1189
237
|
import { createPortal } from "react-dom";
|
|
1190
|
-
import { Fragment, jsx
|
|
238
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
1191
239
|
var HIGHLIGHT_COLOR = "#3b82f6";
|
|
1192
240
|
var DOT_SIZE = 8;
|
|
1193
241
|
var BORDER_WIDTH = 2;
|
|
@@ -1218,7 +266,7 @@ function getAllViolatingIds(violations) {
|
|
|
1218
266
|
return ids;
|
|
1219
267
|
}
|
|
1220
268
|
function OverviewDot({ rect }) {
|
|
1221
|
-
return /* @__PURE__ */
|
|
269
|
+
return /* @__PURE__ */ jsx(
|
|
1222
270
|
"div",
|
|
1223
271
|
{
|
|
1224
272
|
style: {
|
|
@@ -1240,8 +288,8 @@ function HighlightRect({
|
|
|
1240
288
|
rect,
|
|
1241
289
|
badgeNumber
|
|
1242
290
|
}) {
|
|
1243
|
-
return /* @__PURE__ */
|
|
1244
|
-
/* @__PURE__ */
|
|
291
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
292
|
+
/* @__PURE__ */ jsx(
|
|
1245
293
|
"div",
|
|
1246
294
|
{
|
|
1247
295
|
style: {
|
|
@@ -1259,7 +307,7 @@ function HighlightRect({
|
|
|
1259
307
|
}
|
|
1260
308
|
}
|
|
1261
309
|
),
|
|
1262
|
-
badgeNumber !== void 0 && /* @__PURE__ */
|
|
310
|
+
badgeNumber !== void 0 && /* @__PURE__ */ jsx(
|
|
1263
311
|
"div",
|
|
1264
312
|
{
|
|
1265
313
|
style: {
|
|
@@ -1291,11 +339,11 @@ function ConsistencyHighlighter({
|
|
|
1291
339
|
selectedViolation,
|
|
1292
340
|
lockedViolation
|
|
1293
341
|
}) {
|
|
1294
|
-
const [overviewHighlights, setOverviewHighlights] =
|
|
1295
|
-
const [activeHighlights, setActiveHighlights] =
|
|
342
|
+
const [overviewHighlights, setOverviewHighlights] = useState([]);
|
|
343
|
+
const [activeHighlights, setActiveHighlights] = useState(
|
|
1296
344
|
[]
|
|
1297
345
|
);
|
|
1298
|
-
const [mounted, setMounted] =
|
|
346
|
+
const [mounted, setMounted] = useState(false);
|
|
1299
347
|
const activeViolation = lockedViolation || selectedViolation;
|
|
1300
348
|
const updateOverviewHighlights = useCallback(() => {
|
|
1301
349
|
if (activeViolation) {
|
|
@@ -1348,135 +396,13 @@ function ConsistencyHighlighter({
|
|
|
1348
396
|
}, [updateOverviewHighlights, updateActiveHighlights]);
|
|
1349
397
|
if (!mounted) return null;
|
|
1350
398
|
if (violations.length === 0) return null;
|
|
1351
|
-
const content = /* @__PURE__ */
|
|
1352
|
-
!activeViolation && overviewHighlights.map((h) => /* @__PURE__ */
|
|
1353
|
-
activeViolation && activeHighlights.map((h) => /* @__PURE__ */
|
|
399
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
400
|
+
!activeViolation && overviewHighlights.map((h) => /* @__PURE__ */ jsx(OverviewDot, { rect: h.rect }, h.id)),
|
|
401
|
+
activeViolation && activeHighlights.map((h) => /* @__PURE__ */ jsx(HighlightRect, { rect: h.rect, badgeNumber: h.badgeNumber }, h.id))
|
|
1354
402
|
] });
|
|
1355
403
|
return createPortal(content, document.body);
|
|
1356
404
|
}
|
|
1357
405
|
|
|
1358
|
-
// src/components/UILint.tsx
|
|
1359
|
-
import { countElements } from "uilint-core";
|
|
1360
|
-
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1361
|
-
var UILintContext = createContext(null);
|
|
1362
|
-
function useUILint() {
|
|
1363
|
-
const context = useContext(UILintContext);
|
|
1364
|
-
if (!context) {
|
|
1365
|
-
throw new Error("useUILint must be used within a UILint component");
|
|
1366
|
-
}
|
|
1367
|
-
return context;
|
|
1368
|
-
}
|
|
1369
|
-
function UILint({
|
|
1370
|
-
children,
|
|
1371
|
-
enabled = true,
|
|
1372
|
-
position = "bottom-left",
|
|
1373
|
-
autoScan = false,
|
|
1374
|
-
apiEndpoint = "/api/uilint/consistency"
|
|
1375
|
-
}) {
|
|
1376
|
-
const [violations, setViolations] = useState4([]);
|
|
1377
|
-
const [isScanning, setIsScanning] = useState4(false);
|
|
1378
|
-
const [elementCount, setElementCount] = useState4(0);
|
|
1379
|
-
const [selectedViolation, setSelectedViolation] = useState4(
|
|
1380
|
-
null
|
|
1381
|
-
);
|
|
1382
|
-
const [lockedViolation, setLockedViolation] = useState4(
|
|
1383
|
-
null
|
|
1384
|
-
);
|
|
1385
|
-
const [isMounted, setIsMounted] = useState4(false);
|
|
1386
|
-
const hasInitialized = useRef(false);
|
|
1387
|
-
useEffect2(() => {
|
|
1388
|
-
setIsMounted(true);
|
|
1389
|
-
}, []);
|
|
1390
|
-
useEffect2(() => {
|
|
1391
|
-
return () => {
|
|
1392
|
-
if (isBrowser()) {
|
|
1393
|
-
cleanupDataElements();
|
|
1394
|
-
}
|
|
1395
|
-
};
|
|
1396
|
-
}, []);
|
|
1397
|
-
const scan = useCallback2(async () => {
|
|
1398
|
-
if (!isBrowser()) return;
|
|
1399
|
-
setIsScanning(true);
|
|
1400
|
-
setSelectedViolation(null);
|
|
1401
|
-
setLockedViolation(null);
|
|
1402
|
-
try {
|
|
1403
|
-
const snapshot = createSnapshot(document.body);
|
|
1404
|
-
const count = countElements(snapshot);
|
|
1405
|
-
setElementCount(count);
|
|
1406
|
-
const response = await fetch(apiEndpoint, {
|
|
1407
|
-
method: "POST",
|
|
1408
|
-
headers: { "Content-Type": "application/json" },
|
|
1409
|
-
body: JSON.stringify({ snapshot })
|
|
1410
|
-
});
|
|
1411
|
-
if (!response.ok) {
|
|
1412
|
-
const errorData = await response.json().catch(() => ({}));
|
|
1413
|
-
console.error(
|
|
1414
|
-
"[UILint] Analysis failed:",
|
|
1415
|
-
errorData.error || response.statusText
|
|
1416
|
-
);
|
|
1417
|
-
setViolations([]);
|
|
1418
|
-
return;
|
|
1419
|
-
}
|
|
1420
|
-
const result = await response.json();
|
|
1421
|
-
setViolations(result.violations);
|
|
1422
|
-
if (result.violations.length === 0) {
|
|
1423
|
-
console.log(`[UILint] No consistency issues found (${count} elements)`);
|
|
1424
|
-
} else {
|
|
1425
|
-
console.log(
|
|
1426
|
-
`[UILint] Found ${result.violations.length} consistency issue(s)`
|
|
1427
|
-
);
|
|
1428
|
-
}
|
|
1429
|
-
} catch (error) {
|
|
1430
|
-
console.error("[UILint] Scan failed:", error);
|
|
1431
|
-
setViolations([]);
|
|
1432
|
-
} finally {
|
|
1433
|
-
setIsScanning(false);
|
|
1434
|
-
}
|
|
1435
|
-
}, [apiEndpoint]);
|
|
1436
|
-
const clearViolations = useCallback2(() => {
|
|
1437
|
-
setViolations([]);
|
|
1438
|
-
setSelectedViolation(null);
|
|
1439
|
-
setLockedViolation(null);
|
|
1440
|
-
cleanupDataElements();
|
|
1441
|
-
setElementCount(0);
|
|
1442
|
-
}, []);
|
|
1443
|
-
useEffect2(() => {
|
|
1444
|
-
if (!enabled || hasInitialized.current) return;
|
|
1445
|
-
hasInitialized.current = true;
|
|
1446
|
-
if (!isBrowser()) return;
|
|
1447
|
-
if (autoScan) {
|
|
1448
|
-
const timer = setTimeout(scan, 1e3);
|
|
1449
|
-
return () => clearTimeout(timer);
|
|
1450
|
-
}
|
|
1451
|
-
}, [enabled, autoScan, scan]);
|
|
1452
|
-
const contextValue = {
|
|
1453
|
-
violations,
|
|
1454
|
-
isScanning,
|
|
1455
|
-
elementCount,
|
|
1456
|
-
scan,
|
|
1457
|
-
clearViolations,
|
|
1458
|
-
selectedViolation,
|
|
1459
|
-
setSelectedViolation,
|
|
1460
|
-
lockedViolation,
|
|
1461
|
-
setLockedViolation
|
|
1462
|
-
};
|
|
1463
|
-
const shouldRenderOverlay = enabled && isMounted;
|
|
1464
|
-
return /* @__PURE__ */ jsxs5(UILintContext.Provider, { value: contextValue, children: [
|
|
1465
|
-
children,
|
|
1466
|
-
shouldRenderOverlay && /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1467
|
-
/* @__PURE__ */ jsx5(Overlay, { position }),
|
|
1468
|
-
/* @__PURE__ */ jsx5(
|
|
1469
|
-
ConsistencyHighlighter,
|
|
1470
|
-
{
|
|
1471
|
-
violations,
|
|
1472
|
-
selectedViolation,
|
|
1473
|
-
lockedViolation
|
|
1474
|
-
}
|
|
1475
|
-
)
|
|
1476
|
-
] })
|
|
1477
|
-
] });
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
406
|
// src/scanner/dom-scanner.ts
|
|
1481
407
|
import {
|
|
1482
408
|
extractStylesFromDOM,
|
|
@@ -1496,6 +422,19 @@ function scanDOM(root) {
|
|
|
1496
422
|
};
|
|
1497
423
|
}
|
|
1498
424
|
|
|
425
|
+
// src/scanner/environment.ts
|
|
426
|
+
function isBrowser() {
|
|
427
|
+
return typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
428
|
+
}
|
|
429
|
+
function isJSDOM() {
|
|
430
|
+
if (!isBrowser()) return false;
|
|
431
|
+
const userAgent = window.navigator?.userAgent || "";
|
|
432
|
+
return userAgent.includes("jsdom");
|
|
433
|
+
}
|
|
434
|
+
function isNode() {
|
|
435
|
+
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
436
|
+
}
|
|
437
|
+
|
|
1499
438
|
// src/index.ts
|
|
1500
439
|
import {
|
|
1501
440
|
extractStylesFromDOM as extractStylesFromDOM2,
|
|
@@ -1510,7 +449,7 @@ import {
|
|
|
1510
449
|
buildStyleGuidePrompt,
|
|
1511
450
|
UILINT_DEFAULT_OLLAMA_MODEL
|
|
1512
451
|
} from "uilint-core";
|
|
1513
|
-
var DEFAULT_API_ENDPOINT = "/api
|
|
452
|
+
var DEFAULT_API_ENDPOINT = "/api/.uilint/analyze";
|
|
1514
453
|
var LLMClient = class {
|
|
1515
454
|
apiEndpoint;
|
|
1516
455
|
model;
|
|
@@ -1589,8 +528,7 @@ export {
|
|
|
1589
528
|
FILE_COLORS,
|
|
1590
529
|
InspectionPanel,
|
|
1591
530
|
LLMClient,
|
|
1592
|
-
|
|
1593
|
-
UILint,
|
|
531
|
+
LocatorOverlay,
|
|
1594
532
|
UILintProvider,
|
|
1595
533
|
UILintToolbar,
|
|
1596
534
|
buildEditorUrl,
|
|
@@ -1624,7 +562,5 @@ export {
|
|
|
1624
562
|
scanDOMForSources,
|
|
1625
563
|
serializeStyles2 as serializeStyles,
|
|
1626
564
|
updateElementRects,
|
|
1627
|
-
useElementScan,
|
|
1628
|
-
useUILint,
|
|
1629
565
|
useUILintContext
|
|
1630
566
|
};
|