@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.
- package/dist/bin.js +996 -79
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
- package/dist/chunk-6JBGU74P.js.map +1 -0
- package/dist/chunk-7OPWMLOE.js +1625 -0
- package/dist/chunk-7OPWMLOE.js.map +1 -0
- package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
- package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
- package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
- package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
- package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
- package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
- package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
- package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
- package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
- package/dist/mcp-bin.js +8 -220
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-WY23TJCP.js +12 -0
- package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
- package/dist/static-viewer-GBR7YNF3.js +12 -0
- package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
- package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
- package/dist/viewer-SUFOISZM.js +1822 -0
- package/dist/viewer-SUFOISZM.js.map +1 -0
- package/package.json +6 -5
- package/src/bin.ts +31 -0
- package/src/build.ts +147 -13
- package/src/cli-commands.ts +18 -0
- package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
- package/src/commands/a11y-report.ts +625 -0
- package/src/commands/a11y.ts +168 -14
- package/src/commands/build.ts +16 -0
- package/src/commands/graph.ts +274 -0
- package/src/core/auto-props.ts +464 -0
- package/src/core/composition.ts +64 -1
- package/src/core/graph-extractor.test.ts +542 -0
- package/src/core/graph-extractor.ts +601 -0
- package/src/core/importAnalyzer.ts +5 -0
- package/src/core/schema.ts +2 -0
- package/src/core/types.ts +3 -1
- package/src/index.ts +4 -0
- package/src/mcp/server.ts +13 -220
- package/src/theme/__tests__/component-contrast.test.ts +338 -0
- package/src/theme/__tests__/contrast-validation.test.ts +326 -0
- package/src/theme/contrast.test.ts +331 -0
- package/src/theme/contrast.ts +246 -0
- package/src/theme/generator.ts +213 -1
- package/src/theme/index.ts +16 -0
- package/src/theme/types.ts +51 -0
- package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
- package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
- package/src/viewer/components/AccessibilityPanel.tsx +493 -433
- package/src/viewer/components/ActionCapture.tsx +1 -1
- package/src/viewer/components/ActionsPanel.tsx +142 -183
- package/src/viewer/components/App.tsx +276 -183
- package/src/viewer/components/BottomPanel.tsx +40 -80
- package/src/viewer/components/CodePanel.tsx +9 -87
- package/src/viewer/components/CommandPalette.tsx +117 -74
- package/src/viewer/components/ComponentGraph.tsx +143 -126
- package/src/viewer/components/ComponentHeader.tsx +46 -43
- package/src/viewer/components/ContractPanel.tsx +124 -117
- package/src/viewer/components/ErrorBoundary.tsx +47 -35
- package/src/viewer/components/FigmaEmbed.tsx +18 -13
- package/src/viewer/components/FragmentEditor.tsx +126 -63
- package/src/viewer/components/HealthDashboard.tsx +146 -171
- package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
- package/src/viewer/components/Icons.tsx +151 -98
- package/src/viewer/components/InteractionsPanel.tsx +317 -264
- package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
- package/src/viewer/components/IsolatedRender.tsx +12 -6
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
- package/src/viewer/components/LandingPage.tsx +285 -305
- package/src/viewer/components/Layout.tsx +12 -10
- package/src/viewer/components/LeftSidebar.tsx +103 -155
- package/src/viewer/components/MultiViewportPreview.tsx +254 -63
- package/src/viewer/components/PreviewArea.tsx +113 -44
- package/src/viewer/components/PreviewFrameHost.tsx +36 -6
- package/src/viewer/components/PreviewPane.tsx +2 -3
- package/src/viewer/components/PreviewToolbar.tsx +109 -105
- package/src/viewer/components/PropsEditor.tsx +154 -74
- package/src/viewer/components/PropsTable.tsx +95 -82
- package/src/viewer/components/RelationsSection.tsx +71 -40
- package/src/viewer/components/ResizablePanel.tsx +158 -55
- package/src/viewer/components/RightSidebar.tsx +46 -56
- package/src/viewer/components/ScreenshotButton.tsx +12 -12
- package/src/viewer/components/SkeletonLoader.tsx +99 -83
- package/src/viewer/components/StoryRenderer.tsx +4 -11
- package/src/viewer/components/Toast.tsx +3 -67
- package/src/viewer/components/TokenStylePanel.tsx +136 -118
- package/src/viewer/components/UsageSection.tsx +26 -26
- package/src/viewer/components/VariantMatrix.tsx +140 -47
- package/src/viewer/components/VariantTabs.tsx +24 -68
- package/src/viewer/components/ViewportSelector.tsx +121 -114
- package/src/viewer/constants/ui.ts +23 -22
- package/src/viewer/entry.tsx +8 -3
- package/src/viewer/index.ts +3 -6
- package/src/viewer/preview-frame.html +43 -18
- package/src/viewer/server.ts +7 -16
- package/src/viewer/styles/globals.css +46 -85
- package/src/viewer/utils/a11y-fixes.ts +53 -30
- package/dist/chunk-ICAIQ57V.js.map +0 -1
- package/dist/chunk-U4GQ2JTD.js +0 -832
- package/dist/chunk-U4GQ2JTD.js.map +0 -1
- package/dist/scan-ESEXV7LF.js +0 -12
- package/dist/static-viewer-O37MJ5B6.js +0 -12
- package/dist/viewer-YDGFDTK5.js +0 -11104
- package/dist/viewer-YDGFDTK5.js.map +0 -1
- package/src/viewer/postcss.config.js +0 -6
- package/src/viewer/tailwind.config.js +0 -37
- /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
- /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
- /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
- /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
- /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
- /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
- /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
- /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
- /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
- /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
- /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
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
<AppShell layout="inset">
|
|
13
|
+
<AppShell.Header>
|
|
14
|
+
{header}
|
|
15
|
+
</AppShell.Header>
|
|
16
|
+
<AppShell.Sidebar width="256px" collapsible="icon">
|
|
13
17
|
{leftSidebar}
|
|
14
|
-
</
|
|
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
|
-
</
|
|
20
|
-
</
|
|
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
|
|
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}
|
|
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 {
|
|
134
|
-
const
|
|
135
|
-
const
|
|
132
|
+
const { setTheme, resolvedTheme } = useTheme();
|
|
133
|
+
const { isMobile, setOpen } = useSidebar();
|
|
134
|
+
const navRef = useRef<HTMLDivElement>(null);
|
|
136
135
|
|
|
137
|
-
const debouncedSearch = useDebounce(
|
|
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 <
|
|
201
|
-
|
|
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,
|
|
207
|
+
}, [focusedIndex, keyboardItems.length]);
|
|
204
208
|
|
|
205
209
|
useEffect(() => {
|
|
206
210
|
setFocusedIndex(-1);
|
|
207
|
-
}, [
|
|
211
|
+
}, [searchQuery]);
|
|
208
212
|
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
return;
|
|
213
|
+
const handleSelect = (path: string) => {
|
|
214
|
+
onSelect(path);
|
|
215
|
+
if (isMobile) {
|
|
216
|
+
setOpen(false);
|
|
214
217
|
}
|
|
218
|
+
};
|
|
215
219
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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) >=
|
|
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 ?
|
|
244
|
-
} else if (e.key === 'Enter' && focusedIndex >= 0 && focusedIndex <
|
|
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
|
-
|
|
246
|
+
const currentItem = keyboardItems[focusedIndex];
|
|
247
|
+
if (currentItem.type === 'dashboard') {
|
|
248
|
+
handleHealthClick();
|
|
249
|
+
} else {
|
|
250
|
+
handleSelect(currentItem.path);
|
|
251
|
+
}
|
|
247
252
|
}
|
|
248
|
-
}, [
|
|
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
|
-
|
|
267
|
-
{/* Header
|
|
268
|
-
<
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
<
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
<
|
|
309
|
+
<Sidebar.Item
|
|
350
310
|
key={item.path}
|
|
351
|
-
|
|
352
|
-
onClick={() =>
|
|
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
|
-
</
|
|
318
|
+
</Sidebar.Item>
|
|
369
319
|
);
|
|
370
320
|
})}
|
|
371
|
-
</
|
|
372
|
-
|
|
373
|
-
)
|
|
374
|
-
})}
|
|
321
|
+
</Sidebar.Section>
|
|
322
|
+
);
|
|
323
|
+
})}
|
|
375
324
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
<
|
|
385
|
-
<
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
334
|
+
<Sidebar.Footer>
|
|
335
|
+
<Badge size="sm">{segments.length} components</Badge>
|
|
336
|
+
</Sidebar.Footer>
|
|
337
|
+
</>
|
|
390
338
|
);
|
|
391
339
|
}
|