@fragments-sdk/viewer 0.2.1 → 0.2.4
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/package.json +10 -3
- package/src/components/App.tsx +67 -4
- package/src/components/BottomPanel.tsx +31 -1
- package/src/components/ComponentGraph.tsx +1 -1
- package/src/components/EmptyVariantMessage.tsx +1 -1
- package/src/components/ErrorBoundary.tsx +2 -4
- package/src/components/FragmentRenderer.tsx +27 -4
- package/src/components/HeaderSearch.tsx +1 -1
- package/src/components/InteractionsPanel.tsx +5 -5
- package/src/components/LoadErrorMessage.tsx +26 -30
- package/src/components/NoVariantsMessage.tsx +1 -1
- package/src/components/PanelShell.tsx +1 -1
- package/src/components/PerformancePanel.tsx +4 -4
- package/src/components/PreviewArea.tsx +6 -0
- package/src/components/PropsEditor.tsx +33 -17
- package/src/components/SkeletonLoader.tsx +0 -1
- package/src/components/TokenStylePanel.tsx +3 -3
- package/src/components/TopToolbar.tsx +1 -1
- package/src/components/VariantMatrix.tsx +11 -1
- package/src/components/ViewerHeader.tsx +1 -1
- package/src/components/WebMCPDevTools.tsx +2 -2
- package/src/entry.tsx +4 -6
- package/src/hooks/useAppState.ts +1 -1
- package/src/preview-frame-entry.tsx +0 -2
- package/src/shared/DocsHeaderBar.module.scss +174 -0
- package/src/shared/DocsHeaderBar.tsx +149 -14
- package/src/shared/DocsSidebarNav.tsx +3 -3
- package/src/shared/index.ts +4 -1
- package/src/shared/types.ts +29 -3
- package/src/style-utils.ts +1 -1
- package/src/webmcp/runtime-tools.ts +1 -1
- package/tsconfig.json +2 -2
- package/src/assets/fragments-logo.ts +0 -4
- package/src/components/ActionsPanel.tsx +0 -332
- package/src/components/ComponentHeader.tsx +0 -88
- package/src/components/ContractPanel.tsx +0 -241
- package/src/components/FragmentEditor.tsx +0 -525
- package/src/components/HmrStatusIndicator.tsx +0 -61
- package/src/components/LandingPage.tsx +0 -420
- package/src/components/PropsTable.tsx +0 -111
- package/src/components/RelationsSection.tsx +0 -88
- package/src/components/ResizablePanel.tsx +0 -271
- package/src/components/RightSidebar.tsx +0 -102
- package/src/components/ScreenshotButton.tsx +0 -90
- package/src/components/Sidebar.tsx +0 -169
- package/src/components/UsageSection.tsx +0 -95
- package/src/components/VariantTabs.tsx +0 -40
- package/src/components/ViewportSelector.tsx +0 -172
- package/src/components/_future/CreatePage.tsx +0 -835
- package/src/composition-renderer.ts +0 -381
- package/src/constants/index.ts +0 -1
- package/src/hooks/index.ts +0 -2
- package/src/hooks/useHmrStatus.ts +0 -109
- package/src/hooks/useScrollSpy.ts +0 -78
- package/src/intelligence/healthReport.ts +0 -505
- package/src/intelligence/styleDrift.ts +0 -340
- package/src/intelligence/usageScanner.ts +0 -309
- package/src/utils/actionExport.ts +0 -372
- package/src/utils/colorSchemes.ts +0 -201
- package/src/webmcp/index.ts +0 -3
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import type { FragmentUsage } from '@fragments-sdk/core';
|
|
2
|
-
import { CheckIcon, XIcon, AccessibilityIcon } from './Icons.js';
|
|
3
|
-
|
|
4
|
-
interface UsageSectionProps {
|
|
5
|
-
usage: FragmentUsage;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function UsageSection({ usage }: UsageSectionProps) {
|
|
9
|
-
const hasWhen = usage.when && usage.when.length > 0;
|
|
10
|
-
const hasWhenNot = usage.whenNot && usage.whenNot.length > 0;
|
|
11
|
-
const hasGuidelines = usage.guidelines && usage.guidelines.length > 0;
|
|
12
|
-
const hasAccessibility = usage.accessibility && usage.accessibility.length > 0;
|
|
13
|
-
|
|
14
|
-
if (!hasWhen && !hasWhenNot && !hasGuidelines && !hasAccessibility) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<section id="usage" style={{ scrollMarginTop: '96px' }}>
|
|
20
|
-
<h2 style={{ fontSize: '16px', fontWeight: 600, color: 'var(--text-primary)', marginBottom: '20px' }}>Usage</h2>
|
|
21
|
-
|
|
22
|
-
{/* When to use / When not to use */}
|
|
23
|
-
{(hasWhen || hasWhenNot) && (
|
|
24
|
-
<div style={{ display: 'grid', gridTemplateColumns: hasWhen && hasWhenNot ? '1fr 1fr' : '1fr', gap: '32px', marginBottom: '32px' }}>
|
|
25
|
-
{hasWhen && (
|
|
26
|
-
<div style={{ padding: '16px', borderRadius: '12px', background: 'var(--color-success-bg)', border: '1px solid rgba(16, 163, 127, 0.2)' }}>
|
|
27
|
-
<h3 style={{ fontSize: '13px', fontWeight: 500, color: 'var(--color-success)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
28
|
-
<CheckIcon style={{ width: '16px', height: '16px' }} />
|
|
29
|
-
When to use
|
|
30
|
-
</h3>
|
|
31
|
-
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
32
|
-
{usage.when!.map((item, index) => (
|
|
33
|
-
<li key={index} style={{ fontSize: '13px', color: 'var(--text-primary)', lineHeight: 1.6, display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
|
|
34
|
-
<span style={{ color: 'var(--color-success)', marginTop: '6px', fontSize: '12px' }}>•</span>
|
|
35
|
-
<span>{item}</span>
|
|
36
|
-
</li>
|
|
37
|
-
))}
|
|
38
|
-
</ul>
|
|
39
|
-
</div>
|
|
40
|
-
)}
|
|
41
|
-
|
|
42
|
-
{hasWhenNot && (
|
|
43
|
-
<div style={{ padding: '16px', borderRadius: '12px', background: 'var(--color-danger-bg)', border: '1px solid rgba(239, 68, 68, 0.2)' }}>
|
|
44
|
-
<h3 style={{ fontSize: '13px', fontWeight: 500, color: 'var(--color-danger)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
45
|
-
<XIcon style={{ width: '16px', height: '16px' }} />
|
|
46
|
-
When not to use
|
|
47
|
-
</h3>
|
|
48
|
-
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
49
|
-
{usage.whenNot!.map((item, index) => (
|
|
50
|
-
<li key={index} style={{ fontSize: '13px', color: 'var(--text-primary)', lineHeight: 1.6, display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
|
|
51
|
-
<span style={{ color: 'var(--color-danger)', marginTop: '6px', fontSize: '12px' }}>•</span>
|
|
52
|
-
<span>{item}</span>
|
|
53
|
-
</li>
|
|
54
|
-
))}
|
|
55
|
-
</ul>
|
|
56
|
-
</div>
|
|
57
|
-
)}
|
|
58
|
-
</div>
|
|
59
|
-
)}
|
|
60
|
-
|
|
61
|
-
{/* Guidelines */}
|
|
62
|
-
{hasGuidelines && (
|
|
63
|
-
<div style={{ marginBottom: '24px' }}>
|
|
64
|
-
<h3 style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', marginBottom: '12px' }}>Guidelines</h3>
|
|
65
|
-
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
66
|
-
{usage.guidelines!.map((item, index) => (
|
|
67
|
-
<li key={index} style={{ fontSize: '13px', color: 'var(--text-secondary)', lineHeight: 1.6, display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
|
|
68
|
-
<span style={{ color: 'var(--color-accent)', marginTop: '6px', fontSize: '12px' }}>•</span>
|
|
69
|
-
<span>{item}</span>
|
|
70
|
-
</li>
|
|
71
|
-
))}
|
|
72
|
-
</ul>
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
|
|
76
|
-
{/* Accessibility */}
|
|
77
|
-
{hasAccessibility && (
|
|
78
|
-
<div style={{ padding: '16px', borderRadius: '12px', border: '1px solid var(--border)', background: 'var(--bg-secondary)' }}>
|
|
79
|
-
<h3 style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
80
|
-
<AccessibilityIcon style={{ width: '16px', height: '16px', color: 'var(--text-secondary)' }} />
|
|
81
|
-
Accessibility
|
|
82
|
-
</h3>
|
|
83
|
-
<ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
84
|
-
{usage.accessibility!.map((item, index) => (
|
|
85
|
-
<li key={index} style={{ fontSize: '13px', color: 'var(--text-secondary)', lineHeight: 1.6, display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
|
|
86
|
-
<span style={{ color: 'var(--text-tertiary)', marginTop: '6px', fontSize: '12px' }}>•</span>
|
|
87
|
-
<span>{item}</span>
|
|
88
|
-
</li>
|
|
89
|
-
))}
|
|
90
|
-
</ul>
|
|
91
|
-
</div>
|
|
92
|
-
)}
|
|
93
|
-
</section>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Tabs, Stack } from '@fragments-sdk/ui';
|
|
2
|
-
import type { FragmentVariant } from '@fragments-sdk/core';
|
|
3
|
-
import { PlayIcon } from './Icons.js';
|
|
4
|
-
|
|
5
|
-
interface VariantTabsProps {
|
|
6
|
-
variants: FragmentVariant[];
|
|
7
|
-
activeIndex: number;
|
|
8
|
-
onSelect: (index: number) => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function VariantTabs({ variants, activeIndex, onSelect }: VariantTabsProps) {
|
|
12
|
-
if (variants.length === 0) return null;
|
|
13
|
-
|
|
14
|
-
const activeValue = variants[activeIndex]?.name ?? '';
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<Tabs
|
|
18
|
-
value={activeValue}
|
|
19
|
-
onValueChange={(value: string | number) => {
|
|
20
|
-
const index = variants.findIndex(v => v.name === value);
|
|
21
|
-
if (index >= 0) onSelect(index);
|
|
22
|
-
}}
|
|
23
|
-
>
|
|
24
|
-
<Tabs.List variant="pills">
|
|
25
|
-
{variants.map((variant) => (
|
|
26
|
-
<Tabs.Tab key={variant.name} value={variant.name}>
|
|
27
|
-
<Stack direction="row" align="center" gap="xs" as="span">
|
|
28
|
-
{variant.name}
|
|
29
|
-
{variant.hasPlayFunction && (
|
|
30
|
-
<span style={{ display: 'inline-flex', width: '12px', height: '12px', color: 'var(--color-accent)' }}>
|
|
31
|
-
<PlayIcon />
|
|
32
|
-
</span>
|
|
33
|
-
)}
|
|
34
|
-
</Stack>
|
|
35
|
-
</Tabs.Tab>
|
|
36
|
-
))}
|
|
37
|
-
</Tabs.List>
|
|
38
|
-
</Tabs>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { Button, Menu, Stack, Input, Text, Box } from '@fragments-sdk/ui';
|
|
2
|
-
import { VIEWPORT_PRESETS, type ViewportPreset } from '../constants/ui.js';
|
|
3
|
-
import {
|
|
4
|
-
ViewportIcon,
|
|
5
|
-
ChevronDownIcon,
|
|
6
|
-
ResponsiveIcon,
|
|
7
|
-
DesktopIcon,
|
|
8
|
-
TabletIcon,
|
|
9
|
-
MobileIcon,
|
|
10
|
-
SettingsIcon,
|
|
11
|
-
} from './Icons.js';
|
|
12
|
-
|
|
13
|
-
// Re-export for consumers
|
|
14
|
-
export type { ViewportPreset };
|
|
15
|
-
|
|
16
|
-
export interface ViewportSize {
|
|
17
|
-
width: number | null;
|
|
18
|
-
height: number | null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Viewport presets with display metadata
|
|
22
|
-
const VIEWPORT_PRESETS_UI = Object.entries(VIEWPORT_PRESETS).map(([value, config]) => ({
|
|
23
|
-
value: value as ViewportPreset,
|
|
24
|
-
label: config.label,
|
|
25
|
-
width: config.width,
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
function PresetIcon({ preset }: { preset: ViewportPreset }) {
|
|
29
|
-
const commonStyle = { width: '16px', height: '16px' };
|
|
30
|
-
|
|
31
|
-
switch (preset) {
|
|
32
|
-
case 'desktop':
|
|
33
|
-
return <DesktopIcon style={commonStyle} />;
|
|
34
|
-
case 'tablet':
|
|
35
|
-
return <TabletIcon style={commonStyle} />;
|
|
36
|
-
case 'mobile':
|
|
37
|
-
return <MobileIcon style={commonStyle} />;
|
|
38
|
-
case 'responsive':
|
|
39
|
-
default:
|
|
40
|
-
return <ResponsiveIcon style={commonStyle} />;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface ViewportSelectorProps {
|
|
45
|
-
viewport: ViewportPreset;
|
|
46
|
-
customSize: ViewportSize;
|
|
47
|
-
onViewportChange: (viewport: ViewportPreset) => void;
|
|
48
|
-
onCustomSizeChange: (size: ViewportSize) => void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function ViewportSelector({
|
|
52
|
-
viewport,
|
|
53
|
-
customSize,
|
|
54
|
-
onViewportChange,
|
|
55
|
-
onCustomSizeChange,
|
|
56
|
-
}: ViewportSelectorProps) {
|
|
57
|
-
const currentPreset = VIEWPORT_PRESETS_UI.find(p => p.value === viewport);
|
|
58
|
-
const isCustomViewport = viewport === 'custom';
|
|
59
|
-
const displayLabel = viewport === 'custom'
|
|
60
|
-
? `${customSize.width || '?'}\u00D7${customSize.height || 'auto'}`
|
|
61
|
-
: currentPreset?.label || 'Responsive';
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<Menu>
|
|
65
|
-
<Menu.Trigger asChild>
|
|
66
|
-
<Button variant="ghost" size="sm" title="Viewport size">
|
|
67
|
-
<Stack direction="row" gap="xs" align="center">
|
|
68
|
-
<span style={{ display: 'inline-flex', width: '14px', height: '14px' }}>
|
|
69
|
-
<ViewportIcon />
|
|
70
|
-
</span>
|
|
71
|
-
<span>{displayLabel}</span>
|
|
72
|
-
<span style={{ display: 'inline-flex', width: '12px', height: '12px' }}>
|
|
73
|
-
<ChevronDownIcon />
|
|
74
|
-
</span>
|
|
75
|
-
</Stack>
|
|
76
|
-
</Button>
|
|
77
|
-
</Menu.Trigger>
|
|
78
|
-
<Menu.Content side="bottom" align="end">
|
|
79
|
-
<Menu.RadioGroup
|
|
80
|
-
value={isCustomViewport ? 'custom' : viewport}
|
|
81
|
-
onValueChange={(value: string) => {
|
|
82
|
-
if (value === 'custom') {
|
|
83
|
-
onViewportChange('custom');
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
onViewportChange(value as ViewportPreset);
|
|
87
|
-
}}
|
|
88
|
-
>
|
|
89
|
-
{VIEWPORT_PRESETS_UI.map((preset) => (
|
|
90
|
-
<Menu.RadioItem key={preset.value} value={preset.value}>
|
|
91
|
-
<Stack direction="row" gap="sm" align="center" justify="between" style={{ width: '100%' }}>
|
|
92
|
-
<Stack direction="row" gap="sm" align="center">
|
|
93
|
-
<PresetIcon preset={preset.value} />
|
|
94
|
-
<span>{preset.label}</span>
|
|
95
|
-
</Stack>
|
|
96
|
-
{preset.width && (
|
|
97
|
-
<Text size="2xs" color="tertiary">
|
|
98
|
-
{preset.width}px
|
|
99
|
-
</Text>
|
|
100
|
-
)}
|
|
101
|
-
</Stack>
|
|
102
|
-
</Menu.RadioItem>
|
|
103
|
-
))}
|
|
104
|
-
</Menu.RadioGroup>
|
|
105
|
-
|
|
106
|
-
<Menu.Separator />
|
|
107
|
-
|
|
108
|
-
<Menu.Item onSelect={() => onViewportChange('custom')}>
|
|
109
|
-
<Stack direction="row" gap="sm" align="center" style={{
|
|
110
|
-
width: '100%',
|
|
111
|
-
fontWeight: isCustomViewport ? 600 : 400,
|
|
112
|
-
color: isCustomViewport ? 'var(--color-accent)' : undefined,
|
|
113
|
-
}}>
|
|
114
|
-
<SettingsIcon style={{ width: '16px', height: '16px' }} />
|
|
115
|
-
Custom size
|
|
116
|
-
</Stack>
|
|
117
|
-
</Menu.Item>
|
|
118
|
-
|
|
119
|
-
{isCustomViewport && (
|
|
120
|
-
<div
|
|
121
|
-
onClick={(e) => e.stopPropagation()}
|
|
122
|
-
onKeyDown={(e) => e.stopPropagation()}
|
|
123
|
-
>
|
|
124
|
-
<Box padding="sm">
|
|
125
|
-
<Stack gap="sm">
|
|
126
|
-
<Stack direction="row" gap="sm" align="center">
|
|
127
|
-
<Text size="2xs" color="tertiary" style={{ width: '20px' }}>W:</Text>
|
|
128
|
-
<Input
|
|
129
|
-
type="number"
|
|
130
|
-
size="sm"
|
|
131
|
-
value={customSize.width != null ? String(customSize.width) : ''}
|
|
132
|
-
onChange={(value) => {
|
|
133
|
-
const width = value ? parseInt(value, 10) : null;
|
|
134
|
-
onCustomSizeChange({ ...customSize, width });
|
|
135
|
-
onViewportChange('custom');
|
|
136
|
-
}}
|
|
137
|
-
placeholder="auto"
|
|
138
|
-
style={{ width: '64px' }}
|
|
139
|
-
/>
|
|
140
|
-
<Text size="2xs" color="tertiary">px</Text>
|
|
141
|
-
</Stack>
|
|
142
|
-
<Stack direction="row" gap="sm" align="center">
|
|
143
|
-
<Text size="2xs" color="tertiary" style={{ width: '20px' }}>H:</Text>
|
|
144
|
-
<Input
|
|
145
|
-
type="number"
|
|
146
|
-
size="sm"
|
|
147
|
-
value={customSize.height != null ? String(customSize.height) : ''}
|
|
148
|
-
onChange={(value) => {
|
|
149
|
-
const height = value ? parseInt(value, 10) : null;
|
|
150
|
-
onCustomSizeChange({ ...customSize, height });
|
|
151
|
-
onViewportChange('custom');
|
|
152
|
-
}}
|
|
153
|
-
placeholder="auto"
|
|
154
|
-
style={{ width: '64px' }}
|
|
155
|
-
/>
|
|
156
|
-
<Text size="2xs" color="tertiary">px</Text>
|
|
157
|
-
</Stack>
|
|
158
|
-
</Stack>
|
|
159
|
-
</Box>
|
|
160
|
-
</div>
|
|
161
|
-
)}
|
|
162
|
-
</Menu.Content>
|
|
163
|
-
</Menu>
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Get viewport width from preset
|
|
168
|
-
export function getViewportWidth(viewport: ViewportPreset, customSize: ViewportSize): number | null {
|
|
169
|
-
if (viewport === 'custom') return customSize.width;
|
|
170
|
-
if (viewport === 'responsive') return null;
|
|
171
|
-
return VIEWPORT_PRESETS[viewport]?.width ?? null;
|
|
172
|
-
}
|