@fragments-sdk/cli 0.9.0 → 0.9.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 (123) hide show
  1. package/dist/bin.js +83 -33
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-WI6SLMSO.js → chunk-5GT62FCB.js} +2 -2
  4. package/dist/{chunk-CJEGT3WD.js → chunk-BW3ZATBW.js} +20 -3
  5. package/dist/chunk-BW3ZATBW.js.map +1 -0
  6. package/dist/{chunk-2JIKCJX3.js → chunk-D7372LQX.js} +13 -6
  7. package/dist/chunk-D7372LQX.js.map +1 -0
  8. package/dist/chunk-EZYXYWNF.js +131 -0
  9. package/dist/chunk-EZYXYWNF.js.map +1 -0
  10. package/dist/{chunk-NGIMCIK2.js → chunk-GF6OVPIN.js} +2 -2
  11. package/dist/{chunk-GOVI6COW.js → chunk-NVSPGSKB.js} +12 -4
  12. package/dist/chunk-NVSPGSKB.js.map +1 -0
  13. package/dist/core/index.d.ts +105 -3
  14. package/dist/core/index.js +12 -2
  15. package/dist/{defineFragment-D0UTve-I.d.ts → defineFragment-CBMS7Bab.d.ts} +21 -1
  16. package/dist/generate-LQA2R7FN.js +461 -0
  17. package/dist/generate-LQA2R7FN.js.map +1 -0
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +5 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/{init-KSAAS7X3.js → init-2GEGVIUQ.js} +13 -75
  22. package/dist/init-2GEGVIUQ.js.map +1 -0
  23. package/dist/mcp-bin.js +4 -3
  24. package/dist/mcp-bin.js.map +1 -1
  25. package/dist/{scan-65RH3QMM.js → scan-JGS65S7P.js} +6 -5
  26. package/dist/{service-A5GIGGGK.js → service-XP2EAJXD.js} +4 -3
  27. package/dist/{static-viewer-NSODM5VX.js → static-viewer-XCS7UJTO.js} +4 -3
  28. package/dist/storyFilters-3LUYAFZF.js +15 -0
  29. package/dist/storyFilters-3LUYAFZF.js.map +1 -0
  30. package/dist/{test-RPWZAYSJ.js → test-TD6TJNVY.js} +3 -3
  31. package/dist/{tokens-NIXSZRX7.js → tokens-2EXPCVP3.js} +5 -4
  32. package/dist/{tokens-NIXSZRX7.js.map → tokens-2EXPCVP3.js.map} +1 -1
  33. package/dist/{viewer-SBTJDMP7.js → viewer-RFA2KVBG.js} +243 -18
  34. package/dist/viewer-RFA2KVBG.js.map +1 -0
  35. package/package.json +1 -1
  36. package/src/build.ts +12 -2
  37. package/src/commands/build.ts +16 -2
  38. package/src/commands/generate.ts +383 -68
  39. package/src/commands/init.ts +9 -51
  40. package/src/core/config.ts +15 -2
  41. package/src/core/generators/typescript-extractor.ts +10 -0
  42. package/src/core/index.ts +15 -0
  43. package/src/core/schema.ts +10 -2
  44. package/src/core/storyFilters.test.ts +350 -0
  45. package/src/core/storyFilters.ts +253 -0
  46. package/src/core/types.ts +22 -0
  47. package/src/migrate/converter.ts +9 -1
  48. package/src/migrate/parser.ts +2 -0
  49. package/src/migrate/types.ts +2 -0
  50. package/src/setup.ts +69 -24
  51. package/src/viewer/__tests__/viewer-integration.test.ts +1 -1
  52. package/src/viewer/components/AccessibilityPanel.tsx +305 -312
  53. package/src/viewer/components/ActionsPanel.tsx +31 -29
  54. package/src/viewer/components/AllVariantsPreview.tsx +78 -0
  55. package/src/viewer/components/App.tsx +187 -740
  56. package/src/viewer/components/BottomPanel.tsx +228 -132
  57. package/src/viewer/components/CodePanel.tsx +1 -1
  58. package/src/viewer/components/CommandPalette.tsx +7 -10
  59. package/src/viewer/components/ComponentDocView.tsx +164 -0
  60. package/src/viewer/components/ComponentGraph.tsx +111 -142
  61. package/src/viewer/components/ContractPanel.tsx +6 -6
  62. package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
  63. package/src/viewer/components/FigmaEmbed.tsx +20 -18
  64. package/src/viewer/components/FragmentEditor.tsx +92 -115
  65. package/src/viewer/components/HeaderSearch.tsx +24 -0
  66. package/src/viewer/components/HealthDashboard.tsx +16 -2
  67. package/src/viewer/components/Icons.tsx +9 -0
  68. package/src/viewer/components/InteractionsPanel.tsx +101 -117
  69. package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
  70. package/src/viewer/components/LandingPage.tsx +3 -3
  71. package/src/viewer/components/LeftSidebar.tsx +141 -63
  72. package/src/viewer/components/LoadErrorMessage.tsx +102 -0
  73. package/src/viewer/components/MultiViewportPreview.tsx +61 -142
  74. package/src/viewer/components/NoVariantsMessage.tsx +59 -0
  75. package/src/viewer/components/PanelShell.tsx +161 -0
  76. package/src/viewer/components/PerformancePanel.tsx +31 -28
  77. package/src/viewer/components/PreviewArea.tsx +1 -1
  78. package/src/viewer/components/PreviewAside.tsx +168 -0
  79. package/src/viewer/components/PreviewFrameHost.tsx +3 -3
  80. package/src/viewer/components/PropsEditor.tsx +70 -156
  81. package/src/viewer/components/ResizablePanel.tsx +103 -263
  82. package/src/viewer/components/RightSidebar.tsx +3 -9
  83. package/src/viewer/components/SkeletonLoader.tsx +13 -13
  84. package/src/viewer/components/TokenStylePanel.tsx +182 -209
  85. package/src/viewer/components/TopToolbar.tsx +159 -0
  86. package/src/viewer/components/VariantMatrix.tsx +42 -86
  87. package/src/viewer/components/VariantTabs.tsx +3 -3
  88. package/src/viewer/components/ViewerHeader.tsx +69 -0
  89. package/src/viewer/components/WebMCPDevTools.tsx +17 -23
  90. package/src/viewer/components/viewer-utils.ts +16 -0
  91. package/src/viewer/entry.tsx +5 -0
  92. package/src/viewer/hooks/useAppState.ts +27 -4
  93. package/src/viewer/hooks/usePreviewBridge.ts +2 -2
  94. package/src/viewer/preview-frame.html +6 -12
  95. package/src/viewer/server.ts +169 -2
  96. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
  97. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
  98. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
  99. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +6 -18
  100. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
  101. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +5 -16
  102. package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
  103. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
  104. package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
  105. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +122 -0
  106. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
  107. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
  108. package/src/viewer/vendor/shared/src/index.ts +8 -0
  109. package/src/viewer/vendor/shared/src/types.ts +12 -0
  110. package/src/viewer/vite-plugin.ts +109 -4
  111. package/dist/chunk-2JIKCJX3.js.map +0 -1
  112. package/dist/chunk-CJEGT3WD.js.map +0 -1
  113. package/dist/chunk-GOVI6COW.js.map +0 -1
  114. package/dist/generate-35OIMW4Y.js +0 -252
  115. package/dist/generate-35OIMW4Y.js.map +0 -1
  116. package/dist/init-KSAAS7X3.js.map +0 -1
  117. package/dist/viewer-SBTJDMP7.js.map +0 -1
  118. /package/dist/{chunk-WI6SLMSO.js.map → chunk-5GT62FCB.js.map} +0 -0
  119. /package/dist/{chunk-NGIMCIK2.js.map → chunk-GF6OVPIN.js.map} +0 -0
  120. /package/dist/{scan-65RH3QMM.js.map → scan-JGS65S7P.js.map} +0 -0
  121. /package/dist/{service-A5GIGGGK.js.map → service-XP2EAJXD.js.map} +0 -0
  122. /package/dist/{static-viewer-NSODM5VX.js.map → static-viewer-XCS7UJTO.js.map} +0 -0
  123. /package/dist/{test-RPWZAYSJ.js.map → test-TD6TJNVY.js.map} +0 -0
@@ -1,38 +1,39 @@
1
1
  /**
2
- * ResizablePanel - A panel that can be resized by dragging and docked to bottom or right
2
+ * ResizablePanel - A bottom-docked panel that can be resized by dragging
3
3
  *
4
4
  * Features:
5
- * - Drag-to-resize from the edge
6
- * - Dock to bottom or right side
7
- * - Collapsible
8
- * - Persists size and dock position to localStorage
5
+ * - Drag-to-resize from the top edge
6
+ * - Collapsible via chevron toggle
7
+ * - Persists size and open state to localStorage
9
8
  */
10
9
 
11
- import { useState, useEffect, useCallback, useRef, type ReactNode, type CSSProperties } from "react";
10
+ import {
11
+ useState,
12
+ useEffect,
13
+ useCallback,
14
+ useRef,
15
+ type ReactNode,
16
+ type CSSProperties,
17
+ } from "react";
12
18
  import { BRAND } from "../../core/index.js";
13
19
  import { ChevronDownIcon } from "./Icons.js";
20
+ import { Box, Stack, Button } from "@fragments-sdk/ui";
14
21
 
15
22
  // Storage key for persisting panel state
16
23
  const STORAGE_KEY = `${BRAND.storagePrefix}panel-state`;
17
24
 
18
25
  interface PanelState {
19
26
  height: number;
20
- width: number;
21
- dock: "bottom" | "right";
22
27
  isOpen: boolean;
23
28
  }
24
29
 
25
30
  const DEFAULT_STATE: PanelState = {
26
- height: 180, // Reduced from 256 to give preview area more space
27
- width: 400,
28
- dock: "bottom",
31
+ height: 180,
29
32
  isOpen: true,
30
33
  };
31
34
 
32
35
  const MIN_HEIGHT = 120;
33
36
  const MAX_HEIGHT = 600;
34
- const MIN_WIDTH = 280;
35
- const MAX_WIDTH = 800;
36
37
 
37
38
  function loadPanelState(): PanelState {
38
39
  try {
@@ -55,7 +56,7 @@ function savePanelState(state: PanelState): void {
55
56
  }
56
57
  }
57
58
 
58
- interface ResizablePanelProps {
59
+ interface ResizablePanelProps extends React.HTMLAttributes<HTMLDivElement> {
59
60
  /** Panel header content (tabs, title, etc.) */
60
61
  header: ReactNode;
61
62
  /** Panel body content */
@@ -71,12 +72,13 @@ export function ResizablePanel({
71
72
  children,
72
73
  visible = true,
73
74
  className,
75
+ ...props
74
76
  }: ResizablePanelProps) {
75
77
  const [state, setState] = useState<PanelState>(loadPanelState);
76
78
  const [isResizing, setIsResizing] = useState(false);
77
79
  const panelRef = useRef<HTMLDivElement>(null);
78
- const startPosRef = useRef({ x: 0, y: 0 });
79
- const startSizeRef = useRef({ width: 0, height: 0 });
80
+ const startYRef = useRef(0);
81
+ const startHeightRef = useRef(0);
80
82
 
81
83
  // Save state changes to localStorage
82
84
  useEffect(() => {
@@ -87,35 +89,21 @@ export function ResizablePanel({
87
89
  (e: React.MouseEvent) => {
88
90
  e.preventDefault();
89
91
  setIsResizing(true);
90
- startPosRef.current = { x: e.clientX, y: e.clientY };
91
- startSizeRef.current = { width: state.width, height: state.height };
92
+ startYRef.current = e.clientY;
93
+ startHeightRef.current = state.height;
92
94
  },
93
- [state.width, state.height]
95
+ [state.height]
94
96
  );
95
97
 
96
98
  const handleMouseMove = useCallback(
97
99
  (e: MouseEvent) => {
98
100
  if (!isResizing) return;
99
101
 
100
- if (state.dock === "bottom") {
101
- // Resize height (drag up = increase, drag down = decrease)
102
- const deltaY = startPosRef.current.y - e.clientY;
103
- const newHeight = Math.max(
104
- MIN_HEIGHT,
105
- Math.min(MAX_HEIGHT, startSizeRef.current.height + deltaY)
106
- );
107
- setState((s) => ({ ...s, height: newHeight }));
108
- } else {
109
- // Resize width (drag left = increase, drag right = decrease)
110
- const deltaX = startPosRef.current.x - e.clientX;
111
- const newWidth = Math.max(
112
- MIN_WIDTH,
113
- Math.min(MAX_WIDTH, startSizeRef.current.width + deltaX)
114
- );
115
- setState((s) => ({ ...s, width: newWidth }));
116
- }
102
+ const deltaY = startYRef.current - e.clientY;
103
+ const newHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, startHeightRef.current + deltaY));
104
+ setState((s) => ({ ...s, height: newHeight }));
117
105
  },
118
- [isResizing, state.dock]
106
+ [isResizing]
119
107
  );
120
108
 
121
109
  const handleMouseUp = useCallback(() => {
@@ -127,7 +115,7 @@ export function ResizablePanel({
127
115
  if (isResizing) {
128
116
  document.addEventListener("mousemove", handleMouseMove);
129
117
  document.addEventListener("mouseup", handleMouseUp);
130
- document.body.style.cursor = state.dock === "bottom" ? "ns-resize" : "ew-resize";
118
+ document.body.style.cursor = "ns-resize";
131
119
  document.body.style.userSelect = "none";
132
120
 
133
121
  return () => {
@@ -137,295 +125,147 @@ export function ResizablePanel({
137
125
  document.body.style.userSelect = "";
138
126
  };
139
127
  }
140
- }, [isResizing, handleMouseMove, handleMouseUp, state.dock]);
128
+ }, [isResizing, handleMouseMove, handleMouseUp]);
141
129
 
142
130
  const toggleOpen = useCallback(() => {
143
131
  setState((s) => ({ ...s, isOpen: !s.isOpen }));
144
132
  }, []);
145
133
 
146
- const toggleDock = useCallback(() => {
147
- setState((s) => ({
148
- ...s,
149
- dock: s.dock === "bottom" ? "right" : "bottom",
150
- }));
151
- }, []);
152
-
153
134
  if (!visible) return null;
154
135
 
155
- const isBottom = state.dock === "bottom";
156
136
  const isOpen = state.isOpen;
157
137
 
158
- // Header height for collapsed state
138
+ // Heights for layout calculation
159
139
  const headerHeight = 40; // h-10 = 2.5rem = 40px
140
+ const grabHandleHeight = 12; // 6px top padding + 4px pill + 2px bottom padding
160
141
 
161
- // Compute container styles
142
+ // Compute container styles — drawer appearance
162
143
  const containerStyle: CSSProperties = {
163
144
  flexShrink: 0,
164
- backgroundColor: 'var(--bg-secondary)',
165
- position: 'relative',
166
- ...(isBottom
167
- ? { borderTop: '1px solid var(--border)' }
168
- : { borderLeft: '1px solid var(--border)' }),
145
+ backgroundColor: "var(--bg-secondary)",
169
146
  // Only transition when NOT resizing (for smooth open/close animations)
170
- // Disable during drag to prevent sluggish feel
171
- transition: isResizing ? 'none' : 'height 150ms ease, width 150ms ease',
172
- ...(isBottom
173
- ? { height: isOpen ? state.height : headerHeight }
174
- : { width: isOpen ? state.width : headerHeight }),
147
+ transition: isResizing ? "none" : "height 300ms ease",
148
+ height: isOpen ? state.height : headerHeight,
175
149
  // Prevent content from being selected during resize
176
150
  ...(isResizing && { pointerEvents: "none" as const }),
151
+ position: "sticky",
152
+ bottom: 0,
153
+ // Drawer visual treatment
154
+ borderTopLeftRadius: "12px",
155
+ borderTopRightRadius: "12px",
156
+ boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08), 0 -1px 4px rgba(0, 0, 0, 0.04)",
157
+ borderTop: "1px solid var(--border)",
177
158
  };
178
159
 
179
- // Resize handle styles
180
- const resizeHandleStyle: CSSProperties = isBottom
181
- ? {
182
- position: 'absolute',
183
- zIndex: 20,
184
- top: '-8px',
185
- left: 0,
186
- right: 0,
187
- height: '16px',
188
- cursor: 'ns-resize',
189
- ...(isResizing && { backgroundColor: 'rgba(var(--color-accent-rgb, 59, 130, 246), 0.2)' }),
190
- }
191
- : {
192
- position: 'absolute',
193
- zIndex: 20,
194
- top: 0,
195
- left: '-8px',
196
- bottom: 0,
197
- width: '16px',
198
- cursor: 'ew-resize',
199
- ...(isResizing && { backgroundColor: 'rgba(var(--color-accent-rgb, 59, 130, 246), 0.2)' }),
200
- };
160
+ // Resize handle styles — extends above panel for easier grabbing
161
+ const resizeHandleStyle: CSSProperties = {
162
+ position: "absolute",
163
+ zIndex: 20,
164
+ top: "-8px",
165
+ left: 0,
166
+ right: 0,
167
+ height: "16px",
168
+ cursor: "ns-resize",
169
+ ...(isResizing && { backgroundColor: "rgba(var(--color-accent-rgb, 59, 130, 246), 0.2)" }),
170
+ };
201
171
 
202
- // Visual indicator styles
203
- const indicatorStyle: CSSProperties = isBottom
204
- ? {
205
- position: 'absolute',
206
- backgroundColor: 'var(--color-accent)',
207
- opacity: 0,
208
- left: '50%',
209
- top: '50%',
210
- transform: 'translate(-50%, -50%)',
211
- width: '48px',
212
- height: '4px',
213
- borderRadius: '9999px',
214
- transition: 'opacity 150ms ease',
215
- }
216
- : {
217
- position: 'absolute',
218
- backgroundColor: 'var(--color-accent)',
219
- opacity: 0,
220
- top: '50%',
221
- left: '50%',
222
- transform: 'translate(-50%, -50%)',
223
- width: '4px',
224
- height: '48px',
225
- borderRadius: '9999px',
226
- transition: 'opacity 150ms ease',
227
- };
172
+ // Grab handle pill — always visible in drawer style
173
+ const grabHandleStyle: CSSProperties = {
174
+ width: "100%",
175
+ display: "flex",
176
+ justifyContent: "center",
177
+ padding: "6px 0 2px",
178
+ cursor: "ns-resize",
179
+ };
180
+
181
+ const grabPillStyle: CSSProperties = {
182
+ width: "36px",
183
+ height: "4px",
184
+ borderRadius: "9999px",
185
+ backgroundColor: "var(--text-muted)",
186
+ opacity: isResizing ? 0.8 : 0.3,
187
+ transition: "opacity 150ms ease",
188
+ };
228
189
 
229
190
  // Chevron icon rotation
230
191
  const chevronStyle: CSSProperties = {
231
- width: '16px',
232
- height: '16px',
233
- transition: 'transform 150ms ease',
234
- ...(!isOpen && isBottom && { transform: 'rotate(180deg)' }),
235
- ...(!isOpen && !isBottom && { transform: 'rotate(-90deg)' }),
236
- ...(isOpen && !isBottom && { transform: 'rotate(90deg)' }),
192
+ width: "16px",
193
+ height: "16px",
194
+ transition: "transform 150ms ease",
195
+ ...(!isOpen && { transform: "rotate(180deg)" }),
237
196
  };
238
197
 
239
198
  return (
240
- <div
241
- ref={panelRef}
242
- className={className}
243
- style={containerStyle}
244
- >
199
+ <div ref={panelRef} className={className} style={containerStyle} {...props}>
245
200
  {/* Full-viewport overlay during resize - prevents iframes from capturing events */}
246
201
  {isResizing && (
247
202
  <div
248
203
  style={{
249
- position: 'fixed',
204
+ position: "fixed",
250
205
  inset: 0,
251
206
  zIndex: 50,
252
- cursor: isBottom ? "ns-resize" : "ew-resize",
207
+ cursor: "ns-resize",
253
208
  // Must explicitly enable pointer events since parent has pointerEvents: none
254
209
  pointerEvents: "auto",
255
210
  }}
256
211
  />
257
212
  )}
258
213
 
259
- {/* Resize Handle - extends above/left of panel for easier grabbing */}
214
+ {/* Resize Handle - extends above panel for easier grabbing */}
260
215
  {isOpen && (
261
216
  <div
262
217
  style={resizeHandleStyle}
263
218
  onMouseDown={handleMouseDown}
219
+ />
220
+ )}
221
+
222
+ {/* Grab handle pill — always visible drawer indicator */}
223
+ {isOpen && (
224
+ <div
225
+ style={grabHandleStyle}
226
+ onMouseDown={handleMouseDown}
264
227
  onMouseEnter={(e) => {
265
- const indicator = e.currentTarget.querySelector('[data-resize-indicator]') as HTMLElement;
266
- if (indicator) indicator.style.opacity = '0.5';
228
+ const pill = e.currentTarget.querySelector("[data-grab-pill]") as HTMLElement;
229
+ if (pill) pill.style.opacity = "0.6";
267
230
  }}
268
231
  onMouseLeave={(e) => {
269
- const indicator = e.currentTarget.querySelector('[data-resize-indicator]') as HTMLElement;
270
- if (indicator) indicator.style.opacity = '0';
232
+ const pill = e.currentTarget.querySelector("[data-grab-pill]") as HTMLElement;
233
+ if (pill) pill.style.opacity = isResizing ? "0.8" : "0.3";
271
234
  }}
272
235
  >
273
- {/* Visual indicator - shows on hover */}
274
- <div data-resize-indicator style={indicatorStyle} />
236
+ <div data-grab-pill style={grabPillStyle} />
275
237
  </div>
276
238
  )}
277
239
 
278
240
  {/* Panel Header */}
279
- <div
280
- style={{
281
- display: 'flex',
282
- alignItems: 'center',
283
- justifyContent: 'space-between',
284
- padding: '0 16px',
285
- height: '40px',
286
- borderBottom: '1px solid var(--border-subtle)',
287
- }}
288
- >
289
- <div style={{
290
- display: 'flex',
291
- alignItems: 'center',
292
- gap: '4px',
293
- flex: 1,
294
- overflow: 'hidden',
295
- }}>
241
+ <Stack direction="row" align="center" justify="between" style={{ padding: '0 16px', height: '40px', borderBottom: '1px solid var(--border-subtle)' }}>
242
+ <Stack direction="row" align="center" gap="xs" style={{ flex: 1, overflow: 'hidden' }}>
296
243
  {header}
297
- </div>
298
- <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
299
- {/* Dock toggle button */}
300
- <button
301
- onClick={toggleDock}
302
- style={{
303
- padding: '4px',
304
- color: 'var(--text-tertiary)',
305
- backgroundColor: 'transparent',
306
- border: 'none',
307
- borderRadius: '4px',
308
- cursor: 'pointer',
309
- display: 'inline-flex',
310
- transition: 'color 150ms ease, background-color 150ms ease',
311
- }}
312
- onMouseEnter={(e) => {
313
- e.currentTarget.style.color = 'var(--text-primary)';
314
- e.currentTarget.style.backgroundColor = 'var(--bg-hover)';
315
- }}
316
- onMouseLeave={(e) => {
317
- e.currentTarget.style.color = 'var(--text-tertiary)';
318
- e.currentTarget.style.backgroundColor = 'transparent';
319
- }}
320
- title={isBottom ? "Dock to right" : "Dock to bottom"}
321
- >
322
- <span style={{ display: 'inline-flex', width: '16px', height: '16px' }}>
323
- <DockIcon dock={state.dock} />
324
- </span>
325
- </button>
244
+ </Stack>
245
+ <Stack direction="row" align="center" gap="xs">
326
246
  {/* Collapse toggle button */}
327
- <button
247
+ <Button
248
+ variant="ghost"
249
+ size="sm"
250
+ icon
328
251
  onClick={toggleOpen}
329
- style={{
330
- padding: '4px',
331
- color: 'var(--text-tertiary)',
332
- backgroundColor: 'transparent',
333
- border: 'none',
334
- borderRadius: '4px',
335
- cursor: 'pointer',
336
- display: 'inline-flex',
337
- transition: 'color 150ms ease, background-color 150ms ease',
338
- }}
339
- onMouseEnter={(e) => {
340
- e.currentTarget.style.color = 'var(--text-primary)';
341
- e.currentTarget.style.backgroundColor = 'var(--bg-hover)';
342
- }}
343
- onMouseLeave={(e) => {
344
- e.currentTarget.style.color = 'var(--text-tertiary)';
345
- e.currentTarget.style.backgroundColor = 'transparent';
346
- }}
347
- title={isOpen ? "Collapse panel" : "Expand panel"}
252
+ aria-label={isOpen ? "Collapse panel" : "Expand panel"}
348
253
  >
349
254
  <span style={chevronStyle}>
350
255
  <ChevronDownIcon />
351
256
  </span>
352
- </button>
353
- </div>
354
- </div>
257
+ </Button>
258
+ </Stack>
259
+ </Stack>
355
260
 
356
261
  {/* Panel Content */}
357
262
  {isOpen && (
358
- <div
359
- style={{
360
- overflow: 'auto',
361
- height: isBottom ? `calc(100% - ${headerHeight}px)` : "100%",
362
- width: isBottom ? "100%" : `calc(100% - ${headerHeight}px)`,
363
- }}
364
- >
263
+ <Box overflow="auto" style={{ height: `calc(100% - ${headerHeight}px)`, width: '100%' }}>
365
264
  {children}
366
- </div>
265
+ </Box>
367
266
  )}
368
267
  </div>
369
268
  );
370
269
  }
371
270
 
372
- /**
373
- * Hook to get panel dock position for layout purposes
374
- */
375
- export function usePanelDock(): "bottom" | "right" {
376
- const [dock, setDock] = useState<"bottom" | "right">(() => {
377
- const state = loadPanelState();
378
- return state.dock;
379
- });
380
-
381
- useEffect(() => {
382
- const handleStorage = () => {
383
- const state = loadPanelState();
384
- setDock(state.dock);
385
- };
386
-
387
- // Listen for storage changes (from other tabs)
388
- window.addEventListener("storage", handleStorage);
389
-
390
- // Also poll for changes since localStorage events don't fire in same tab
391
- const interval = setInterval(handleStorage, 500);
392
-
393
- return () => {
394
- window.removeEventListener("storage", handleStorage);
395
- clearInterval(interval);
396
- };
397
- }, []);
398
-
399
- return dock;
400
- }
401
-
402
- // Icon for dock position toggle
403
- function DockIcon({ dock }: { dock: "bottom" | "right" }) {
404
- const svgStyle: CSSProperties = { width: '100%', height: '100%' };
405
-
406
- if (dock === "bottom") {
407
- // Show icon indicating "dock to right" action
408
- return (
409
- <svg style={svgStyle} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5">
410
- {/* Outer frame */}
411
- <rect x="1" y="2" width="14" height="12" rx="1" />
412
- {/* Right panel indicator */}
413
- <line x1="10" y1="2" x2="10" y2="14" />
414
- <line x1="10" y1="8" x2="15" y2="8" strokeDasharray="2 1" />
415
- </svg>
416
- );
417
- }
418
-
419
- // Show icon indicating "dock to bottom" action
420
- return (
421
- <svg style={svgStyle} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5">
422
- {/* Outer frame */}
423
- <rect x="1" y="2" width="14" height="12" rx="1" />
424
- {/* Bottom panel indicator */}
425
- <line x1="1" y1="10" x2="15" y2="10" />
426
- <line x1="8" y1="10" x2="8" y2="14" strokeDasharray="2 1" />
427
- </svg>
428
- );
429
- }
430
-
431
271
  export default ResizablePanel;
@@ -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-sdk/ui';
3
+ import { Sidebar, Text } from '@fragments-sdk/ui';
4
4
 
5
5
  interface RightSidebarProps {
6
6
  fragment: FragmentDefinition;
@@ -60,15 +60,9 @@ export function RightSidebar({ fragment }: RightSidebarProps) {
60
60
  return (
61
61
  <Sidebar position="right" collapsible="none" style={{ padding: '8px 0' }}>
62
62
  <Sidebar.Header>
63
- <span style={{
64
- fontSize: '11px',
65
- fontWeight: 500,
66
- color: 'var(--text-tertiary)',
67
- textTransform: 'uppercase',
68
- letterSpacing: '0.05em',
69
- }}>
63
+ <Text size="2xs" weight="medium" color="tertiary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
70
64
  On this page
71
- </span>
65
+ </Text>
72
66
  </Sidebar.Header>
73
67
  <Sidebar.Nav aria-label="On this page">
74
68
  {tocItems.map((item) => {
@@ -4,7 +4,7 @@
4
4
  * Shows animated placeholders while the app is loading.
5
5
  */
6
6
 
7
- import { Skeleton, Loading } from '@fragments-sdk/ui';
7
+ import { Skeleton, Loading, Stack, Box } from '@fragments-sdk/ui';
8
8
 
9
9
  /**
10
10
  * Full app skeleton shown during initial load
@@ -34,18 +34,18 @@ export function AppSkeleton() {
34
34
  backgroundColor: 'var(--bg-primary)',
35
35
  }}
36
36
  >
37
- <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
37
+ <Stack direction="row" align="center" gap="sm">
38
38
  <Skeleton.Circle size={20} />
39
39
  <Skeleton variant="text" width={96} />
40
40
  <Skeleton variant="text" width={64} />
41
- </div>
41
+ </Stack>
42
42
  <Skeleton variant="rect" width={240} height={32} radius="md" />
43
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
43
+ <Stack direction="row" align="center" gap="sm">
44
44
  <Skeleton variant="rect" width={64} height={28} radius="md" />
45
45
  <Skeleton.Circle size={20} />
46
46
  <Skeleton.Circle size={20} />
47
47
  <Skeleton.Circle size={20} />
48
- </div>
48
+ </Stack>
49
49
  </div>
50
50
 
51
51
  <aside
@@ -58,10 +58,10 @@ export function AppSkeleton() {
58
58
  minHeight: 0,
59
59
  }}
60
60
  >
61
- <div style={{ padding: '12px 16px' }}>
61
+ <Box paddingX="md" paddingY="sm">
62
62
  <Skeleton variant="rect" height={32} radius="md" />
63
- </div>
64
- <div style={{ padding: '0 12px', display: 'flex', flexDirection: 'column', gap: '12px', overflow: 'hidden' }}>
63
+ </Box>
64
+ <Stack gap="sm" style={{ padding: '0 12px', overflow: 'hidden' }}>
65
65
  <Skeleton variant="text" width={68} />
66
66
  <Skeleton variant="rect" height={30} radius="md" />
67
67
  <Skeleton variant="rect" height={30} radius="md" />
@@ -70,7 +70,7 @@ export function AppSkeleton() {
70
70
  <Skeleton variant="rect" height={30} radius="md" />
71
71
  <Skeleton variant="rect" height={30} radius="md" />
72
72
  <Skeleton variant="rect" width="78%" height={30} radius="md" />
73
- </div>
73
+ </Stack>
74
74
  <div style={{ marginTop: 'auto', padding: '12px 16px', borderTop: '1px solid var(--border-subtle)' }}>
75
75
  <Skeleton variant="text" width={92} />
76
76
  </div>
@@ -131,7 +131,7 @@ export function AppSkeleton() {
131
131
  padding: '16px 14px',
132
132
  }}
133
133
  >
134
- <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
134
+ <Stack gap="sm">
135
135
  <Skeleton variant="text" width={92} />
136
136
  <Skeleton variant="text" width={64} />
137
137
  <Skeleton variant="text" width={48} />
@@ -141,7 +141,7 @@ export function AppSkeleton() {
141
141
  <Skeleton variant="rect" width="100%" height={26} radius="md" />
142
142
  <Skeleton variant="rect" width="84%" height={26} radius="md" />
143
143
  <Skeleton variant="rect" width="70%" height={26} radius="md" />
144
- </div>
144
+ </Stack>
145
145
  </aside>
146
146
  </div>
147
147
  );
@@ -152,9 +152,9 @@ export function AppSkeleton() {
152
152
  */
153
153
  export function PreviewSkeleton() {
154
154
  return (
155
- <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
155
+ <Stack direction="row" align="center" justify="center" style={{ padding: '32px' }}>
156
156
  <Loading size="md" />
157
- </div>
157
+ </Stack>
158
158
  );
159
159
  }
160
160