@fragments-sdk/viewer 0.2.1

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 (141) hide show
  1. package/LICENSE +84 -0
  2. package/index.html +28 -0
  3. package/package.json +71 -0
  4. package/src/__tests__/a11y-fixes.test.ts +358 -0
  5. package/src/__tests__/jsx-parser.test.ts +502 -0
  6. package/src/__tests__/render-utils.test.ts +232 -0
  7. package/src/__tests__/style-utils.test.ts +404 -0
  8. package/src/app/index.ts +1 -0
  9. package/src/assets/fragments-logo.ts +4 -0
  10. package/src/assets/fragments_logo.png +0 -0
  11. package/src/components/AccessibilityPanel.tsx +1457 -0
  12. package/src/components/ActionCapture.tsx +172 -0
  13. package/src/components/ActionsPanel.tsx +332 -0
  14. package/src/components/AllVariantsPreview.tsx +78 -0
  15. package/src/components/App.tsx +604 -0
  16. package/src/components/BottomPanel.tsx +288 -0
  17. package/src/components/CodePanel.naming.test.tsx +59 -0
  18. package/src/components/CodePanel.tsx +118 -0
  19. package/src/components/CommandPalette.tsx +392 -0
  20. package/src/components/ComponentDocView.tsx +164 -0
  21. package/src/components/ComponentGraph.tsx +380 -0
  22. package/src/components/ComponentHeader.tsx +88 -0
  23. package/src/components/ContractPanel.tsx +241 -0
  24. package/src/components/DeviceMockup.tsx +156 -0
  25. package/src/components/EmptyVariantMessage.tsx +54 -0
  26. package/src/components/ErrorBoundary.tsx +97 -0
  27. package/src/components/FigmaEmbed.tsx +238 -0
  28. package/src/components/FragmentEditor.tsx +525 -0
  29. package/src/components/FragmentRenderer.tsx +61 -0
  30. package/src/components/HeaderSearch.tsx +24 -0
  31. package/src/components/HealthDashboard.tsx +441 -0
  32. package/src/components/HmrStatusIndicator.tsx +61 -0
  33. package/src/components/Icons.tsx +479 -0
  34. package/src/components/InteractionsPanel.tsx +757 -0
  35. package/src/components/IsolatedPreviewFrame.tsx +390 -0
  36. package/src/components/IsolatedRender.tsx +113 -0
  37. package/src/components/KeyboardShortcutsHelp.tsx +53 -0
  38. package/src/components/LandingPage.tsx +420 -0
  39. package/src/components/Layout.tsx +27 -0
  40. package/src/components/LeftSidebar.tsx +472 -0
  41. package/src/components/LoadErrorMessage.tsx +102 -0
  42. package/src/components/MultiViewportPreview.tsx +527 -0
  43. package/src/components/NoVariantsMessage.tsx +59 -0
  44. package/src/components/PanelShell.tsx +161 -0
  45. package/src/components/PerformancePanel.tsx +304 -0
  46. package/src/components/PreviewArea.tsx +254 -0
  47. package/src/components/PreviewAside.tsx +168 -0
  48. package/src/components/PreviewFrameHost.tsx +304 -0
  49. package/src/components/PreviewToolbar.tsx +80 -0
  50. package/src/components/PropsEditor.tsx +506 -0
  51. package/src/components/PropsTable.tsx +111 -0
  52. package/src/components/RelationsSection.tsx +88 -0
  53. package/src/components/ResizablePanel.tsx +271 -0
  54. package/src/components/RightSidebar.tsx +102 -0
  55. package/src/components/RuntimeToolsRegistrar.tsx +17 -0
  56. package/src/components/ScreenshotButton.tsx +90 -0
  57. package/src/components/ShadowPreview.tsx +204 -0
  58. package/src/components/Sidebar.tsx +169 -0
  59. package/src/components/SkeletonLoader.tsx +161 -0
  60. package/src/components/ThemeProvider.tsx +42 -0
  61. package/src/components/Toast.tsx +3 -0
  62. package/src/components/TokenStylePanel.tsx +699 -0
  63. package/src/components/TopToolbar.tsx +159 -0
  64. package/src/components/Untitled +1 -0
  65. package/src/components/UsageSection.tsx +95 -0
  66. package/src/components/VariantMatrix.tsx +391 -0
  67. package/src/components/VariantRenderer.tsx +131 -0
  68. package/src/components/VariantTabs.tsx +40 -0
  69. package/src/components/ViewerHeader.tsx +69 -0
  70. package/src/components/ViewerStateSync.tsx +52 -0
  71. package/src/components/ViewportSelector.tsx +172 -0
  72. package/src/components/WebMCPDevTools.tsx +503 -0
  73. package/src/components/WebMCPIntegration.tsx +47 -0
  74. package/src/components/WebMCPStatusIndicator.tsx +60 -0
  75. package/src/components/_future/CreatePage.tsx +835 -0
  76. package/src/components/viewer-utils.ts +16 -0
  77. package/src/composition-renderer.ts +381 -0
  78. package/src/constants/index.ts +1 -0
  79. package/src/constants/ui.ts +166 -0
  80. package/src/entry.tsx +335 -0
  81. package/src/hooks/index.ts +2 -0
  82. package/src/hooks/useA11yCache.ts +383 -0
  83. package/src/hooks/useA11yService.ts +364 -0
  84. package/src/hooks/useActions.ts +138 -0
  85. package/src/hooks/useAppState.ts +147 -0
  86. package/src/hooks/useCompiledFragments.ts +42 -0
  87. package/src/hooks/useFigmaIntegration.ts +132 -0
  88. package/src/hooks/useHmrStatus.ts +109 -0
  89. package/src/hooks/useKeyboardShortcuts.ts +270 -0
  90. package/src/hooks/usePreviewBridge.ts +347 -0
  91. package/src/hooks/useScrollSpy.ts +78 -0
  92. package/src/hooks/useShadowStyles.ts +221 -0
  93. package/src/hooks/useUrlState.ts +318 -0
  94. package/src/hooks/useViewSettings.ts +111 -0
  95. package/src/intelligence/healthReport.ts +505 -0
  96. package/src/intelligence/styleDrift.ts +340 -0
  97. package/src/intelligence/usageScanner.ts +309 -0
  98. package/src/jsx-parser.ts +486 -0
  99. package/src/preview-frame-entry.tsx +25 -0
  100. package/src/preview-frame.html +148 -0
  101. package/src/render-template.html +68 -0
  102. package/src/render-utils.ts +311 -0
  103. package/src/shared/ComponentDocContent.module.scss +10 -0
  104. package/src/shared/ComponentDocContent.module.scss.d.ts +2 -0
  105. package/src/shared/ComponentDocContent.tsx +274 -0
  106. package/src/shared/DocsHeaderBar.tsx +129 -0
  107. package/src/shared/DocsPageAsideHost.tsx +89 -0
  108. package/src/shared/DocsPageShell.tsx +124 -0
  109. package/src/shared/DocsSearchCommand.tsx +99 -0
  110. package/src/shared/DocsSidebarNav.tsx +66 -0
  111. package/src/shared/PropsTable.module.scss +68 -0
  112. package/src/shared/PropsTable.module.scss.d.ts +2 -0
  113. package/src/shared/PropsTable.tsx +76 -0
  114. package/src/shared/VariantPreviewCard.module.scss +114 -0
  115. package/src/shared/VariantPreviewCard.module.scss.d.ts +2 -0
  116. package/src/shared/VariantPreviewCard.tsx +137 -0
  117. package/src/shared/docs-data/index.ts +32 -0
  118. package/src/shared/docs-data/mcp-configs.ts +72 -0
  119. package/src/shared/docs-data/palettes.ts +75 -0
  120. package/src/shared/docs-data/setup-examples.ts +55 -0
  121. package/src/shared/docs-layout.scss +28 -0
  122. package/src/shared/docs-layout.scss.d.ts +2 -0
  123. package/src/shared/index.ts +34 -0
  124. package/src/shared/types.ts +53 -0
  125. package/src/style-utils.ts +414 -0
  126. package/src/styles/globals.css +278 -0
  127. package/src/types/a11y.ts +197 -0
  128. package/src/utils/a11y-fixes.ts +509 -0
  129. package/src/utils/actionExport.ts +372 -0
  130. package/src/utils/colorSchemes.ts +201 -0
  131. package/src/utils/contrast.ts +246 -0
  132. package/src/utils/detectRelationships.ts +256 -0
  133. package/src/webmcp/__tests__/analytics.test.ts +108 -0
  134. package/src/webmcp/analytics.ts +165 -0
  135. package/src/webmcp/index.ts +3 -0
  136. package/src/webmcp/posthog-bridge.ts +39 -0
  137. package/src/webmcp/runtime-tools.ts +152 -0
  138. package/src/webmcp/scan-utils.ts +135 -0
  139. package/src/webmcp/use-tool-analytics.ts +69 -0
  140. package/src/webmcp/viewer-state.ts +45 -0
  141. package/tsconfig.json +20 -0
@@ -0,0 +1,169 @@
1
+ import React, { useState, useMemo } from 'react';
2
+ import type { FragmentDefinition } from '@fragments-sdk/core';
3
+
4
+ interface SidebarProps {
5
+ fragments: Array<{ path: string; fragment: FragmentDefinition }>;
6
+ activeFragment: string | null;
7
+ onSelect: (path: string) => void;
8
+ }
9
+
10
+ export function Sidebar({ fragments, activeFragment, onSelect }: SidebarProps): React.ReactElement {
11
+ const [search, setSearch] = useState('');
12
+
13
+ // Group fragments by category
14
+ const grouped = useMemo(() => {
15
+ const groups: Record<string, typeof fragments> = {};
16
+
17
+ for (const item of fragments) {
18
+ const category = item.fragment.meta.category || 'uncategorized';
19
+ if (!groups[category]) {
20
+ groups[category] = [];
21
+ }
22
+ groups[category].push(item);
23
+ }
24
+
25
+ // Filter by search
26
+ if (search) {
27
+ const filtered: Record<string, typeof fragments> = {};
28
+ for (const [category, items] of Object.entries(groups)) {
29
+ const matchingItems = items.filter(
30
+ (item) =>
31
+ item.fragment.meta.name.toLowerCase().includes(search.toLowerCase()) ||
32
+ item.fragment.meta.description.toLowerCase().includes(search.toLowerCase())
33
+ );
34
+ if (matchingItems.length > 0) {
35
+ filtered[category] = matchingItems;
36
+ }
37
+ }
38
+ return filtered;
39
+ }
40
+
41
+ return groups;
42
+ }, [fragments, search]);
43
+
44
+ return (
45
+ <div
46
+ style={{
47
+ width: '280px',
48
+ height: '100vh',
49
+ background: '#f9fafb',
50
+ borderRight: '1px solid #e5e7eb',
51
+ display: 'flex',
52
+ flexDirection: 'column',
53
+ overflow: 'hidden',
54
+ }}
55
+ >
56
+ {/* Header */}
57
+ <div
58
+ style={{
59
+ padding: '16px',
60
+ borderBottom: '1px solid #e5e7eb',
61
+ }}
62
+ >
63
+ <h1
64
+ style={{
65
+ margin: 0,
66
+ fontSize: '18px',
67
+ fontWeight: 700,
68
+ color: '#111827',
69
+ }}
70
+ >
71
+ Fragments
72
+ </h1>
73
+ <p
74
+ style={{
75
+ margin: '4px 0 0',
76
+ fontSize: '12px',
77
+ color: '#6b7280',
78
+ }}
79
+ >
80
+ {fragments.length} component{fragments.length !== 1 ? 's' : ''}
81
+ </p>
82
+ </div>
83
+
84
+ {/* Search */}
85
+ <div style={{ padding: '12px 16px' }}>
86
+ <input
87
+ type="text"
88
+ placeholder="Search components..."
89
+ value={search}
90
+ onChange={(e) => setSearch(e.target.value)}
91
+ style={{
92
+ width: '100%',
93
+ padding: '8px 12px',
94
+ border: '1px solid #d1d5db',
95
+ borderRadius: '6px',
96
+ fontSize: '13px',
97
+ outline: 'none',
98
+ boxSizing: 'border-box',
99
+ }}
100
+ />
101
+ </div>
102
+
103
+ {/* Component list */}
104
+ <div
105
+ style={{
106
+ flex: 1,
107
+ overflow: 'auto',
108
+ padding: '0 8px 16px',
109
+ }}
110
+ >
111
+ {Object.entries(grouped).map(([category, items]) => (
112
+ <div key={category} style={{ marginBottom: '16px' }}>
113
+ <div
114
+ style={{
115
+ padding: '8px',
116
+ fontSize: '11px',
117
+ fontWeight: 600,
118
+ color: '#6b7280',
119
+ textTransform: 'uppercase',
120
+ letterSpacing: '0.05em',
121
+ }}
122
+ >
123
+ {category}
124
+ </div>
125
+ {items.map((item) => (
126
+ <button
127
+ key={item.path}
128
+ onClick={() => onSelect(item.path)}
129
+ style={{
130
+ display: 'block',
131
+ width: '100%',
132
+ padding: '8px 12px',
133
+ border: 'none',
134
+ borderRadius: '6px',
135
+ background: activeFragment === item.path ? '#e5e7eb' : 'transparent',
136
+ textAlign: 'left',
137
+ cursor: 'pointer',
138
+ transition: 'background 0.15s',
139
+ }}
140
+ >
141
+ <div
142
+ style={{
143
+ fontSize: '13px',
144
+ fontWeight: 500,
145
+ color: '#111827',
146
+ }}
147
+ >
148
+ {item.fragment.meta.name}
149
+ </div>
150
+ <div
151
+ style={{
152
+ fontSize: '12px',
153
+ color: '#6b7280',
154
+ marginTop: '2px',
155
+ overflow: 'hidden',
156
+ textOverflow: 'ellipsis',
157
+ whiteSpace: 'nowrap',
158
+ }}
159
+ >
160
+ {item.fragment.meta.description}
161
+ </div>
162
+ </button>
163
+ ))}
164
+ </div>
165
+ ))}
166
+ </div>
167
+ </div>
168
+ );
169
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Skeleton Loading Components
3
+ *
4
+ * Shows animated placeholders while the app is loading.
5
+ */
6
+
7
+ import { Skeleton, Loading, Stack, Box } from '@fragments-sdk/ui';
8
+
9
+ /**
10
+ * Full app skeleton shown during initial load
11
+ */
12
+ export function AppSkeleton() {
13
+ return (
14
+ <div
15
+ style={{
16
+ display: 'grid',
17
+ minHeight: '100vh',
18
+ minHeight: '100dvh',
19
+ gridTemplateRows: '56px 1fr',
20
+ gridTemplateColumns: '260px 1fr 240px',
21
+ gridTemplateAreas: '"header header header" "sidebar main aside"',
22
+ backgroundColor: 'var(--bg-primary)',
23
+ }}
24
+ >
25
+ <div
26
+ style={{
27
+ gridArea: 'header',
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ justifyContent: 'space-between',
31
+ gap: '16px',
32
+ padding: '0 16px',
33
+ borderBottom: '1px solid var(--border)',
34
+ backgroundColor: 'var(--bg-primary)',
35
+ }}
36
+ >
37
+ <Stack direction="row" align="center" gap="sm">
38
+ <Skeleton.Circle size={20} />
39
+ <Skeleton variant="text" width={96} />
40
+ <Skeleton variant="text" width={64} />
41
+ </Stack>
42
+ <Skeleton variant="rect" width={240} height={32} radius="md" />
43
+ <Stack direction="row" align="center" gap="sm">
44
+ <Skeleton variant="rect" width={64} height={28} radius="md" />
45
+ <Skeleton.Circle size={20} />
46
+ <Skeleton.Circle size={20} />
47
+ <Skeleton.Circle size={20} />
48
+ </Stack>
49
+ </div>
50
+
51
+ <aside
52
+ style={{
53
+ gridArea: 'sidebar',
54
+ borderRight: '1px solid var(--border)',
55
+ backgroundColor: 'var(--bg-primary)',
56
+ display: 'flex',
57
+ flexDirection: 'column',
58
+ minHeight: 0,
59
+ }}
60
+ >
61
+ <Box paddingX="md" paddingY="sm">
62
+ <Skeleton variant="rect" height={32} radius="md" />
63
+ </Box>
64
+ <Stack gap="sm" style={{ padding: '0 12px', overflow: 'hidden' }}>
65
+ <Skeleton variant="text" width={68} />
66
+ <Skeleton variant="rect" height={30} radius="md" />
67
+ <Skeleton variant="rect" height={30} radius="md" />
68
+ <Skeleton variant="rect" height={30} radius="md" />
69
+ <Skeleton variant="text" width={84} />
70
+ <Skeleton variant="rect" height={30} radius="md" />
71
+ <Skeleton variant="rect" height={30} radius="md" />
72
+ <Skeleton variant="rect" width="78%" height={30} radius="md" />
73
+ </Stack>
74
+ <div style={{ marginTop: 'auto', padding: '12px 16px', borderTop: '1px solid var(--border-subtle)' }}>
75
+ <Skeleton variant="text" width={92} />
76
+ </div>
77
+ </aside>
78
+
79
+ <main
80
+ style={{
81
+ gridArea: 'main',
82
+ display: 'flex',
83
+ flexDirection: 'column',
84
+ minWidth: 0,
85
+ minHeight: 0,
86
+ backgroundColor: 'var(--bg-primary)',
87
+ }}
88
+ >
89
+ <div style={{ flex: 1, padding: '20px', overflow: 'hidden' }}>
90
+ <div
91
+ style={{
92
+ border: '1px solid var(--border)',
93
+ borderRadius: '10px',
94
+ overflow: 'hidden',
95
+ marginBottom: '16px',
96
+ }}
97
+ >
98
+ <div style={{ padding: '12px 14px', borderBottom: '1px solid var(--border-subtle)', backgroundColor: 'var(--bg-secondary)', display: 'flex', gap: '10px' }}>
99
+ <Skeleton variant="text" width={84} />
100
+ <Skeleton variant="text" width={120} />
101
+ </div>
102
+ <div style={{ padding: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
103
+ <Skeleton variant="rect" width={280} height={120} radius="lg" />
104
+ </div>
105
+ </div>
106
+
107
+ <div
108
+ style={{
109
+ border: '1px solid var(--border)',
110
+ borderRadius: '10px',
111
+ overflow: 'hidden',
112
+ }}
113
+ >
114
+ <div style={{ height: '40px', display: 'flex', alignItems: 'center', gap: '6px', padding: '0 14px', borderBottom: '1px solid var(--border-subtle)', backgroundColor: 'var(--bg-secondary)' }}>
115
+ <Skeleton variant="rect" width={52} height={22} radius="md" />
116
+ <Skeleton variant="rect" width={88} height={22} radius="md" />
117
+ <Skeleton variant="rect" width={58} height={22} radius="md" />
118
+ </div>
119
+ <div style={{ padding: '16px' }}>
120
+ <Skeleton.Text lines={4} lastLineWidth={72} />
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </main>
125
+
126
+ <aside
127
+ style={{
128
+ gridArea: 'aside',
129
+ borderLeft: '1px solid var(--border)',
130
+ backgroundColor: 'var(--bg-primary)',
131
+ padding: '16px 14px',
132
+ }}
133
+ >
134
+ <Stack gap="sm">
135
+ <Skeleton variant="text" width={92} />
136
+ <Skeleton variant="text" width={64} />
137
+ <Skeleton variant="text" width={48} />
138
+ <Skeleton variant="text" width={76} />
139
+ <Skeleton variant="rect" height={1} />
140
+ <Skeleton variant="text" width={86} />
141
+ <Skeleton variant="rect" width="100%" height={26} radius="md" />
142
+ <Skeleton variant="rect" width="84%" height={26} radius="md" />
143
+ <Skeleton variant="rect" width="70%" height={26} radius="md" />
144
+ </Stack>
145
+ </aside>
146
+ </div>
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Component preview skeleton
152
+ */
153
+ export function PreviewSkeleton() {
154
+ return (
155
+ <Stack direction="row" align="center" justify="center" style={{ padding: '32px' }}>
156
+ <Loading size="md" />
157
+ </Stack>
158
+ );
159
+ }
160
+
161
+ export default AppSkeleton;
@@ -0,0 +1,42 @@
1
+ import type { ReactNode } from 'react';
2
+ import {
3
+ ThemeProvider as FragmentsThemeProvider,
4
+ useTheme as useFragmentsTheme,
5
+ } from '@fragments-sdk/ui';
6
+ import { BRAND } from '@fragments-sdk/core';
7
+
8
+ type Theme = 'light' | 'dark' | 'system';
9
+
10
+ interface ViewerThemeContextValue {
11
+ theme: Theme;
12
+ resolvedTheme: 'light' | 'dark';
13
+ setTheme: (theme: Theme) => void;
14
+ }
15
+
16
+ const STORAGE_KEY = `${BRAND.storagePrefix}theme`;
17
+
18
+ /**
19
+ * Viewer theme adapter that reuses the shared UI ThemeProvider while preserving
20
+ * existing viewer storage keys and API shape.
21
+ */
22
+ export function ThemeProvider({ children }: { children: ReactNode }) {
23
+ return (
24
+ <FragmentsThemeProvider
25
+ defaultMode="system"
26
+ storageKey={STORAGE_KEY}
27
+ attribute="class"
28
+ >
29
+ {children}
30
+ </FragmentsThemeProvider>
31
+ );
32
+ }
33
+
34
+ export function useTheme(): ViewerThemeContextValue {
35
+ const { mode, setMode, resolvedMode } = useFragmentsTheme();
36
+
37
+ return {
38
+ theme: mode,
39
+ resolvedTheme: resolvedMode,
40
+ setTheme: setMode,
41
+ };
42
+ }
@@ -0,0 +1,3 @@
1
+ // Re-export Fragments UI Toast for use in the viewer
2
+ export { ToastProvider, useToast } from '@fragments-sdk/ui';
3
+ export type { ToastData as ToastMessage } from '@fragments-sdk/ui';