uilint-react 0.1.19 → 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.
@@ -0,0 +1,276 @@
1
+ "use client";
2
+ import {
3
+ useUILintContext
4
+ } from "./chunk-DAFFOBEU.js";
5
+
6
+ // src/components/ui-lint/UILintToolbar.tsx
7
+ import { useState, useRef, useEffect } from "react";
8
+ import { createPortal } from "react-dom";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ var STYLES = {
11
+ bg: "rgba(17, 24, 39, 0.9)",
12
+ bgHover: "rgba(31, 41, 55, 0.95)",
13
+ border: "rgba(75, 85, 99, 0.5)",
14
+ text: "#F9FAFB",
15
+ textMuted: "#9CA3AF",
16
+ accent: "#3B82F6",
17
+ accentHover: "#2563EB",
18
+ shadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
19
+ blur: "blur(12px)",
20
+ font: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
21
+ fontMono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace'
22
+ };
23
+ function UILintToolbar() {
24
+ const { settings, updateSettings, inspectedElement } = useUILintContext();
25
+ const [showSettings, setShowSettings] = useState(false);
26
+ const [mounted, setMounted] = useState(false);
27
+ const settingsRef = useRef(null);
28
+ useEffect(() => {
29
+ setMounted(true);
30
+ }, []);
31
+ useEffect(() => {
32
+ const handleClickOutside = (e) => {
33
+ if (settingsRef.current && !settingsRef.current.contains(e.target)) {
34
+ setShowSettings(false);
35
+ }
36
+ };
37
+ if (showSettings) {
38
+ document.addEventListener("mousedown", handleClickOutside);
39
+ return () => document.removeEventListener("mousedown", handleClickOutside);
40
+ }
41
+ }, [showSettings]);
42
+ if (!mounted) return null;
43
+ if (inspectedElement) return null;
44
+ const content = /* @__PURE__ */ jsxs(
45
+ "div",
46
+ {
47
+ "data-ui-lint": true,
48
+ style: {
49
+ position: "fixed",
50
+ top: "24px",
51
+ right: "24px",
52
+ zIndex: 99999,
53
+ fontFamily: STYLES.font
54
+ },
55
+ children: [
56
+ /* @__PURE__ */ jsx("style", { children: `
57
+ @keyframes uilint-fade-in {
58
+ from { opacity: 0; transform: scale(0.95); }
59
+ to { opacity: 1; transform: scale(1); }
60
+ }
61
+ ` }),
62
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, ref: settingsRef, children: [
63
+ /* @__PURE__ */ jsx(
64
+ "button",
65
+ {
66
+ onClick: () => setShowSettings(!showSettings),
67
+ style: {
68
+ display: "flex",
69
+ alignItems: "center",
70
+ justifyContent: "center",
71
+ width: "48px",
72
+ height: "48px",
73
+ borderRadius: "50%",
74
+ border: `1px solid ${STYLES.border}`,
75
+ backgroundColor: showSettings ? STYLES.bgHover : STYLES.bg,
76
+ backdropFilter: STYLES.blur,
77
+ WebkitBackdropFilter: STYLES.blur,
78
+ boxShadow: STYLES.shadow,
79
+ cursor: "pointer",
80
+ transition: "all 0.2s ease-out",
81
+ color: showSettings ? STYLES.text : STYLES.textMuted,
82
+ fontSize: "20px"
83
+ },
84
+ onMouseEnter: (e) => {
85
+ e.currentTarget.style.transform = "scale(1.05)";
86
+ e.currentTarget.style.boxShadow = "0 12px 40px rgba(0, 0, 0, 0.5)";
87
+ },
88
+ onMouseLeave: (e) => {
89
+ e.currentTarget.style.transform = "scale(1)";
90
+ e.currentTarget.style.boxShadow = STYLES.shadow;
91
+ },
92
+ title: "UILint Settings (Alt+Click any element to inspect)",
93
+ children: /* @__PURE__ */ jsx(UILintIcon, { active: showSettings })
94
+ }
95
+ ),
96
+ showSettings && /* @__PURE__ */ jsx(SettingsPopover, { settings, onUpdate: updateSettings }),
97
+ /* @__PURE__ */ jsxs(
98
+ "div",
99
+ {
100
+ style: {
101
+ position: "absolute",
102
+ left: "100%",
103
+ top: "50%",
104
+ transform: "translateY(-50%)",
105
+ marginLeft: "12px",
106
+ padding: "8px 12px",
107
+ borderRadius: "8px",
108
+ backgroundColor: STYLES.bg,
109
+ border: `1px solid ${STYLES.border}`,
110
+ backdropFilter: STYLES.blur,
111
+ WebkitBackdropFilter: STYLES.blur,
112
+ boxShadow: STYLES.shadow,
113
+ fontSize: "12px",
114
+ color: STYLES.textMuted,
115
+ whiteSpace: "nowrap",
116
+ opacity: 0,
117
+ transition: "opacity 0.2s",
118
+ pointerEvents: "none"
119
+ },
120
+ className: "uilint-hint",
121
+ children: [
122
+ /* @__PURE__ */ jsx("span", { style: { color: STYLES.text }, children: "Alt+Click" }),
123
+ " any element to inspect"
124
+ ]
125
+ }
126
+ )
127
+ ] })
128
+ ]
129
+ }
130
+ );
131
+ return createPortal(content, document.body);
132
+ }
133
+ function SettingsPopover({
134
+ settings,
135
+ onUpdate
136
+ }) {
137
+ return /* @__PURE__ */ jsxs(
138
+ "div",
139
+ {
140
+ style: {
141
+ position: "absolute",
142
+ top: "100%",
143
+ right: 0,
144
+ marginTop: "8px",
145
+ width: "280px",
146
+ padding: "16px",
147
+ borderRadius: "12px",
148
+ border: `1px solid ${STYLES.border}`,
149
+ backgroundColor: STYLES.bg,
150
+ backdropFilter: STYLES.blur,
151
+ WebkitBackdropFilter: STYLES.blur,
152
+ boxShadow: STYLES.shadow,
153
+ animation: "uilint-fade-in 0.15s ease-out"
154
+ },
155
+ children: [
156
+ /* @__PURE__ */ jsx(
157
+ "div",
158
+ {
159
+ style: {
160
+ fontSize: "13px",
161
+ fontWeight: 600,
162
+ color: STYLES.text,
163
+ marginBottom: "12px"
164
+ },
165
+ children: "UILint Settings"
166
+ }
167
+ ),
168
+ /* @__PURE__ */ jsx(
169
+ SettingToggle,
170
+ {
171
+ label: "Hide node_modules",
172
+ checked: settings.hideNodeModules,
173
+ onChange: (checked) => onUpdate({ hideNodeModules: checked })
174
+ }
175
+ ),
176
+ /* @__PURE__ */ jsxs(
177
+ "div",
178
+ {
179
+ style: {
180
+ marginTop: "12px",
181
+ paddingTop: "12px",
182
+ borderTop: `1px solid ${STYLES.border}`,
183
+ fontSize: "11px",
184
+ color: STYLES.textMuted,
185
+ lineHeight: 1.5
186
+ },
187
+ children: [
188
+ /* @__PURE__ */ jsx("strong", { style: { color: STYLES.text }, children: "Alt+Click" }),
189
+ " any element to open the inspector sidebar"
190
+ ]
191
+ }
192
+ )
193
+ ]
194
+ }
195
+ );
196
+ }
197
+ function SettingToggle({
198
+ label,
199
+ checked,
200
+ onChange
201
+ }) {
202
+ return /* @__PURE__ */ jsxs(
203
+ "label",
204
+ {
205
+ style: {
206
+ display: "flex",
207
+ alignItems: "center",
208
+ justifyContent: "space-between",
209
+ padding: "8px 0",
210
+ cursor: "pointer"
211
+ },
212
+ children: [
213
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: STYLES.textMuted }, children: label }),
214
+ /* @__PURE__ */ jsx(
215
+ "div",
216
+ {
217
+ onClick: () => onChange(!checked),
218
+ style: {
219
+ width: "36px",
220
+ height: "20px",
221
+ borderRadius: "10px",
222
+ backgroundColor: checked ? STYLES.accent : "rgba(75, 85, 99, 0.5)",
223
+ position: "relative",
224
+ transition: "background-color 0.2s"
225
+ },
226
+ children: /* @__PURE__ */ jsx(
227
+ "div",
228
+ {
229
+ style: {
230
+ position: "absolute",
231
+ top: "2px",
232
+ left: checked ? "18px" : "2px",
233
+ width: "16px",
234
+ height: "16px",
235
+ borderRadius: "50%",
236
+ backgroundColor: "#FFFFFF",
237
+ transition: "left 0.2s",
238
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
239
+ }
240
+ }
241
+ )
242
+ }
243
+ )
244
+ ]
245
+ }
246
+ );
247
+ }
248
+ function UILintIcon({ active }) {
249
+ return /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", children: [
250
+ /* @__PURE__ */ jsx(
251
+ "rect",
252
+ {
253
+ x: "3",
254
+ y: "3",
255
+ width: "18",
256
+ height: "18",
257
+ rx: "3",
258
+ stroke: active ? STYLES.accent : "currentColor",
259
+ strokeWidth: "2"
260
+ }
261
+ ),
262
+ /* @__PURE__ */ jsx(
263
+ "path",
264
+ {
265
+ d: "M7 12h10M12 7v10",
266
+ stroke: active ? STYLES.accent : "currentColor",
267
+ strokeWidth: "2",
268
+ strokeLinecap: "round"
269
+ }
270
+ )
271
+ ] });
272
+ }
273
+
274
+ export {
275
+ UILintToolbar
276
+ };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  useUILintContext
4
- } from "./chunk-7WYVWDRU.js";
4
+ } from "./chunk-DAFFOBEU.js";
5
5
 
6
6
  // src/components/ui-lint/LocatorOverlay.tsx
7
7
  import { useState, useEffect, useMemo } from "react";
@@ -207,7 +207,77 @@ function InfoTooltip({
207
207
  }
208
208
  );
209
209
  }
210
+ function InspectedElementHighlight() {
211
+ const { inspectedElement } = useUILintContext();
212
+ const [mounted, setMounted] = useState(false);
213
+ const [rect, setRect] = useState(null);
214
+ useEffect(() => {
215
+ setMounted(true);
216
+ }, []);
217
+ useEffect(() => {
218
+ if (!inspectedElement?.element) return;
219
+ const updateRect = () => {
220
+ if (inspectedElement.element) {
221
+ setRect(inspectedElement.element.getBoundingClientRect());
222
+ }
223
+ };
224
+ updateRect();
225
+ window.addEventListener("scroll", updateRect, true);
226
+ window.addEventListener("resize", updateRect);
227
+ return () => {
228
+ window.removeEventListener("scroll", updateRect, true);
229
+ window.removeEventListener("resize", updateRect);
230
+ };
231
+ }, [inspectedElement?.element]);
232
+ if (!mounted || !inspectedElement || !rect) return null;
233
+ const content = /* @__PURE__ */ jsxs("div", { "data-ui-lint": true, style: { pointerEvents: "none" }, children: [
234
+ /* @__PURE__ */ jsx("style", { children: `
235
+ @keyframes uilint-inspected-pulse {
236
+ 0%, 100% { box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.6), 0 0 8px rgba(16, 185, 129, 0.3); }
237
+ 50% { box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.8), 0 0 16px rgba(16, 185, 129, 0.5); }
238
+ }
239
+ ` }),
240
+ /* @__PURE__ */ jsx(
241
+ "div",
242
+ {
243
+ style: {
244
+ position: "fixed",
245
+ top: rect.top - 3,
246
+ left: rect.left - 3,
247
+ width: rect.width + 6,
248
+ height: rect.height + 6,
249
+ border: "2px solid #10B981",
250
+ borderRadius: "6px",
251
+ backgroundColor: "rgba(16, 185, 129, 0.08)",
252
+ animation: "uilint-inspected-pulse 2s ease-in-out infinite",
253
+ zIndex: 99996
254
+ }
255
+ }
256
+ ),
257
+ /* @__PURE__ */ jsx(
258
+ "div",
259
+ {
260
+ style: {
261
+ position: "fixed",
262
+ top: rect.top - 24,
263
+ left: rect.left - 3,
264
+ padding: "2px 8px",
265
+ backgroundColor: "#10B981",
266
+ color: "#FFFFFF",
267
+ fontSize: "10px",
268
+ fontWeight: 600,
269
+ fontFamily: STYLES.font,
270
+ borderRadius: "4px 4px 0 0",
271
+ zIndex: 99996
272
+ },
273
+ children: "Inspecting"
274
+ }
275
+ )
276
+ ] });
277
+ return createPortal(content, document.body);
278
+ }
210
279
 
211
280
  export {
212
- LocatorOverlay
281
+ LocatorOverlay,
282
+ InspectedElementHighlight
213
283
  };
package/dist/index.d.ts CHANGED
@@ -1,29 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React$1 from 'react';
3
- import { Violation, GroupedSnapshot, DOMSnapshot, ExtractedStyles, AnalysisResult } from 'uilint-core';
3
+ import { GroupedSnapshot, Violation, DOMSnapshot, ExtractedStyles, AnalysisResult } from 'uilint-core';
4
4
  export { AnalysisResult, ConsistencyResult, DOMSnapshot, ElementRole, ElementSnapshot, ExtractedStyles, GroupedSnapshot, SerializedStyles, StyleGuide, StyleSnapshot, UILintIssue, Violation, ViolationCategory, ViolationSeverity, createEmptyStyleGuide, createStyleSummary, extractStylesFromDOM, generateStyleGuideFromStyles as generateStyleGuide, mergeStyleGuides, parseStyleGuide, serializeStyles } from 'uilint-core';
5
5
 
6
- interface UILintProps {
7
- children: React$1.ReactNode;
8
- enabled?: boolean;
9
- position?: "bottom-left" | "bottom-right" | "top-left" | "top-right";
10
- autoScan?: boolean;
11
- apiEndpoint?: string;
12
- }
13
- interface UILintContextValue$1 {
14
- violations: Violation[];
15
- isScanning: boolean;
16
- elementCount: number;
17
- scan: () => Promise<void>;
18
- clearViolations: () => void;
19
- selectedViolation: Violation | null;
20
- setSelectedViolation: (violation: Violation | null) => void;
21
- lockedViolation: Violation | null;
22
- setLockedViolation: (violation: Violation | null) => void;
23
- }
24
- declare function useUILint(): UILintContextValue$1;
25
- declare function UILint({ children, enabled, position, autoScan, apiEndpoint, }: UILintProps): react_jsx_runtime.JSX.Element;
26
-
27
6
  /**
28
7
  * Types for UILint Source Visualization
29
8
  */
@@ -67,15 +46,8 @@ interface SourceFile {
67
46
  * User-configurable settings for the overlay
68
47
  */
69
48
  interface UILintSettings {
70
- showLabels: boolean;
71
49
  hideNodeModules: boolean;
72
- overlayOpacity: number;
73
- labelPosition: "top-left" | "top-right" | "bottom-left" | "bottom-right";
74
50
  }
75
- /**
76
- * Operating modes for the UILint overlay
77
- */
78
- type UILintMode = "off" | "sources" | "inspect";
79
51
  /**
80
52
  * Element detected under the cursor during Alt-key locator mode
81
53
  */
@@ -87,22 +59,21 @@ interface LocatorTarget {
87
59
  /** Index in the component stack (0 = current element, higher = parent) */
88
60
  stackIndex: number;
89
61
  }
62
+ /**
63
+ * Element being inspected in the sidebar
64
+ */
65
+ interface InspectedElement {
66
+ element: Element;
67
+ source: SourceLocation | null;
68
+ componentStack: ComponentInfo[];
69
+ rect: DOMRect;
70
+ }
90
71
  /**
91
72
  * Context value provided by UILintProvider
92
73
  */
93
74
  interface UILintContextValue {
94
- mode: UILintMode;
95
- setMode: (mode: UILintMode) => void;
96
- scannedElements: ScannedElement[];
97
- sourceFiles: SourceFile[];
98
- selectedElement: ScannedElement | null;
99
- setSelectedElement: (element: ScannedElement | null) => void;
100
- hoveredElement: ScannedElement | null;
101
- setHoveredElement: (element: ScannedElement | null) => void;
102
75
  settings: UILintSettings;
103
76
  updateSettings: (settings: Partial<UILintSettings>) => void;
104
- rescan: () => void;
105
- isScanning: boolean;
106
77
  /** True when Alt/Option key is held down */
107
78
  altKeyHeld: boolean;
108
79
  /** Current element under cursor when Alt is held */
@@ -111,6 +82,10 @@ interface UILintContextValue {
111
82
  locatorGoUp: () => void;
112
83
  /** Navigate to child component in locator mode */
113
84
  locatorGoDown: () => void;
85
+ /** Element currently being inspected in sidebar */
86
+ inspectedElement: InspectedElement | null;
87
+ /** Set the element to inspect (opens sidebar) */
88
+ setInspectedElement: (element: InspectedElement | null) => void;
114
89
  }
115
90
  /**
116
91
  * Props for the UILintProvider component
@@ -118,7 +93,6 @@ interface UILintContextValue {
118
93
  interface UILintProviderProps {
119
94
  children: React.ReactNode;
120
95
  enabled?: boolean;
121
- defaultMode?: UILintMode;
122
96
  }
123
97
  /**
124
98
  * Response from the source API
@@ -155,34 +129,36 @@ declare function useUILintContext(): UILintContextValue;
155
129
  /**
156
130
  * UILint Provider Component
157
131
  */
158
- declare function UILintProvider({ children, enabled, defaultMode, }: UILintProviderProps): react_jsx_runtime.JSX.Element;
132
+ declare function UILintProvider({ children, enabled, }: UILintProviderProps): react_jsx_runtime.JSX.Element;
159
133
 
160
134
  /**
161
- * UILint Toolbar - Floating pill-shaped toolbar with mode toggles and settings
135
+ * UILint Toolbar - Simple floating button with settings popover
162
136
  */
163
137
 
164
138
  /**
165
- * Main Toolbar Component
139
+ * Main Toolbar Component - Simple floating button with settings popover
166
140
  */
167
141
  declare function UILintToolbar(): React$1.ReactPortal | null;
168
142
 
169
143
  /**
170
- * Source Overlays - Colored rectangles over components with file labels
144
+ * Inspection Panel - Slide-out sidebar showing element details, source preview,
145
+ * and LLM analysis with clipboard-ready fix prompts
171
146
  */
172
147
 
173
148
  /**
174
- * Main Source Overlays Component
149
+ * Main Inspection Panel Component
175
150
  */
176
- declare function SourceOverlays(): React$1.ReactPortal | null;
151
+ declare function InspectionPanel(): React$1.ReactPortal | null;
177
152
 
178
153
  /**
179
- * Inspection Panel - Slide-out panel showing element details and source preview
154
+ * Locator Overlay - Shows element info when Alt/Option key is held
155
+ * Inspired by LocatorJS for a quick "hover to find source" experience
180
156
  */
181
157
 
182
158
  /**
183
- * Main Inspection Panel Component
159
+ * Main Locator Overlay Component
184
160
  */
185
- declare function InspectionPanel(): React$1.ReactPortal | null;
161
+ declare function LocatorOverlay(): React$1.ReactPortal | null;
186
162
 
187
163
  /**
188
164
  * React Fiber inspection utilities for source file visualization
@@ -287,21 +263,6 @@ declare function getCachedSource(filePath: string): SourceApiResponse | null;
287
263
  */
288
264
  declare function prefetchSources(filePaths: string[]): Promise<void>;
289
265
 
290
- interface UseElementScanOptions {
291
- enabled: boolean;
292
- settings: UILintSettings;
293
- }
294
- interface UseElementScanResult {
295
- elements: ScannedElement[];
296
- sourceFiles: SourceFile[];
297
- isScanning: boolean;
298
- rescan: () => void;
299
- }
300
- /**
301
- * Hook for managing element scanning
302
- */
303
- declare function useElementScan({ enabled, settings, }: UseElementScanOptions): UseElementScanResult;
304
-
305
266
  /**
306
267
  * DOM snapshotting for consistency analysis
307
268
  */
@@ -382,4 +343,4 @@ declare class LLMClient {
382
343
  generateStyleGuide(styles: ExtractedStyles): Promise<string | null>;
383
344
  }
384
345
 
385
- export { type CachedSource, type ComponentInfo, ConsistencyHighlighter, DATA_UILINT_ID, DEFAULT_SETTINGS, FILE_COLORS, InspectionPanel, LLMClient, type ScannedElement, type SourceApiResponse, type SourceFile, type SourceLocation, SourceOverlays, UILint, type UILintContextValue, type UILintMode, type UILintProps, UILintProvider, type UILintProviderProps, type UILintSettings, UILintToolbar, buildEditorUrl, cleanupDataAttributes, cleanupDataElements, clearSourceCache, createSnapshot, fetchSource, fetchSourceWithContext, getCachedSource, getComponentStack, getDebugOwner, getDebugSource, getDisplayName, getElementById, getElementBySnapshotId, getFiberFromElement, groupBySourceFile, isBrowser, isJSDOM, isNode, isNodeModulesPath, prefetchSources, scanDOM, scanDOMForSources, updateElementRects, useElementScan, useUILint, useUILintContext };
346
+ export { type CachedSource, type ComponentInfo, ConsistencyHighlighter, DATA_UILINT_ID, DEFAULT_SETTINGS, FILE_COLORS, type InspectedElement, InspectionPanel, LLMClient, LocatorOverlay, type LocatorTarget, type ScannedElement, type SourceApiResponse, type SourceFile, type SourceLocation, type UILintContextValue, UILintProvider, type UILintProviderProps, type UILintSettings, UILintToolbar, buildEditorUrl, cleanupDataAttributes, cleanupDataElements, clearSourceCache, createSnapshot, fetchSource, fetchSourceWithContext, getCachedSource, getComponentStack, getDebugOwner, getDebugSource, getDisplayName, getElementById, getElementBySnapshotId, getFiberFromElement, groupBySourceFile, isBrowser, isJSDOM, isNode, isNodeModulesPath, prefetchSources, scanDOM, scanDOMForSources, updateElementRects, useUILintContext };