@fragments-sdk/cli 0.7.8 → 0.7.10
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/dist/bin.js +13 -13
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-CWKQQR6C.js → chunk-57OW43NL.js} +3 -3
- package/dist/chunk-57OW43NL.js.map +1 -0
- package/dist/{chunk-AA6CAHCZ.js → chunk-7CRC46HV.js} +2 -2
- package/dist/chunk-7CRC46HV.js.map +1 -0
- package/dist/{chunk-3JPJTU25.js → chunk-CRTN6BIW.js} +5 -5
- package/dist/chunk-CRTN6BIW.js.map +1 -0
- package/dist/{chunk-LHIIBI6F.js → chunk-M42XIHPV.js} +2 -2
- package/dist/{chunk-2EFVPE5Q.js → chunk-TQOGBAOZ.js} +2 -2
- package/dist/chunk-TQOGBAOZ.js.map +1 -0
- package/dist/core/index.d.ts +1944 -0
- package/dist/{core-YAPWXDZW.js → core/index.js} +5 -5
- package/dist/defineFragment-C6PFzZyo.d.ts +656 -0
- package/dist/{generate-LEBVZCCH.js → generate-ZPERYZLF.js} +4 -4
- package/dist/index.d.ts +4 -159
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/{init-4VXL3Q6N.js → init-GID2DXB3.js} +69 -7
- package/dist/init-GID2DXB3.js.map +1 -0
- package/dist/mcp-bin.js +3 -3
- package/dist/{scan-3NYSRF6G.js → scan-BSMLGBX4.js} +5 -5
- package/dist/{service-HL6TMP3B.js → service-QACVPR37.js} +3 -3
- package/dist/{static-viewer-KLD24I4R.js → static-viewer-2RQD5QLR.js} +3 -3
- package/dist/{test-Y7YZOJLE.js → test-36UELXTE.js} +3 -3
- package/dist/{tokens-M4FCJKBK.js → tokens-A3BZIQPB.js} +4 -4
- package/dist/{viewer-ZWQQ74FV.js → viewer-ZA7WK3EY.js} +137 -30
- package/dist/viewer-ZA7WK3EY.js.map +1 -0
- package/package.json +6 -1
- package/src/commands/add.ts +1 -1
- package/src/commands/init.ts +84 -4
- package/src/core/defineFragment.ts +1 -1
- package/src/core/figma.ts +1 -1
- package/src/core/index.ts +2 -2
- package/src/core/loader.ts +3 -3
- package/src/core/schema.ts +1 -1
- package/src/index.ts +6 -0
- package/src/migrate/converter.ts +1 -1
- package/src/service/snippet-validation.test.ts +5 -5
- package/src/service/snippet-validation.ts +0 -1
- package/src/viewer/__tests__/viewer-integration.test.ts +16 -23
- package/src/viewer/assets/fragments-logo.ts +4 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1 -1
- package/src/viewer/components/ActionsPanel.tsx +1 -1
- package/src/viewer/components/App.tsx +138 -97
- package/src/viewer/components/BottomPanel.tsx +1 -1
- package/src/viewer/components/CodePanel.naming.test.tsx +1 -1
- package/src/viewer/components/CodePanel.tsx +1 -1
- package/src/viewer/components/CommandPalette.tsx +1 -1
- package/src/viewer/components/ComponentGraph.tsx +1 -1
- package/src/viewer/components/ComponentHeader.tsx +1 -1
- package/src/viewer/components/ContractPanel.tsx +1 -1
- package/src/viewer/components/ErrorBoundary.tsx +1 -1
- package/src/viewer/components/HealthDashboard.tsx +1 -1
- package/src/viewer/components/HmrStatusIndicator.tsx +1 -1
- package/src/viewer/components/InteractionsPanel.tsx +1 -1
- package/src/viewer/components/IsolatedRender.tsx +1 -1
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +1 -1
- package/src/viewer/components/LandingPage.tsx +1 -1
- package/src/viewer/components/Layout.tsx +1 -1
- package/src/viewer/components/LeftSidebar.tsx +105 -18
- package/src/viewer/components/MultiViewportPreview.tsx +1 -1
- package/src/viewer/components/PreviewArea.tsx +1 -2
- package/src/viewer/components/PreviewFrameHost.tsx +0 -4
- package/src/viewer/components/PreviewMenu.tsx +247 -0
- package/src/viewer/components/PreviewToolbar.tsx +1 -1
- package/src/viewer/components/PropsEditor.tsx +1 -1
- package/src/viewer/components/PropsTable.tsx +1 -1
- package/src/viewer/components/RightSidebar.tsx +1 -1
- package/src/viewer/components/ScreenshotButton.tsx +1 -1
- package/src/viewer/components/SkeletonLoader.tsx +1 -1
- package/src/viewer/components/Toast.tsx +2 -2
- package/src/viewer/components/TokenStylePanel.tsx +1 -1
- package/src/viewer/components/VariantMatrix.tsx +1 -1
- package/src/viewer/components/VariantTabs.tsx +1 -1
- package/src/viewer/components/ViewportSelector.tsx +1 -1
- package/src/viewer/constants/ui.ts +14 -0
- package/src/viewer/entry.tsx +3 -4
- package/src/viewer/hooks/useKeyboardShortcuts.ts +65 -17
- package/src/viewer/hooks/useViewSettings.ts +1 -2
- package/src/viewer/index.ts +1 -1
- package/src/viewer/preview-frame.html +6 -9
- package/src/viewer/server.ts +80 -7
- package/src/viewer/styles/globals.css +12 -51
- package/src/viewer/vite-plugin.ts +70 -9
- package/dist/chunk-2EFVPE5Q.js.map +0 -1
- package/dist/chunk-3JPJTU25.js.map +0 -1
- package/dist/chunk-AA6CAHCZ.js.map +0 -1
- package/dist/chunk-CWKQQR6C.js.map +0 -1
- package/dist/init-4VXL3Q6N.js.map +0 -1
- package/dist/viewer-ZWQQ74FV.js.map +0 -1
- /package/dist/{chunk-LHIIBI6F.js.map → chunk-M42XIHPV.js.map} +0 -0
- /package/dist/{core-YAPWXDZW.js.map → core/index.js.map} +0 -0
- /package/dist/{generate-LEBVZCCH.js.map → generate-ZPERYZLF.js.map} +0 -0
- /package/dist/{scan-3NYSRF6G.js.map → scan-BSMLGBX4.js.map} +0 -0
- /package/dist/{service-HL6TMP3B.js.map → service-QACVPR37.js.map} +0 -0
- /package/dist/{static-viewer-KLD24I4R.js.map → static-viewer-2RQD5QLR.js.map} +0 -0
- /package/dist/{test-Y7YZOJLE.js.map → test-36UELXTE.js.map} +0 -0
- /package/dist/{tokens-M4FCJKBK.js.map → tokens-A3BZIQPB.js.map} +0 -0
|
@@ -12,7 +12,7 @@ import { useMemo, useState } from "react";
|
|
|
12
12
|
import type { FragmentDefinition, ComponentRelation, RelationshipType } from "../../core/index.js";
|
|
13
13
|
import { ChevronRightIcon, EmptyIcon, WandIcon } from "./Icons.js";
|
|
14
14
|
import { detectAllRelationships, mergeRelationships } from "../utils/detectRelationships.js";
|
|
15
|
-
import { Stack, Text, Badge, Button, EmptyState, CodeBlock, Grid, Separator } from "@fragments/ui";
|
|
15
|
+
import { Stack, Text, Badge, Button, EmptyState, CodeBlock, Grid, Separator } from "@fragments-sdk/ui";
|
|
16
16
|
|
|
17
17
|
interface ComponentGraphProps {
|
|
18
18
|
/** Current fragment definition */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FragmentMeta } from '../../core/index.js';
|
|
2
|
-
import { Badge, Alert, Text, Stack, Separator } from '@fragments/ui';
|
|
2
|
+
import { Badge, Alert, Text, Stack, Separator } from '@fragments-sdk/ui';
|
|
3
3
|
import { WarningIcon, BeakerIcon } from './Icons.js';
|
|
4
4
|
|
|
5
5
|
interface ComponentHeaderProps {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { memo } from 'react';
|
|
7
7
|
import type { FragmentContract } from '../../core/index.js';
|
|
8
|
-
import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge } from '@fragments/ui';
|
|
8
|
+
import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge } from '@fragments-sdk/ui';
|
|
9
9
|
|
|
10
10
|
interface ContractPanelProps {
|
|
11
11
|
contract?: FragmentContract;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Component, type ReactNode, type ErrorInfo } from 'react';
|
|
2
|
-
import { Button, Alert, CodeBlock, Collapsible, Stack, Text } from '@fragments/ui';
|
|
2
|
+
import { Button, Alert, CodeBlock, Collapsible, Stack, Text } from '@fragments-sdk/ui';
|
|
3
3
|
import { ErrorIcon, RefreshIcon } from './Icons.js';
|
|
4
4
|
|
|
5
5
|
interface ErrorBoundaryProps {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { useMemo, useState, useCallback, useEffect } from 'react';
|
|
8
8
|
import type { FragmentDefinition } from '../../core/index.js';
|
|
9
9
|
import type { ImpactValue } from 'axe-core';
|
|
10
|
-
import { Badge, Progress, Stack, Text, Card, EmptyState, Table } from '@fragments/ui';
|
|
10
|
+
import { Badge, Progress, Stack, Text, Card, EmptyState, Table } from '@fragments-sdk/ui';
|
|
11
11
|
import {
|
|
12
12
|
getAllA11yData,
|
|
13
13
|
getA11ySummary,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Badge, Stack, Text } from '@fragments/ui';
|
|
1
|
+
import { Badge, Stack, Text } from '@fragments-sdk/ui';
|
|
2
2
|
import { HMR_STATUS } from '../constants/ui.js';
|
|
3
3
|
import { useHmrStatus } from '../hooks/useHmrStatus.js';
|
|
4
4
|
import { WifiIcon, WifiOffIcon } from './Icons.js';
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { useState, useCallback, useRef, useEffect } from "react";
|
|
13
|
-
import { Button, Stack, Text, Badge, EmptyState, CodeBlock, Alert } from "@fragments/ui";
|
|
13
|
+
import { Button, Stack, Text, Badge, EmptyState, CodeBlock, Alert } from "@fragments-sdk/ui";
|
|
14
14
|
import type { PlayFunction, PlayFunctionContext, FragmentVariant } from "../../core/index.js";
|
|
15
15
|
import {
|
|
16
16
|
PlayIcon,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useMemo, useEffect, useState } from "react";
|
|
2
2
|
import type { FragmentDefinition } from "../../core/index.js";
|
|
3
3
|
import { VariantRenderer } from "./VariantRenderer.js";
|
|
4
|
-
import { getBackgroundStyle, type BackgroundOption, type ZoomLevel } from "
|
|
4
|
+
import { getBackgroundStyle, type BackgroundOption, type ZoomLevel } from "../constants/ui.js";
|
|
5
5
|
|
|
6
6
|
interface IsolatedRenderProps {
|
|
7
7
|
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Uses Fragments UI Dialog compound component.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Dialog, Stack, Text, Badge } from '@fragments/ui';
|
|
8
|
+
import { Dialog, Stack, Text, Badge } from '@fragments-sdk/ui';
|
|
9
9
|
import { SHORTCUTS } from "../hooks/useKeyboardShortcuts.js";
|
|
10
10
|
|
|
11
11
|
interface KeyboardShortcutsHelpProps {
|
|
@@ -3,7 +3,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { useState, useRef, useEffect } from 'react';
|
|
5
5
|
import { HexColorPicker } from 'react-colorful';
|
|
6
|
-
import { Button, Input, Toggle, Stack, Text, Card, Field, Separator, Select } from '@fragments/ui';
|
|
6
|
+
import { Button, Input, Toggle, Stack, Text, Card, Field, Separator, Select } from '@fragments-sdk/ui';
|
|
7
7
|
import { generateColorScheme, type ColorSchemeType } from '../utils/colorSchemes.js';
|
|
8
8
|
import { ChevronDownIcon } from './Icons.js';
|
|
9
9
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
|
|
2
2
|
import type { FragmentDefinition } from '../../core/index.js';
|
|
3
|
-
import { Sidebar, useSidebar, Text } from '@fragments/ui';
|
|
3
|
+
import { Sidebar, useSidebar, Text, Menu } from '@fragments-sdk/ui';
|
|
4
4
|
|
|
5
5
|
// Fuzzy matching utility
|
|
6
6
|
interface FuzzyMatch {
|
|
@@ -125,11 +125,40 @@ interface LeftSidebarProps {
|
|
|
125
125
|
onHealthClick?: () => void;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
const FilterIcon = ({ active }: { active?: boolean }) => (
|
|
129
|
+
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
130
|
+
<path
|
|
131
|
+
d="M2 3h12l-4.5 5.5V13l-3-1.5V8.5L2 3z"
|
|
132
|
+
stroke={active ? 'var(--color-brand, #3b82f6)' : 'currentColor'}
|
|
133
|
+
strokeWidth="1.5"
|
|
134
|
+
strokeLinejoin="round"
|
|
135
|
+
fill={active ? 'var(--color-brand, #3b82f6)' : 'none'}
|
|
136
|
+
fillOpacity={active ? 0.15 : 0}
|
|
137
|
+
/>
|
|
138
|
+
</svg>
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const isInstalled = (path: string) => path.startsWith('@');
|
|
142
|
+
|
|
128
143
|
export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect, showHealth, onHealthClick }: LeftSidebarProps) {
|
|
129
144
|
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
145
|
+
const [showCustom, setShowCustom] = useState(true);
|
|
146
|
+
const [showLibrary, setShowLibrary] = useState(true);
|
|
130
147
|
const { isMobile, setOpen } = useSidebar();
|
|
131
148
|
const navRef = useRef<HTMLDivElement>(null);
|
|
132
149
|
|
|
150
|
+
// Determine if both sources exist (only show filter when useful)
|
|
151
|
+
const hasBothSources = useMemo(() => {
|
|
152
|
+
let hasCustom = false;
|
|
153
|
+
let hasLibrary = false;
|
|
154
|
+
for (const item of fragments) {
|
|
155
|
+
if (isInstalled(item.path)) hasLibrary = true;
|
|
156
|
+
else hasCustom = true;
|
|
157
|
+
if (hasCustom && hasLibrary) return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}, [fragments]);
|
|
161
|
+
|
|
133
162
|
const debouncedSearch = useDebounce(searchQuery, 150);
|
|
134
163
|
|
|
135
164
|
const searchResults = useMemo(() => {
|
|
@@ -155,19 +184,30 @@ export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect,
|
|
|
155
184
|
return map;
|
|
156
185
|
}, [searchResults]);
|
|
157
186
|
|
|
187
|
+
const isFilterActive = showCustom !== showLibrary;
|
|
188
|
+
|
|
189
|
+
// Apply source filter
|
|
190
|
+
const sourceFiltered = useMemo(() => {
|
|
191
|
+
if (!isFilterActive) return fragments;
|
|
192
|
+
return fragments.filter(item =>
|
|
193
|
+
showLibrary ? isInstalled(item.path) : !isInstalled(item.path)
|
|
194
|
+
);
|
|
195
|
+
}, [fragments, isFilterActive, showLibrary]);
|
|
196
|
+
|
|
158
197
|
// Flat alphabetical list (search results sorted by relevance, otherwise alphabetical)
|
|
159
198
|
const flatItems = useMemo(() => {
|
|
160
199
|
if (searchResults) {
|
|
161
200
|
return searchResults
|
|
162
201
|
.map(r => r.item)
|
|
163
|
-
.filter(item => item.fragment?.meta?.name)
|
|
202
|
+
.filter(item => item.fragment?.meta?.name)
|
|
203
|
+
.filter(item => !isFilterActive || (showLibrary ? isInstalled(item.path) : !isInstalled(item.path)));
|
|
164
204
|
}
|
|
165
|
-
return [...
|
|
205
|
+
return [...sourceFiltered]
|
|
166
206
|
.filter(item => item.fragment?.meta?.name)
|
|
167
207
|
.sort((a, b) =>
|
|
168
208
|
a.fragment.meta.name.toLowerCase().localeCompare(b.fragment.meta.name.toLowerCase())
|
|
169
209
|
);
|
|
170
|
-
}, [
|
|
210
|
+
}, [sourceFiltered, searchResults, isFilterActive, showLibrary]);
|
|
171
211
|
|
|
172
212
|
const keyboardItems = useMemo(() => {
|
|
173
213
|
const componentItems = flatItems.map((item) => ({ type: 'component' as const, path: item.path }));
|
|
@@ -252,19 +292,64 @@ export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect,
|
|
|
252
292
|
</Sidebar.Section>
|
|
253
293
|
)}
|
|
254
294
|
|
|
255
|
-
<Sidebar.Section
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
295
|
+
<Sidebar.Section
|
|
296
|
+
label="Components"
|
|
297
|
+
action={hasBothSources ? (
|
|
298
|
+
<Menu modal={false}>
|
|
299
|
+
<Menu.Trigger asChild>
|
|
300
|
+
<button
|
|
301
|
+
type="button"
|
|
302
|
+
aria-label="Filter components by source"
|
|
303
|
+
style={{
|
|
304
|
+
background: 'none',
|
|
305
|
+
border: 'none',
|
|
306
|
+
cursor: 'pointer',
|
|
307
|
+
padding: '2px',
|
|
308
|
+
display: 'flex',
|
|
309
|
+
alignItems: 'center',
|
|
310
|
+
color: 'var(--text-tertiary)',
|
|
311
|
+
borderRadius: '4px',
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
<FilterIcon active={isFilterActive} />
|
|
315
|
+
</button>
|
|
316
|
+
</Menu.Trigger>
|
|
317
|
+
<Menu.Content>
|
|
318
|
+
<Menu.CheckboxItem checked={showCustom} onCheckedChange={setShowCustom}>
|
|
319
|
+
Custom
|
|
320
|
+
</Menu.CheckboxItem>
|
|
321
|
+
<Menu.CheckboxItem checked={showLibrary} onCheckedChange={setShowLibrary}>
|
|
322
|
+
Fragments UI
|
|
323
|
+
</Menu.CheckboxItem>
|
|
324
|
+
</Menu.Content>
|
|
325
|
+
</Menu>
|
|
326
|
+
) : undefined}
|
|
327
|
+
>
|
|
328
|
+
{flatItems.map((item) => {
|
|
329
|
+
const hasError = !!(item.fragment as any)?._loadError;
|
|
330
|
+
return (
|
|
331
|
+
<Sidebar.Item
|
|
332
|
+
key={item.path}
|
|
333
|
+
active={activeFragment === item.path}
|
|
334
|
+
onClick={() => handleSelect(item.path)}
|
|
335
|
+
>
|
|
336
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: '6px', width: '100%' }}>
|
|
337
|
+
<HighlightedText
|
|
338
|
+
text={item.fragment.meta.name}
|
|
339
|
+
indices={highlightMap.get(item.path) || []}
|
|
340
|
+
/>
|
|
341
|
+
{hasError && (
|
|
342
|
+
<span
|
|
343
|
+
title="Missing dependencies"
|
|
344
|
+
style={{ color: 'var(--color-warning, #f59e0b)', fontSize: '14px', lineHeight: 1, flexShrink: 0 }}
|
|
345
|
+
>
|
|
346
|
+
⚠
|
|
347
|
+
</span>
|
|
348
|
+
)}
|
|
349
|
+
</span>
|
|
350
|
+
</Sidebar.Item>
|
|
351
|
+
);
|
|
352
|
+
})}
|
|
268
353
|
</Sidebar.Section>
|
|
269
354
|
|
|
270
355
|
{flatItems.length === 0 && (
|
|
@@ -278,7 +363,9 @@ export function LeftSidebar({ fragments, activeFragment, searchQuery, onSelect,
|
|
|
278
363
|
{/* Footer */}
|
|
279
364
|
<Sidebar.Footer>
|
|
280
365
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
|
281
|
-
<Text size="xs" color="tertiary">
|
|
366
|
+
<Text size="xs" color="tertiary">
|
|
367
|
+
{isFilterActive ? `${flatItems.length} / ${fragments.length}` : fragments.length} components
|
|
368
|
+
</Text>
|
|
282
369
|
<Sidebar.CollapseToggle />
|
|
283
370
|
</div>
|
|
284
371
|
</Sidebar.Footer>
|
|
@@ -10,7 +10,7 @@ import { useState, type ReactNode } from "react";
|
|
|
10
10
|
import { ErrorBoundary } from "./ErrorBoundary.js";
|
|
11
11
|
import { IsolatedPreviewFrame } from "./IsolatedPreviewFrame.js";
|
|
12
12
|
import { ChevronDownIcon } from "./Icons.js";
|
|
13
|
-
import { getBackgroundStyle, type BackgroundOption } from "
|
|
13
|
+
import { getBackgroundStyle, type BackgroundOption } from "../constants/ui.js";
|
|
14
14
|
|
|
15
15
|
interface ViewportConfig {
|
|
16
16
|
name: string;
|
|
@@ -13,9 +13,8 @@ import { FigmaEmbed } from './FigmaEmbed.js';
|
|
|
13
13
|
import { VariantMatrix } from './VariantMatrix.js';
|
|
14
14
|
import { MultiViewportPreview } from './MultiViewportPreview.js';
|
|
15
15
|
import { IsolatedPreviewFrame } from './IsolatedPreviewFrame.js';
|
|
16
|
-
import { getBackgroundStyle, type ZoomLevel, type BackgroundOption } from '
|
|
16
|
+
import { getBackgroundStyle, getViewportWidth, type ZoomLevel, type BackgroundOption, type ViewportPreset, type ViewportSize } from '../constants/ui.js';
|
|
17
17
|
import type { PreviewTheme } from '../hooks/useViewSettings.js';
|
|
18
|
-
import { getViewportWidth, type ViewportPreset, type ViewportSize } from './ViewportSelector.js';
|
|
19
18
|
|
|
20
19
|
interface PreviewAreaProps {
|
|
21
20
|
// Component data
|
|
@@ -89,10 +89,6 @@ function resolvePreviewMode(fragment: FragmentDefinition): PreviewMode {
|
|
|
89
89
|
const name = fragment.meta.name.toLowerCase();
|
|
90
90
|
const category = (fragment.meta.category || '').toLowerCase();
|
|
91
91
|
|
|
92
|
-
if (category === 'layout' || category === 'navigation') {
|
|
93
|
-
return 'full-bleed';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
92
|
if (name.includes('appshell') || name.includes('sidebar') || name.includes('header') || name.includes('layout')) {
|
|
97
93
|
return 'full-bleed';
|
|
98
94
|
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreviewMenu — Storybook-style hamburger menu for the viewer.
|
|
3
|
+
* Provides navigation, view mode toggles, panel controls, and settings submenus.
|
|
4
|
+
* Placed left of the header beside the logo.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useEffect, useCallback } from 'react';
|
|
8
|
+
import { Button, Menu, Text, Stack } from '@fragments-sdk/ui';
|
|
9
|
+
import { List, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, GridFour, DeviceMobile, Rows, Eye } from '@phosphor-icons/react';
|
|
10
|
+
import {
|
|
11
|
+
ZOOM_LEVELS,
|
|
12
|
+
VIEWPORT_PRESETS,
|
|
13
|
+
type ZoomLevel,
|
|
14
|
+
type BackgroundOption,
|
|
15
|
+
type ViewportPreset,
|
|
16
|
+
} from '../constants/ui.js';
|
|
17
|
+
|
|
18
|
+
// Background options with display labels
|
|
19
|
+
const BACKGROUND_OPTIONS_UI: { value: BackgroundOption; label: string }[] = [
|
|
20
|
+
{ value: 'checkerboard', label: 'Checkerboard' },
|
|
21
|
+
{ value: 'black', label: 'Dark' },
|
|
22
|
+
{ value: 'white', label: 'Light' },
|
|
23
|
+
{ value: 'transparent', label: 'Transparent' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Viewport presets for submenu display
|
|
27
|
+
const VIEWPORT_OPTIONS = Object.entries(VIEWPORT_PRESETS).map(([value, config]) => ({
|
|
28
|
+
value: value as Exclude<ViewportPreset, 'custom'>,
|
|
29
|
+
label: config.label,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
/** Shortcut label helper — renders a right-aligned keyboard hint */
|
|
33
|
+
function Shortcut({ keys }: { keys: string }) {
|
|
34
|
+
return (
|
|
35
|
+
<Text as="span" size="2xs" color="tertiary" style={{ marginLeft: 'auto', paddingLeft: '16px', fontFamily: 'inherit' }}>
|
|
36
|
+
{keys}
|
|
37
|
+
</Text>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface PreviewMenuProps {
|
|
42
|
+
zoom: ZoomLevel;
|
|
43
|
+
background: BackgroundOption;
|
|
44
|
+
viewport: ViewportPreset;
|
|
45
|
+
showMatrixView: boolean;
|
|
46
|
+
showMultiViewport: boolean;
|
|
47
|
+
panelOpen: boolean;
|
|
48
|
+
onZoomChange: (zoom: ZoomLevel) => void;
|
|
49
|
+
onBackgroundChange: (bg: BackgroundOption) => void;
|
|
50
|
+
onViewportChange: (viewport: ViewportPreset) => void;
|
|
51
|
+
onToggleMatrix: () => void;
|
|
52
|
+
onToggleMultiViewport: () => void;
|
|
53
|
+
onTogglePanel: () => void;
|
|
54
|
+
onPrevComponent: () => void;
|
|
55
|
+
onNextComponent: () => void;
|
|
56
|
+
onPrevVariant: () => void;
|
|
57
|
+
onNextVariant: () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function PreviewMenu({
|
|
61
|
+
zoom,
|
|
62
|
+
background,
|
|
63
|
+
viewport,
|
|
64
|
+
showMatrixView,
|
|
65
|
+
showMultiViewport,
|
|
66
|
+
panelOpen,
|
|
67
|
+
onZoomChange,
|
|
68
|
+
onBackgroundChange,
|
|
69
|
+
onViewportChange,
|
|
70
|
+
onToggleMatrix,
|
|
71
|
+
onToggleMultiViewport,
|
|
72
|
+
onTogglePanel,
|
|
73
|
+
onPrevComponent,
|
|
74
|
+
onNextComponent,
|
|
75
|
+
onPrevVariant,
|
|
76
|
+
onNextVariant,
|
|
77
|
+
}: PreviewMenuProps) {
|
|
78
|
+
// Keyboard shortcuts for zoom
|
|
79
|
+
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
|
80
|
+
const target = e.target as HTMLElement;
|
|
81
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (e.key === '=' || e.key === '+') {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
const currentIndex = ZOOM_LEVELS.indexOf(zoom);
|
|
88
|
+
if (currentIndex < ZOOM_LEVELS.length - 1) {
|
|
89
|
+
onZoomChange(ZOOM_LEVELS[currentIndex + 1]);
|
|
90
|
+
}
|
|
91
|
+
} else if (e.key === '-') {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
const currentIndex = ZOOM_LEVELS.indexOf(zoom);
|
|
94
|
+
if (currentIndex > 0) {
|
|
95
|
+
onZoomChange(ZOOM_LEVELS[currentIndex - 1]);
|
|
96
|
+
}
|
|
97
|
+
} else if (e.key === '0') {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
onZoomChange(100);
|
|
100
|
+
}
|
|
101
|
+
}, [zoom, onZoomChange]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
105
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
106
|
+
}, [handleKeyDown]);
|
|
107
|
+
|
|
108
|
+
const iconSize = 16;
|
|
109
|
+
const iconWeight = 'regular' as const;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Menu modal={false}>
|
|
113
|
+
<Menu.Trigger asChild>
|
|
114
|
+
<Button variant="ghost" size="sm" aria-label="Options menu">
|
|
115
|
+
<Stack direction="row" gap="xs" align="center">
|
|
116
|
+
<List size={18} weight="bold" />
|
|
117
|
+
<Text size="sm" weight="medium">Options</Text>
|
|
118
|
+
</Stack>
|
|
119
|
+
</Button>
|
|
120
|
+
</Menu.Trigger>
|
|
121
|
+
<Menu.Content side="bottom" align="start" style={{ minWidth: '14rem' }}>
|
|
122
|
+
{/* View modes */}
|
|
123
|
+
<Menu.Item
|
|
124
|
+
checked={showMatrixView}
|
|
125
|
+
icon={<GridFour size={iconSize} weight={iconWeight} />}
|
|
126
|
+
onSelect={onToggleMatrix}
|
|
127
|
+
>
|
|
128
|
+
<Stack direction="row" align="center" style={{ flex: 1 }}>
|
|
129
|
+
Matrix view
|
|
130
|
+
<Shortcut keys="M" />
|
|
131
|
+
</Stack>
|
|
132
|
+
</Menu.Item>
|
|
133
|
+
<Menu.Item
|
|
134
|
+
checked={showMultiViewport}
|
|
135
|
+
icon={<DeviceMobile size={iconSize} weight={iconWeight} />}
|
|
136
|
+
onSelect={onToggleMultiViewport}
|
|
137
|
+
>
|
|
138
|
+
<Stack direction="row" align="center" style={{ flex: 1 }}>
|
|
139
|
+
Responsive view
|
|
140
|
+
<Shortcut keys="V" />
|
|
141
|
+
</Stack>
|
|
142
|
+
</Menu.Item>
|
|
143
|
+
<Menu.Item
|
|
144
|
+
checked={panelOpen}
|
|
145
|
+
icon={<Rows size={iconSize} weight={iconWeight} />}
|
|
146
|
+
onSelect={onTogglePanel}
|
|
147
|
+
>
|
|
148
|
+
<Stack direction="row" align="center" style={{ flex: 1 }}>
|
|
149
|
+
Show addons
|
|
150
|
+
<Shortcut keys="P" />
|
|
151
|
+
</Stack>
|
|
152
|
+
</Menu.Item>
|
|
153
|
+
|
|
154
|
+
<Menu.Separator />
|
|
155
|
+
|
|
156
|
+
{/* Navigation */}
|
|
157
|
+
<Menu.Group>
|
|
158
|
+
<Menu.GroupLabel>Navigate</Menu.GroupLabel>
|
|
159
|
+
<Menu.Item
|
|
160
|
+
icon={<ArrowUp size={iconSize} weight={iconWeight} />}
|
|
161
|
+
onSelect={onPrevComponent}
|
|
162
|
+
>
|
|
163
|
+
<Stack direction="row" align="center" style={{ flex: 1 }}>
|
|
164
|
+
Previous component
|
|
165
|
+
<Shortcut keys="⌘↑" />
|
|
166
|
+
</Stack>
|
|
167
|
+
</Menu.Item>
|
|
168
|
+
<Menu.Item
|
|
169
|
+
icon={<ArrowDown size={iconSize} weight={iconWeight} />}
|
|
170
|
+
onSelect={onNextComponent}
|
|
171
|
+
>
|
|
172
|
+
<Stack direction="row" align="center" style={{ flex: 1 }}>
|
|
173
|
+
Next component
|
|
174
|
+
<Shortcut keys="⌘↓" />
|
|
175
|
+
</Stack>
|
|
176
|
+
</Menu.Item>
|
|
177
|
+
<Menu.Item
|
|
178
|
+
icon={<ArrowLeft size={iconSize} weight={iconWeight} />}
|
|
179
|
+
onSelect={onPrevVariant}
|
|
180
|
+
>
|
|
181
|
+
<Stack direction="row" align="center" style={{ flex: 1 }}>
|
|
182
|
+
Previous variant
|
|
183
|
+
<Shortcut keys="⌘←" />
|
|
184
|
+
</Stack>
|
|
185
|
+
</Menu.Item>
|
|
186
|
+
<Menu.Item
|
|
187
|
+
icon={<ArrowRight size={iconSize} weight={iconWeight} />}
|
|
188
|
+
onSelect={onNextVariant}
|
|
189
|
+
>
|
|
190
|
+
<Stack direction="row" align="center" style={{ flex: 1 }}>
|
|
191
|
+
Next variant
|
|
192
|
+
<Shortcut keys="⌘→" />
|
|
193
|
+
</Stack>
|
|
194
|
+
</Menu.Item>
|
|
195
|
+
</Menu.Group>
|
|
196
|
+
|
|
197
|
+
<Menu.Separator />
|
|
198
|
+
|
|
199
|
+
{/* Settings submenus */}
|
|
200
|
+
<Menu.Submenu>
|
|
201
|
+
<Menu.SubmenuTrigger icon={<Eye size={iconSize} weight={iconWeight} />}>Viewport</Menu.SubmenuTrigger>
|
|
202
|
+
<Menu.Content side="right" align="start">
|
|
203
|
+
{VIEWPORT_OPTIONS.map((option) => (
|
|
204
|
+
<Menu.Item
|
|
205
|
+
key={option.value}
|
|
206
|
+
checked={viewport === option.value}
|
|
207
|
+
onSelect={() => onViewportChange(option.value)}
|
|
208
|
+
>
|
|
209
|
+
{option.label}
|
|
210
|
+
</Menu.Item>
|
|
211
|
+
))}
|
|
212
|
+
</Menu.Content>
|
|
213
|
+
</Menu.Submenu>
|
|
214
|
+
|
|
215
|
+
<Menu.Submenu>
|
|
216
|
+
<Menu.SubmenuTrigger>Background</Menu.SubmenuTrigger>
|
|
217
|
+
<Menu.Content side="right" align="start">
|
|
218
|
+
{BACKGROUND_OPTIONS_UI.map((option) => (
|
|
219
|
+
<Menu.Item
|
|
220
|
+
key={option.value}
|
|
221
|
+
checked={background === option.value}
|
|
222
|
+
onSelect={() => onBackgroundChange(option.value)}
|
|
223
|
+
>
|
|
224
|
+
{option.label}
|
|
225
|
+
</Menu.Item>
|
|
226
|
+
))}
|
|
227
|
+
</Menu.Content>
|
|
228
|
+
</Menu.Submenu>
|
|
229
|
+
|
|
230
|
+
<Menu.Submenu>
|
|
231
|
+
<Menu.SubmenuTrigger>Zoom ({zoom}%)</Menu.SubmenuTrigger>
|
|
232
|
+
<Menu.Content side="right" align="start">
|
|
233
|
+
{ZOOM_LEVELS.map((level) => (
|
|
234
|
+
<Menu.Item
|
|
235
|
+
key={level}
|
|
236
|
+
checked={zoom === level}
|
|
237
|
+
onSelect={() => onZoomChange(level)}
|
|
238
|
+
>
|
|
239
|
+
{level}%
|
|
240
|
+
</Menu.Item>
|
|
241
|
+
))}
|
|
242
|
+
</Menu.Content>
|
|
243
|
+
</Menu.Submenu>
|
|
244
|
+
</Menu.Content>
|
|
245
|
+
</Menu>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import type { PropDefinition } from "../../core/index.js";
|
|
3
|
-
import { Input, Select, Toggle } from "@fragments/ui";
|
|
3
|
+
import { Input, Select, Toggle } from "@fragments-sdk/ui";
|
|
4
4
|
import { ControlsIcon, ChevronDownIcon, RefreshIcon } from "./Icons.js";
|
|
5
5
|
|
|
6
6
|
interface PropsEditorProps {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import type { PropDefinition } from '../../core/index.js';
|
|
3
|
-
import { Table, createColumns, Badge, Text, Stack } from '@fragments/ui';
|
|
3
|
+
import { Table, createColumns, Badge, Text, Stack } from '@fragments-sdk/ui';
|
|
4
4
|
import { WarningIcon } from './Icons.js';
|
|
5
5
|
|
|
6
6
|
interface PropsTableProps {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FragmentDefinition } from '../../core/index.js';
|
|
2
2
|
import { useScrollSpy } from '../hooks/useScrollSpy.js';
|
|
3
|
-
import { Sidebar } from '@fragments/ui';
|
|
3
|
+
import { Sidebar } from '@fragments-sdk/ui';
|
|
4
4
|
|
|
5
5
|
interface RightSidebarProps {
|
|
6
6
|
fragment: FragmentDefinition;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { useState, memo } from 'react';
|
|
7
7
|
import html2canvas from 'html2canvas';
|
|
8
|
-
import { Button, Tooltip } from '@fragments/ui';
|
|
8
|
+
import { Button, Tooltip } from '@fragments-sdk/ui';
|
|
9
9
|
import { CameraIcon } from './Icons.js';
|
|
10
10
|
|
|
11
11
|
interface ScreenshotButtonProps {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Re-export Fragments UI Toast for use in the viewer
|
|
2
|
-
export { ToastProvider, useToast } from '@fragments/ui';
|
|
3
|
-
export type { ToastData as ToastMessage } from '@fragments/ui';
|
|
2
|
+
export { ToastProvider, useToast } from '@fragments-sdk/ui';
|
|
3
|
+
export type { ToastData as ToastMessage } from '@fragments-sdk/ui';
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { useState, useEffect, useCallback } from "react";
|
|
12
|
-
import { Badge } from "@fragments/ui";
|
|
12
|
+
import { Badge } from "@fragments-sdk/ui";
|
|
13
13
|
import type { DesignToken, EnhancedStyleDiffItem, TokenUsageSummary } from "../../core/index.js";
|
|
14
14
|
import { CheckIcon, XIcon, LoadingIcon, FigmaIcon, WandIcon } from "./Icons.js";
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ import { ErrorBoundary } from "./ErrorBoundary.js";
|
|
|
17
17
|
import { StoryRenderer, LoaderIndicator } from "./StoryRenderer.js";
|
|
18
18
|
import { IsolatedPreviewFrame } from "./IsolatedPreviewFrame.js";
|
|
19
19
|
import { ChevronDownIcon } from "./Icons.js";
|
|
20
|
-
import { getBackgroundStyle, type BackgroundOption } from "
|
|
20
|
+
import { getBackgroundStyle, type BackgroundOption } from "../constants/ui.js";
|
|
21
21
|
|
|
22
22
|
interface VariantMatrixProps {
|
|
23
23
|
/** All variants to display */
|