uilint-react 0.1.8 → 0.1.9
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/dist/index.js +89 -96
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
useCallback,
|
|
10
10
|
useRef
|
|
11
11
|
} from "react";
|
|
12
|
-
import { generateStyleGuideFromStyles as generateStyleGuide } from "uilint-core";
|
|
13
12
|
|
|
14
13
|
// src/scanner/dom-scanner.ts
|
|
15
14
|
import {
|
|
@@ -281,6 +280,7 @@ function QuestionPanel() {
|
|
|
281
280
|
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
282
281
|
const [isSaving, setIsSaving] = useState(false);
|
|
283
282
|
const [saveSuccess, setSaveSuccess] = useState(false);
|
|
283
|
+
const [saveError, setSaveError] = useState(null);
|
|
284
284
|
const questions = generateQuestionsFromIssues(issues);
|
|
285
285
|
if (questions.length === 0) {
|
|
286
286
|
return /* @__PURE__ */ jsxs2(
|
|
@@ -313,20 +313,24 @@ function QuestionPanel() {
|
|
|
313
313
|
console.log("[UILint] Saving preferences:", answers);
|
|
314
314
|
setIsSaving(true);
|
|
315
315
|
setSaveSuccess(false);
|
|
316
|
+
setSaveError(null);
|
|
316
317
|
try {
|
|
317
318
|
const getResponse = await fetch("/api/uilint/styleguide");
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
const data = await getResponse.json().catch(() => ({}));
|
|
320
|
+
if (!getResponse.ok || !data?.exists || !data?.content) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
data?.error || 'No style guide found. Create ".uilint/styleguide.md" at your workspace root first.'
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
const updatedContent = applyAnswersToStyleGuide(data.content, answers);
|
|
323
326
|
const postResponse = await fetch("/api/uilint/styleguide", {
|
|
324
327
|
method: "POST",
|
|
325
328
|
headers: { "Content-Type": "application/json" },
|
|
326
329
|
body: JSON.stringify({ content: updatedContent })
|
|
327
330
|
});
|
|
328
331
|
if (!postResponse.ok) {
|
|
329
|
-
|
|
332
|
+
const err = await postResponse.json().catch(() => ({}));
|
|
333
|
+
throw new Error(err?.error || "Failed to save style guide");
|
|
330
334
|
}
|
|
331
335
|
console.log("[UILint] Style guide saved successfully!");
|
|
332
336
|
setSaveSuccess(true);
|
|
@@ -337,6 +341,7 @@ function QuestionPanel() {
|
|
|
337
341
|
}, 1500);
|
|
338
342
|
} catch (error) {
|
|
339
343
|
console.error("[UILint] Error saving style guide:", error);
|
|
344
|
+
setSaveError(error instanceof Error ? error.message : "Save failed");
|
|
340
345
|
} finally {
|
|
341
346
|
setIsSaving(false);
|
|
342
347
|
}
|
|
@@ -496,6 +501,22 @@ function QuestionPanel() {
|
|
|
496
501
|
)
|
|
497
502
|
]
|
|
498
503
|
}
|
|
504
|
+
),
|
|
505
|
+
saveError && /* @__PURE__ */ jsx2(
|
|
506
|
+
"div",
|
|
507
|
+
{
|
|
508
|
+
style: {
|
|
509
|
+
marginTop: "12px",
|
|
510
|
+
padding: "10px",
|
|
511
|
+
borderRadius: "8px",
|
|
512
|
+
backgroundColor: "#7F1D1D",
|
|
513
|
+
border: "1px solid #EF4444",
|
|
514
|
+
color: "#FEE2E2",
|
|
515
|
+
fontSize: "12px",
|
|
516
|
+
lineHeight: 1.4
|
|
517
|
+
},
|
|
518
|
+
children: saveError
|
|
519
|
+
}
|
|
499
520
|
)
|
|
500
521
|
] });
|
|
501
522
|
}
|
|
@@ -572,98 +593,75 @@ function generateQuestionsFromIssues(issues) {
|
|
|
572
593
|
}
|
|
573
594
|
return questions;
|
|
574
595
|
}
|
|
575
|
-
function
|
|
576
|
-
|
|
577
|
-
lines.push("# UI Style Guide");
|
|
578
|
-
lines.push("");
|
|
579
|
-
lines.push(
|
|
580
|
-
"> Auto-generated by UILint. Edit this file to define your design system."
|
|
581
|
-
);
|
|
582
|
-
lines.push("");
|
|
583
|
-
lines.push("## Colors");
|
|
584
|
-
lines.push("");
|
|
596
|
+
function applyAnswersToStyleGuide(existingContent, answers) {
|
|
597
|
+
let content = existingContent;
|
|
585
598
|
if (answers["primary-color"]) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
599
|
+
content = upsertBulletInSection(
|
|
600
|
+
content,
|
|
601
|
+
"Colors",
|
|
602
|
+
"Primary",
|
|
603
|
+
answers["primary-color"]
|
|
604
|
+
);
|
|
592
605
|
}
|
|
593
|
-
lines.push("");
|
|
594
|
-
lines.push("## Typography");
|
|
595
|
-
lines.push("");
|
|
596
606
|
if (answers["font-weights"]) {
|
|
597
607
|
const weightMap = {
|
|
598
608
|
"400-600-700": "400 (Regular), 600 (Semibold), 700 (Bold)",
|
|
599
609
|
"400-500-700": "400 (Regular), 500 (Medium), 700 (Bold)",
|
|
600
610
|
"300-400-600": "300 (Light), 400 (Regular), 600 (Semibold)"
|
|
601
611
|
};
|
|
602
|
-
|
|
603
|
-
|
|
612
|
+
const value = weightMap[answers["font-weights"]] || answers["font-weights"];
|
|
613
|
+
content = upsertBulletInSection(
|
|
614
|
+
content,
|
|
615
|
+
"Typography",
|
|
616
|
+
"Font Weights",
|
|
617
|
+
value
|
|
604
618
|
);
|
|
605
619
|
}
|
|
606
|
-
const existingTypography = extractSection(existingContent, "Typography");
|
|
607
|
-
existingTypography.filter((line) => !line.includes("**Font Weights**")).forEach((line) => lines.push(line));
|
|
608
|
-
if (!answers["font-weights"] && existingTypography.length === 0) {
|
|
609
|
-
lines.push("- Define your typography scale here");
|
|
610
|
-
}
|
|
611
|
-
lines.push("");
|
|
612
|
-
lines.push("## Spacing");
|
|
613
|
-
lines.push("");
|
|
614
620
|
if (answers["spacing-scale"]) {
|
|
615
621
|
const spacingMap = {
|
|
616
622
|
"4": "4px (4, 8, 12, 16, 20, 24, 32, 40, 48...)",
|
|
617
623
|
"8": "8px (8, 16, 24, 32, 40, 48, 56, 64...)",
|
|
618
624
|
tailwind: "Tailwind (4, 8, 12, 16, 20, 24, 32, 40, 48...)"
|
|
619
625
|
};
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
);
|
|
626
|
+
const value = spacingMap[answers["spacing-scale"]] || answers["spacing-scale"];
|
|
627
|
+
content = upsertBulletInSection(content, "Spacing", "Base unit", value);
|
|
623
628
|
}
|
|
624
|
-
|
|
625
|
-
existingSpacing.filter((line) => !line.includes("**Base unit**")).forEach((line) => lines.push(line));
|
|
626
|
-
if (!answers["spacing-scale"] && existingSpacing.length === 0) {
|
|
627
|
-
lines.push("- Define your spacing scale here");
|
|
628
|
-
}
|
|
629
|
-
lines.push("");
|
|
630
|
-
const otherSections = ["Border Radius", "Components"];
|
|
631
|
-
otherSections.forEach((section) => {
|
|
632
|
-
const sectionLines = extractSection(existingContent, section);
|
|
633
|
-
if (sectionLines.length > 0) {
|
|
634
|
-
lines.push(`## ${section}`);
|
|
635
|
-
lines.push("");
|
|
636
|
-
sectionLines.forEach((line) => lines.push(line));
|
|
637
|
-
lines.push("");
|
|
638
|
-
}
|
|
639
|
-
});
|
|
640
|
-
if (!existingContent?.includes("## Components")) {
|
|
641
|
-
lines.push("## Components");
|
|
642
|
-
lines.push("");
|
|
643
|
-
lines.push("- **Buttons**: Define button styles here");
|
|
644
|
-
lines.push("- **Cards**: Define card styles here");
|
|
645
|
-
lines.push("- **Inputs**: Define input styles here");
|
|
646
|
-
lines.push("");
|
|
647
|
-
}
|
|
648
|
-
return lines.join("\n");
|
|
629
|
+
return content;
|
|
649
630
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
652
|
-
const lines = content.split("\n");
|
|
631
|
+
function upsertBulletInSection(markdown, sectionName, label, value) {
|
|
632
|
+
const lines = markdown.split("\n");
|
|
653
633
|
const sectionStart = lines.findIndex(
|
|
654
|
-
(line) => line.match(new RegExp(`^##\\s+${sectionName}
|
|
634
|
+
(line) => line.match(new RegExp(`^##\\s+${sectionName}\\s*$`, "i"))
|
|
655
635
|
);
|
|
656
|
-
if (sectionStart === -1)
|
|
657
|
-
|
|
636
|
+
if (sectionStart === -1) {
|
|
637
|
+
throw new Error(
|
|
638
|
+
`Style guide is missing section "## ${sectionName}". Add it to your style guide and try again.`
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
let sectionEnd = lines.length;
|
|
658
642
|
for (let i = sectionStart + 1; i < lines.length; i++) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
643
|
+
if (lines[i].startsWith("## ")) {
|
|
644
|
+
sectionEnd = i;
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
const bulletRe = new RegExp(
|
|
649
|
+
`^-\\s+\\*\\*${escapeRegExp(label)}\\*\\*:\\s+.*$`
|
|
650
|
+
);
|
|
651
|
+
const newBullet = `- **${label}**: ${value}`;
|
|
652
|
+
for (let i = sectionStart + 1; i < sectionEnd; i++) {
|
|
653
|
+
if (bulletRe.test(lines[i])) {
|
|
654
|
+
lines[i] = newBullet;
|
|
655
|
+
return lines.join("\n");
|
|
664
656
|
}
|
|
665
657
|
}
|
|
666
|
-
|
|
658
|
+
let insertAt = sectionStart + 1;
|
|
659
|
+
while (insertAt < sectionEnd && lines[insertAt].trim() === "") insertAt++;
|
|
660
|
+
lines.splice(insertAt, 0, newBullet);
|
|
661
|
+
return lines.join("\n");
|
|
662
|
+
}
|
|
663
|
+
function escapeRegExp(s) {
|
|
664
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
667
665
|
}
|
|
668
666
|
|
|
669
667
|
// src/components/Overlay.tsx
|
|
@@ -979,25 +977,17 @@ function UILint({
|
|
|
979
977
|
if (!isBrowser()) return;
|
|
980
978
|
try {
|
|
981
979
|
const response = await fetch("/api/uilint/styleguide");
|
|
982
|
-
const data = await response.json();
|
|
983
|
-
|
|
984
|
-
|
|
980
|
+
const data = await response.json().catch(() => ({}));
|
|
981
|
+
if (!response.ok) {
|
|
982
|
+
setStyleGuideExists(false);
|
|
983
|
+
setStyleGuideContent(null);
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
setStyleGuideExists(!!data.exists);
|
|
987
|
+
setStyleGuideContent(data.content ?? null);
|
|
985
988
|
} catch {
|
|
986
989
|
setStyleGuideExists(false);
|
|
987
|
-
|
|
988
|
-
}, []);
|
|
989
|
-
const saveStyleGuide = useCallback(async (content) => {
|
|
990
|
-
if (!isBrowser()) return;
|
|
991
|
-
try {
|
|
992
|
-
await fetch("/api/uilint/styleguide", {
|
|
993
|
-
method: "POST",
|
|
994
|
-
headers: { "Content-Type": "application/json" },
|
|
995
|
-
body: JSON.stringify({ content })
|
|
996
|
-
});
|
|
997
|
-
setStyleGuideExists(true);
|
|
998
|
-
setStyleGuideContent(content);
|
|
999
|
-
} catch (error) {
|
|
1000
|
-
console.error("[UILint] Failed to save style guide:", error);
|
|
990
|
+
setStyleGuideContent(null);
|
|
1001
991
|
}
|
|
1002
992
|
}, []);
|
|
1003
993
|
const scan = useCallback(async () => {
|
|
@@ -1006,8 +996,11 @@ function UILint({
|
|
|
1006
996
|
try {
|
|
1007
997
|
const snapshot = scanDOM(document.body);
|
|
1008
998
|
if (!styleGuideContent) {
|
|
1009
|
-
|
|
1010
|
-
|
|
999
|
+
console.error(
|
|
1000
|
+
'[UILint] No style guide found. Create ".uilint/styleguide.md" at your workspace root.'
|
|
1001
|
+
);
|
|
1002
|
+
setIssues([]);
|
|
1003
|
+
return;
|
|
1011
1004
|
}
|
|
1012
1005
|
const result = await llmClient.current.analyze(
|
|
1013
1006
|
snapshot.styles,
|
|
@@ -1019,7 +1012,7 @@ function UILint({
|
|
|
1019
1012
|
} finally {
|
|
1020
1013
|
setIsScanning(false);
|
|
1021
1014
|
}
|
|
1022
|
-
}, [styleGuideContent
|
|
1015
|
+
}, [styleGuideContent]);
|
|
1023
1016
|
const clearIssues = useCallback(() => {
|
|
1024
1017
|
setIssues([]);
|
|
1025
1018
|
setHighlightedIssue(null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uilint-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "React component for AI-powered UI consistency checking",
|
|
5
5
|
"author": "Peter Suggate",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"node": ">=20.0.0"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"uilint-core": "^0.1.
|
|
37
|
+
"uilint-core": "^0.1.9"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": "^19.0.0",
|