@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.
Files changed (61) hide show
  1. package/README.md +27 -0
  2. package/dist/api-source-route.d.ts +14 -0
  3. package/dist/api-source-route.d.ts.map +1 -0
  4. package/dist/api-source-route.js +54 -0
  5. package/dist/api-source-route.js.map +1 -0
  6. package/dist/box-model-section.d.ts +5 -0
  7. package/dist/box-model-section.d.ts.map +1 -0
  8. package/dist/box-model-section.js +15 -0
  9. package/dist/box-model-section.js.map +1 -0
  10. package/dist/highlight-overlay.d.ts +4 -0
  11. package/dist/highlight-overlay.d.ts.map +1 -0
  12. package/dist/highlight-overlay.js +14 -0
  13. package/dist/highlight-overlay.js.map +1 -0
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +3 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/inspector-fab.d.ts +6 -0
  19. package/dist/inspector-fab.d.ts.map +1 -0
  20. package/dist/inspector-fab.js +14 -0
  21. package/dist/inspector-fab.js.map +1 -0
  22. package/dist/inspector-panel.d.ts +14 -0
  23. package/dist/inspector-panel.d.ts.map +1 -0
  24. package/dist/inspector-panel.js +36 -0
  25. package/dist/inspector-panel.js.map +1 -0
  26. package/dist/panel-sections.d.ts +25 -0
  27. package/dist/panel-sections.d.ts.map +1 -0
  28. package/dist/panel-sections.js +48 -0
  29. package/dist/panel-sections.js.map +1 -0
  30. package/dist/plugins/data-src-plugin.d.ts +38 -0
  31. package/dist/plugins/data-src-plugin.d.ts.map +1 -0
  32. package/dist/plugins/data-src-plugin.js +90 -0
  33. package/dist/plugins/data-src-plugin.js.map +1 -0
  34. package/dist/select-element-fab.d.ts +2 -0
  35. package/dist/select-element-fab.d.ts.map +1 -0
  36. package/{select-element-fab.tsx → dist/select-element-fab.js} +8 -45
  37. package/dist/select-element-fab.js.map +1 -0
  38. package/dist/types.d.ts +43 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +2 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/use-element-inspector.d.ts +19 -0
  43. package/dist/use-element-inspector.d.ts.map +1 -0
  44. package/dist/use-element-inspector.js +239 -0
  45. package/dist/use-element-inspector.js.map +1 -0
  46. package/dist/use-panel-drag.d.ts +11 -0
  47. package/dist/use-panel-drag.d.ts.map +1 -0
  48. package/dist/use-panel-drag.js +35 -0
  49. package/dist/use-panel-drag.js.map +1 -0
  50. package/package.json +25 -9
  51. package/api-source-route.ts +0 -60
  52. package/box-model-section.tsx +0 -97
  53. package/highlight-overlay.tsx +0 -23
  54. package/index.ts +0 -10
  55. package/inspector-fab.tsx +0 -63
  56. package/inspector-panel.tsx +0 -190
  57. package/panel-sections.tsx +0 -207
  58. package/plugins/data-src-plugin.ts +0 -110
  59. package/types.ts +0 -45
  60. package/use-element-inspector.ts +0 -264
  61. package/use-panel-drag.ts +0 -47
@@ -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
- }
@@ -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
@@ -1,10 +0,0 @@
1
- export { SelectElementFab } from './select-element-fab';
2
-
3
- export type {
4
- SourceInfo,
5
- ElementInfo,
6
- SnippetData,
7
- BoxModel,
8
- } from './types';
9
-
10
- export { dataSrcPlugin } from './plugins/data-src-plugin';
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
- }
@@ -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
- &lt;/&gt;
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
- }
@@ -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
- }