uilint-react 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,213 @@
1
+ "use client";
2
+ import {
3
+ useUILintContext
4
+ } from "./chunk-7WYVWDRU.js";
5
+
6
+ // src/components/ui-lint/LocatorOverlay.tsx
7
+ import { useState, useEffect, useMemo } from "react";
8
+ import { createPortal } from "react-dom";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ var STYLES = {
11
+ bg: "rgba(17, 24, 39, 0.95)",
12
+ border: "rgba(59, 130, 246, 0.8)",
13
+ borderHighlight: "#3B82F6",
14
+ text: "#F9FAFB",
15
+ textMuted: "#9CA3AF",
16
+ accent: "#3B82F6",
17
+ font: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
18
+ fontMono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
19
+ shadow: "0 4px 20px rgba(0, 0, 0, 0.5)",
20
+ blur: "blur(12px)"
21
+ };
22
+ function getFileName(path) {
23
+ const parts = path.split("/");
24
+ return parts[parts.length - 1] || path;
25
+ }
26
+ function LocatorOverlay() {
27
+ const { locatorTarget } = useUILintContext();
28
+ const [mounted, setMounted] = useState(false);
29
+ useEffect(() => {
30
+ setMounted(true);
31
+ }, []);
32
+ const currentSource = useMemo(() => {
33
+ if (!locatorTarget) return null;
34
+ if (locatorTarget.stackIndex === 0) {
35
+ return locatorTarget.source;
36
+ }
37
+ const stackItem = locatorTarget.componentStack[locatorTarget.stackIndex - 1];
38
+ return stackItem?.source || null;
39
+ }, [locatorTarget]);
40
+ const currentName = useMemo(() => {
41
+ if (!locatorTarget) return "";
42
+ if (locatorTarget.stackIndex === 0) {
43
+ return locatorTarget.element.tagName.toLowerCase();
44
+ }
45
+ const stackItem = locatorTarget.componentStack[locatorTarget.stackIndex - 1];
46
+ return stackItem?.name || "Unknown";
47
+ }, [locatorTarget]);
48
+ if (!mounted || !locatorTarget) return null;
49
+ const { rect } = locatorTarget;
50
+ const hasParents = locatorTarget.componentStack.length > 0;
51
+ const content = /* @__PURE__ */ jsxs("div", { "data-ui-lint": true, style: { pointerEvents: "none" }, children: [
52
+ /* @__PURE__ */ jsx("style", { children: `
53
+ @keyframes uilint-locator-fade-in {
54
+ from { opacity: 0; }
55
+ to { opacity: 1; }
56
+ }
57
+ @keyframes uilint-locator-pulse {
58
+ 0%, 100% { opacity: 0.8; }
59
+ 50% { opacity: 1; }
60
+ }
61
+ ` }),
62
+ /* @__PURE__ */ jsx(
63
+ "div",
64
+ {
65
+ style: {
66
+ position: "fixed",
67
+ top: rect.top - 2,
68
+ left: rect.left - 2,
69
+ width: rect.width + 4,
70
+ height: rect.height + 4,
71
+ border: `2px solid ${STYLES.borderHighlight}`,
72
+ borderRadius: "4px",
73
+ boxShadow: `0 0 0 1px rgba(59, 130, 246, 0.3), inset 0 0 0 1px rgba(59, 130, 246, 0.1)`,
74
+ animation: "uilint-locator-fade-in 0.1s ease-out",
75
+ zIndex: 99997
76
+ }
77
+ }
78
+ ),
79
+ /* @__PURE__ */ jsx(
80
+ InfoTooltip,
81
+ {
82
+ rect,
83
+ source: currentSource,
84
+ componentName: currentName,
85
+ stackIndex: locatorTarget.stackIndex,
86
+ stackLength: locatorTarget.componentStack.length,
87
+ hasParents
88
+ }
89
+ )
90
+ ] });
91
+ return createPortal(content, document.body);
92
+ }
93
+ function InfoTooltip({
94
+ rect,
95
+ source,
96
+ componentName,
97
+ stackIndex,
98
+ stackLength,
99
+ hasParents
100
+ }) {
101
+ const viewportHeight = window.innerHeight;
102
+ const spaceAbove = rect.top;
103
+ const spaceBelow = viewportHeight - rect.bottom;
104
+ const positionAbove = spaceAbove > 100 || spaceBelow < 100;
105
+ const tooltipStyle = {
106
+ position: "fixed",
107
+ left: Math.max(8, Math.min(rect.left, window.innerWidth - 320)),
108
+ zIndex: 99999,
109
+ animation: "uilint-locator-fade-in 0.15s ease-out"
110
+ };
111
+ if (positionAbove) {
112
+ tooltipStyle.bottom = viewportHeight - rect.top + 8;
113
+ } else {
114
+ tooltipStyle.top = rect.bottom + 8;
115
+ }
116
+ return /* @__PURE__ */ jsxs(
117
+ "div",
118
+ {
119
+ style: {
120
+ ...tooltipStyle,
121
+ display: "flex",
122
+ flexDirection: "column",
123
+ gap: "6px",
124
+ padding: "10px 12px",
125
+ borderRadius: "8px",
126
+ backgroundColor: STYLES.bg,
127
+ backdropFilter: STYLES.blur,
128
+ WebkitBackdropFilter: STYLES.blur,
129
+ border: `1px solid ${STYLES.border}`,
130
+ boxShadow: STYLES.shadow,
131
+ fontFamily: STYLES.font,
132
+ maxWidth: "320px",
133
+ pointerEvents: "auto"
134
+ },
135
+ children: [
136
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
137
+ /* @__PURE__ */ jsxs(
138
+ "span",
139
+ {
140
+ style: {
141
+ fontSize: "13px",
142
+ fontWeight: 600,
143
+ color: STYLES.accent
144
+ },
145
+ children: [
146
+ "<",
147
+ componentName,
148
+ " />"
149
+ ]
150
+ }
151
+ ),
152
+ hasParents && /* @__PURE__ */ jsx(
153
+ "span",
154
+ {
155
+ style: {
156
+ fontSize: "10px",
157
+ color: STYLES.textMuted,
158
+ padding: "2px 6px",
159
+ backgroundColor: "rgba(59, 130, 246, 0.15)",
160
+ borderRadius: "4px"
161
+ },
162
+ children: stackIndex === 0 ? "element" : `parent ${stackIndex}/${stackLength}`
163
+ }
164
+ )
165
+ ] }),
166
+ source && /* @__PURE__ */ jsxs(
167
+ "div",
168
+ {
169
+ style: {
170
+ display: "flex",
171
+ alignItems: "center",
172
+ gap: "6px",
173
+ fontSize: "11px",
174
+ fontFamily: STYLES.fontMono,
175
+ color: STYLES.text
176
+ },
177
+ children: [
178
+ /* @__PURE__ */ jsx("span", { style: { opacity: 0.9 }, children: getFileName(source.fileName) }),
179
+ /* @__PURE__ */ jsx("span", { style: { color: STYLES.textMuted }, children: ":" }),
180
+ /* @__PURE__ */ jsx("span", { style: { color: STYLES.accent }, children: source.lineNumber })
181
+ ]
182
+ }
183
+ ),
184
+ /* @__PURE__ */ jsxs(
185
+ "div",
186
+ {
187
+ style: {
188
+ display: "flex",
189
+ alignItems: "center",
190
+ gap: "12px",
191
+ fontSize: "10px",
192
+ color: STYLES.textMuted,
193
+ borderTop: `1px solid rgba(75, 85, 99, 0.3)`,
194
+ paddingTop: "8px",
195
+ marginTop: "2px"
196
+ },
197
+ children: [
198
+ /* @__PURE__ */ jsx("span", { children: "Click to open" }),
199
+ hasParents && /* @__PURE__ */ jsxs(Fragment, { children: [
200
+ /* @__PURE__ */ jsx("span", { style: { opacity: 0.5 }, children: "\u2022" }),
201
+ /* @__PURE__ */ jsx("span", { children: "Scroll to navigate parents" })
202
+ ] })
203
+ ]
204
+ }
205
+ )
206
+ ]
207
+ }
208
+ );
209
+ }
210
+
211
+ export {
212
+ LocatorOverlay
213
+ };
@@ -0,0 +1,210 @@
1
+ "use client";
2
+ import {
3
+ buildEditorUrl,
4
+ useUILintContext
5
+ } from "./chunk-7WYVWDRU.js";
6
+
7
+ // src/components/ui-lint/SourceOverlays.tsx
8
+ import { useState, useEffect, useMemo, useCallback } from "react";
9
+ import { createPortal } from "react-dom";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ var STYLES = {
12
+ font: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
13
+ fontMono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
14
+ shadow: "0 2px 8px rgba(0, 0, 0, 0.3)"
15
+ };
16
+ function getLabelPositionStyles(position) {
17
+ switch (position) {
18
+ case "top-left":
19
+ return { top: "-1px", left: "-1px" };
20
+ case "top-right":
21
+ return { top: "-1px", right: "-1px" };
22
+ case "bottom-left":
23
+ return { bottom: "-1px", left: "-1px" };
24
+ case "bottom-right":
25
+ return { bottom: "-1px", right: "-1px" };
26
+ }
27
+ }
28
+ function SourceOverlays() {
29
+ const {
30
+ sourceFiles,
31
+ scannedElements,
32
+ settings,
33
+ selectedElement,
34
+ setSelectedElement,
35
+ hoveredElement,
36
+ setHoveredElement,
37
+ mode
38
+ } = useUILintContext();
39
+ const [mounted, setMounted] = useState(false);
40
+ useEffect(() => {
41
+ setMounted(true);
42
+ }, []);
43
+ const elementToFile = useMemo(() => {
44
+ const map = /* @__PURE__ */ new Map();
45
+ for (const file of sourceFiles) {
46
+ for (const element of file.elements) {
47
+ map.set(element.id, file);
48
+ }
49
+ }
50
+ return map;
51
+ }, [sourceFiles]);
52
+ const handleElementClick = useCallback(
53
+ (element) => {
54
+ if (mode === "inspect") {
55
+ setSelectedElement(
56
+ selectedElement?.id === element.id ? null : element
57
+ );
58
+ }
59
+ },
60
+ [mode, selectedElement, setSelectedElement]
61
+ );
62
+ const handleLabelClick = useCallback(
63
+ (element, e) => {
64
+ e.stopPropagation();
65
+ if (element.source) {
66
+ const url = buildEditorUrl(element.source, "cursor");
67
+ window.open(url, "_blank");
68
+ }
69
+ },
70
+ []
71
+ );
72
+ if (!mounted) return null;
73
+ const content = /* @__PURE__ */ jsxs("div", { "data-ui-lint": true, style: { pointerEvents: "none" }, children: [
74
+ /* @__PURE__ */ jsx("style", { children: `
75
+ @keyframes uilint-overlay-fade-in {
76
+ from { opacity: 0; }
77
+ to { opacity: 1; }
78
+ }
79
+ ` }),
80
+ scannedElements.map((element) => {
81
+ const file = elementToFile.get(element.id);
82
+ if (!file) return null;
83
+ const isSelected = selectedElement?.id === element.id;
84
+ const isHovered = hoveredElement?.id === element.id;
85
+ return /* @__PURE__ */ jsx(
86
+ ElementOverlay,
87
+ {
88
+ element,
89
+ file,
90
+ settings,
91
+ isSelected,
92
+ isHovered,
93
+ onHover: () => setHoveredElement(element),
94
+ onLeave: () => setHoveredElement(null),
95
+ onClick: () => handleElementClick(element),
96
+ onLabelClick: (e) => handleLabelClick(element, e),
97
+ showClickable: mode === "inspect"
98
+ },
99
+ element.id
100
+ );
101
+ })
102
+ ] });
103
+ return createPortal(content, document.body);
104
+ }
105
+ function ElementOverlay({
106
+ element,
107
+ file,
108
+ settings,
109
+ isSelected,
110
+ isHovered,
111
+ onHover,
112
+ onLeave,
113
+ onClick,
114
+ onLabelClick,
115
+ showClickable
116
+ }) {
117
+ const { rect } = element;
118
+ if (rect.width < 20 || rect.height < 20) return null;
119
+ if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) {
120
+ return null;
121
+ }
122
+ const borderWidth = isSelected ? 3 : isHovered ? 2 : 1.5;
123
+ return /* @__PURE__ */ jsx(
124
+ "div",
125
+ {
126
+ style: {
127
+ position: "fixed",
128
+ top: rect.top,
129
+ left: rect.left,
130
+ width: rect.width,
131
+ height: rect.height,
132
+ border: `${borderWidth}px solid ${file.color}`,
133
+ borderRadius: "2px",
134
+ pointerEvents: showClickable ? "auto" : "none",
135
+ cursor: showClickable ? "pointer" : "default",
136
+ zIndex: isSelected ? 99998 : isHovered ? 99997 : 99996,
137
+ animation: "uilint-overlay-fade-in 0.15s ease-out",
138
+ transition: "border-width 0.15s"
139
+ },
140
+ onMouseEnter: onHover,
141
+ onMouseLeave: onLeave,
142
+ onClick,
143
+ children: settings.showLabels && /* @__PURE__ */ jsx(
144
+ FileLabel,
145
+ {
146
+ file,
147
+ element,
148
+ position: settings.labelPosition,
149
+ isHovered,
150
+ isSelected,
151
+ onClick: onLabelClick
152
+ }
153
+ )
154
+ }
155
+ );
156
+ }
157
+ function FileLabel({
158
+ file,
159
+ element,
160
+ position,
161
+ isHovered,
162
+ isSelected,
163
+ onClick
164
+ }) {
165
+ const [showFullPath, setShowFullPath] = useState(false);
166
+ const positionStyles = getLabelPositionStyles(position);
167
+ const displayName = file.displayName.length > 20 ? file.displayName.slice(0, 17) + "..." : file.displayName;
168
+ return /* @__PURE__ */ jsxs(
169
+ "div",
170
+ {
171
+ style: {
172
+ position: "absolute",
173
+ ...positionStyles,
174
+ display: "flex",
175
+ alignItems: "center",
176
+ gap: "4px",
177
+ padding: "2px 6px",
178
+ borderRadius: "3px",
179
+ backgroundColor: file.color,
180
+ color: "#FFFFFF",
181
+ fontSize: "10px",
182
+ fontWeight: 600,
183
+ fontFamily: STYLES.fontMono,
184
+ whiteSpace: "nowrap",
185
+ pointerEvents: "auto",
186
+ cursor: "pointer",
187
+ boxShadow: STYLES.shadow,
188
+ transition: "transform 0.1s, padding 0.1s",
189
+ transform: isHovered || isSelected ? "scale(1.05)" : "scale(1)",
190
+ zIndex: 99999
191
+ },
192
+ onMouseEnter: () => setShowFullPath(true),
193
+ onMouseLeave: () => setShowFullPath(false),
194
+ onClick,
195
+ title: `${file.path}:${element.source?.lineNumber || "?"}
196
+ Click to open in editor`,
197
+ children: [
198
+ /* @__PURE__ */ jsx("span", { children: displayName }),
199
+ element.source?.lineNumber && /* @__PURE__ */ jsxs("span", { style: { opacity: 0.8, fontSize: "9px" }, children: [
200
+ ":",
201
+ element.source.lineNumber
202
+ ] })
203
+ ]
204
+ }
205
+ );
206
+ }
207
+
208
+ export {
209
+ SourceOverlays
210
+ };