@gsa-tts/graymatter-ui 0.3.13 → 0.3.15

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/README.md CHANGED
@@ -190,6 +190,7 @@ import {
190
190
  ### Layout Components
191
191
  - **GlobalAppLayout** - Main application layout wrapper
192
192
  - **BaseLayout** - Base page layout structure
193
+ - **DocsLayout** - Documentation layout with dynamic navigation (Astro only)
193
194
 
194
195
  ### UI Components
195
196
  - **ProfileMenu** - User profile dropdown menu with event-driven actions
@@ -235,15 +236,18 @@ Detailed documentation for specific components is available in the package:
235
236
 
236
237
  - **[NAVIGATION_CONTROL.md](https://github.com/GSA-TTS/usai-gov/blob/main/packages/ui/NAVIGATION_CONTROL.md)** - DesktopSideNav external control system with event-based API
237
238
  - **[PROFILE_MENU.md](https://github.com/GSA-TTS/usai-gov/blob/main/packages/ui/PROFILE_MENU.md)** - ProfileMenu component usage and event handling
239
+ - **[README_DocsLayout.md](https://github.com/GSA-TTS/usai-gov/blob/main/packages/ui/README_DocsLayout.md)** - DocsLayout component with dynamic navigation generation (Astro only)
238
240
 
239
241
  These files are also included with the package in your `node_modules/@gsa-tts/graymatter-ui/` directory. If you don't have access to the private repository, you can find the documentation files locally after installation.
240
242
 
241
243
  ## Framework Support
242
244
 
243
- - **Svelte 5** - Native Svelte components
244
- - **Astro** - Full support with client-side hydration
245
- - **React/Vue/Angular** - Via web components
246
- - **Vanilla JavaScript** - Via web components
245
+ - **Svelte 5** - Native Svelte components (individual components only)
246
+ - **Astro** - Full support with client-side hydration (including DocsLayout)
247
+ - **React/Vue/Angular** - Via web components (individual components only)
248
+ - **Vanilla JavaScript** - Via web components (individual components only)
249
+
250
+ **Note:** The `DocsLayout` component is Astro-specific and cannot be used in other frameworks. For non-Astro applications, use the individual Svelte components and shared utilities instead.
247
251
 
248
252
  ## Troubleshooting
249
253
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsa-tts/graymatter-ui",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -18,6 +18,9 @@
18
18
  "./components": "./src/components/index.ts",
19
19
  "./components/*": "./src/components/*",
20
20
  "./constants": "./src/constants/index.ts",
21
+ "./data": "./src/data/index.ts",
22
+ "./data/*": "./src/data/*",
23
+ "./content/api/*": "./src/content/api/*",
21
24
  "./helpers": "./src/helpers/index.ts",
22
25
  "./helpers/*": "./src/helpers/*",
23
26
  "./utils": "./src/utils/index.ts",
@@ -56,8 +59,8 @@
56
59
  "vite": "^6.3.5",
57
60
  "vitest": "^3.0.7",
58
61
  "@gsa-tts/graymatter-typescript-config": "0.0.2",
59
- "@gsa-tts/graymatter-eslint": "0.0.2",
60
- "@gsa-tts/graymatter-vitest-config": "0.0.1"
62
+ "@gsa-tts/graymatter-vitest-config": "0.0.1",
63
+ "@gsa-tts/graymatter-eslint": "0.0.2"
61
64
  },
62
65
  "dependencies": {
63
66
  "@fontsource-variable/archivo": "^5.2.6",
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import ApiDocSubNavMenuItem from './ApiDocSubNavMenuItem.svelte';
3
+ import Self from './ApiDocSubNavList.svelte';
4
+
5
+ const { items, level = 0, selectedId, onClick, onKeyDown } = $props();
6
+ </script>
7
+
8
+ {#each items as item}
9
+ <ApiDocSubNavMenuItem
10
+ {item}
11
+ {level}
12
+ isSelected={selectedId === item.id}
13
+ {onClick}
14
+ {onKeyDown}
15
+ />
16
+ {#if item.children}
17
+ <Self
18
+ items={item.children}
19
+ level={level + 1}
20
+ {selectedId}
21
+ {onClick}
22
+ {onKeyDown}
23
+ />
24
+ {/if}
25
+ {/each}
@@ -0,0 +1,464 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher, onMount } from 'svelte';
3
+ import { subNavReady } from '@gsa-tts/graymatter-ui/stores/subNavReadyStore';
4
+ import type { SubNavData, SubNavItem } from '../types/subnav.ts';
5
+ import ApiDocSubNavList from './ApiDocSubNavList.svelte';
6
+
7
+ const { data = null } = $props();
8
+
9
+ const sections = $derived(() =>
10
+ !Array.isArray(data) &&
11
+ data &&
12
+ typeof data === 'object' &&
13
+ 'sections' in data &&
14
+ Array.isArray(
15
+ (data as { sections: { header: string; items: SubNavItem[] }[] }).sections
16
+ )
17
+ ? (data as { sections: { header: string; items: SubNavItem[] }[] })
18
+ .sections
19
+ : null
20
+ );
21
+
22
+ const dispatch = createEventDispatcher<{
23
+ navigate: { item: SubNavItem };
24
+ itemSelect: { item: SubNavItem };
25
+ mobileAnchorNavigate: { hash: string };
26
+ }>();
27
+
28
+ function findFirstItem(data: SubNavData | null): SubNavItem | null {
29
+ if (!data) return null;
30
+
31
+ if (Array.isArray(data)) {
32
+ return data[0] || null;
33
+ } else if (
34
+ data &&
35
+ typeof data === 'object' &&
36
+ 'sections' in data &&
37
+ Array.isArray(
38
+ (data as { sections: { header: string; items: SubNavItem[] }[] })
39
+ .sections
40
+ )
41
+ ) {
42
+ const sections = (
43
+ data as { sections: { header: string; items: SubNavItem[] }[] }
44
+ ).sections;
45
+ if (
46
+ sections.length > 0 &&
47
+ sections[0] &&
48
+ sections[0].items &&
49
+ sections[0].items.length > 0
50
+ ) {
51
+ return sections[0].items[0] || null;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+
57
+ function findItemByHash(
58
+ data: SubNavData | null,
59
+ hash: string
60
+ ): SubNavItem | null {
61
+ if (!data) return null;
62
+
63
+ function searchInItems(items: SubNavItem[]): SubNavItem | null {
64
+ for (const item of items) {
65
+ if (item.href && item.href.includes('#')) {
66
+ const itemHash = item.href.split('#')[1];
67
+ if (itemHash === hash) {
68
+ return item;
69
+ }
70
+ }
71
+ if (item.children) {
72
+ const found = searchInItems(item.children);
73
+ if (found) return found;
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+
79
+ if (Array.isArray(data)) {
80
+ return searchInItems(data);
81
+ } else if (
82
+ data &&
83
+ typeof data === 'object' &&
84
+ 'sections' in data &&
85
+ Array.isArray(
86
+ (data as { sections: { header: string; items: SubNavItem[] }[] })
87
+ .sections
88
+ )
89
+ ) {
90
+ for (const section of (
91
+ data as { sections: { header: string; items: SubNavItem[] }[] }
92
+ ).sections) {
93
+ const found = searchInItems(section.items);
94
+ if (found) return found;
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+
100
+ function findItemById(
101
+ data: SubNavData | null,
102
+ id: string
103
+ ): SubNavItem | null {
104
+ if (!data) return null;
105
+
106
+ function searchInItems(items: SubNavItem[]): SubNavItem | null {
107
+ for (const item of items) {
108
+ if (item.id === id) {
109
+ return item;
110
+ }
111
+ if (item.children) {
112
+ const found = searchInItems(item.children);
113
+ if (found) return found;
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+
119
+ if (Array.isArray(data)) {
120
+ return searchInItems(data);
121
+ } else if (
122
+ data &&
123
+ typeof data === 'object' &&
124
+ 'sections' in data &&
125
+ Array.isArray(
126
+ (data as { sections: { header: string; items: SubNavItem[] }[] })
127
+ .sections
128
+ )
129
+ ) {
130
+ for (const section of (
131
+ data as { sections: { header: string; items: SubNavItem[] }[] }
132
+ ).sections) {
133
+ const found = searchInItems(section.items);
134
+ if (found) return found;
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+
140
+ // Utility: Find top-level parent of a submenu item
141
+ function findTopLevelParent(
142
+ item: SubNavItem,
143
+ data: SubNavData | null
144
+ ): SubNavItem | null {
145
+ if (!data || !item) return null;
146
+ function search(
147
+ items: SubNavItem[],
148
+ parent: SubNavItem | null
149
+ ): SubNavItem | null {
150
+ for (const it of items) {
151
+ if (it.id === item.id) return parent ?? it;
152
+ if (it.children) {
153
+ const found = search(it.children, parent ?? it);
154
+ if (found) return found;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ if (Array.isArray(data)) {
160
+ return search(data, null);
161
+ } else if (
162
+ data &&
163
+ typeof data === 'object' &&
164
+ 'sections' in data &&
165
+ Array.isArray(
166
+ (data as { sections: { header: string; items: SubNavItem[] }[] })
167
+ .sections
168
+ )
169
+ ) {
170
+ for (const section of (
171
+ data as { sections: { header: string; items: SubNavItem[] }[] }
172
+ ).sections) {
173
+ const found = search(section.items, null);
174
+ if (found) return found;
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+
180
+ let currentSelectedId = $state<string | null>(null);
181
+ let hydrated = $state(false);
182
+
183
+ const effectiveSelectedId = $derived(() => {
184
+ if (!hydrated || !data) return null;
185
+
186
+ // If we already have a selectedId, use it
187
+ if (currentSelectedId) return currentSelectedId;
188
+
189
+ // First priority: URL hash
190
+ if (typeof window !== 'undefined' && window.location.hash) {
191
+ const hash = window.location.hash.substring(1);
192
+ const targetItem = findItemByHash(data, hash);
193
+ if (targetItem) {
194
+ return targetItem.id;
195
+ }
196
+ }
197
+
198
+ // Second priority: sessionStorage
199
+ if (typeof window !== 'undefined') {
200
+ const storedId = sessionStorage.getItem('subNav-selectedId');
201
+ if (storedId) {
202
+ try {
203
+ const parsedId = JSON.parse(storedId);
204
+ if (parsedId && parsedId !== 'null' && parsedId !== '') {
205
+ const targetItem = findItemById(data, parsedId);
206
+ if (targetItem) {
207
+ return targetItem.id;
208
+ }
209
+ }
210
+ } catch (e) {
211
+ console.log('[SubNavMenu] Error parsing stored ID:', e);
212
+ }
213
+ }
214
+ }
215
+
216
+ // Third priority: first item
217
+ const firstItem = findFirstItem(data);
218
+ if (firstItem && firstItem.title) {
219
+ return firstItem.id;
220
+ }
221
+
222
+ return null;
223
+ });
224
+
225
+ // Update currentSelectedId when effectiveSelectedId changes
226
+ $effect(() => {
227
+ if (effectiveSelectedId() && effectiveSelectedId() !== currentSelectedId) {
228
+ currentSelectedId = effectiveSelectedId();
229
+ }
230
+ });
231
+
232
+ onMount(() => {
233
+ hydrated = true;
234
+
235
+ if (!data) return;
236
+
237
+ // Handle header updates and scrolling
238
+ const targetItem = currentSelectedId
239
+ ? findItemById(data, currentSelectedId)
240
+ : null;
241
+ if (targetItem) {
242
+ // Always resolve top-level parent from data
243
+ const topLevel = findTopLevelParent(targetItem, data) || targetItem;
244
+ import('@gsa-tts/graymatter-ui/stores/navigationStore')
245
+ .then(({ selectedSubNavItemTitle }) => {
246
+ selectedSubNavItemTitle.set(topLevel.title || '');
247
+ if (typeof window !== 'undefined') {
248
+ sessionStorage.setItem(
249
+ 'subNav-selectedId',
250
+ JSON.stringify(targetItem.id)
251
+ );
252
+ sessionStorage.setItem(
253
+ 'subNav-topLevelId',
254
+ JSON.stringify(topLevel.id)
255
+ );
256
+ sessionStorage.setItem(
257
+ 'subNav-topLevelTitle',
258
+ JSON.stringify(topLevel.title || '')
259
+ );
260
+ }
261
+ })
262
+ .catch();
263
+
264
+ // Handle scrolling if we have a URL hash
265
+ if (targetItem.href && targetItem.href.includes('#')) {
266
+ const hash = targetItem.href.split('#')[1];
267
+ if (hash) {
268
+ setTimeout(() => scrollToAnchorWithOffset(hash), 100);
269
+ }
270
+ }
271
+
272
+ // Always dispatch sectionVisible with top-level parent
273
+ if (topLevel.title && typeof window !== 'undefined') {
274
+ window.dispatchEvent(
275
+ new CustomEvent('sectionVisible', {
276
+ detail: {
277
+ title: topLevel.title,
278
+ id: topLevel.id,
279
+ },
280
+ })
281
+ );
282
+ }
283
+ }
284
+ subNavReady.set(true);
285
+ });
286
+
287
+ function handleItemClick(event: Event, item: SubNavItem) {
288
+ // Always prevent default for anchor links
289
+ if (item.href && item.href.includes('#')) {
290
+ event.preventDefault();
291
+ } else if (!item.href || item.href === '#') {
292
+ event.preventDefault();
293
+ }
294
+ currentSelectedId = item.id;
295
+ const topLevel = findTopLevelParent(item, data) || item;
296
+ import('@gsa-tts/graymatter-ui/stores/navigationStore')
297
+ .then(({ selectedSubNavItemTitle }) => {
298
+ selectedSubNavItemTitle.set(topLevel.title || '');
299
+ if (typeof window !== 'undefined') {
300
+ sessionStorage.setItem('subNav-selectedId', JSON.stringify(item.id));
301
+ sessionStorage.setItem(
302
+ 'subNav-topLevelId',
303
+ JSON.stringify(topLevel.id)
304
+ );
305
+ sessionStorage.setItem(
306
+ 'subNav-topLevelTitle',
307
+ JSON.stringify(topLevel.title || '')
308
+ );
309
+ }
310
+ })
311
+ .catch();
312
+ if (typeof window !== 'undefined') {
313
+ if (item.href && item.href.includes('#')) {
314
+ const hash = item.href.split('#')[1];
315
+ if (hash) {
316
+ window.location.hash = hash;
317
+ if (window.innerWidth > 640) {
318
+ // Desktop: use delay for consistent timing
319
+ setTimeout(() => {
320
+ scrollToAnchorWithOffset(hash);
321
+ }, 350);
322
+ } else {
323
+ // Mobile: immediate scroll without delay
324
+ window.dispatchEvent(
325
+ new CustomEvent('mobileAnchorNavigate', { detail: { hash } })
326
+ );
327
+ }
328
+ }
329
+ }
330
+ // Always use top-level parent for header
331
+ if (topLevel.title) {
332
+ window.dispatchEvent(
333
+ new CustomEvent('sectionVisible', {
334
+ detail: {
335
+ title: topLevel.title,
336
+ id: topLevel.id,
337
+ },
338
+ })
339
+ );
340
+ }
341
+ // Mobile menu logic unchanged
342
+ if (window.innerWidth <= 640 && item.href && item.href !== '#') {
343
+ import('@gsa-tts/graymatter-ui/stores/navigationStore').then(
344
+ ({ navigationStore }) => {
345
+ navigationStore.setExpanded(false);
346
+ }
347
+ );
348
+ }
349
+ if (window.innerWidth <= 640) {
350
+ window.dispatchEvent(new CustomEvent('closeMobileMenu'));
351
+ }
352
+ }
353
+ if (item.href && item.href !== '#') {
354
+ dispatch('navigate', { item });
355
+ } else {
356
+ dispatch('itemSelect', { item });
357
+ }
358
+ }
359
+
360
+ function handleKeyDown(event: KeyboardEvent, item: SubNavItem) {
361
+ if (event.key === 'Enter' || event.key === ' ') {
362
+ event.preventDefault();
363
+ handleItemClick(event, item);
364
+ }
365
+ }
366
+
367
+ // Utility: Scroll to anchor with header offset
368
+ function scrollToAnchorWithOffset(anchorId: string) {
369
+ const mainContent = document.getElementById('main-content');
370
+ if (mainContent) {
371
+ const anchor = mainContent.querySelector(
372
+ `[id="${anchorId}"]`
373
+ ) as HTMLElement;
374
+ if (anchor) {
375
+ // Use instant scrolling without animation
376
+ try {
377
+ // Add temporary scroll margin to account for header
378
+ const originalScrollMargin = anchor.style.scrollMarginTop;
379
+ anchor.style.scrollMarginTop = '56px';
380
+
381
+ anchor.scrollIntoView({
382
+ behavior: 'auto',
383
+ block: 'start',
384
+ inline: 'nearest',
385
+ });
386
+
387
+ // Restore original scroll margin after a delay
388
+ setTimeout(() => {
389
+ anchor.style.scrollMarginTop = originalScrollMargin;
390
+ }, 1000);
391
+ } catch {
392
+ // Fallback to manual calculation
393
+ const headerHeight = 56;
394
+ const rect = anchor.getBoundingClientRect();
395
+ const scrollTop = window.scrollY + rect.top - headerHeight;
396
+ const finalScrollTop = Math.max(0, scrollTop);
397
+ window.scrollTo({ top: finalScrollTop, behavior: 'auto' });
398
+ }
399
+ }
400
+ }
401
+ }
402
+ </script>
403
+
404
+ <div class="sub-menu-container">
405
+ <div class="sub-menu-content">
406
+ {#if data}
407
+ {#if Array.isArray(data)}
408
+ <ApiDocSubNavList
409
+ items={data}
410
+ level={0}
411
+ selectedId={effectiveSelectedId()}
412
+ onClick={handleItemClick}
413
+ onKeyDown={handleKeyDown}
414
+ />
415
+ {/if}
416
+ {#each sections() ?? [] as section: { header: string; items: SubNavItem[] }}
417
+ <div class="sub-menu-header">
418
+ {section.header}
419
+ </div>
420
+ <ApiDocSubNavList
421
+ items={section.items}
422
+ level={0}
423
+ selectedId={effectiveSelectedId()}
424
+ onClick={handleItemClick}
425
+ onKeyDown={handleKeyDown}
426
+ />
427
+ {/each}
428
+ {/if}
429
+ </div>
430
+ </div>
431
+
432
+ <style>
433
+ .sub-menu-container {
434
+ display: flex;
435
+ padding: 0 var(--ai-size-8) var(--ai-size-16) var(--ai-size-8);
436
+ flex-direction: column;
437
+ align-items: flex-start;
438
+ gap: var(--ai-size-32);
439
+ align-self: stretch;
440
+ min-height: 6.25rem;
441
+ }
442
+ .sub-menu-content {
443
+ display: flex;
444
+ flex-direction: column;
445
+ align-items: flex-start;
446
+ gap: var(--ai-size-2);
447
+ align-self: stretch;
448
+ }
449
+ .sub-menu-header {
450
+ display: flex;
451
+ padding: var(--ai-size-8);
452
+ justify-content: flex-start;
453
+ align-items: center;
454
+ gap: var(--ai-size-10);
455
+ align-self: stretch;
456
+ color: var(--ai-color-steel-900);
457
+ font-size: var(--ai-size-12);
458
+ font-style: normal;
459
+ font-weight: 600;
460
+ line-height: 1.2;
461
+ letter-spacing: 0.0075rem;
462
+ text-transform: uppercase;
463
+ }
464
+ </style>
@@ -0,0 +1,116 @@
1
+ <script lang="ts">
2
+ const { item, level = 0, isSelected = false, onClick, onKeyDown } = $props();
3
+ const levelClass = $derived(() => `level-${level}`);
4
+ const hasHref = $derived(() => item.href && item.href !== '#');
5
+ </script>
6
+
7
+ {#if hasHref()}
8
+ <a
9
+ href={item.href}
10
+ class={`menu-item ${levelClass()}`}
11
+ class:selected={isSelected}
12
+ onclick={e => onClick(e, item)}
13
+ >
14
+ <div class="menu-item-content">
15
+ <span class="menu-item-text" class:selected={isSelected}>
16
+ {item.title}
17
+ </span>
18
+ </div>
19
+ </a>
20
+ {:else}
21
+ <div
22
+ class={`menu-item ${levelClass()}${!hasHref() ? ' non-clickable' : ''}`}
23
+ class:selected={isSelected}
24
+ tabindex="0"
25
+ role="button"
26
+ aria-label={`Select ${item.title}`}
27
+ onclick={e => onClick(e, item)}
28
+ onkeydown={e => onKeyDown(e, item)}
29
+ >
30
+ <div class="menu-item-content">
31
+ <span class="menu-item-text" class:selected={isSelected}>
32
+ {item.title}
33
+ </span>
34
+ </div>
35
+ </div>
36
+ {/if}
37
+
38
+ <style>
39
+ .menu-item {
40
+ display: flex;
41
+ height: 2.1625rem; /* 34.6px */
42
+ padding: var(--ai-size-8);
43
+ align-items: center;
44
+ gap: var(--ai-size-4);
45
+ align-self: stretch;
46
+ width: 100%;
47
+ background: none;
48
+ border: none;
49
+ cursor: pointer;
50
+ text-decoration: none;
51
+ color: inherit;
52
+ border-radius: var(--ai-size-4);
53
+ transition: background-color var(--ai-transition-ease-fast);
54
+ }
55
+
56
+ .menu-item:hover {
57
+ background-color: var(--ai-color-steel-200);
58
+ }
59
+
60
+ .menu-item:focus {
61
+ outline: var(--ai-size-2) solid var(--ai-color-blue-600);
62
+ outline-offset: var(--ai-size-2);
63
+ }
64
+
65
+ /* Hide focus outline on click */
66
+ .menu-item:focus:not(:focus-visible) {
67
+ outline: none;
68
+ }
69
+
70
+ .menu-item.selected {
71
+ background-color: var(--ai-color-steel-200);
72
+ }
73
+
74
+ .menu-item.non-clickable {
75
+ cursor: default;
76
+ }
77
+
78
+ .menu-item.non-clickable:hover {
79
+ background-color: transparent;
80
+ }
81
+
82
+ /* Nesting levels with indentation */
83
+ .menu-item.level-0 {
84
+ padding-left: var(--ai-size-8);
85
+ }
86
+
87
+ .menu-item.level-1 {
88
+ padding-left: var(--ai-size-20);
89
+ }
90
+
91
+ .menu-item.level-2 {
92
+ padding-left: var(--ai-size-32);
93
+ }
94
+
95
+ .menu-item-content {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: var(--ai-size-10);
99
+ flex: 1 0 0;
100
+ }
101
+
102
+ .menu-item-text {
103
+ color: var(--ai-color-steel-800);
104
+ font-size: var(--ai-size-14);
105
+ font-style: normal;
106
+ font-weight: 400;
107
+ line-height: 1.3;
108
+ transition: all var(--ai-transition-ease-fast);
109
+ }
110
+
111
+ .menu-item-text.selected,
112
+ .menu-item.selected .menu-item-text {
113
+ color: var(--ai-color-black);
114
+ font-weight: 600;
115
+ }
116
+ </style>
@@ -12,6 +12,9 @@ export { default as MobileBottomNav } from './MobileBottomNav.svelte';
12
12
  export { default as MobileSideMenu } from './MobileSideMenu.svelte';
13
13
  export { default as NavigationInitializer } from './NavigationInitializer.svelte';
14
14
  export { default as UsaSkipNav } from './UsaSkipNav.svelte';
15
+ export { default as ApiDocSubNavMenu } from './ApiDocSubNavMenu.svelte';
16
+ export { default as ApiDocSubNavList } from './ApiDocSubNavList.svelte';
17
+ export { default as ApiDocSubNavMenuItem } from './ApiDocSubNavMenuItem.svelte';
15
18
 
16
19
  // Icon Components
17
20
  export * from './icons/index.js';