@gsa-tts/graymatter-ui 0.0.1 → 0.1.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 (32) hide show
  1. package/assets/images/chat-hero-image.png +0 -0
  2. package/assets/images/console-hero-image.png +0 -0
  3. package/index.ts +2 -0
  4. package/package.json +9 -6
  5. package/src/components/DesktopHeader.svelte +89 -0
  6. package/src/components/DesktopSideNav.svelte +375 -0
  7. package/src/components/GoogleTagManager.astro +16 -0
  8. package/src/components/Logo.svelte +8 -7
  9. package/src/components/MobileBottomNav.svelte +99 -0
  10. package/src/components/MobileSideMenu.svelte +170 -0
  11. package/src/components/MobileTopNav.svelte +101 -0
  12. package/src/components/NavigationInitializer.svelte +19 -0
  13. package/src/components/icons/ApiIcon.svelte +49 -0
  14. package/src/components/icons/ChatIcon.svelte +27 -0
  15. package/src/components/icons/CloseIcon.svelte +18 -0
  16. package/src/components/icons/ConsoleIcon.svelte +25 -0
  17. package/src/components/icons/DiscoverIcon.svelte +30 -0
  18. package/src/components/icons/MenuIcon.svelte +38 -0
  19. package/src/components/icons/index.ts +6 -0
  20. package/src/components/index.ts +9 -0
  21. package/src/layouts/AppLayout.astro +1 -1
  22. package/src/layouts/BaseLayout.astro +4 -1
  23. package/src/layouts/GlobalAppLayout.astro +585 -0
  24. package/src/stores/navigationStore.ts +153 -0
  25. package/src/stores/subNavReadyStore.ts +3 -0
  26. package/src/styles/base/global.css +8 -1
  27. package/src/styles/base/typography.css +15 -14
  28. package/src/styles/components/globalAppLayout.css +76 -0
  29. package/src/styles/components/globalAppNav.css +548 -0
  30. package/src/styles/components/heroCards.css +21 -1
  31. package/assets/images/gsai-logo-black.svg +0 -18
  32. package/assets/images/gsai-logo-white.svg +0 -18
Binary file
package/index.ts CHANGED
@@ -2,3 +2,5 @@ export * from './src/components';
2
2
  export * from './src/constants';
3
3
  export * from './src/helpers';
4
4
  export * from './src/component-options';
5
+ export * from './src/stores/navigationStore';
6
+ export * from './src/stores/subNavReadyStore';
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@gsa-tts/graymatter-ui",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "exports": {
9
9
  ".": "./index.ts",
10
+ "./stores/navigationStore": "./src/stores/navigationStore.ts",
11
+ "./stores/subNavReadyStore": "./src/stores/subNavReadyStore.ts",
10
12
  "./assets/*": "./assets/*",
11
13
  "./astro-config": "./src/astro-config/index.ts",
12
14
  "./component-options": "./src/component-options/index.ts",
@@ -45,19 +47,20 @@
45
47
  "astro-purgecss": "^5.2.2",
46
48
  "eslint": "^9.21.0",
47
49
  "gulp": "^5.0.0",
48
- "svelte": "^5.28.2",
50
+ "svelte": "^5.35.4",
49
51
  "typescript": "5.7.3",
50
52
  "vitest": "^3.0.7",
51
- "@gsa-tts/graymatter-eslint-config": "0.0.1",
53
+ "vite": "^6.3.5",
52
54
  "@gsa-tts/graymatter-typescript-config": "0.0.1",
53
- "@gsa-tts/graymatter-vitest-config": "0.0.1"
55
+ "@gsa-tts/graymatter-vitest-config": "0.0.1",
56
+ "@gsa-tts/graymatter-eslint": "0.0.1"
54
57
  },
55
58
  "dependencies": {
56
59
  "@fontsource-variable/archivo": "^5.2.6",
57
60
  "@uswds/uswds": "3.12.0",
58
- "astro": "^5.7.10",
61
+ "astro": "^5.11.0",
59
62
  "slugify": "^1.6.6",
60
- "@gsa-tts/graymatter-style-tokens": "0.0.1"
63
+ "@gsa-tts/graymatter-style-tokens": "0.0.2"
61
64
  },
62
65
  "scripts": {
63
66
  "build": "gulp update",
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import { selectedItem, isExpanded, selectedSubNavItemTitle } from '../stores/navigationStore.js';
3
+ import Logo from './Logo.svelte';
4
+ import { onMount } from 'svelte';
5
+ import { subNavReady } from '../stores/subNavReadyStore';
6
+
7
+ const { customHeaderText = '' } = $props();
8
+
9
+ let isLayoutTransitioning = false;
10
+
11
+ const showHeader = $derived(() =>
12
+ $selectedItem && $selectedItem !== 'discover' && !$isExpanded && !isLayoutTransitioning && $subNavReady
13
+ );
14
+ const headerText = $derived(() =>
15
+ customHeaderText || $selectedSubNavItemTitle
16
+ );
17
+
18
+ onMount(() => {
19
+ const handleSectionVisible = (event: Event) => {
20
+ if (typeof window !== 'undefined' && window.innerWidth <= 640) {
21
+ return;
22
+ }
23
+ let topLevelTitle = null;
24
+ if (typeof window !== 'undefined') {
25
+ topLevelTitle = sessionStorage.getItem('subNav-topLevelTitle');
26
+ if (topLevelTitle) {
27
+ try {
28
+ topLevelTitle = JSON.parse(topLevelTitle);
29
+ } catch {/* ignore parse error */}
30
+ }
31
+ }
32
+ if (typeof topLevelTitle === 'string' && topLevelTitle.trim() !== '') {
33
+ selectedSubNavItemTitle.set(topLevelTitle);
34
+ sessionStorage.setItem('subNav-selectedTitle', JSON.stringify(topLevelTitle));
35
+ } else {
36
+ const customEvent = event as CustomEvent;
37
+ if (customEvent.detail && customEvent.detail.title) {
38
+ selectedSubNavItemTitle.set(customEvent.detail.title);
39
+ sessionStorage.setItem('subNav-selectedTitle', JSON.stringify(customEvent.detail.title));
40
+ }
41
+ }
42
+ };
43
+
44
+ const handleLayoutTransition = (event: Event) => {
45
+ const customEvent = event as CustomEvent;
46
+ const { isMobile } = customEvent.detail;
47
+ if (!isMobile) {
48
+ isLayoutTransitioning = true;
49
+ setTimeout(() => {
50
+ isLayoutTransitioning = false;
51
+ }, 300);
52
+ }
53
+ };
54
+
55
+ window.addEventListener('sectionVisible', handleSectionVisible);
56
+ window.addEventListener('layoutTransition', handleLayoutTransition);
57
+
58
+ return () => {
59
+ window.removeEventListener('sectionVisible', handleSectionVisible);
60
+ window.removeEventListener('layoutTransition', handleLayoutTransition);
61
+ };
62
+ });
63
+ </script>
64
+
65
+ {#if showHeader()}
66
+ <div class="global-header">
67
+ <div class="header-content">
68
+ <Logo width={75} height={22} />
69
+ <span class="header-text">{headerText()}</span>
70
+ </div>
71
+ </div>
72
+ {/if}
73
+
74
+ <style>
75
+ .global-header {
76
+ transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
77
+ }
78
+
79
+ /* Optional: Add a subtle slide effect */
80
+ .global-header {
81
+ transform: translateY(0);
82
+ }
83
+
84
+ /* When hiding, we can add a subtle slide up effect */
85
+ .global-header:not(:defined) {
86
+ opacity: 0;
87
+ transform: translateY(-10px);
88
+ }
89
+ </style>
@@ -0,0 +1,375 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Controls which nav item is selected on initial server-side render (SSR) or first paint.
4
+ * - For the Discover page, set ssrSelectedItem="discover".
5
+ * - For other pages, set ssrSelectedItem to the corresponding nav key (e.g., "api", "chat", "console").
6
+ * - If not provided, the navigation will default to expanded/open on SSR.
7
+ */
8
+ import { onMount } from 'svelte';
9
+ import Logo from './Logo.svelte';
10
+ import {
11
+ isExpanded,
12
+ selectedItem,
13
+ isMenuButtonDisabled,
14
+ navigationStore,
15
+ type NavigationItem,
16
+ } from '../stores/navigationStore.js';
17
+ import DiscoverIcon from './icons/DiscoverIcon.svelte';
18
+ import ChatIcon from './icons/ChatIcon.svelte';
19
+ import ConsoleIcon from './icons/ConsoleIcon.svelte';
20
+ import ApiIcon from './icons/ApiIcon.svelte';
21
+ import MenuIcon from './icons/MenuIcon.svelte';
22
+
23
+ const { ssrSelectedItem = undefined, onNavToggle = undefined, onNavItemClick = undefined, onProfileClick = undefined, children } = $props();
24
+
25
+ let hydrated = false;
26
+ let navContainer: HTMLDivElement;
27
+ let hoveredItem = $state<string | null>(null);
28
+ let menuButtonHovered = $state(false);
29
+ let canHover = $state(false);
30
+
31
+ function ssrOrHydrated<T>(hydratedValue: T, ssrValue: T): T {
32
+ return hydrated ? hydratedValue : ssrValue;
33
+ }
34
+
35
+ function isNavSelected(key: NavigationItem) {
36
+ return ssrOrHydrated($selectedItem === key, ssrSelectedItem === key);
37
+ }
38
+
39
+ function isNavHovered(key: string) {
40
+ return hoveredItem === key;
41
+ }
42
+
43
+ const sideNavExpanded = $derived(() =>
44
+ ssrOrHydrated($isExpanded, ssrSelectedItem ? ssrSelectedItem !== 'discover' : true)
45
+ );
46
+ const menuBtnDisabled = $derived(() =>
47
+ ssrOrHydrated($isMenuButtonDisabled, ssrSelectedItem ? ssrSelectedItem === 'discover' : false)
48
+ );
49
+ const menuBtnOpen = $derived(() =>
50
+ ssrOrHydrated($isExpanded, ssrSelectedItem ? ssrSelectedItem !== 'discover' : true)
51
+ );
52
+ const menuBtnHovered = $derived(() =>
53
+ canHover && menuButtonHovered && !menuBtnDisabled()
54
+ );
55
+
56
+ onMount(() => {
57
+ hydrated = true;
58
+ // Remove the CSS class since reactive state takes over
59
+ document.documentElement.classList.remove('nav-initially-expanded');
60
+ // Remove the custom property since reactive CSS takes over
61
+ document.documentElement.style.removeProperty('--initial-main-top-padding');
62
+ // Add hydrated class to re-enable transitions
63
+ if (navContainer) {
64
+ navContainer.classList.add('hydrated');
65
+ }
66
+ // Detect if device supports hover (desktop only)
67
+ if (typeof window !== 'undefined') {
68
+ const mq = window.matchMedia('(hover: hover)');
69
+ canHover = mq.matches;
70
+ mq.addEventListener('change', e => {
71
+ canHover = e.matches;
72
+ });
73
+
74
+ // Listen for layout transitions
75
+ const handleLayoutTransition = (event: Event) => {
76
+ const customEvent = event as CustomEvent;
77
+ const { isMobile } = customEvent.detail;
78
+ console.log('[DesktopSideNav] Layout transition detected:', { isMobile });
79
+
80
+ if (isMobile) {
81
+ // Switching to mobile: close navigation
82
+ console.log('[DesktopSideNav] Closing navigation for mobile');
83
+ navigationStore.setExpanded(false);
84
+ } else {
85
+ // Switching to desktop: open navigation if on non-discover page
86
+ const currentItem = $selectedItem;
87
+ console.log('[DesktopSideNav] Desktop transition - current item:', currentItem);
88
+
89
+ if (currentItem && currentItem !== 'discover') {
90
+ console.log('[DesktopSideNav] Opening navigation for desktop (non-discover page)');
91
+ navigationStore.setExpanded(true);
92
+ } else {
93
+ console.log('[DesktopSideNav] Keeping navigation closed for desktop (discover page or no selection)');
94
+ navigationStore.setExpanded(false);
95
+ }
96
+ }
97
+ };
98
+
99
+ window.addEventListener('layoutTransition', handleLayoutTransition);
100
+
101
+ return () => {
102
+ window.removeEventListener('layoutTransition', handleLayoutTransition);
103
+ };
104
+ }
105
+ });
106
+
107
+ function toggleNav() {
108
+ if ($isMenuButtonDisabled) return;
109
+ navigationStore.toggleExpanded();
110
+ onNavToggle?.({ expanded: $isExpanded });
111
+ }
112
+
113
+ function handleNavItemClick(item: NavigationItem) {
114
+ console.log('[DesktopSideNav] handleNavItemClick called for:', item, 'at:', Date.now());
115
+
116
+ // Set navigation flag immediately to prevent header restoration
117
+ if (item !== 'discover' && typeof window !== 'undefined') {
118
+ sessionStorage.setItem('navigatingToMainNav', 'true');
119
+ console.log('[DesktopSideNav] Set navigation flag for:', item, 'at:', Date.now());
120
+ }
121
+
122
+ navigationStore.setSelectedItem(item);
123
+
124
+ // Clear header state for main navigation items (including discover)
125
+ if (item !== 'discover') {
126
+ try {
127
+ import('@gsa-tts/graymatter-ui/stores/navigationStore').then(({ selectedSubNavItemTitle }) => {
128
+ // Clear the store value immediately
129
+ selectedSubNavItemTitle.set('');
130
+ if (typeof window !== 'undefined') {
131
+ sessionStorage.removeItem('subNav-selectedTitle');
132
+ sessionStorage.removeItem('subNav-selectedId');
133
+ }
134
+
135
+ // Set default header from first submenu item after a short delay
136
+ setTimeout(() => {
137
+ const mainContent = document.getElementById('main-content');
138
+ if (mainContent) {
139
+ const firstSection = mainContent.querySelector('h2[id], h3[id], h4[id], h5[id], h6[id]');
140
+ if (firstSection && firstSection.textContent) {
141
+ const firstSectionTitle = firstSection.textContent.trim();
142
+ console.log('[DesktopSideNav] Setting default header for', item, ':', firstSectionTitle);
143
+ selectedSubNavItemTitle.set(firstSectionTitle);
144
+ if (typeof window !== 'undefined') {
145
+ sessionStorage.setItem('subNav-selectedTitle', JSON.stringify(firstSectionTitle));
146
+ }
147
+ }
148
+ }
149
+ }, 200); // Delay to ensure page content is loaded
150
+ }).catch((err) => {
151
+ console.log('[DesktopSideNav] Error clearing header state:', err);
152
+ });
153
+ } catch (err) {
154
+ console.log('[DesktopSideNav] Import failed for clearing state:', err);
155
+ }
156
+ } else {
157
+ // Clear header state for Discover page
158
+ try {
159
+ import('@gsa-tts/graymatter-ui/stores/navigationStore').then(({ selectedSubNavItemTitle }) => {
160
+ selectedSubNavItemTitle.set('');
161
+ if (typeof window !== 'undefined') {
162
+ sessionStorage.removeItem('subNav-selectedTitle');
163
+ sessionStorage.removeItem('subNav-selectedId');
164
+ }
165
+ }).catch((err) => {
166
+ console.log('[DesktopSideNav] Error clearing header state for Discover:', err);
167
+ });
168
+ } catch (err) {
169
+ console.log('[DesktopSideNav] Import failed for clearing Discover state:', err);
170
+ }
171
+ }
172
+
173
+ onNavItemClick?.({ item, expanded: $isExpanded });
174
+ }
175
+
176
+ function handleKeyDown(event: KeyboardEvent, item: NavigationItem) {
177
+ if (event.key === 'Enter' || event.key === ' ') {
178
+ event.preventDefault();
179
+ handleNavItemClick(item);
180
+ }
181
+ }
182
+ </script>
183
+
184
+ <div
185
+ class="side-nav-container"
186
+ class:expanded={sideNavExpanded()}
187
+ bind:this={navContainer}
188
+ >
189
+ <div class="nav-content">
190
+ <div class="nav-main">
191
+ <div class="menu-section">
192
+ <button
193
+ class="menu-button"
194
+ class:disabled={$isMenuButtonDisabled}
195
+ disabled={$isMenuButtonDisabled}
196
+ onclick={toggleNav}
197
+ onmouseenter={() => (menuButtonHovered = true)}
198
+ onmouseleave={() => (menuButtonHovered = false)}
199
+ aria-label="Open navigation menu"
200
+ >
201
+ <span class="sr-only">Open navigation menu</span>
202
+ <div class="icon-container">
203
+ <div class="icon-wrapper">
204
+ <MenuIcon
205
+ isHovered={menuBtnHovered()}
206
+ isDisabled={menuBtnDisabled()}
207
+ isOpen={menuBtnOpen()}
208
+ />
209
+ </div>
210
+ </div>
211
+ </button>
212
+ </div>
213
+
214
+ <div class="nav-items">
215
+ <a
216
+ href="/discover"
217
+ class="nav-item discover-item"
218
+ class:selected={isNavSelected('discover')}
219
+ class:hovered={isNavHovered('discover')}
220
+ role="button"
221
+ tabindex="0"
222
+ onclick={() => handleNavItemClick('discover')}
223
+ onkeydown={e => handleKeyDown(e, 'discover')}
224
+ onmouseenter={() => (hoveredItem = 'discover')}
225
+ onmouseleave={() => (hoveredItem = null)}
226
+ >
227
+ <div class="icon-container">
228
+ <div class="icon-wrapper">
229
+ <DiscoverIcon isSelected={isNavSelected('discover')} />
230
+ </div>
231
+ </div>
232
+ <span class="nav-text">Discover</span>
233
+ </a>
234
+ <a
235
+ href="/#chat"
236
+ class="nav-item chat-item"
237
+ class:selected={isNavSelected('chat')}
238
+ class:hovered={isNavHovered('chat')}
239
+ role="button"
240
+ tabindex="0"
241
+ onclick={() => handleNavItemClick('chat')}
242
+ onkeydown={e => handleKeyDown(e, 'chat')}
243
+ onmouseenter={() => (hoveredItem = 'chat')}
244
+ onmouseleave={() => (hoveredItem = null)}
245
+ >
246
+ <div class="icon-container">
247
+ <div class="icon-wrapper">
248
+ <ChatIcon isSelected={isNavSelected('chat') as boolean} />
249
+ </div>
250
+ </div>
251
+ <span class="nav-text">Chat</span>
252
+ </a>
253
+
254
+ <a
255
+ href="/"
256
+ class="nav-item console-item"
257
+ class:selected={isNavSelected('console')}
258
+ class:hovered={isNavHovered('console')}
259
+ role="button"
260
+ tabindex="0"
261
+ onclick={() => handleNavItemClick('console')}
262
+ onkeydown={e => handleKeyDown(e, 'console')}
263
+ onmouseenter={() => (hoveredItem = 'console')}
264
+ onmouseleave={() => (hoveredItem = null)}
265
+ >
266
+ <div class="icon-container">
267
+ <div class="icon-wrapper">
268
+ <ConsoleIcon isSelected={isNavSelected('console') as boolean} />
269
+ </div>
270
+ </div>
271
+ <span class="nav-text">Console</span>
272
+ </a>
273
+
274
+ <a
275
+ href="/api"
276
+ class="nav-item api-item"
277
+ class:selected={isNavSelected('api')}
278
+ class:hovered={isNavHovered('api')}
279
+ role="button"
280
+ tabindex="0"
281
+ onclick={() => handleNavItemClick('api')}
282
+ onkeydown={e => handleKeyDown(e, 'api')}
283
+ onmouseenter={() => (hoveredItem = 'api')}
284
+ onmouseleave={() => (hoveredItem = null)}
285
+ >
286
+ <div class="icon-container">
287
+ <div class="icon-wrapper">
288
+ <ApiIcon isSelected={isNavSelected('api')} />
289
+ </div>
290
+ </div>
291
+ <span class="nav-text">API</span>
292
+ </a>
293
+ </div>
294
+ </div>
295
+
296
+ <div class="nav-footer">
297
+ <button
298
+ class="user-profile-button"
299
+ tabindex="0"
300
+ onclick={() => onProfileClick?.()}
301
+ onkeydown={e => {
302
+ if (e.key === 'Enter' || e.key === ' ') {
303
+ e.preventDefault();
304
+ onProfileClick?.();
305
+ }
306
+ }}
307
+ >
308
+ <div class="user-profile-container">
309
+ <div class="user-profile-icon">
310
+ <span class="user-initials">ZW</span>
311
+ </div>
312
+ </div>
313
+ </button>
314
+ </div>
315
+ </div>
316
+
317
+ <div class="expanded-content drawer-anim" class:open={sideNavExpanded()}>
318
+ <div class="drawer-content" class:visible={sideNavExpanded()}>
319
+ <div class="logo-container">
320
+ <div class="logo-wrapper">
321
+ <div class="logo-inner">
322
+ <div class="logo-component padding-left-2">
323
+ <Logo width={75} height={22} />
324
+ </div>
325
+ </div>
326
+ </div>
327
+ </div>
328
+ <nav aria-label="Sub navigation">
329
+ {@render children?.()}
330
+ </nav>
331
+ </div>
332
+ </div>
333
+ </div>
334
+
335
+ <style>
336
+ .expanded-content.drawer-anim {
337
+ width: 0;
338
+ min-width: 0;
339
+ max-width: 0;
340
+ overflow: hidden;
341
+ opacity: 0.7;
342
+ pointer-events: none;
343
+ transition:
344
+ width 0.4s cubic-bezier(.4,0,.2,1),
345
+ min-width 0.4s cubic-bezier(.4,0,.2,1),
346
+ max-width 0.4s cubic-bezier(.4,0,.2,1),
347
+ opacity 0.2s;
348
+ }
349
+ .expanded-content.drawer-anim.open {
350
+ width: 12.5rem;
351
+ min-width: 12.5rem;
352
+ max-width: 12.5rem;
353
+ opacity: 1;
354
+ pointer-events: auto;
355
+ }
356
+ .drawer-content {
357
+ opacity: 0;
358
+ transform: translateX(-10px);
359
+ transition:
360
+ opacity 0.4s 0.20s cubic-bezier(.4,0,.2,1),
361
+ transform 0.4s 0.20s cubic-bezier(.4,0,.2,1);
362
+ }
363
+ .drawer-content.visible {
364
+ opacity: 1;
365
+ transform: translateX(0);
366
+ transition:
367
+ opacity 0.4s 0.20s cubic-bezier(.4,0,.2,1),
368
+ transform 0.4s 0.20s cubic-bezier(.4,0,.2,1);
369
+ }
370
+ .expanded-content.drawer-anim:not(.open) .drawer-content {
371
+ transition:
372
+ opacity 0.20s cubic-bezier(.4,0,.2,1),
373
+ transform 0.20s cubic-bezier(.4,0,.2,1);
374
+ }
375
+ </style>
@@ -0,0 +1,16 @@
1
+ ---
2
+ let gtmCode = '';
3
+ if (Astro.props.gtmID) {
4
+ gtmCode = `<!-- Google tag (gtag.js) -->
5
+ <script async src="https://www.googletagmanager.com/gtag/js?id=${Astro.props.gtmID}"></script>
6
+ <script>
7
+ window.dataLayer = window.dataLayer || [];
8
+ function gtag(){dataLayer.push(arguments);}
9
+ gtag('js', new Date());
10
+
11
+ gtag('config', '${Astro.props.gtmID}');
12
+ </script>`;
13
+ }
14
+ ---
15
+
16
+ <Fragment set:html={gtmCode} />
@@ -2,8 +2,8 @@
2
2
  export let isInverted: boolean = false;
3
3
  export let width: number | undefined;
4
4
  export let height: number | undefined;
5
- const defaultWidth = 105;
6
- const defaultHeight = 33;
5
+ const defaultWidth = 102;
6
+ const defaultHeight = 30;
7
7
  width = width || defaultWidth;
8
8
  height = height || defaultHeight;
9
9
 
@@ -13,7 +13,7 @@
13
13
  <svg
14
14
  {width}
15
15
  {height}
16
- viewBox="0 0 105 33"
16
+ viewBox="0 0 102 30"
17
17
  fill="none"
18
18
  xmlns="http://www.w3.org/2000/svg"
19
19
  style="fill: {logoColor};"
@@ -21,14 +21,15 @@
21
21
  >
22
22
  <g>
23
23
  <path
24
- d="M13.4937 19.864H26.5932C25.3427 24.7736 21.4655 27.9119 16.5655 27.9119C10.167 27.9119 4.96116 22.7532 4.96116 16.4125C4.96116 10.0717 10.1636 4.91296 16.5621 4.91296C20.1777 4.91296 23.5248 6.53939 25.7437 9.37806L27.2626 11.321L31.184 8.31061L29.665 6.36765C26.4981 2.3201 21.7238 0 16.5621 0C7.43155 0 0 7.36102 0 16.4125C0 25.4639 7.43155 32.8249 16.5621 32.8249C24.9553 32.8249 31.4218 26.4 31.9383 17.5506L32.0913 14.951H13.4937V19.864Z"
24
+ d="M57.5791 20.4177C57.5791 15.5645 53.7309 11.6187 48.9988 11.6187H40.188C38.6688 11.6187 37.4304 10.3567 37.4304 8.80197C37.4304 7.24729 38.6658 5.98522 40.188 5.98522H56.2373V0H38.8757C34.1466 0 30.2983 3.94877 30.2983 8.80197C30.2983 13.6552 34.1466 17.6039 38.8757 17.6039H47.6865C49.2057 17.6039 50.4442 18.866 50.4442 20.4207C50.4442 21.9754 49.2087 23.2374 47.6865 23.2374H31.0284V29.2197H48.9988C53.7309 29.2197 57.5791 25.2709 57.5791 20.4207V20.4177Z"
25
25
  />
26
26
  <path
27
- d="M52.9238 13.9544H44.0379C41.6559 13.9544 39.7156 12.035 39.7156 9.67113C39.7156 7.30725 41.6559 5.38786 44.0379 5.38786H61.0282V0.478271H44.0379C38.9204 0.478271 34.7578 4.60327 34.7578 9.6745C34.7578 14.7457 38.9204 18.8707 44.0379 18.8707H52.9238C55.3059 18.8707 57.2462 20.7901 57.2462 23.154C57.2462 25.5179 55.3059 27.4373 52.9238 27.4373H35.4V32.3502H52.9238C58.0413 32.3502 62.2039 28.2252 62.2039 23.154C62.2039 18.0828 58.0413 13.9578 52.9238 13.9578V13.9544Z"
27
+ d="M82.1466 24.7153L82.188 24.7685L83.6747 29.2197H90.6914L80.917 0H68.3525L58.5781 29.2167H65.5949L67.0993 24.7153H82.1466ZM69.186 18.4818L73.3653 5.98522H75.9013L75.919 6.03547L80.1161 18.5793H69.1535L69.186 18.4818Z"
28
28
  />
29
- <path d="M104.809 0.478271H99.8516V32.3468H104.809V0.478271Z" />
30
29
  <path
31
- d="M76.2292 0.478271L62.7559 32.3536H68.1316L79.5287 5.39123H79.9874L91.3811 32.3536H96.7568L83.2835 0.478271H76.2292Z"
30
+ d="M27.198 0H20.5626V15.8158C20.5626 21.4345 16.9626 23.4355 13.5961 23.4355C9.03842 23.4355 6.62956 20.799 6.62956 15.8158V0H0V17.0069C0 20.6985 1.46305 24.0532 4.11724 26.4532C6.64729 28.7409 10.0138 30 13.599 30C20.3586 30 27.198 25.5369 27.198 17.0039V0Z"
32
31
  />
32
+ <path d="M101.708 9.78915H93.8662V29.2197H101.708V9.78915Z" />
33
+ <path d="M101.823 0H93.8662V5.98227H101.823V0Z" />
33
34
  </g>
34
35
  </svg>
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ import { selectedItem, navigationStore } from '../stores/navigationStore.js';
3
+ import type { NavigationItem } from '../stores/navigationStore.js';
4
+ import DiscoverIcon from './icons/DiscoverIcon.svelte';
5
+ import ChatIcon from './icons/ChatIcon.svelte';
6
+ import ConsoleIcon from './icons/ConsoleIcon.svelte';
7
+ import ApiIcon from './icons/ApiIcon.svelte';
8
+
9
+ export let onNavItemClick: ((data: { item: NavigationItem; expanded: boolean }) => void) | undefined = undefined;
10
+
11
+ const navItems = [
12
+ { id: 'discover', label: 'Discover', icon: DiscoverIcon, href: '/discover' },
13
+ { id: 'chat', label: 'Chat', icon: ChatIcon, href: '/' },
14
+ { id: 'console', label: 'Console', icon: ConsoleIcon, href: '/' },
15
+ { id: 'api', label: 'API', icon: ApiIcon, href: '/api' }
16
+ ] as const;
17
+
18
+ function handleNavItemClick(item: NavigationItem) {
19
+ console.log('[MobileBottomNav] handleNavItemClick called for:', item, 'at:', Date.now());
20
+
21
+ // Set navigation flag immediately to prevent header restoration
22
+ if (item !== 'discover' && typeof window !== 'undefined') {
23
+ sessionStorage.setItem('navigatingToMainNav', 'true');
24
+ console.log('[MobileBottomNav] Set navigation flag for:', item, 'at:', Date.now());
25
+ }
26
+
27
+ navigationStore.setSelectedItem(item);
28
+
29
+ // Clear header state for main navigation items (including discover)
30
+ if (item !== 'discover') {
31
+ try {
32
+ import('@gsa-tts/graymatter-ui/stores/navigationStore').then(({ selectedSubNavItemTitle }) => {
33
+ // Clear the store value immediately
34
+ selectedSubNavItemTitle.set('');
35
+ if (typeof window !== 'undefined') {
36
+ sessionStorage.removeItem('subNav-selectedTitle');
37
+ sessionStorage.removeItem('subNav-selectedId');
38
+ }
39
+
40
+ // Set default header from first submenu item after a short delay
41
+ setTimeout(() => {
42
+ const mainContent = document.getElementById('main-content');
43
+ if (mainContent) {
44
+ const firstSection = mainContent.querySelector('h2[id], h3[id], h4[id], h5[id], h6[id]');
45
+ if (firstSection && firstSection.textContent) {
46
+ const firstSectionTitle = firstSection.textContent.trim();
47
+ console.log('[MobileBottomNav] Setting default header for', item, ':', firstSectionTitle);
48
+ selectedSubNavItemTitle.set(firstSectionTitle);
49
+ if (typeof window !== 'undefined') {
50
+ sessionStorage.setItem('subNav-selectedTitle', JSON.stringify(firstSectionTitle));
51
+ }
52
+ }
53
+ }
54
+ }, 200); // Delay to ensure page content is loaded
55
+ }).catch((err) => {
56
+ console.log('[MobileBottomNav] Error clearing header state:', err);
57
+ });
58
+ } catch (err) {
59
+ console.log('[MobileBottomNav] Import failed for clearing state:', err);
60
+ }
61
+ } else {
62
+ // Clear header state for Discover page
63
+ try {
64
+ import('@gsa-tts/graymatter-ui/stores/navigationStore').then(({ selectedSubNavItemTitle }) => {
65
+ selectedSubNavItemTitle.set('');
66
+ if (typeof window !== 'undefined') {
67
+ sessionStorage.removeItem('subNav-selectedTitle');
68
+ sessionStorage.removeItem('subNav-selectedId');
69
+ }
70
+ }).catch((err) => {
71
+ console.log('[MobileBottomNav] Error clearing header state for Discover:', err);
72
+ });
73
+ } catch (err) {
74
+ console.log('[MobileBottomNav] Import failed for clearing Discover state:', err);
75
+ }
76
+ }
77
+
78
+ onNavItemClick?.({ item, expanded: false });
79
+ }
80
+ </script>
81
+
82
+ <nav class="mobile-bottom-nav">
83
+ {#each navItems as navItem}
84
+ <a
85
+ href={navItem.href}
86
+ class="nav-item"
87
+ class:active={$selectedItem === navItem.id}
88
+ on:click={() => handleNavItemClick(navItem.id)}
89
+ >
90
+ <div class="icon">
91
+ <svelte:component
92
+ this={navItem.icon}
93
+ isActive={$selectedItem === navItem.id}
94
+ />
95
+ </div>
96
+ <span class="label">{navItem.label}</span>
97
+ </a>
98
+ {/each}
99
+ </nav>