@gsa-tts/graymatter-ui 0.0.2 → 0.2.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/assets/images/ai-icons/chevron-up-down.svg +4 -0
- package/dist/graymatter-ui.js +4082 -0
- package/dist/graymatter-ui.umd.cjs +40 -0
- package/index.ts +3 -0
- package/package.json +10 -5
- package/src/components/Button.svelte +11 -23
- package/src/components/Button.webcomponent.svelte +44 -0
- package/src/components/DesktopHeader.svelte +101 -0
- package/src/components/DesktopHeader.webcomponent.svelte +17 -0
- package/src/components/DesktopSideNav.svelte +645 -0
- package/src/components/DesktopSideNav.webcomponent.svelte +19 -0
- package/src/components/GoogleTagManager.astro +16 -0
- package/src/components/Logo.svelte +8 -7
- package/src/components/Logo.webcomponent.svelte +26 -0
- package/src/components/MobileBottomNav.svelte +145 -0
- package/src/components/MobileBottomNav.webcomponent.svelte +14 -0
- package/src/components/MobileSideMenu.svelte +225 -0
- package/src/components/MobileSideMenu.webcomponent.svelte +11 -0
- package/src/components/MobileTopNav.svelte +235 -0
- package/src/components/MobileTopNav.webcomponent.svelte +14 -0
- package/src/components/NavigationInitializer.svelte +19 -0
- package/src/components/icons/ApiIcon.svelte +49 -0
- package/src/components/icons/ChatIcon.svelte +27 -0
- package/src/components/icons/CloseIcon.svelte +18 -0
- package/src/components/icons/ConsoleIcon.svelte +25 -0
- package/src/components/icons/DiscoverIcon.svelte +30 -0
- package/src/components/icons/MenuIcon.svelte +38 -0
- package/src/components/icons/index.ts +6 -0
- package/src/components/index.ts +9 -0
- package/src/index.ts +16 -0
- package/src/layouts/AppLayout.astro +1 -1
- package/src/layouts/BaseLayout.astro +4 -0
- package/src/layouts/GlobalAppLayout.astro +594 -0
- package/src/stores/navigationStore.ts +138 -0
- package/src/stores/subNavReadyStore.ts +3 -0
- package/src/styles/base/global.css +8 -1
- package/src/styles/base/typography.css +15 -14
- package/src/styles/components/globalAppLayout.css +73 -0
- package/src/styles/components/globalAppNav.css +95 -0
- package/src/utils/getAppUrls.ts +29 -0
- package/src/utils/index.ts +1 -0
- package/assets/images/gsai-logo-black.svg +0 -18
- package/assets/images/gsai-logo-white.svg +0 -18
|
@@ -0,0 +1,645 @@
|
|
|
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 {
|
|
10
|
+
isExpanded,
|
|
11
|
+
selectedItem,
|
|
12
|
+
isMenuButtonDisabled,
|
|
13
|
+
navigationStore,
|
|
14
|
+
type NavigationItem,
|
|
15
|
+
} from '../stores/navigationStore.js';
|
|
16
|
+
import DiscoverIcon from './icons/DiscoverIcon.svelte';
|
|
17
|
+
import ChatIcon from './icons/ChatIcon.svelte';
|
|
18
|
+
import ConsoleIcon from './icons/ConsoleIcon.svelte';
|
|
19
|
+
import ApiIcon from './icons/ApiIcon.svelte';
|
|
20
|
+
import MenuIcon from './icons/MenuIcon.svelte';
|
|
21
|
+
import { getAppUrls } from '../utils/getAppUrls';
|
|
22
|
+
const urls = getAppUrls();
|
|
23
|
+
|
|
24
|
+
const { ssrSelectedItem = undefined, onNavToggle = undefined, onNavItemClick = undefined, onProfileClick = undefined, children } = $props();
|
|
25
|
+
|
|
26
|
+
let hydrated = false;
|
|
27
|
+
let navContainer: HTMLDivElement;
|
|
28
|
+
let hoveredItem = $state<string | null>(null);
|
|
29
|
+
let menuButtonHovered = $state(false);
|
|
30
|
+
let canHover = $state(false);
|
|
31
|
+
|
|
32
|
+
function ssrOrHydrated<T>(hydratedValue: T, ssrValue: T): T {
|
|
33
|
+
return hydrated ? hydratedValue : ssrValue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isNavSelected(key: NavigationItem) {
|
|
37
|
+
return ssrOrHydrated($selectedItem === key, ssrSelectedItem === key);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isNavHovered(key: string) {
|
|
41
|
+
return hoveredItem === key;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sideNavExpanded = $derived(() =>
|
|
45
|
+
ssrOrHydrated($isExpanded, ssrSelectedItem ? ssrSelectedItem !== 'discover' : true)
|
|
46
|
+
);
|
|
47
|
+
const menuBtnDisabled = $derived(() =>
|
|
48
|
+
ssrOrHydrated($isMenuButtonDisabled, ssrSelectedItem ? ssrSelectedItem === 'discover' : false)
|
|
49
|
+
);
|
|
50
|
+
const menuBtnOpen = $derived(() =>
|
|
51
|
+
ssrOrHydrated($isExpanded, ssrSelectedItem ? ssrSelectedItem !== 'discover' : true)
|
|
52
|
+
);
|
|
53
|
+
const menuBtnHovered = $derived(() =>
|
|
54
|
+
canHover && menuButtonHovered && !menuBtnDisabled()
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
onMount(() => {
|
|
58
|
+
hydrated = true;
|
|
59
|
+
// Remove the CSS class since reactive state takes over
|
|
60
|
+
document.documentElement.classList.remove('nav-initially-expanded');
|
|
61
|
+
// Remove the custom property since reactive CSS takes over
|
|
62
|
+
document.documentElement.style.removeProperty('--initial-main-top-padding');
|
|
63
|
+
// Add hydrated class to re-enable transitions
|
|
64
|
+
if (navContainer) {
|
|
65
|
+
navContainer.classList.add('hydrated');
|
|
66
|
+
}
|
|
67
|
+
// Detect if device supports hover (desktop only)
|
|
68
|
+
if (typeof window !== 'undefined') {
|
|
69
|
+
const mq = window.matchMedia('(hover: hover)');
|
|
70
|
+
canHover = mq.matches;
|
|
71
|
+
mq.addEventListener('change', e => {
|
|
72
|
+
canHover = e.matches;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Listen for layout transitions
|
|
76
|
+
const handleLayoutTransition = (event: Event) => {
|
|
77
|
+
const customEvent = event as CustomEvent;
|
|
78
|
+
const { isMobile } = customEvent.detail;
|
|
79
|
+
|
|
80
|
+
if (isMobile) {
|
|
81
|
+
// Switching to mobile: close navigation
|
|
82
|
+
navigationStore.setExpanded(false);
|
|
83
|
+
} else {
|
|
84
|
+
// Switching to desktop: open navigation if on non-discover page
|
|
85
|
+
const currentItem = $selectedItem;
|
|
86
|
+
|
|
87
|
+
if (currentItem && currentItem !== 'discover') {
|
|
88
|
+
navigationStore.setExpanded(true);
|
|
89
|
+
} else {
|
|
90
|
+
navigationStore.setExpanded(false);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
window.addEventListener('layoutTransition', handleLayoutTransition);
|
|
96
|
+
|
|
97
|
+
return () => {
|
|
98
|
+
window.removeEventListener('layoutTransition', handleLayoutTransition);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
function toggleNav() {
|
|
104
|
+
if ($isMenuButtonDisabled) return;
|
|
105
|
+
navigationStore.toggleExpanded();
|
|
106
|
+
onNavToggle?.({ expanded: $isExpanded });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handleNavItemClick(item: NavigationItem) {
|
|
110
|
+
// Set navigation flag immediately to prevent header restoration
|
|
111
|
+
if (item !== 'discover' && typeof window !== 'undefined') {
|
|
112
|
+
sessionStorage.setItem('navigatingToMainNav', 'true');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
navigationStore.setSelectedItem(item);
|
|
116
|
+
|
|
117
|
+
// Clear header state for main navigation items (including discover)
|
|
118
|
+
if (item !== 'discover') {
|
|
119
|
+
try {
|
|
120
|
+
import('@gsa-tts/graymatter-ui/stores/navigationStore').then(({ selectedSubNavItemTitle }) => {
|
|
121
|
+
// Clear the store value immediately
|
|
122
|
+
selectedSubNavItemTitle.set('');
|
|
123
|
+
if (typeof window !== 'undefined') {
|
|
124
|
+
sessionStorage.removeItem('subNav-selectedTitle');
|
|
125
|
+
sessionStorage.removeItem('subNav-selectedId');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Set default header from first submenu item after a short delay
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
const mainContent = document.getElementById('main-content');
|
|
131
|
+
if (mainContent) {
|
|
132
|
+
const firstSection = mainContent.querySelector('h2[id], h3[id], h4[id], h5[id], h6[id]');
|
|
133
|
+
if (firstSection && firstSection.textContent) {
|
|
134
|
+
const firstSectionTitle = firstSection.textContent.trim();
|
|
135
|
+
selectedSubNavItemTitle.set(firstSectionTitle);
|
|
136
|
+
if (typeof window !== 'undefined') {
|
|
137
|
+
sessionStorage.setItem('subNav-selectedTitle', JSON.stringify(firstSectionTitle));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, 200); // Delay to ensure page content is loaded
|
|
142
|
+
}).catch((err) => {
|
|
143
|
+
console.log('[DesktopSideNav] Error clearing header state:', err);
|
|
144
|
+
});
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.log('[DesktopSideNav] Import failed for clearing state:', err);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
// Clear header state for Discover page
|
|
150
|
+
try {
|
|
151
|
+
import('@gsa-tts/graymatter-ui/stores/navigationStore').then(({ selectedSubNavItemTitle }) => {
|
|
152
|
+
selectedSubNavItemTitle.set('');
|
|
153
|
+
if (typeof window !== 'undefined') {
|
|
154
|
+
sessionStorage.removeItem('subNav-selectedTitle');
|
|
155
|
+
sessionStorage.removeItem('subNav-selectedId');
|
|
156
|
+
}
|
|
157
|
+
}).catch((err) => {
|
|
158
|
+
console.log('[DesktopSideNav] Error clearing header state for Discover:', err);
|
|
159
|
+
});
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.log('[DesktopSideNav] Import failed for clearing Discover state:', err);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
onNavItemClick?.({ item, expanded: $isExpanded });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handleKeyDown(event: KeyboardEvent, item: NavigationItem) {
|
|
169
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
handleNavItemClick(item);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
<div
|
|
177
|
+
class="side-nav-container"
|
|
178
|
+
class:expanded={sideNavExpanded()}
|
|
179
|
+
bind:this={navContainer}
|
|
180
|
+
part="side-nav-container"
|
|
181
|
+
>
|
|
182
|
+
<div class="nav-content">
|
|
183
|
+
<div class="nav-main" part="nav-main">
|
|
184
|
+
<div class="menu-section">
|
|
185
|
+
<button
|
|
186
|
+
class="menu-button"
|
|
187
|
+
class:disabled={$isMenuButtonDisabled}
|
|
188
|
+
disabled={$isMenuButtonDisabled}
|
|
189
|
+
onclick={toggleNav}
|
|
190
|
+
onmouseenter={() => (menuButtonHovered = true)}
|
|
191
|
+
onmouseleave={() => (menuButtonHovered = false)}
|
|
192
|
+
aria-label="Open navigation menu"
|
|
193
|
+
>
|
|
194
|
+
<span class="sr-only">Open navigation menu</span>
|
|
195
|
+
<div class="icon-container">
|
|
196
|
+
<div class="icon-wrapper">
|
|
197
|
+
<MenuIcon
|
|
198
|
+
isHovered={menuBtnHovered()}
|
|
199
|
+
isDisabled={menuBtnDisabled()}
|
|
200
|
+
isOpen={menuBtnOpen()}
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<div class="nav-items" part="nav-items">
|
|
208
|
+
<a
|
|
209
|
+
href={urls.discover}
|
|
210
|
+
class="nav-item discover-item"
|
|
211
|
+
class:selected={isNavSelected('discover')}
|
|
212
|
+
class:hovered={isNavHovered('discover')}
|
|
213
|
+
role="button"
|
|
214
|
+
tabindex="0"
|
|
215
|
+
onclick={() => handleNavItemClick('discover')}
|
|
216
|
+
onkeydown={e => handleKeyDown(e, 'discover')}
|
|
217
|
+
onmouseenter={() => (hoveredItem = 'discover')}
|
|
218
|
+
onmouseleave={() => (hoveredItem = null)}
|
|
219
|
+
>
|
|
220
|
+
<div class="icon-container">
|
|
221
|
+
<div class="icon-wrapper">
|
|
222
|
+
<DiscoverIcon isSelected={isNavSelected('discover')} />
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
<span class="nav-text">Discover</span>
|
|
226
|
+
</a>
|
|
227
|
+
<a
|
|
228
|
+
href={urls.chat}
|
|
229
|
+
class="nav-item chat-item"
|
|
230
|
+
class:selected={isNavSelected('chat')}
|
|
231
|
+
class:hovered={isNavHovered('chat')}
|
|
232
|
+
role="button"
|
|
233
|
+
tabindex="0"
|
|
234
|
+
onclick={() => handleNavItemClick('chat')}
|
|
235
|
+
onkeydown={e => handleKeyDown(e, 'chat')}
|
|
236
|
+
onmouseenter={() => (hoveredItem = 'chat')}
|
|
237
|
+
onmouseleave={() => (hoveredItem = null)}
|
|
238
|
+
>
|
|
239
|
+
<div class="icon-container">
|
|
240
|
+
<div class="icon-wrapper">
|
|
241
|
+
<ChatIcon isSelected={isNavSelected('chat') as boolean} />
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
<span class="nav-text">Chat</span>
|
|
245
|
+
</a>
|
|
246
|
+
|
|
247
|
+
<a
|
|
248
|
+
href={urls.console}
|
|
249
|
+
class="nav-item console-item"
|
|
250
|
+
class:selected={isNavSelected('console')}
|
|
251
|
+
class:hovered={isNavHovered('console')}
|
|
252
|
+
role="button"
|
|
253
|
+
tabindex="0"
|
|
254
|
+
onclick={() => handleNavItemClick('console')}
|
|
255
|
+
onkeydown={e => handleKeyDown(e, 'console')}
|
|
256
|
+
onmouseenter={() => (hoveredItem = 'console')}
|
|
257
|
+
onmouseleave={() => (hoveredItem = null)}
|
|
258
|
+
>
|
|
259
|
+
<div class="icon-container">
|
|
260
|
+
<div class="icon-wrapper">
|
|
261
|
+
<ConsoleIcon isSelected={isNavSelected('console') as boolean} />
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
<span class="nav-text">Console</span>
|
|
265
|
+
</a>
|
|
266
|
+
|
|
267
|
+
<a
|
|
268
|
+
href={urls.api}
|
|
269
|
+
class="nav-item api-item"
|
|
270
|
+
class:selected={isNavSelected('api')}
|
|
271
|
+
class:hovered={isNavHovered('api')}
|
|
272
|
+
role="button"
|
|
273
|
+
tabindex="0"
|
|
274
|
+
onclick={() => handleNavItemClick('api')}
|
|
275
|
+
onkeydown={e => handleKeyDown(e, 'api')}
|
|
276
|
+
onmouseenter={() => (hoveredItem = 'api')}
|
|
277
|
+
onmouseleave={() => (hoveredItem = null)}
|
|
278
|
+
>
|
|
279
|
+
<div class="icon-container">
|
|
280
|
+
<div class="icon-wrapper">
|
|
281
|
+
<ApiIcon isSelected={isNavSelected('api')} />
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
<span class="nav-text">API</span>
|
|
285
|
+
</a>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div class="nav-footer" part="nav-footer">
|
|
290
|
+
<button
|
|
291
|
+
class="user-profile-button"
|
|
292
|
+
tabindex="0"
|
|
293
|
+
onclick={() => onProfileClick?.()}
|
|
294
|
+
onkeydown={e => {
|
|
295
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
296
|
+
e.preventDefault();
|
|
297
|
+
onProfileClick?.();
|
|
298
|
+
}
|
|
299
|
+
}}
|
|
300
|
+
>
|
|
301
|
+
<div class="user-profile-container">
|
|
302
|
+
<div class="user-profile-icon" part="user-profile-icon">
|
|
303
|
+
<span class="user-initials">ZW</span>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
</button>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<div class="expanded-content drawer-anim" class:open={sideNavExpanded()} part="expanded-content">
|
|
311
|
+
{#if sideNavExpanded()}
|
|
312
|
+
<div class="drawer-content" part="drawer-content">
|
|
313
|
+
<div class="logo-placeholder"></div>
|
|
314
|
+
<nav aria-label="Sub navigation">
|
|
315
|
+
{@render children?.()}
|
|
316
|
+
</nav>
|
|
317
|
+
</div>
|
|
318
|
+
{/if}
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
<style>
|
|
323
|
+
.expanded-content.drawer-anim {
|
|
324
|
+
max-width: 0;
|
|
325
|
+
overflow: hidden;
|
|
326
|
+
pointer-events: none;
|
|
327
|
+
transition:
|
|
328
|
+
max-width 0.2s cubic-bezier(.4, 0, .2, 1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.expanded-content.drawer-anim.open {
|
|
332
|
+
max-width: 12.5rem;
|
|
333
|
+
pointer-events: auto;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.drawer-content {
|
|
337
|
+
width: 12.5rem;
|
|
338
|
+
height: 100%;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.logo-placeholder {
|
|
342
|
+
height: var(--ai-size-72);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.sr-only {
|
|
346
|
+
position: absolute;
|
|
347
|
+
width: 1px;
|
|
348
|
+
height: 1px;
|
|
349
|
+
padding: 0;
|
|
350
|
+
margin: -1px;
|
|
351
|
+
overflow: hidden;
|
|
352
|
+
clip: rect(0, 0, 0, 0);
|
|
353
|
+
border: 0;
|
|
354
|
+
white-space: nowrap;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.user-profile-container,
|
|
358
|
+
.user-profile-icon {
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: center;
|
|
361
|
+
justify-content: center;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.menu-button {
|
|
365
|
+
display: flex;
|
|
366
|
+
align-items: center;
|
|
367
|
+
justify-content: center;
|
|
368
|
+
border: none;
|
|
369
|
+
background: none;
|
|
370
|
+
cursor: pointer;
|
|
371
|
+
border-radius: var(--ai-size-8);
|
|
372
|
+
transition: background-color 0.2s ease;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.menu-button:disabled,
|
|
376
|
+
.menu-button.disabled {
|
|
377
|
+
cursor: not-allowed;
|
|
378
|
+
opacity: 0.5;
|
|
379
|
+
background-color: transparent;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.user-profile-icon {
|
|
383
|
+
border-radius: var(--ai-size-6);
|
|
384
|
+
background: var(--ai-color-steel-700);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.user-initials {
|
|
388
|
+
color: var(--ai-color-white);
|
|
389
|
+
text-align: center;
|
|
390
|
+
font-weight: 400;
|
|
391
|
+
line-height: 1.3;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.side-nav-container {
|
|
395
|
+
display: flex;
|
|
396
|
+
flex-shrink: 0;
|
|
397
|
+
position: relative;
|
|
398
|
+
z-index: var(--ai-layer-2);
|
|
399
|
+
height: 100%;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.side-nav-container.expanded {
|
|
403
|
+
width: 16.875rem;
|
|
404
|
+
min-width: 12.5rem;
|
|
405
|
+
max-width: 16.875rem;
|
|
406
|
+
height: 100%;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.expanded-content {
|
|
410
|
+
display: flex;
|
|
411
|
+
width: 12.5rem;
|
|
412
|
+
opacity: 1;
|
|
413
|
+
flex: none;
|
|
414
|
+
height: 100%;
|
|
415
|
+
background: var(--ai-color-steel-100);
|
|
416
|
+
border-right: 0.0625rem solid var(--ai-color-steel-100);
|
|
417
|
+
overflow: hidden;
|
|
418
|
+
flex-direction: column;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.side-nav-container.expanded .expanded-content {
|
|
422
|
+
width: 12.5rem;
|
|
423
|
+
opacity: 1;
|
|
424
|
+
overflow-y: auto;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.side-nav-container:not(.expanded) .expanded-content {
|
|
428
|
+
width: 0;
|
|
429
|
+
opacity: 0;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.nav-content {
|
|
433
|
+
display: flex;
|
|
434
|
+
width: var(--ai-size-72);
|
|
435
|
+
height: 100%;
|
|
436
|
+
padding-bottom: var(--ai-size-16);
|
|
437
|
+
flex-direction: column;
|
|
438
|
+
align-items: flex-start;
|
|
439
|
+
gap: var(--ai-size-4);
|
|
440
|
+
flex: none;
|
|
441
|
+
background: var(--ai-color-steel-200);
|
|
442
|
+
border-right: 0.0625rem solid var(--ai-color-steel-200);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.nav-main {
|
|
446
|
+
display: flex;
|
|
447
|
+
flex-direction: column;
|
|
448
|
+
align-items: flex-start;
|
|
449
|
+
gap: var(--ai-size-4);
|
|
450
|
+
flex: 1 0 0;
|
|
451
|
+
align-self: stretch;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.nav-footer {
|
|
455
|
+
display: flex;
|
|
456
|
+
padding: var(--ai-size-8) var(--ai-size-4);
|
|
457
|
+
flex-direction: column;
|
|
458
|
+
justify-content: center;
|
|
459
|
+
align-items: center;
|
|
460
|
+
gap: var(--ai-size-4);
|
|
461
|
+
align-self: stretch;
|
|
462
|
+
margin-top: auto;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.user-profile-button {
|
|
466
|
+
background: none;
|
|
467
|
+
border: none;
|
|
468
|
+
padding: 0;
|
|
469
|
+
cursor: pointer;
|
|
470
|
+
display: flex;
|
|
471
|
+
justify-content: center;
|
|
472
|
+
align-items: center;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.user-profile-button:focus {
|
|
476
|
+
outline: var(--ai-size-2) solid var(--ai-color-blue-600);
|
|
477
|
+
outline-offset: var(--ai-size-2);
|
|
478
|
+
border-radius: var(--ai-size-4);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.user-profile-container {
|
|
482
|
+
width: var(--ai-size-40);
|
|
483
|
+
height: var(--ai-size-40);
|
|
484
|
+
aspect-ratio: 1/1;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.user-profile-icon {
|
|
488
|
+
padding: 0.5556rem 0.4931rem 0.5694rem 0.3819rem;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.user-initials {
|
|
492
|
+
font-size: var(--ai-size-16);
|
|
493
|
+
font-style: normal;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.menu-section {
|
|
497
|
+
display: flex;
|
|
498
|
+
padding: var(--ai-size-4);
|
|
499
|
+
justify-content: center;
|
|
500
|
+
align-items: center;
|
|
501
|
+
align-self: stretch;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.menu-button {
|
|
505
|
+
display: flex;
|
|
506
|
+
padding: var(--ai-size-4);
|
|
507
|
+
justify-content: center;
|
|
508
|
+
align-items: center;
|
|
509
|
+
background: none;
|
|
510
|
+
border: none;
|
|
511
|
+
cursor: pointer;
|
|
512
|
+
opacity: 1;
|
|
513
|
+
transition: opacity 0.2s ease;
|
|
514
|
+
will-change: opacity;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.menu-button.disabled {
|
|
518
|
+
cursor: not-allowed;
|
|
519
|
+
opacity: 0.5;
|
|
520
|
+
will-change: opacity;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.menu-button:focus,
|
|
524
|
+
.nav-item:focus {
|
|
525
|
+
outline: none;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.menu-button:focus .icon-container,
|
|
529
|
+
.nav-item:focus .icon-container {
|
|
530
|
+
outline: var(--ai-size-2) solid var(--ai-color-blue-600);
|
|
531
|
+
outline-offset: var(--ai-size-2);
|
|
532
|
+
border-radius: var(--ai-size-4);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.nav-items {
|
|
536
|
+
display: flex;
|
|
537
|
+
width: var(--ai-size-72);
|
|
538
|
+
flex-direction: column;
|
|
539
|
+
align-items: flex-start;
|
|
540
|
+
gap: var(--ai-size-4);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.nav-item {
|
|
544
|
+
display: flex;
|
|
545
|
+
height: 4.125rem;
|
|
546
|
+
padding: var(--ai-size-4);
|
|
547
|
+
flex-direction: column;
|
|
548
|
+
justify-content: center;
|
|
549
|
+
align-items: center;
|
|
550
|
+
gap: var(--ai-size-2);
|
|
551
|
+
align-self: stretch;
|
|
552
|
+
cursor: pointer;
|
|
553
|
+
text-decoration: none;
|
|
554
|
+
color: inherit;
|
|
555
|
+
position: relative;
|
|
556
|
+
flex-shrink: 0;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.icon-container {
|
|
560
|
+
display: flex;
|
|
561
|
+
width: var(--ai-size-40);
|
|
562
|
+
height: var(--ai-size-40);
|
|
563
|
+
padding: var(--ai-size-8);
|
|
564
|
+
justify-content: center;
|
|
565
|
+
align-items: center;
|
|
566
|
+
flex-shrink: 0;
|
|
567
|
+
position: relative;
|
|
568
|
+
contain: layout style;
|
|
569
|
+
transition: background-color 0.3s ease,
|
|
570
|
+
border-radius 0.3s ease;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.icon-wrapper {
|
|
574
|
+
display: flex;
|
|
575
|
+
width: var(--ai-size-28);
|
|
576
|
+
height: var(--ai-size-28);
|
|
577
|
+
justify-content: center;
|
|
578
|
+
align-items: center;
|
|
579
|
+
flex-shrink: 0;
|
|
580
|
+
aspect-ratio: 1/1;
|
|
581
|
+
position: relative;
|
|
582
|
+
overflow: hidden;
|
|
583
|
+
transition: transform 0.3s ease;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.icon-wrapper :global(svg) {
|
|
587
|
+
width: var(--ai-size-24);
|
|
588
|
+
height: var(--ai-size-24);
|
|
589
|
+
flex-shrink: 0;
|
|
590
|
+
position: absolute;
|
|
591
|
+
top: 50%;
|
|
592
|
+
left: 50%;
|
|
593
|
+
transform: translate(-50%, -50%);
|
|
594
|
+
transition: all 0.3s ease;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.icon-wrapper :global(svg path) {
|
|
598
|
+
stroke-width: 1.5;
|
|
599
|
+
vector-effect: non-scaling-stroke;
|
|
600
|
+
transition: stroke 0.3s ease,
|
|
601
|
+
fill 0.3s ease,
|
|
602
|
+
stroke-width 0.3s ease;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.nav-text {
|
|
606
|
+
font-size: var(--ai-size-12);
|
|
607
|
+
text-align: center;
|
|
608
|
+
color: var(--ai-color-neutral-900);
|
|
609
|
+
line-height: 1.3;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.menu-button.disabled:hover .icon-container,
|
|
613
|
+
.menu-button:disabled:hover .icon-container {
|
|
614
|
+
background: none !important;
|
|
615
|
+
border-radius: var(--ai-size-4);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.nav-item.hovered .icon-container,
|
|
619
|
+
.nav-item:active .icon-container,
|
|
620
|
+
.nav-item.selected .icon-container,
|
|
621
|
+
.nav-item.selected.hovered .icon-container,
|
|
622
|
+
.menu-button:hover .icon-container,
|
|
623
|
+
.menu-button:active .icon-container {
|
|
624
|
+
background-color: var(--ai-color-steel-300);
|
|
625
|
+
border-radius: var(--ai-size-4);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.nav-item {
|
|
629
|
+
flex: 1;
|
|
630
|
+
display: flex;
|
|
631
|
+
flex-direction: column;
|
|
632
|
+
align-items: center;
|
|
633
|
+
justify-content: center;
|
|
634
|
+
padding: var(--ai-size-4);
|
|
635
|
+
text-decoration: none;
|
|
636
|
+
color: var(--ai-color-neutral-600);
|
|
637
|
+
transition: color 0.2s ease;
|
|
638
|
+
height: 100%;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.nav-item:hover {
|
|
642
|
+
color: var(--ai-color-neutral-700);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<svelte:options customElement={{
|
|
2
|
+
tag: 'graymatter-desktop-side-nav'
|
|
3
|
+
}} />
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import DesktopSideNav from './DesktopSideNav.svelte';
|
|
7
|
+
|
|
8
|
+
// Props for the web component
|
|
9
|
+
let { ssrSelectedItem, onNavToggle, onNavItemClick, onProfileClick } = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<DesktopSideNav
|
|
13
|
+
{ssrSelectedItem}
|
|
14
|
+
{onNavToggle}
|
|
15
|
+
{onNavItemClick}
|
|
16
|
+
{onProfileClick}
|
|
17
|
+
>
|
|
18
|
+
<slot />
|
|
19
|
+
</DesktopSideNav>
|
|
@@ -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} />
|