@stsgs1980/fab-inspector 3.4.1 → 3.5.0
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 +27 -0
- package/dist/api-source-route.d.ts +14 -0
- package/dist/api-source-route.d.ts.map +1 -0
- package/dist/api-source-route.js +54 -0
- package/dist/api-source-route.js.map +1 -0
- package/dist/box-model-section.d.ts +5 -0
- package/dist/box-model-section.d.ts.map +1 -0
- package/dist/box-model-section.js +15 -0
- package/dist/box-model-section.js.map +1 -0
- package/dist/highlight-overlay.d.ts +4 -0
- package/dist/highlight-overlay.d.ts.map +1 -0
- package/dist/highlight-overlay.js +14 -0
- package/dist/highlight-overlay.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector-fab.d.ts +6 -0
- package/dist/inspector-fab.d.ts.map +1 -0
- package/dist/inspector-fab.js +14 -0
- package/dist/inspector-fab.js.map +1 -0
- package/dist/inspector-panel.d.ts +14 -0
- package/dist/inspector-panel.d.ts.map +1 -0
- package/dist/inspector-panel.js +36 -0
- package/dist/inspector-panel.js.map +1 -0
- package/dist/panel-sections.d.ts +25 -0
- package/dist/panel-sections.d.ts.map +1 -0
- package/dist/panel-sections.js +48 -0
- package/dist/panel-sections.js.map +1 -0
- package/dist/plugins/data-src-plugin.d.ts +38 -0
- package/dist/plugins/data-src-plugin.d.ts.map +1 -0
- package/dist/plugins/data-src-plugin.js +90 -0
- package/dist/plugins/data-src-plugin.js.map +1 -0
- package/dist/select-element-fab.d.ts +2 -0
- package/dist/select-element-fab.d.ts.map +1 -0
- package/{select-element-fab.tsx → dist/select-element-fab.js} +8 -45
- package/dist/select-element-fab.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/use-element-inspector.d.ts +19 -0
- package/dist/use-element-inspector.d.ts.map +1 -0
- package/dist/use-element-inspector.js +239 -0
- package/dist/use-element-inspector.js.map +1 -0
- package/dist/use-panel-drag.d.ts +11 -0
- package/dist/use-panel-drag.d.ts.map +1 -0
- package/dist/use-panel-drag.js +35 -0
- package/dist/use-panel-drag.js.map +1 -0
- package/package.json +25 -9
- package/api-source-route.ts +0 -60
- package/box-model-section.tsx +0 -97
- package/highlight-overlay.tsx +0 -23
- package/index.ts +0 -10
- package/inspector-fab.tsx +0 -63
- package/inspector-panel.tsx +0 -190
- package/panel-sections.tsx +0 -207
- package/plugins/data-src-plugin.ts +0 -110
- package/types.ts +0 -45
- package/use-element-inspector.ts +0 -264
- package/use-panel-drag.ts +0 -47
package/box-model-section.tsx
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import type { BoxModel } from './types';
|
|
2
|
-
|
|
3
|
-
function fmt(val: string): string {
|
|
4
|
-
if (val === '0px') return '0';
|
|
5
|
-
return val;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function BoxRow({
|
|
9
|
-
top,
|
|
10
|
-
right,
|
|
11
|
-
bottom,
|
|
12
|
-
left,
|
|
13
|
-
color,
|
|
14
|
-
bg,
|
|
15
|
-
label,
|
|
16
|
-
inner,
|
|
17
|
-
}: {
|
|
18
|
-
top: string; right: string; bottom: string; left: string;
|
|
19
|
-
color: string; bg: string; label: string;
|
|
20
|
-
inner: React.ReactNode;
|
|
21
|
-
}) {
|
|
22
|
-
const allSame = top === right && right === bottom && bottom === left;
|
|
23
|
-
const display = allSame ? fmt(top) : `${fmt(top)} ${fmt(right)} ${fmt(bottom)} ${fmt(left)}`;
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div className="flex items-center gap-2 text-[10px] font-mono">
|
|
27
|
-
<span className="w-12 text-right text-[#8B949E] flex-shrink-0">{label}</span>
|
|
28
|
-
<div className="flex-1">
|
|
29
|
-
<div
|
|
30
|
-
className="border text-center py-1 text-[#E6EDF3] rounded-sm"
|
|
31
|
-
style={{ borderColor: color, backgroundColor: bg }}
|
|
32
|
-
>
|
|
33
|
-
{allSame ? (
|
|
34
|
-
<span>{display}</span>
|
|
35
|
-
) : (
|
|
36
|
-
<div className="flex justify-between px-1">
|
|
37
|
-
<span>{fmt(top)}</span>
|
|
38
|
-
<span>{fmt(right)}</span>
|
|
39
|
-
<span>{fmt(bottom)}</span>
|
|
40
|
-
<span>{fmt(left)}</span>
|
|
41
|
-
</div>
|
|
42
|
-
)}
|
|
43
|
-
<div className="mt-1">{inner}</div>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function BoxModelSection({ boxModel }: { boxModel: BoxModel }) {
|
|
51
|
-
return (
|
|
52
|
-
<div className="px-4 py-2.5 border-b border-[#30363D]">
|
|
53
|
-
<div className="text-[11px] font-semibold text-[#8B949E] uppercase tracking-wider mb-2">
|
|
54
|
-
Box Model
|
|
55
|
-
</div>
|
|
56
|
-
<div className="flex flex-col gap-1.5">
|
|
57
|
-
<BoxRow
|
|
58
|
-
top={boxModel.marginTop}
|
|
59
|
-
right={boxModel.marginRight}
|
|
60
|
-
bottom={boxModel.marginBottom}
|
|
61
|
-
left={boxModel.marginLeft}
|
|
62
|
-
color="#D29922"
|
|
63
|
-
bg="rgba(210, 153, 34, 0.06)"
|
|
64
|
-
label="margin"
|
|
65
|
-
inner={
|
|
66
|
-
<BoxRow
|
|
67
|
-
top={boxModel.borderTop}
|
|
68
|
-
right={boxModel.borderRight}
|
|
69
|
-
bottom={boxModel.borderBottom}
|
|
70
|
-
left={boxModel.borderLeft}
|
|
71
|
-
color="#58A6FF"
|
|
72
|
-
bg="rgba(88, 166, 255, 0.06)"
|
|
73
|
-
label="border"
|
|
74
|
-
inner={
|
|
75
|
-
<BoxRow
|
|
76
|
-
top={boxModel.paddingTop}
|
|
77
|
-
right={boxModel.paddingRight}
|
|
78
|
-
bottom={boxModel.paddingBottom}
|
|
79
|
-
left={boxModel.paddingLeft}
|
|
80
|
-
color="#3FB950"
|
|
81
|
-
bg="rgba(63, 185, 80, 0.06)"
|
|
82
|
-
label="padding"
|
|
83
|
-
inner={
|
|
84
|
-
<div className="text-center py-1 bg-[#0D1117] rounded-sm text-[#E6EDF3]">
|
|
85
|
-
<div className="text-[9px] text-[#6E7681]">content</div>
|
|
86
|
-
<div>{fmt(boxModel.width)} x {fmt(boxModel.height)}</div>
|
|
87
|
-
</div>
|
|
88
|
-
}
|
|
89
|
-
/>
|
|
90
|
-
}
|
|
91
|
-
/>
|
|
92
|
-
}
|
|
93
|
-
/>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
}
|
package/highlight-overlay.tsx
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export function HighlightOverlay({
|
|
2
|
-
highlightBox,
|
|
3
|
-
}: {
|
|
4
|
-
highlightBox: DOMRect;
|
|
5
|
-
}) {
|
|
6
|
-
return (
|
|
7
|
-
<div
|
|
8
|
-
data-se-highlight
|
|
9
|
-
className="fixed pointer-events-none z-[90]"
|
|
10
|
-
style={{
|
|
11
|
-
top: highlightBox.top,
|
|
12
|
-
left: highlightBox.left,
|
|
13
|
-
width: highlightBox.width,
|
|
14
|
-
height: highlightBox.height,
|
|
15
|
-
border: '1px dashed #58A6FF',
|
|
16
|
-
backgroundColor: 'rgba(56, 139, 253, 0.06)',
|
|
17
|
-
borderRadius: '3px',
|
|
18
|
-
transition: 'all 0.1s ease-out',
|
|
19
|
-
}}
|
|
20
|
-
aria-hidden="true"
|
|
21
|
-
/>
|
|
22
|
-
);
|
|
23
|
-
}
|
package/index.ts
DELETED
package/inspector-fab.tsx
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { motion, AnimatePresence } from 'framer-motion';
|
|
2
|
-
|
|
3
|
-
export function InspectorFab({
|
|
4
|
-
active,
|
|
5
|
-
onToggle,
|
|
6
|
-
showTooltip,
|
|
7
|
-
}: {
|
|
8
|
-
active: boolean;
|
|
9
|
-
onToggle: () => void;
|
|
10
|
-
showTooltip: boolean;
|
|
11
|
-
}) {
|
|
12
|
-
return (
|
|
13
|
-
<>
|
|
14
|
-
<motion.button
|
|
15
|
-
data-se-fab
|
|
16
|
-
onClick={onToggle}
|
|
17
|
-
className={`
|
|
18
|
-
fixed bottom-6 right-6 z-[90] w-12 h-12 rounded-full
|
|
19
|
-
flex items-center justify-center
|
|
20
|
-
shadow-lg transition-all duration-200 cursor-pointer
|
|
21
|
-
focus:outline-none focus-visible:ring-2 focus-visible:ring-[#58A6FF] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0D1117]
|
|
22
|
-
${active
|
|
23
|
-
? 'bg-[#1F6FEB] text-[#F0F6FC] shadow-[#1F6FEB]/30'
|
|
24
|
-
: 'bg-[#21262D] text-[#E6EDF3]/80 hover:bg-[#30363D] hover:text-[#E6EDF3] shadow-black/40'
|
|
25
|
-
}
|
|
26
|
-
`}
|
|
27
|
-
whileHover={{ scale: 1.08 }}
|
|
28
|
-
whileTap={{ scale: 0.95 }}
|
|
29
|
-
aria-label={active ? 'Закрыть инспектор элементов' : 'Открыть инспектор элементов'}
|
|
30
|
-
title={active ? 'Закрыть инспектор (Esc)' : 'Инспектор элементов'}
|
|
31
|
-
>
|
|
32
|
-
{active ? (
|
|
33
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
34
|
-
<path d="M18 6 6 18" />
|
|
35
|
-
<path d="m6 6 12 12" />
|
|
36
|
-
</svg>
|
|
37
|
-
) : (
|
|
38
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
39
|
-
<path d="m19 19-14-14" />
|
|
40
|
-
<path d="m5 5 14 0" />
|
|
41
|
-
<path d="m5 5 0 14" />
|
|
42
|
-
</svg>
|
|
43
|
-
)}
|
|
44
|
-
<span className="absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full bg-[#58A6FF] text-[#0D1117] text-[9px] font-bold flex items-center justify-center leading-none select-none">
|
|
45
|
-
3.4.1
|
|
46
|
-
</span>
|
|
47
|
-
</motion.button>
|
|
48
|
-
|
|
49
|
-
<AnimatePresence>
|
|
50
|
-
{showTooltip && (
|
|
51
|
-
<motion.div
|
|
52
|
-
initial={{ opacity: 0, y: 8 }}
|
|
53
|
-
animate={{ opacity: 1, y: 0 }}
|
|
54
|
-
exit={{ opacity: 0, y: 8 }}
|
|
55
|
-
className="fixed bottom-20 right-6 z-[90] bg-[#21262D] text-[#E6EDF3] text-xs px-3 py-1.5 rounded-lg shadow-lg border border-[#30363D] whitespace-nowrap pointer-events-none"
|
|
56
|
-
>
|
|
57
|
-
Кликните на элемент для инспекции
|
|
58
|
-
</motion.div>
|
|
59
|
-
)}
|
|
60
|
-
</AnimatePresence>
|
|
61
|
-
</>
|
|
62
|
-
);
|
|
63
|
-
}
|
package/inspector-panel.tsx
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
-
import type { ElementInfo, SnippetData } from './types';
|
|
4
|
-
import {
|
|
5
|
-
SourceSection,
|
|
6
|
-
ClassesSection,
|
|
7
|
-
TextSection,
|
|
8
|
-
CssPathSection,
|
|
9
|
-
HtmlSection,
|
|
10
|
-
StylesSection,
|
|
11
|
-
SnippetSection,
|
|
12
|
-
} from './panel-sections';
|
|
13
|
-
import { BoxModelSection } from './box-model-section';
|
|
14
|
-
|
|
15
|
-
export function InspectorPanel({
|
|
16
|
-
elementInfo,
|
|
17
|
-
panelPos,
|
|
18
|
-
isDragging,
|
|
19
|
-
onDragStart,
|
|
20
|
-
onClose,
|
|
21
|
-
snippet,
|
|
22
|
-
snippetLoading,
|
|
23
|
-
}: {
|
|
24
|
-
elementInfo: ElementInfo;
|
|
25
|
-
panelPos: { x: number; y: number };
|
|
26
|
-
isDragging: boolean;
|
|
27
|
-
onDragStart: (e: React.MouseEvent) => void;
|
|
28
|
-
onClose: () => void;
|
|
29
|
-
snippet: SnippetData | null;
|
|
30
|
-
snippetLoading: boolean;
|
|
31
|
-
}) {
|
|
32
|
-
const [expanded, setExpanded] = useState(false);
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<motion.div
|
|
36
|
-
ref={(node) => {
|
|
37
|
-
if (node) {
|
|
38
|
-
(node as unknown as HTMLElement).setAttribute('data-se-panel', '');
|
|
39
|
-
}
|
|
40
|
-
}}
|
|
41
|
-
initial={{ opacity: 0, scale: 0.95, y: 4 }}
|
|
42
|
-
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
43
|
-
exit={{ opacity: 0, scale: 0.95, y: 4 }}
|
|
44
|
-
transition={{ duration: 0.15 }}
|
|
45
|
-
className={`fixed z-[95] bg-[#161B22] rounded-lg shadow-[0_8px_32px_rgba(0,0,0,0.4)] border border-[#30363D] overflow-hidden${isDragging ? '' : ' cursor-default'}`}
|
|
46
|
-
style={{
|
|
47
|
-
top: panelPos.y,
|
|
48
|
-
left: panelPos.x,
|
|
49
|
-
width: 400,
|
|
50
|
-
maxHeight: 'calc(100vh - 32px)',
|
|
51
|
-
userSelect: isDragging ? 'none' : 'auto',
|
|
52
|
-
}}
|
|
53
|
-
onClick={(e) => e.stopPropagation()}
|
|
54
|
-
>
|
|
55
|
-
{/* Header (drag handle) */}
|
|
56
|
-
<div
|
|
57
|
-
onMouseDown={onDragStart}
|
|
58
|
-
className={`flex items-center justify-between px-4 py-2.5 bg-[#1C2128] border-b border-[#30363D] select-none${isDragging ? ' cursor-grabbing' : ' cursor-grab'}`}
|
|
59
|
-
>
|
|
60
|
-
<div className="flex items-center gap-2 min-w-0">
|
|
61
|
-
<span className="inline-flex items-center justify-center w-5 h-5 rounded bg-[#1F6FEB] text-[#F0F6FC] text-[10px] font-bold flex-shrink-0">
|
|
62
|
-
</>
|
|
63
|
-
</span>
|
|
64
|
-
<span className="text-sm font-semibold text-[#E6EDF3] truncate">
|
|
65
|
-
{elementInfo.tag}
|
|
66
|
-
{elementInfo.id && <span className="text-[#58A6FF]">#{elementInfo.id}</span>}
|
|
67
|
-
</span>
|
|
68
|
-
{elementInfo.source && (
|
|
69
|
-
<span className="text-[11px] text-[#6E7681] font-mono truncate hidden sm:inline">
|
|
70
|
-
{elementInfo.source.file.split('/').pop()}:{elementInfo.source.line}
|
|
71
|
-
</span>
|
|
72
|
-
)}
|
|
73
|
-
</div>
|
|
74
|
-
<div className="flex items-center gap-0.5 flex-shrink-0">
|
|
75
|
-
{elementInfo.source && (
|
|
76
|
-
<button
|
|
77
|
-
onClick={() => {
|
|
78
|
-
const lines = [
|
|
79
|
-
elementInfo.source ? `File: ${elementInfo.source.file}:${elementInfo.source.line}` : '',
|
|
80
|
-
`Tag: <${elementInfo.tag}${elementInfo.id ? `#${elementInfo.id}` : ''}>`,
|
|
81
|
-
elementInfo.text ? `Text: "${elementInfo.text}"` : '',
|
|
82
|
-
].filter(Boolean).join('\n');
|
|
83
|
-
navigator.clipboard.writeText(lines).catch(() => {});
|
|
84
|
-
}}
|
|
85
|
-
className="p-1.5 rounded hover:bg-[#21262D] text-[#8B949E] hover:text-[#58A6FF] transition-colors cursor-pointer"
|
|
86
|
-
aria-label="Copy task context"
|
|
87
|
-
title="Copy task context (file, tag, text)"
|
|
88
|
-
>
|
|
89
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
90
|
-
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" />
|
|
91
|
-
<polyline points="14 2 14 8 20 8" />
|
|
92
|
-
<line x1="16" y1="13" x2="8" y2="13" />
|
|
93
|
-
<line x1="16" y1="17" x2="8" y2="17" />
|
|
94
|
-
<line x1="10" y1="9" x2="8" y2="9" />
|
|
95
|
-
</svg>
|
|
96
|
-
</button>
|
|
97
|
-
)}
|
|
98
|
-
{elementInfo.source && (
|
|
99
|
-
<button
|
|
100
|
-
onClick={() =>
|
|
101
|
-
navigator.clipboard
|
|
102
|
-
.writeText(`${elementInfo.source!.file}:${elementInfo.source!.line}`)
|
|
103
|
-
.catch(() => {})
|
|
104
|
-
}
|
|
105
|
-
className="p-1.5 rounded hover:bg-[#21262D] text-[#8B949E] hover:text-[#58A6FF] transition-colors cursor-pointer"
|
|
106
|
-
aria-label="Copy file path"
|
|
107
|
-
title="Copy file:line"
|
|
108
|
-
>
|
|
109
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
110
|
-
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
|
111
|
-
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
112
|
-
</svg>
|
|
113
|
-
</button>
|
|
114
|
-
)}
|
|
115
|
-
{/* Always-visible quick copy: tag + id + classes + text.
|
|
116
|
-
Работает без data-src — можно скопировать базу из свёрнутой панели. */}
|
|
117
|
-
<button
|
|
118
|
-
onClick={() => {
|
|
119
|
-
const parts = [
|
|
120
|
-
`<${elementInfo.tag}${elementInfo.id ? `#${elementInfo.id}` : ''}>`,
|
|
121
|
-
elementInfo.classes ? elementInfo.classes : '',
|
|
122
|
-
elementInfo.text ? `"${elementInfo.text}"` : '',
|
|
123
|
-
].filter(Boolean);
|
|
124
|
-
navigator.clipboard.writeText(parts.join('\n')).catch(() => {});
|
|
125
|
-
}}
|
|
126
|
-
className="p-1.5 rounded hover:bg-[#21262D] text-[#8B949E] hover:text-[#58A6FF] transition-colors cursor-pointer"
|
|
127
|
-
aria-label="Скопировать информацию об элементе"
|
|
128
|
-
title="Копировать (тег + классы + текст)"
|
|
129
|
-
>
|
|
130
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
131
|
-
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
|
132
|
-
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
133
|
-
</svg>
|
|
134
|
-
</button>
|
|
135
|
-
<div className="w-px h-4 bg-[#30363D] mx-0.5" />
|
|
136
|
-
<button
|
|
137
|
-
onClick={() => setExpanded((v) => !v)}
|
|
138
|
-
className="p-1.5 rounded hover:bg-[#21262D] text-[#8B949E] hover:text-[#E6EDF3] transition-colors cursor-pointer"
|
|
139
|
-
aria-label={expanded ? 'Свернуть детали' : 'Развернуть детали'}
|
|
140
|
-
title={expanded ? 'Свернуть' : 'Детали'}
|
|
141
|
-
>
|
|
142
|
-
<svg
|
|
143
|
-
width="14" height="14" viewBox="0 0 24 24" fill="none"
|
|
144
|
-
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
|
145
|
-
style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}
|
|
146
|
-
>
|
|
147
|
-
<polyline points="6 9 12 15 18 9" />
|
|
148
|
-
</svg>
|
|
149
|
-
</button>
|
|
150
|
-
<button
|
|
151
|
-
onClick={onClose}
|
|
152
|
-
className="p-1.5 rounded hover:bg-[#DA3633]/20 text-[#8B949E] hover:text-[#F85149] transition-colors cursor-pointer"
|
|
153
|
-
aria-label="Закрыть панель"
|
|
154
|
-
>
|
|
155
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
156
|
-
<path d="M18 6 6 18" />
|
|
157
|
-
<path d="m6 6 12 12" />
|
|
158
|
-
</svg>
|
|
159
|
-
</button>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
{/* Collapsible content */}
|
|
164
|
-
<AnimatePresence initial={false}>
|
|
165
|
-
{expanded && (
|
|
166
|
-
<motion.div
|
|
167
|
-
initial={{ height: 0, opacity: 0 }}
|
|
168
|
-
animate={{ height: 'auto', opacity: 1 }}
|
|
169
|
-
exit={{ height: 0, opacity: 0 }}
|
|
170
|
-
transition={{ duration: 0.2, ease: 'easeInOut' }}
|
|
171
|
-
className="overflow-hidden"
|
|
172
|
-
>
|
|
173
|
-
<div className="overflow-y-auto custom-scrollbar" style={{ maxHeight: 'calc(100vh - 120px)' }}>
|
|
174
|
-
{elementInfo.source && <SourceSection source={elementInfo.source} />}
|
|
175
|
-
{elementInfo.classes && <ClassesSection classes={elementInfo.classes} />}
|
|
176
|
-
{elementInfo.text && <TextSection text={elementInfo.text} />}
|
|
177
|
-
{elementInfo.cssPath && <CssPathSection cssPath={elementInfo.cssPath} />}
|
|
178
|
-
<HtmlSection outerHTML={elementInfo.outerHTML} />
|
|
179
|
-
<StylesSection styles={elementInfo.computedStyles} />
|
|
180
|
-
{elementInfo.boxModel && <BoxModelSection boxModel={elementInfo.boxModel} />}
|
|
181
|
-
{elementInfo.source && (
|
|
182
|
-
<SnippetSection source={elementInfo.source} snippet={snippet} snippetLoading={snippetLoading} />
|
|
183
|
-
)}
|
|
184
|
-
</div>
|
|
185
|
-
</motion.div>
|
|
186
|
-
)}
|
|
187
|
-
</AnimatePresence>
|
|
188
|
-
</motion.div>
|
|
189
|
-
);
|
|
190
|
-
}
|
package/panel-sections.tsx
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import type { ElementInfo, SnippetData } from './types';
|
|
2
|
-
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
3
|
-
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
4
|
-
|
|
5
|
-
function CopyButton({ text, label }: { text: string; label?: string }) {
|
|
6
|
-
const copy = () => navigator.clipboard.writeText(text).catch(() => {});
|
|
7
|
-
return (
|
|
8
|
-
<button
|
|
9
|
-
onClick={copy}
|
|
10
|
-
className="text-[10px] text-[#58A6FF] hover:text-[#79C0FF] transition-colors cursor-pointer"
|
|
11
|
-
title={label ?? 'Копировать'}
|
|
12
|
-
>
|
|
13
|
-
{label ?? 'копировать'}
|
|
14
|
-
</button>
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function SourceSection({ source }: { source: NonNullable<ElementInfo['source']> }) {
|
|
19
|
-
const copy = () =>
|
|
20
|
-
navigator.clipboard.writeText(`${source.file}:${source.line}`).catch(() => {});
|
|
21
|
-
return (
|
|
22
|
-
<div className="px-4 py-2.5 bg-[#0D1117] border-b border-[#30363D]">
|
|
23
|
-
<div className="flex items-center justify-between mb-1">
|
|
24
|
-
<div className="text-[11px] font-semibold text-[#58A6FF] uppercase tracking-wider">
|
|
25
|
-
Источник
|
|
26
|
-
</div>
|
|
27
|
-
<CopyButton text={`${source.file}:${source.line}`} />
|
|
28
|
-
</div>
|
|
29
|
-
<div
|
|
30
|
-
className="font-mono text-xs text-[#E6EDF3] cursor-pointer hover:text-[#58A6FF] transition-colors break-all"
|
|
31
|
-
onClick={copy}
|
|
32
|
-
title="Кликните, чтобы скопировать"
|
|
33
|
-
>
|
|
34
|
-
{source.file}
|
|
35
|
-
<span className="text-[#58A6FF]">:{source.line}</span>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function ClassesSection({ classes }: { classes: string }) {
|
|
42
|
-
const copy = () => navigator.clipboard.writeText(classes).catch(() => {});
|
|
43
|
-
return (
|
|
44
|
-
<div className="px-4 py-2.5 border-b border-[#30363D]">
|
|
45
|
-
<div className="flex items-center justify-between mb-1">
|
|
46
|
-
<div className="text-[11px] font-semibold text-[#8B949E] uppercase tracking-wider">Classes</div>
|
|
47
|
-
<CopyButton text={classes} />
|
|
48
|
-
</div>
|
|
49
|
-
<div
|
|
50
|
-
className="font-mono text-xs text-[#E6EDF3] bg-[#0D1117] rounded px-2.5 py-1.5 break-all cursor-pointer hover:bg-[#161B22] transition-colors border border-[#30363D]"
|
|
51
|
-
onClick={copy}
|
|
52
|
-
title="Кликните, чтобы скопировать"
|
|
53
|
-
>
|
|
54
|
-
{classes.length > 300 ? classes.slice(0, 300) + '...' : classes}
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function TextSection({ text }: { text: string }) {
|
|
61
|
-
const copy = () => navigator.clipboard.writeText(text).catch(() => {});
|
|
62
|
-
return (
|
|
63
|
-
<div className="px-4 py-2.5 border-b border-[#30363D]">
|
|
64
|
-
<div className="flex items-center justify-between mb-1">
|
|
65
|
-
<div className="text-[11px] font-semibold text-[#8B949E] uppercase tracking-wider">Text</div>
|
|
66
|
-
<CopyButton text={text} />
|
|
67
|
-
</div>
|
|
68
|
-
<div
|
|
69
|
-
className="text-xs text-[#E6EDF3] bg-[#0D1117] rounded px-2.5 py-1.5 cursor-pointer hover:bg-[#161B22] transition-colors border border-[#30363D]"
|
|
70
|
-
onClick={copy}
|
|
71
|
-
title="Кликните, чтобы скопировать текст"
|
|
72
|
-
>
|
|
73
|
-
{text}
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function CssPathSection({ cssPath }: { cssPath: string }) {
|
|
80
|
-
return (
|
|
81
|
-
<div className="px-4 py-2.5 border-b border-[#30363D]">
|
|
82
|
-
<div className="flex items-center justify-between mb-1">
|
|
83
|
-
<div className="text-[11px] font-semibold text-[#8B949E] uppercase tracking-wider">CSS Path</div>
|
|
84
|
-
<CopyButton text={cssPath} />
|
|
85
|
-
</div>
|
|
86
|
-
<div
|
|
87
|
-
className="font-mono text-[11px] text-[#E6EDF3] bg-[#0D1117] rounded px-2.5 py-1.5 break-all cursor-pointer hover:bg-[#161B22] transition-colors border border-[#30363D]"
|
|
88
|
-
onClick={() => navigator.clipboard.writeText(cssPath).catch(() => {})}
|
|
89
|
-
title="Кликните, чтобы скопировать CSS-путь"
|
|
90
|
-
>
|
|
91
|
-
{cssPath}
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function HtmlSection({ outerHTML }: { outerHTML: string }) {
|
|
98
|
-
return (
|
|
99
|
-
<div className="px-4 py-2.5 border-b border-[#30363D]">
|
|
100
|
-
<div className="flex items-center justify-between mb-1">
|
|
101
|
-
<div className="text-[11px] font-semibold text-[#8B949E] uppercase tracking-wider">HTML</div>
|
|
102
|
-
<CopyButton text={outerHTML} />
|
|
103
|
-
</div>
|
|
104
|
-
<pre
|
|
105
|
-
className="font-mono text-[11px] text-[#E6EDF3] bg-[#0D1117] rounded px-2.5 py-1.5 overflow-x-auto whitespace-pre-wrap break-all cursor-pointer hover:bg-[#161B22] transition-colors max-h-40 border border-[#30363D]"
|
|
106
|
-
onClick={() => navigator.clipboard.writeText(outerHTML).catch(() => {})}
|
|
107
|
-
title="Кликните, чтобы скопировать HTML"
|
|
108
|
-
>
|
|
109
|
-
{outerHTML}
|
|
110
|
-
</pre>
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function StylesSection({ styles }: { styles: Record<string, string> }) {
|
|
116
|
-
return (
|
|
117
|
-
<div className="px-4 py-2.5 border-b border-[#30363D]">
|
|
118
|
-
<div className="text-[11px] font-semibold text-[#8B949E] uppercase tracking-wider mb-1.5">
|
|
119
|
-
Styles
|
|
120
|
-
</div>
|
|
121
|
-
<div className="grid grid-cols-2 gap-1.5">
|
|
122
|
-
<div className="bg-[#0D1117] rounded px-2 py-1 text-[11px] font-mono text-[#E6EDF3] border border-[#30363D]">
|
|
123
|
-
{styles.width} x {styles.height}
|
|
124
|
-
</div>
|
|
125
|
-
<div className="bg-[#0D1117] rounded px-2 py-1 text-[11px] font-mono text-[#E6EDF3] border border-[#30363D]">
|
|
126
|
-
{styles.fontSize}
|
|
127
|
-
</div>
|
|
128
|
-
<div className="bg-[#0D1117] rounded px-2 py-1 text-[11px] font-mono text-[#E6EDF3] border border-[#30363D]">
|
|
129
|
-
weight: {styles.fontWeight}
|
|
130
|
-
</div>
|
|
131
|
-
<div className="bg-[#0D1117] rounded px-2 py-1 text-[11px] font-mono text-[#E6EDF3] border border-[#30363D]">
|
|
132
|
-
lh: {styles.lineHeight}
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
<div className="mt-1.5 flex items-center gap-1.5 bg-[#0D1117] rounded px-2 py-1 text-[11px] font-mono text-[#E6EDF3] border border-[#30363D]">
|
|
136
|
-
<span
|
|
137
|
-
className="inline-block w-3 h-3 rounded-sm border border-[#30363D] flex-shrink-0"
|
|
138
|
-
style={{ backgroundColor: styles.color }}
|
|
139
|
-
/>
|
|
140
|
-
{styles.color}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function SnippetSection({
|
|
147
|
-
source,
|
|
148
|
-
snippet,
|
|
149
|
-
snippetLoading,
|
|
150
|
-
}: {
|
|
151
|
-
source: NonNullable<ElementInfo['source']>;
|
|
152
|
-
snippet: SnippetData | null;
|
|
153
|
-
snippetLoading: boolean;
|
|
154
|
-
}) {
|
|
155
|
-
const ext = source.file.split('.').pop() || '';
|
|
156
|
-
const lang = ['tsx', 'jsx'].includes(ext) ? 'tsx' : 'typescript';
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
<div className="px-4 py-2.5">
|
|
160
|
-
<div className="text-[11px] font-semibold text-[#8B949E] uppercase tracking-wider mb-1.5">
|
|
161
|
-
Код ({source.file.split('/').pop()})
|
|
162
|
-
</div>
|
|
163
|
-
{snippetLoading && <div className="text-xs text-[#6E7681] py-2">Загрузка...</div>}
|
|
164
|
-
{snippet && (
|
|
165
|
-
<div className="bg-[#0D1117] rounded-md overflow-hidden border border-[#30363D]">
|
|
166
|
-
<div className="flex items-center justify-between px-3 py-1.5 bg-[#161B22] border-b border-[#30363D]">
|
|
167
|
-
<span className="text-[#6E7681] text-[10px] font-mono">
|
|
168
|
-
{snippet.snippet.startLine}–{snippet.snippet.startLine + snippet.snippet.lines.length - 1} из {snippet.totalLines}
|
|
169
|
-
</span>
|
|
170
|
-
<CopyButton text={snippet.snippet.lines.join('\n')} label="копировать" />
|
|
171
|
-
</div>
|
|
172
|
-
<SyntaxHighlighter
|
|
173
|
-
language={lang}
|
|
174
|
-
style={vscDarkPlus}
|
|
175
|
-
showLineNumbers
|
|
176
|
-
startingLineNumber={snippet.snippet.startLine}
|
|
177
|
-
lineProps={(lineNumber) => ({
|
|
178
|
-
style: lineNumber === snippet.snippet.highlightLine
|
|
179
|
-
? { background: 'rgba(56, 139, 253, 0.15)', display: 'block' }
|
|
180
|
-
: { display: 'block' },
|
|
181
|
-
})}
|
|
182
|
-
customStyle={{
|
|
183
|
-
background: 'transparent',
|
|
184
|
-
padding: '8px',
|
|
185
|
-
margin: 0,
|
|
186
|
-
fontSize: '12px',
|
|
187
|
-
lineHeight: '1.6',
|
|
188
|
-
}}
|
|
189
|
-
lineNumberStyle={{
|
|
190
|
-
color: '#484F58',
|
|
191
|
-
minWidth: '2.5em',
|
|
192
|
-
paddingRight: '1em',
|
|
193
|
-
}}
|
|
194
|
-
codeTagProps={{ style: { fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace' } }}
|
|
195
|
-
>
|
|
196
|
-
{snippet.snippet.lines.join('\n')}
|
|
197
|
-
</SyntaxHighlighter>
|
|
198
|
-
</div>
|
|
199
|
-
)}
|
|
200
|
-
{!snippetLoading && !snippet && (
|
|
201
|
-
<div className="text-xs text-[#6E7681] py-2">
|
|
202
|
-
Не удалось загрузить исходный код (только в dev-режиме)
|
|
203
|
-
</div>
|
|
204
|
-
)}
|
|
205
|
-
</div>
|
|
206
|
-
);
|
|
207
|
-
}
|