@fragments-sdk/cli 0.5.2 → 0.7.0

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 (124) hide show
  1. package/dist/bin.js +996 -79
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
  4. package/dist/chunk-6JBGU74P.js.map +1 -0
  5. package/dist/chunk-7OPWMLOE.js +1625 -0
  6. package/dist/chunk-7OPWMLOE.js.map +1 -0
  7. package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
  8. package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
  9. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
  12. package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
  13. package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
  14. package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
  15. package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +15 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
  20. package/dist/mcp-bin.js +8 -220
  21. package/dist/mcp-bin.js.map +1 -1
  22. package/dist/scan-WY23TJCP.js +12 -0
  23. package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
  24. package/dist/static-viewer-GBR7YNF3.js +12 -0
  25. package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
  26. package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
  27. package/dist/viewer-SUFOISZM.js +1822 -0
  28. package/dist/viewer-SUFOISZM.js.map +1 -0
  29. package/package.json +6 -5
  30. package/src/bin.ts +31 -0
  31. package/src/build.ts +147 -13
  32. package/src/cli-commands.ts +18 -0
  33. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  34. package/src/commands/a11y-report.ts +625 -0
  35. package/src/commands/a11y.ts +168 -14
  36. package/src/commands/build.ts +16 -0
  37. package/src/commands/graph.ts +274 -0
  38. package/src/core/auto-props.ts +464 -0
  39. package/src/core/composition.ts +64 -1
  40. package/src/core/graph-extractor.test.ts +542 -0
  41. package/src/core/graph-extractor.ts +601 -0
  42. package/src/core/importAnalyzer.ts +5 -0
  43. package/src/core/schema.ts +2 -0
  44. package/src/core/types.ts +3 -1
  45. package/src/index.ts +4 -0
  46. package/src/mcp/server.ts +13 -220
  47. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  48. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  49. package/src/theme/contrast.test.ts +331 -0
  50. package/src/theme/contrast.ts +246 -0
  51. package/src/theme/generator.ts +213 -1
  52. package/src/theme/index.ts +16 -0
  53. package/src/theme/types.ts +51 -0
  54. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  55. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  56. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  57. package/src/viewer/components/ActionCapture.tsx +1 -1
  58. package/src/viewer/components/ActionsPanel.tsx +142 -183
  59. package/src/viewer/components/App.tsx +276 -183
  60. package/src/viewer/components/BottomPanel.tsx +40 -80
  61. package/src/viewer/components/CodePanel.tsx +9 -87
  62. package/src/viewer/components/CommandPalette.tsx +117 -74
  63. package/src/viewer/components/ComponentGraph.tsx +143 -126
  64. package/src/viewer/components/ComponentHeader.tsx +46 -43
  65. package/src/viewer/components/ContractPanel.tsx +124 -117
  66. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  67. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  68. package/src/viewer/components/FragmentEditor.tsx +126 -63
  69. package/src/viewer/components/HealthDashboard.tsx +146 -171
  70. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  71. package/src/viewer/components/Icons.tsx +151 -98
  72. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  73. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  74. package/src/viewer/components/IsolatedRender.tsx +12 -6
  75. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  76. package/src/viewer/components/LandingPage.tsx +285 -305
  77. package/src/viewer/components/Layout.tsx +12 -10
  78. package/src/viewer/components/LeftSidebar.tsx +103 -155
  79. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  80. package/src/viewer/components/PreviewArea.tsx +113 -44
  81. package/src/viewer/components/PreviewFrameHost.tsx +36 -6
  82. package/src/viewer/components/PreviewPane.tsx +2 -3
  83. package/src/viewer/components/PreviewToolbar.tsx +109 -105
  84. package/src/viewer/components/PropsEditor.tsx +154 -74
  85. package/src/viewer/components/PropsTable.tsx +95 -82
  86. package/src/viewer/components/RelationsSection.tsx +71 -40
  87. package/src/viewer/components/ResizablePanel.tsx +158 -55
  88. package/src/viewer/components/RightSidebar.tsx +46 -56
  89. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  90. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  91. package/src/viewer/components/StoryRenderer.tsx +4 -11
  92. package/src/viewer/components/Toast.tsx +3 -67
  93. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  94. package/src/viewer/components/UsageSection.tsx +26 -26
  95. package/src/viewer/components/VariantMatrix.tsx +140 -47
  96. package/src/viewer/components/VariantTabs.tsx +24 -68
  97. package/src/viewer/components/ViewportSelector.tsx +121 -114
  98. package/src/viewer/constants/ui.ts +23 -22
  99. package/src/viewer/entry.tsx +8 -3
  100. package/src/viewer/index.ts +3 -6
  101. package/src/viewer/preview-frame.html +43 -18
  102. package/src/viewer/server.ts +7 -16
  103. package/src/viewer/styles/globals.css +46 -85
  104. package/src/viewer/utils/a11y-fixes.ts +53 -30
  105. package/dist/chunk-ICAIQ57V.js.map +0 -1
  106. package/dist/chunk-U4GQ2JTD.js +0 -832
  107. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  108. package/dist/scan-ESEXV7LF.js +0 -12
  109. package/dist/static-viewer-O37MJ5B6.js +0 -12
  110. package/dist/viewer-YDGFDTK5.js +0 -11104
  111. package/dist/viewer-YDGFDTK5.js.map +0 -1
  112. package/src/viewer/postcss.config.js +0 -6
  113. package/src/viewer/tailwind.config.js +0 -37
  114. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  115. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  116. /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
  117. /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
  118. /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
  119. /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
  120. /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
  121. /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
  122. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
  123. /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
  124. /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
@@ -1,22 +1,24 @@
1
1
  import type { ReactNode } from 'react';
2
+ import { AppShell } from '@fragments/ui';
2
3
 
3
4
  interface LayoutProps {
4
5
  leftSidebar: ReactNode;
6
+ header: ReactNode;
5
7
  children: ReactNode;
6
8
  }
7
9
 
8
- export function Layout({ leftSidebar, children }: LayoutProps) {
10
+ export function Layout({ leftSidebar, header, children }: LayoutProps) {
9
11
  return (
10
- <div className="flex h-screen overflow-hidden bg-[--bg-primary]">
11
- {/* Left Sidebar */}
12
- <aside className="hidden md:flex md:w-56 lg:w-64 flex-col border-r border-[--border] bg-[--bg-secondary] flex-shrink-0">
12
+ <AppShell layout="inset">
13
+ <AppShell.Header>
14
+ {header}
15
+ </AppShell.Header>
16
+ <AppShell.Sidebar width="256px" collapsible="icon">
13
17
  {leftSidebar}
14
- </aside>
15
-
16
- {/* Main Content - Full width */}
17
- <main className="flex-1 flex flex-col overflow-hidden min-w-0">
18
+ </AppShell.Sidebar>
19
+ <AppShell.Main padding="none">
18
20
  {children}
19
- </main>
20
- </div>
21
+ </AppShell.Main>
22
+ </AppShell>
21
23
  );
22
24
  }
@@ -2,8 +2,7 @@ import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
2
2
  import type { SegmentDefinition } from '../../core/index.js';
3
3
  import { BRAND } from '../../core/index.js';
4
4
  import { useTheme } from './ThemeProvider.js';
5
- import clsx from 'clsx';
6
- import { SearchIcon, SunIcon, MoonIcon, DashboardIcon } from './Icons.js';
5
+ import { Sidebar, useSidebar, Badge, Text, ThemeToggle } from '@fragments/ui';
7
6
 
8
7
  // Fuzzy matching utility
9
8
  interface FuzzyMatch {
@@ -105,7 +104,7 @@ function HighlightedText({ text, indices }: { text: string; indices: number[] })
105
104
  result.push(text.slice(lastIndex, matchIndex));
106
105
  }
107
106
  result.push(
108
- <span key={matchIndex} className="text-primary font-medium">
107
+ <span key={matchIndex} style={{ color: 'var(--text-primary)', fontWeight: 500 }}>
109
108
  {text[matchIndex]}
110
109
  </span>
111
110
  );
@@ -122,19 +121,19 @@ function HighlightedText({ text, indices }: { text: string; indices: number[] })
122
121
  interface LeftSidebarProps {
123
122
  segments: Array<{ path: string; segment: SegmentDefinition }>;
124
123
  activeSegment: string | null;
124
+ searchQuery: string;
125
125
  onSelect: (path: string) => void;
126
126
  showHealth?: boolean;
127
127
  onHealthClick?: () => void;
128
128
  }
129
129
 
130
- export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onHealthClick }: LeftSidebarProps) {
131
- const [search, setSearch] = useState('');
130
+ export function LeftSidebar({ segments, activeSegment, searchQuery, onSelect, showHealth, onHealthClick }: LeftSidebarProps) {
132
131
  const [focusedIndex, setFocusedIndex] = useState(-1);
133
- const { theme, setTheme, resolvedTheme } = useTheme();
134
- const searchInputRef = useRef<HTMLInputElement>(null);
135
- const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
132
+ const { setTheme, resolvedTheme } = useTheme();
133
+ const { isMobile, setOpen } = useSidebar();
134
+ const navRef = useRef<HTMLDivElement>(null);
136
135
 
137
- const debouncedSearch = useDebounce(search, 150);
136
+ const debouncedSearch = useDebounce(searchQuery, 150);
138
137
 
139
138
  const searchResults = useMemo(() => {
140
139
  if (!debouncedSearch) return null;
@@ -175,11 +174,6 @@ export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onH
175
174
  return groups;
176
175
  }, [segments, searchResults]);
177
176
 
178
- const toggleTheme = () => {
179
- // Simple toggle between light and dark
180
- setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
181
- };
182
-
183
177
  const flatItems = useMemo(() => {
184
178
  const items: Array<{ path: string; segment: SegmentDefinition }> = [];
185
179
  const sortedEntries = Object.entries(grouped).sort(([a], [b]) =>
@@ -196,196 +190,150 @@ export function LeftSidebar({ segments, activeSegment, onSelect, showHealth, onH
196
190
  return items;
197
191
  }, [grouped]);
198
192
 
193
+ const keyboardItems = useMemo(() => {
194
+ const componentItems = flatItems.map((item) => ({ type: 'component' as const, path: item.path }));
195
+ if (!onHealthClick) return componentItems;
196
+ return [{ type: 'dashboard' as const }, ...componentItems];
197
+ }, [flatItems, onHealthClick]);
198
+
199
199
  useEffect(() => {
200
- if (focusedIndex >= 0 && focusedIndex < flatItems.length) {
201
- itemRefs.current.get(focusedIndex)?.focus();
200
+ if (focusedIndex >= 0 && focusedIndex < keyboardItems.length && navRef.current) {
201
+ // Query all nav item buttons rendered by Sidebar.Item inside the nav
202
+ const buttons = navRef.current.querySelectorAll<HTMLButtonElement>('li > button[type="button"]');
203
+ if (buttons[focusedIndex]) {
204
+ buttons[focusedIndex].focus();
205
+ }
202
206
  }
203
- }, [focusedIndex, flatItems.length]);
207
+ }, [focusedIndex, keyboardItems.length]);
204
208
 
205
209
  useEffect(() => {
206
210
  setFocusedIndex(-1);
207
- }, [search]);
211
+ }, [searchQuery]);
208
212
 
209
- const handleKeyDown = useCallback((e: KeyboardEvent) => {
210
- const target = e.target as HTMLElement;
211
- if ((target.tagName === 'INPUT' && target !== searchInputRef.current) ||
212
- target.tagName === 'TEXTAREA' || target.isContentEditable) {
213
- return;
213
+ const handleSelect = (path: string) => {
214
+ onSelect(path);
215
+ if (isMobile) {
216
+ setOpen(false);
214
217
  }
218
+ };
215
219
 
216
- if ((e.key === '/' && !e.metaKey && !e.ctrlKey) ||
217
- (e.key === 'k' && (e.metaKey || e.ctrlKey))) {
218
- e.preventDefault();
219
- searchInputRef.current?.focus();
220
- searchInputRef.current?.select();
220
+ const handleHealthClick = () => {
221
+ onHealthClick?.();
222
+ if (isMobile) {
223
+ setOpen(false);
224
+ }
225
+ };
226
+
227
+ const handleKeyDown = useCallback((e: KeyboardEvent) => {
228
+ const target = e.target as HTMLElement;
229
+ if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
221
230
  return;
222
231
  }
223
232
 
224
233
  if (e.key === 'Escape') {
225
- if (document.activeElement === searchInputRef.current) {
226
- setSearch('');
227
- searchInputRef.current?.blur();
228
- if (flatItems.length > 0) setFocusedIndex(0);
229
- } else {
230
- setSearch('');
231
- setFocusedIndex(-1);
232
- }
234
+ setFocusedIndex(-1);
233
235
  return;
234
236
  }
235
237
 
236
- if (document.activeElement === searchInputRef.current) return;
237
-
238
238
  if (e.key === 'ArrowDown') {
239
239
  e.preventDefault();
240
- setFocusedIndex(prev => (prev + 1) >= flatItems.length ? 0 : prev + 1);
240
+ setFocusedIndex(prev => (prev + 1) >= keyboardItems.length ? 0 : prev + 1);
241
241
  } else if (e.key === 'ArrowUp') {
242
242
  e.preventDefault();
243
- setFocusedIndex(prev => (prev - 1) < 0 ? flatItems.length - 1 : prev - 1);
244
- } else if (e.key === 'Enter' && focusedIndex >= 0 && focusedIndex < flatItems.length) {
243
+ setFocusedIndex(prev => (prev - 1) < 0 ? keyboardItems.length - 1 : prev - 1);
244
+ } else if (e.key === 'Enter' && focusedIndex >= 0 && focusedIndex < keyboardItems.length) {
245
245
  e.preventDefault();
246
- onSelect(flatItems[focusedIndex].path);
246
+ const currentItem = keyboardItems[focusedIndex];
247
+ if (currentItem.type === 'dashboard') {
248
+ handleHealthClick();
249
+ } else {
250
+ handleSelect(currentItem.path);
251
+ }
247
252
  }
248
- }, [flatItems, focusedIndex, onSelect]);
253
+ }, [keyboardItems, focusedIndex, handleHealthClick, handleSelect]);
249
254
 
250
255
  useEffect(() => {
251
256
  document.addEventListener('keydown', handleKeyDown);
252
257
  return () => document.removeEventListener('keydown', handleKeyDown);
253
258
  }, [handleKeyDown]);
254
259
 
255
- const setItemRef = useCallback((index: number, el: HTMLButtonElement | null) => {
256
- if (el) itemRefs.current.set(index, el);
257
- else itemRefs.current.delete(index);
258
- }, []);
259
-
260
- let globalIndex = 0;
261
260
  const sortedEntries = Object.entries(grouped).sort(([a], [b]) =>
262
261
  a.toLowerCase().localeCompare(b.toLowerCase())
263
262
  );
264
263
 
265
264
  return (
266
- <div className="flex flex-col h-full bg-[--bg-secondary]">
267
- {/* Header - matches toolbar height (py-2 + border) */}
268
- <div className="flex items-center justify-between px-4 py-2 border-b border-[--border]">
269
- <span className="text-sm font-medium text-primary">{BRAND.name}</span>
270
- <button
271
- onClick={toggleTheme}
272
- className={clsx(
273
- 'p-1.5 rounded-md',
274
- 'text-tertiary hover:text-secondary hover:bg-[--bg-hover]',
275
- 'focus:outline-none'
276
- )}
277
- title={`Theme: ${resolvedTheme}`}
278
- >
279
- {resolvedTheme === 'dark' ? (
280
- <MoonIcon className="w-4 h-4" />
281
- ) : (
282
- <SunIcon className="w-4 h-4" />
283
- )}
284
- </button>
285
- </div>
286
-
287
- {/* Search */}
288
- <div className="px-3 py-3">
289
- <div className="relative">
290
- <SearchIcon className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-tertiary pointer-events-none" />
291
- <input
292
- ref={searchInputRef}
293
- type="text"
294
- placeholder="Search"
295
- value={search}
296
- onChange={(e) => setSearch(e.target.value)}
297
- className={clsx(
298
- 'w-full pl-8 pr-3 py-1.5 text-sm rounded-md',
299
- 'bg-[--bg-primary] text-primary placeholder:text-tertiary',
300
- 'border border-[--border-subtle]',
301
- 'focus:outline-none focus:border-[--border-strong]'
302
- )}
303
- />
304
- </div>
305
- </div>
306
-
307
- {/* Dashboard Link */}
308
- {onHealthClick && (
309
- <div className="px-2 pb-3">
310
- <button
311
- onClick={onHealthClick}
312
- className={clsx(
313
- 'w-full flex items-center gap-2 px-2 py-2 rounded-md text-sm',
314
- 'focus:outline-none',
315
- showHealth
316
- ? 'bg-[--bg-hover] text-primary'
317
- : 'text-secondary hover:bg-[--bg-hover] hover:text-primary'
318
- )}
319
- >
320
- <DashboardIcon className="w-4 h-4" />
321
- <span>Dashboard</span>
322
- </button>
265
+ <>
266
+ {/* Header */}
267
+ <Sidebar.Header>
268
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
269
+ <Text size="sm" weight="medium">{BRAND.name}</Text>
270
+ <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
271
+ <ThemeToggle
272
+ size="sm"
273
+ value={resolvedTheme}
274
+ onValueChange={(value) => setTheme(value)}
275
+ aria-label={`Theme: ${resolvedTheme}`}
276
+ />
277
+ <Sidebar.CollapseToggle />
278
+ </div>
323
279
  </div>
324
- )}
280
+ </Sidebar.Header>
325
281
 
326
282
  {/* Component list */}
327
- <nav className="flex-1 overflow-y-auto px-2 pb-4">
328
- {sortedEntries.map(([category, items]) => {
329
- const sortedItems = [...items].sort((a, b) =>
330
- a.segment.meta.name.toLowerCase().localeCompare(b.segment.meta.name.toLowerCase())
331
- );
332
-
333
- return (
334
- <div key={category} className="mb-1">
335
- {/* Category divider */}
336
- <div className="px-2 py-1.5 text-xs font-medium text-tertiary mt-2 first:mt-0">
337
- {category}
338
- </div>
339
-
340
- {/* Items */}
341
- <div className="space-y-0.5">
283
+ <div ref={navRef} style={{ flex: 1, minHeight: 0, display: 'flex', overflow: 'hidden' }}>
284
+ <Sidebar.Nav aria-label="Components">
285
+ {onHealthClick && (
286
+ <Sidebar.Section>
287
+ <Sidebar.Item
288
+ active={!!showHealth}
289
+ onClick={handleHealthClick}
290
+ >
291
+ Dashboard
292
+ </Sidebar.Item>
293
+ </Sidebar.Section>
294
+ )}
295
+
296
+ {sortedEntries.map(([category, items]) => {
297
+ const sortedItems = [...items]
298
+ .filter(item => item.segment?.meta?.name)
299
+ .sort((a, b) =>
300
+ a.segment.meta.name.toLowerCase().localeCompare(b.segment.meta.name.toLowerCase())
301
+ );
302
+
303
+ return (
304
+ <Sidebar.Section key={category} label={category}>
342
305
  {sortedItems.map((item) => {
343
- const isActive = activeSegment === item.path;
344
- const currentIndex = globalIndex++;
345
- const isFocused = focusedIndex === currentIndex;
346
306
  const nameIndices = highlightMap.get(item.path) || [];
347
307
 
348
308
  return (
349
- <button
309
+ <Sidebar.Item
350
310
  key={item.path}
351
- ref={(el) => setItemRef(currentIndex, el)}
352
- onClick={() => onSelect(item.path)}
353
- onFocus={() => setFocusedIndex(currentIndex)}
354
- className={clsx(
355
- 'w-full text-left px-2 py-1.5 rounded-md text-sm',
356
- 'focus:outline-none',
357
- isActive
358
- ? 'bg-[--bg-hover] text-primary'
359
- : isFocused
360
- ? 'bg-[--bg-hover] text-primary'
361
- : 'text-secondary hover:bg-[--bg-hover] hover:text-primary'
362
- )}
311
+ active={activeSegment === item.path}
312
+ onClick={() => handleSelect(item.path)}
363
313
  >
364
314
  <HighlightedText
365
315
  text={item.segment.meta.name}
366
316
  indices={nameIndices}
367
317
  />
368
- </button>
318
+ </Sidebar.Item>
369
319
  );
370
320
  })}
371
- </div>
372
- </div>
373
- );
374
- })}
321
+ </Sidebar.Section>
322
+ );
323
+ })}
375
324
 
376
- {Object.keys(grouped).length === 0 && (
377
- <div className="px-2 py-8 text-center text-tertiary text-sm">
378
- No results
379
- </div>
380
- )}
381
- </nav>
325
+ {Object.keys(grouped).length === 0 && (
326
+ <div style={{ padding: '8px 32px', textAlign: 'center' }}>
327
+ <Text size="sm" color="tertiary">No results</Text>
328
+ </div>
329
+ )}
330
+ </Sidebar.Nav>
331
+ </div>
382
332
 
383
333
  {/* Footer */}
384
- <div className="px-4 py-3 border-t border-[--border-subtle]">
385
- <div className="text-xs text-tertiary">
386
- {segments.length} components
387
- </div>
388
- </div>
389
- </div>
334
+ <Sidebar.Footer>
335
+ <Badge size="sm">{segments.length} components</Badge>
336
+ </Sidebar.Footer>
337
+ </>
390
338
  );
391
339
  }