@fragments-sdk/cli 0.7.9 → 0.7.11

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 (106) hide show
  1. package/dist/bin.js +13 -13
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-CWKQQR6C.js → chunk-57OW43NL.js} +3 -3
  4. package/dist/chunk-57OW43NL.js.map +1 -0
  5. package/dist/{chunk-AA6CAHCZ.js → chunk-7CRC46HV.js} +2 -2
  6. package/dist/chunk-7CRC46HV.js.map +1 -0
  7. package/dist/{chunk-3JPJTU25.js → chunk-CRTN6BIW.js} +5 -5
  8. package/dist/chunk-CRTN6BIW.js.map +1 -0
  9. package/dist/{chunk-LHIIBI6F.js → chunk-M42XIHPV.js} +2 -2
  10. package/dist/{chunk-2EFVPE5Q.js → chunk-TQOGBAOZ.js} +2 -2
  11. package/dist/chunk-TQOGBAOZ.js.map +1 -0
  12. package/dist/core/index.d.ts +1944 -0
  13. package/dist/{core-YAPWXDZW.js → core/index.js} +5 -5
  14. package/dist/defineFragment-C6PFzZyo.d.ts +656 -0
  15. package/dist/{generate-LEBVZCCH.js → generate-ZPERYZLF.js} +4 -4
  16. package/dist/index.d.ts +4 -159
  17. package/dist/index.js +9 -4
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-4VXL3Q6N.js → init-GID2DXB3.js} +69 -7
  20. package/dist/init-GID2DXB3.js.map +1 -0
  21. package/dist/mcp-bin.js +3 -3
  22. package/dist/{scan-3NYSRF6G.js → scan-BSMLGBX4.js} +5 -5
  23. package/dist/{service-HL6TMP3B.js → service-QACVPR37.js} +3 -3
  24. package/dist/{static-viewer-KLD24I4R.js → static-viewer-2RQD5QLR.js} +3 -3
  25. package/dist/{test-Y7YZOJLE.js → test-36UELXTE.js} +3 -3
  26. package/dist/{tokens-M4FCJKBK.js → tokens-A3BZIQPB.js} +4 -4
  27. package/dist/{viewer-ZWQQ74FV.js → viewer-CNLZQUFO.js} +156 -32
  28. package/dist/viewer-CNLZQUFO.js.map +1 -0
  29. package/package.json +8 -2
  30. package/src/commands/add.ts +1 -1
  31. package/src/commands/init.ts +84 -4
  32. package/src/core/defineFragment.ts +1 -1
  33. package/src/core/figma.ts +1 -1
  34. package/src/core/index.ts +2 -2
  35. package/src/core/loader.ts +3 -3
  36. package/src/core/schema.ts +1 -1
  37. package/src/index.ts +6 -0
  38. package/src/migrate/converter.ts +1 -1
  39. package/src/service/snippet-validation.test.ts +5 -5
  40. package/src/service/snippet-validation.ts +0 -1
  41. package/src/viewer/__tests__/viewer-integration.test.ts +16 -23
  42. package/src/viewer/components/AccessibilityPanel.tsx +1 -1
  43. package/src/viewer/components/ActionsPanel.tsx +1 -1
  44. package/src/viewer/components/App.tsx +563 -166
  45. package/src/viewer/components/BottomPanel.tsx +1 -1
  46. package/src/viewer/components/CodePanel.naming.test.tsx +1 -2
  47. package/src/viewer/components/CodePanel.tsx +1 -2
  48. package/src/viewer/components/CommandPalette.tsx +1 -1
  49. package/src/viewer/components/ComponentGraph.tsx +1 -1
  50. package/src/viewer/components/ComponentHeader.tsx +1 -1
  51. package/src/viewer/components/ContractPanel.tsx +1 -1
  52. package/src/viewer/components/ErrorBoundary.tsx +1 -1
  53. package/src/viewer/components/HealthDashboard.tsx +1 -1
  54. package/src/viewer/components/HmrStatusIndicator.tsx +1 -1
  55. package/src/viewer/components/InteractionsPanel.tsx +1 -1
  56. package/src/viewer/components/IsolatedRender.tsx +1 -1
  57. package/src/viewer/components/KeyboardShortcutsHelp.tsx +1 -1
  58. package/src/viewer/components/LandingPage.tsx +1 -1
  59. package/src/viewer/components/Layout.tsx +16 -13
  60. package/src/viewer/components/LeftSidebar.tsx +105 -18
  61. package/src/viewer/components/MultiViewportPreview.tsx +1 -1
  62. package/src/viewer/components/PreviewArea.tsx +22 -13
  63. package/src/viewer/components/PreviewFrameHost.tsx +0 -4
  64. package/src/viewer/components/PreviewToolbar.tsx +1 -1
  65. package/src/viewer/components/PropsEditor.tsx +1 -1
  66. package/src/viewer/components/PropsTable.tsx +1 -1
  67. package/src/viewer/components/RightSidebar.tsx +1 -1
  68. package/src/viewer/components/ScreenshotButton.tsx +1 -1
  69. package/src/viewer/components/SkeletonLoader.tsx +1 -1
  70. package/src/viewer/components/Toast.tsx +2 -2
  71. package/src/viewer/components/TokenStylePanel.tsx +1 -1
  72. package/src/viewer/components/VariantMatrix.tsx +1 -1
  73. package/src/viewer/components/VariantTabs.tsx +1 -1
  74. package/src/viewer/components/ViewportSelector.tsx +1 -1
  75. package/src/viewer/constants/ui.ts +14 -0
  76. package/src/viewer/entry.tsx +3 -4
  77. package/src/viewer/hooks/useKeyboardShortcuts.ts +65 -17
  78. package/src/viewer/hooks/useViewSettings.ts +1 -2
  79. package/src/viewer/index.ts +1 -1
  80. package/src/viewer/preview-frame.html +6 -9
  81. package/src/viewer/server.ts +106 -9
  82. package/src/viewer/styles/globals.css +12 -51
  83. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +110 -0
  84. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +89 -0
  85. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +119 -0
  86. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +134 -0
  87. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +66 -0
  88. package/src/viewer/vendor/shared/src/docs-layout.scss +28 -0
  89. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +2 -0
  90. package/src/viewer/vendor/shared/src/index.ts +26 -0
  91. package/src/viewer/vendor/shared/src/types.ts +41 -0
  92. package/src/viewer/vite-plugin.ts +70 -9
  93. package/dist/chunk-2EFVPE5Q.js.map +0 -1
  94. package/dist/chunk-3JPJTU25.js.map +0 -1
  95. package/dist/chunk-AA6CAHCZ.js.map +0 -1
  96. package/dist/chunk-CWKQQR6C.js.map +0 -1
  97. package/dist/init-4VXL3Q6N.js.map +0 -1
  98. package/dist/viewer-ZWQQ74FV.js.map +0 -1
  99. /package/dist/{chunk-LHIIBI6F.js.map → chunk-M42XIHPV.js.map} +0 -0
  100. /package/dist/{core-YAPWXDZW.js.map → core/index.js.map} +0 -0
  101. /package/dist/{generate-LEBVZCCH.js.map → generate-ZPERYZLF.js.map} +0 -0
  102. /package/dist/{scan-3NYSRF6G.js.map → scan-BSMLGBX4.js.map} +0 -0
  103. /package/dist/{service-HL6TMP3B.js.map → service-QACVPR37.js.map} +0 -0
  104. /package/dist/{static-viewer-KLD24I4R.js.map → static-viewer-2RQD5QLR.js.map} +0 -0
  105. /package/dist/{test-Y7YZOJLE.js.map → test-36UELXTE.js.map} +0 -0
  106. /package/dist/{tokens-M4FCJKBK.js.map → tokens-A3BZIQPB.js.map} +0 -0
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { memo, useCallback } from 'react';
7
7
  import type { FragmentDefinition, FragmentVariant } from '../../core/index.js';
8
- import { Tabs, Badge } from '@fragments/ui';
8
+ import { Tabs, Badge } from '@fragments-sdk/ui';
9
9
  import { ResizablePanel } from './ResizablePanel.js';
10
10
  import { CodePanel } from './CodePanel.js';
11
11
  import { TokenStylePanel } from './TokenStylePanel.js';
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
2
  import type { FragmentVariant } from '../../core/index.js';
3
3
 
4
- vi.mock('@fragments/ui', () => ({
4
+ vi.mock('@fragments-sdk/ui', () => ({
5
5
  CodeBlock: () => null,
6
6
  }));
7
7
 
@@ -43,7 +43,6 @@ describe('CodePanel authored code pipeline', () => {
43
43
  const code = __buildFallbackSnippetForTest('Button', variant);
44
44
 
45
45
  expect(code).toContain("import { Button } from '@/components/Button';");
46
- expect(code).toContain('TODO: Add explicit `code` for variant "Primary"');
47
46
  expect(code).toContain("<Button variant='primary' disabled={false}>Save Changes</Button>");
48
47
  });
49
48
 
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from 'react';
2
- import { CodeBlock } from '@fragments/ui';
2
+ import { CodeBlock } from '@fragments-sdk/ui';
3
3
  import type { FragmentVariant, PropDefinition } from '../../core/index.js';
4
4
 
5
5
  interface CodePanelProps {
@@ -76,7 +76,6 @@ function buildFallbackSnippet(componentName: string, variant: FragmentVariant):
76
76
 
77
77
  return `import { ${componentName} } from '@/components/${componentName}';
78
78
 
79
- // TODO: Add explicit \`code\` for variant "${variant.name}" in this fragment file.
80
79
  ${usage}`;
81
80
  }
82
81
 
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { useState, useEffect, useRef, useMemo, useCallback } from "react";
13
- import { Dialog, Stack, Text, Badge, Separator, Input } from '@fragments/ui';
13
+ import { Dialog, Stack, Text, Badge, Separator, Input } from '@fragments-sdk/ui';
14
14
  import type { FragmentDefinition } from "../../core/index.js";
15
15
  import { SearchIcon, ChevronRightIcon } from "./Icons.js";
16
16
 
@@ -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 "./PreviewToolbar.js";
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,24 +1,27 @@
1
1
  import type { ReactNode } from 'react';
2
- import { AppShell } from '@fragments/ui';
2
+ import { DocsPageShell } from '@fragments-sdk/shared';
3
3
 
4
4
  interface LayoutProps {
5
5
  leftSidebar: ReactNode;
6
6
  header: ReactNode;
7
7
  children: ReactNode;
8
+ aside?: ReactNode;
8
9
  }
9
10
 
10
- export function Layout({ leftSidebar, header, children }: LayoutProps) {
11
+ export function Layout({ leftSidebar, header, children, aside }: LayoutProps) {
11
12
  return (
12
- <AppShell>
13
- <AppShell.Header>
14
- {header}
15
- </AppShell.Header>
16
- <AppShell.Sidebar width="260px" collapsible="icon">
17
- {leftSidebar}
18
- </AppShell.Sidebar>
19
- <AppShell.Main padding="none">
20
- {children}
21
- </AppShell.Main>
22
- </AppShell>
13
+ <DocsPageShell
14
+ header={header}
15
+ sidebar={leftSidebar}
16
+ sidebarWidth="260px"
17
+ sidebarCollapsible="icon"
18
+ sidebarAriaLabel="Preview sidebar"
19
+ mainAriaLabel="Preview content"
20
+ mainPadding="none"
21
+ aside={aside}
22
+ asideWidth="240px"
23
+ >
24
+ {children}
25
+ </DocsPageShell>
23
26
  );
24
27
  }
@@ -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 [...fragments]
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
- }, [fragments, searchResults]);
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 label="Components">
256
- {flatItems.map((item) => (
257
- <Sidebar.Item
258
- key={item.path}
259
- active={activeFragment === item.path}
260
- onClick={() => handleSelect(item.path)}
261
- >
262
- <HighlightedText
263
- text={item.fragment.meta.name}
264
- indices={highlightMap.get(item.path) || []}
265
- />
266
- </Sidebar.Item>
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
+ &#9888;
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">{fragments.length} components</Text>
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 "./PreviewToolbar.js";
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 './PreviewToolbar.js';
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
@@ -398,11 +397,12 @@ export function PreviewArea({
398
397
  if (useIframeIsolation && variant) {
399
398
  // When no specific viewport width, fill the container
400
399
  const isFullWidth = !viewportWidth;
400
+ const scaleFactor = zoom / 100;
401
401
 
402
402
  return (
403
403
  <div style={isFullWidth
404
- ? { height: '100%', display: 'flex', flexDirection: 'column' }
405
- : { minHeight: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px' }
404
+ ? { height: '100%', display: 'flex', flexDirection: 'column', overflow: 'auto' }
405
+ : { minHeight: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px', overflow: 'auto' }
406
406
  }>
407
407
  <div
408
408
  style={{
@@ -419,15 +419,24 @@ export function PreviewArea({
419
419
  }),
420
420
  }}
421
421
  >
422
- <IsolatedPreviewFrame
423
- fragmentPath={fragmentPath}
424
- variantName={variant.name}
425
- theme={previewTheme}
426
- width="100%"
427
- height="100%"
428
- minHeight={viewportHeight || 200}
429
- previewKey={previewKey}
430
- />
422
+ <div
423
+ style={{
424
+ transform: `scale(${scaleFactor})`,
425
+ transformOrigin: 'top left',
426
+ width: zoom !== 100 ? `${100 / scaleFactor}%` : '100%',
427
+ height: zoom !== 100 ? `${100 / scaleFactor}%` : '100%',
428
+ }}
429
+ >
430
+ <IsolatedPreviewFrame
431
+ fragmentPath={fragmentPath}
432
+ variantName={variant.name}
433
+ theme={previewTheme}
434
+ width="100%"
435
+ height="100%"
436
+ minHeight={viewportHeight || 200}
437
+ previewKey={previewKey}
438
+ />
439
+ </div>
431
440
  </div>
432
441
  </div>
433
442
  );
@@ -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
  }
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useCallback } from 'react';
2
- import { Button, Menu, Stack, Separator } from '@fragments/ui';
2
+ import { Button, Menu, Stack, Separator } from '@fragments-sdk/ui';
3
3
  import {
4
4
  ZOOM_LEVELS,
5
5
  type ZoomLevel,
@@ -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 {
@@ -4,7 +4,7 @@
4
4
  * Shows animated placeholders while the app is loading.
5
5
  */
6
6
 
7
- import { Skeleton, Loading } from '@fragments/ui';
7
+ import { Skeleton, Loading } from '@fragments-sdk/ui';
8
8
 
9
9
  /**
10
10
  * Full app skeleton shown during initial load
@@ -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 "./PreviewToolbar.js";
20
+ import { getBackgroundStyle, type BackgroundOption } from "../constants/ui.js";
21
21
 
22
22
  interface VariantMatrixProps {
23
23
  /** All variants to display */
@@ -1,4 +1,4 @@
1
- import { Tabs } from '@fragments/ui';
1
+ import { Tabs } from '@fragments-sdk/ui';
2
2
  import type { FragmentVariant } from '../../core/index.js';
3
3
  import { PlayIcon } from './Icons.js';
4
4
 
@@ -1,4 +1,4 @@
1
- import { Button, Menu, Stack, Input, Text, Box } from '@fragments/ui';
1
+ import { Button, Menu, Stack, Input, Text, Box } from '@fragments-sdk/ui';
2
2
  import { VIEWPORT_PRESETS, type ViewportPreset } from '../constants/ui.js';
3
3
  import {
4
4
  ViewportIcon,
@@ -126,6 +126,20 @@ export const VIEWPORT_PRESETS = {
126
126
 
127
127
  export type ViewportPreset = keyof typeof VIEWPORT_PRESETS | 'custom';
128
128
 
129
+ export interface ViewportSize {
130
+ width: number | null;
131
+ height: number | null;
132
+ }
133
+
134
+ /**
135
+ * Get viewport width from preset.
136
+ */
137
+ export function getViewportWidth(viewport: ViewportPreset, customSize: ViewportSize): number | null {
138
+ if (viewport === 'custom') return customSize.width;
139
+ if (viewport === 'responsive') return null;
140
+ return VIEWPORT_PRESETS[viewport]?.width ?? null;
141
+ }
142
+
129
143
  /**
130
144
  * Get CSS background style for preview pane.
131
145
  */
@@ -4,11 +4,10 @@ import { App } from "./components/App.js";
4
4
  import { ThemeProvider } from "./components/ThemeProvider.js";
5
5
  import { ToastProvider } from "./components/Toast.js";
6
6
  import { AppSkeleton } from "./components/SkeletonLoader.js";
7
- // Viewer shell styles - independent from UI library
8
- // UI library styles are only loaded in the isolated preview area
7
+ // Fragments UI globals - base resets (box-sizing, body, scrollbars) + --fui-* CSS variables
8
+ import "@fragments-sdk/ui";
9
+ // Viewer-specific token aliases and utility classes
9
10
  import "./styles/globals.css";
10
- // Fragments UI globals - adds --fui-* CSS variables for dogfooding UI components
11
- import "@fragments/ui";
12
11
 
13
12
  // App-level error boundary that catches any unhandled errors
14
13
  class AppErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean; error: Error | null; errorInfo: ErrorInfo | null }> {