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