@gsa-tts/graymatter-ui 1.3.0 → 2.0.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.
@@ -1,536 +0,0 @@
1
- ---
2
- import BaseLayout from '@gsa-tts/graymatter-ui/layouts/BaseLayout.astro';
3
- import '../styles/base/global.css';
4
- import DesktopSideNav from '../components/DesktopSideNav.svelte';
5
- import DesktopHeader from '../components/DesktopHeader.svelte';
6
- import MobileTopNav from '../components/MobileTopNav.svelte';
7
- import MobileBottomNav from '../components/MobileBottomNav.svelte';
8
- import MobileSideMenu from '../components/MobileSideMenu.svelte';
9
- import Logo from '../components/Logo.svelte';
10
- import { siteName } from '../constants';
11
-
12
- const {
13
- componentOptions = {},
14
- ssrSelectedItem,
15
- gtmID,
16
- showAppIcons = true,
17
- profileMenuData = undefined,
18
- supplementalMenuData = undefined,
19
- logoLinkUrl,
20
- logoLinkLabel,
21
- title: pageTitle,
22
- description,
23
- openGraphImage,
24
- hideDiscover = true,
25
- className = '',
26
- } = Astro.props;
27
-
28
- // Read at build time for static output; prefer PUBLIC_* for safe exposure
29
- const chatUrl =
30
- import.meta.env.PUBLIC_CHAT_URL || process.env.PUBLIC_CHAT_URL || undefined;
31
- const consoleUrl =
32
- import.meta.env.PUBLIC_CONSOLE_URL ||
33
- process.env.PUBLIC_CONSOLE_URL ||
34
- undefined;
35
- const apiDocsUrl =
36
- import.meta.env.PUBLIC_API_DOCS_URL ||
37
- process.env.PUBLIC_API_DOCS_URL ||
38
- undefined;
39
- const discoverUrl =
40
- import.meta.env.PUBLIC_DISCOVER_URL ||
41
- process.env.PUBLIC_DISCOVER_URL ||
42
- undefined;
43
-
44
- const chat = chatUrl || '#';
45
- const consoleApp = consoleUrl || '#';
46
- const api = apiDocsUrl || '#';
47
- const discover = discoverUrl || '#';
48
- ---
49
-
50
- <BaseLayout title={pageTitle} {gtmID} {description} {openGraphImage}>
51
- <div class={`app ${className}`}>
52
- <!-- Desktop Navigation -->
53
- <div class="layout-container">
54
- <nav aria-label="Main navigation">
55
- <DesktopSideNav
56
- client:load
57
- ssrSelectedItem={ssrSelectedItem}
58
- showAppIcons={showAppIcons}
59
- hideDiscover={hideDiscover}
60
- profileMenuData={profileMenuData}
61
- apiUrl={api}
62
- chatUrl={chat}
63
- consoleUrl={consoleApp}
64
- discoverUrl={discover}
65
- {supplementalMenuData}
66
- >
67
- <slot name="sub-nav" />
68
- </DesktopSideNav>
69
- </nav>
70
- <div class="main-container">
71
- <header aria-label="Main site header">
72
- <DesktopHeader client:load />
73
- </header>
74
- <main id="main-content" tabindex="0">
75
- <slot />
76
- </main>
77
- </div>
78
- </div>
79
-
80
- <!-- Mobile Navigation -->
81
- <nav aria-label="Mobile navigation">
82
- <MobileTopNav
83
- client:load
84
- profileMenuData={profileMenuData}
85
- logoLinkUrl={logoLinkUrl}
86
- logoLinkLabel={logoLinkLabel}
87
- {supplementalMenuData}
88
- />
89
- <MobileSideMenu client:load {logoLinkUrl} {logoLinkLabel}>
90
- <slot name="sub-nav" />
91
- </MobileSideMenu>
92
- <MobileBottomNav
93
- client:load
94
- showAppIcons={showAppIcons}
95
- hideDiscover={hideDiscover}
96
- apiUrl={api}
97
- chatUrl={chat}
98
- consoleUrl={consoleApp}
99
- discoverUrl={discover}
100
- />
101
- </nav>
102
-
103
- <!-- Fixed Logo (Desktop only) -->
104
- <nav aria-label="Site logo">
105
- <div class="logo-fixed-container display-none tablet:display-flex">
106
- {
107
- logoLinkUrl ? (
108
- <a
109
- class="display-flex"
110
- href={logoLinkUrl}
111
- aria-label={logoLinkLabel}
112
- >
113
- <Logo width={75} height={22} />
114
- </a>
115
- ) : (
116
- <Logo width={75} height={22} />
117
- )
118
- }
119
- </div>
120
- </nav>
121
-
122
- <!-- Mobile Main Content -->
123
- <main id="main-content-mobile" class="main-content-mobile">
124
- <slot />
125
- </main>
126
- </div>
127
- </BaseLayout>
128
-
129
- <script is:inline>
130
- if (typeof window !== 'undefined') {
131
- // Handle URL hash scrolling immediately to prevent initial render at top
132
- if (window.location.hash) {
133
- const anchor = window.location.hash.substring(1);
134
-
135
- // Set a flag to prevent SubNavMenu from also scrolling
136
- sessionStorage.setItem('globalLayoutScrolling', 'true');
137
-
138
- // Use requestAnimationFrame to scroll as early as possible
139
- requestAnimationFrame(() => {
140
- if (window.innerWidth <= 640) {
141
- // Mobile: scroll in mobile content container
142
- const mobileContent = document.getElementById('main-content-mobile');
143
- if (mobileContent) {
144
- const mobileAnchor = mobileContent.querySelector(
145
- `[id="${anchor}"]`
146
- );
147
- if (mobileAnchor) {
148
- const rect = mobileAnchor.getBoundingClientRect();
149
- const mobileRect = mobileContent.getBoundingClientRect();
150
- const scrollTop =
151
- mobileContent.scrollTop + rect.top - mobileRect.top;
152
- mobileContent.scrollTo({ top: scrollTop, behavior: 'auto' });
153
-
154
- setTimeout(() => {
155
- let topLevelTitle = null;
156
- if (typeof window !== 'undefined') {
157
- topLevelTitle = sessionStorage.getItem(
158
- 'subNav-topLevelTitle'
159
- );
160
- if (topLevelTitle) {
161
- try {
162
- topLevelTitle = JSON.parse(topLevelTitle);
163
- } catch {}
164
- }
165
- }
166
- window.dispatchEvent(
167
- new CustomEvent('sectionVisible', {
168
- detail: {
169
- title: topLevelTitle || mobileAnchor.textContent?.trim(),
170
- id: mobileAnchor.id,
171
- },
172
- })
173
- );
174
- sessionStorage.removeItem('globalLayoutScrolling');
175
- }, 100);
176
- }
177
- }
178
- } else {
179
- // Desktop: scroll in desktop content container
180
- const mainContent = document.getElementById('main-content');
181
- if (mainContent) {
182
- const desktopAnchor = mainContent.querySelector(`[id="${anchor}"]`);
183
- if (desktopAnchor) {
184
- try {
185
- // Add temporary scroll margin to account for header
186
- const originalScrollMargin =
187
- desktopAnchor.style.scrollMarginTop;
188
- desktopAnchor.style.scrollMarginTop = '56px';
189
-
190
- desktopAnchor.scrollIntoView({
191
- behavior: 'auto',
192
- block: 'start',
193
- inline: 'nearest',
194
- });
195
-
196
- // Restore original scroll margin after a delay
197
- setTimeout(() => {
198
- desktopAnchor.style.scrollMarginTop = originalScrollMargin;
199
- }, 1000);
200
- } catch (e) {
201
- // Fallback to manual calculation
202
- const headerHeight = 56;
203
- const rect = desktopAnchor.getBoundingClientRect();
204
- const scrollTop = window.scrollY + rect.top - headerHeight;
205
- const finalScrollTop = Math.max(0, scrollTop);
206
- window.scrollTo({ top: finalScrollTop, behavior: 'auto' });
207
- }
208
-
209
- setTimeout(() => {
210
- let topLevelTitle = null;
211
- if (typeof window !== 'undefined') {
212
- topLevelTitle = sessionStorage.getItem(
213
- 'subNav-topLevelTitle'
214
- );
215
- if (topLevelTitle) {
216
- try {
217
- topLevelTitle = JSON.parse(topLevelTitle);
218
- } catch {}
219
- }
220
- }
221
- window.dispatchEvent(
222
- new CustomEvent('sectionVisible', {
223
- detail: {
224
- title: topLevelTitle || desktopAnchor.textContent?.trim(),
225
- id: desktopAnchor.id,
226
- },
227
- })
228
- );
229
- sessionStorage.removeItem('globalLayoutScrolling');
230
- }, 100);
231
- }
232
- }
233
- }
234
- });
235
- }
236
-
237
- window.addEventListener('DOMContentLoaded', () => {
238
- // Check navigation flag first, before any components mount
239
- const isMainNavNavigation =
240
- sessionStorage.getItem('navigatingToMainNav') === 'true';
241
-
242
- if (isMainNavNavigation) {
243
- // Clear the flag immediately
244
- sessionStorage.removeItem('navigatingToMainNav');
245
- // Don't clear header state here - let navigation components handle it
246
- }
247
-
248
- // Handle URL hash scrolling (fallback for cases where immediate scroll didn't work)
249
- if (window.location.hash) {
250
- const anchor = window.location.hash.substring(1);
251
-
252
- // Only proceed if we haven't already scrolled
253
- if (sessionStorage.getItem('globalLayoutScrolling') !== 'true') {
254
- sessionStorage.setItem('globalLayoutScrolling', 'true');
255
-
256
- if (window.innerWidth <= 640) {
257
- // Mobile: scroll in mobile content container
258
- const mobileContent = document.getElementById(
259
- 'main-content-mobile'
260
- );
261
- if (mobileContent) {
262
- const mobileAnchor = mobileContent.querySelector(
263
- `[id="${anchor}"]`
264
- );
265
- if (mobileAnchor) {
266
- setTimeout(() => {
267
- const rect = mobileAnchor.getBoundingClientRect();
268
- const mobileRect = mobileContent.getBoundingClientRect();
269
- const scrollTop =
270
- mobileContent.scrollTop + rect.top - mobileRect.top;
271
- mobileContent.scrollTo({ top: scrollTop, behavior: 'auto' });
272
-
273
- setTimeout(() => {
274
- let topLevelTitle = null;
275
- if (typeof window !== 'undefined') {
276
- topLevelTitle = sessionStorage.getItem(
277
- 'subNav-topLevelTitle'
278
- );
279
- if (topLevelTitle) {
280
- try {
281
- topLevelTitle = JSON.parse(topLevelTitle);
282
- } catch {}
283
- }
284
- }
285
- window.dispatchEvent(
286
- new CustomEvent('sectionVisible', {
287
- detail: {
288
- title:
289
- topLevelTitle || mobileAnchor.textContent?.trim(),
290
- id: mobileAnchor.id,
291
- },
292
- })
293
- );
294
- // Clear the flag after scrolling
295
- if (typeof window !== 'undefined') {
296
- sessionStorage.removeItem('globalLayoutScrolling');
297
- }
298
- }, 100);
299
- }, 100);
300
- }
301
- }
302
- } else {
303
- // Desktop: scroll in desktop content container
304
- const mainContent = document.getElementById('main-content');
305
- if (mainContent) {
306
- const desktopAnchor = mainContent.querySelector(
307
- `[id="${anchor}"]`
308
- );
309
- if (desktopAnchor) {
310
- setTimeout(() => {
311
- // Try scrollIntoView first with a temporary offset
312
- try {
313
- // Add temporary scroll margin to account for header
314
- const originalScrollMargin =
315
- desktopAnchor.style.scrollMarginTop;
316
- desktopAnchor.style.scrollMarginTop = '56px';
317
-
318
- desktopAnchor.scrollIntoView({
319
- behavior: 'auto',
320
- block: 'start',
321
- inline: 'nearest',
322
- });
323
-
324
- // Restore original scroll margin after a delay
325
- setTimeout(() => {
326
- desktopAnchor.style.scrollMarginTop =
327
- originalScrollMargin;
328
- }, 1000);
329
- } catch (e) {
330
- // Fallback to manual calculation
331
- const headerHeight = 56;
332
- const rect = desktopAnchor.getBoundingClientRect();
333
- const scrollTop = window.scrollY + rect.top - headerHeight;
334
- // Ensure scrollTop is not negative
335
- const finalScrollTop = Math.max(0, scrollTop);
336
-
337
- window.scrollTo({ top: finalScrollTop, behavior: 'auto' });
338
- }
339
-
340
- setTimeout(() => {
341
- let topLevelTitle = null;
342
- if (typeof window !== 'undefined') {
343
- topLevelTitle = sessionStorage.getItem(
344
- 'subNav-topLevelTitle'
345
- );
346
- if (topLevelTitle) {
347
- try {
348
- topLevelTitle = JSON.parse(topLevelTitle);
349
- } catch {}
350
- }
351
- }
352
- window.dispatchEvent(
353
- new CustomEvent('sectionVisible', {
354
- detail: {
355
- title:
356
- topLevelTitle || desktopAnchor.textContent?.trim(),
357
- id: desktopAnchor.id,
358
- },
359
- })
360
- );
361
- // Clear the flag after scrolling
362
- if (typeof window !== 'undefined') {
363
- sessionStorage.removeItem('globalLayoutScrolling');
364
- }
365
- }, 100);
366
- }, 100);
367
- } else {
368
- // Log all available anchors for debugging
369
- const allAnchors = mainContent.querySelectorAll('[id]');
370
- // Clear the flag if anchor not found
371
- if (typeof window !== 'undefined') {
372
- sessionStorage.removeItem('globalLayoutScrolling');
373
- }
374
- }
375
- } else {
376
- // Clear the flag if container not found
377
- if (typeof window !== 'undefined') {
378
- sessionStorage.removeItem('globalLayoutScrolling');
379
- }
380
- }
381
- }
382
- }
383
- }
384
- });
385
- }
386
-
387
- // Handle layout changes (mobile ↔ desktop)
388
- if (typeof window !== 'undefined') {
389
- let lastWidth = window.innerWidth;
390
- let resizeTimeout = null;
391
-
392
- window.addEventListener('resize', () => {
393
- // Debounce resize events
394
- if (resizeTimeout) {
395
- clearTimeout(resizeTimeout);
396
- }
397
-
398
- resizeTimeout = setTimeout(() => {
399
- const currentWidth = window.innerWidth;
400
- const wasMobile = lastWidth <= 640;
401
- const isMobile = currentWidth <= 640;
402
-
403
- if (wasMobile !== isMobile) {
404
- // Handle anchor scrolling for layout changes
405
- if (window.location.hash) {
406
- const anchor = window.location.hash.substring(1);
407
-
408
- // Wait for navigation state to update, then scroll
409
- setTimeout(() => {
410
- if (isMobile) {
411
- // Mobile: scroll in mobile content container
412
- const mobileContent = document.getElementById(
413
- 'main-content-mobile'
414
- );
415
- if (mobileContent) {
416
- const mobileAnchor = mobileContent.querySelector(
417
- `[id="${anchor}"]`
418
- );
419
- if (mobileAnchor) {
420
- const rect = mobileAnchor.getBoundingClientRect();
421
- const mobileRect = mobileContent.getBoundingClientRect();
422
- const scrollTop =
423
- mobileContent.scrollTop + rect.top - mobileRect.top;
424
- mobileContent.scrollTo({
425
- top: scrollTop,
426
- behavior: 'auto',
427
- });
428
-
429
- setTimeout(() => {
430
- let topLevelTitle = null;
431
- if (typeof window !== 'undefined') {
432
- topLevelTitle = sessionStorage.getItem(
433
- 'subNav-topLevelTitle'
434
- );
435
- if (topLevelTitle) {
436
- try {
437
- topLevelTitle = JSON.parse(topLevelTitle);
438
- } catch {}
439
- }
440
- }
441
- window.dispatchEvent(
442
- new CustomEvent('sectionVisible', {
443
- detail: {
444
- title:
445
- topLevelTitle || mobileAnchor.textContent?.trim(),
446
- id: mobileAnchor.id,
447
- },
448
- })
449
- );
450
- }, 500);
451
- }
452
- }
453
- } else {
454
- // Desktop: scroll in desktop content container
455
- const mainContent = document.getElementById('main-content');
456
- if (mainContent) {
457
- const desktopAnchor = mainContent.querySelector(
458
- `[id="${anchor}"]`
459
- );
460
- if (desktopAnchor) {
461
- // Try scrollIntoView first with a temporary offset
462
- try {
463
- // Add temporary scroll margin to account for header
464
- const originalScrollMargin =
465
- desktopAnchor.style.scrollMarginTop;
466
- desktopAnchor.style.scrollMarginTop = '56px';
467
-
468
- desktopAnchor.scrollIntoView({
469
- behavior: 'auto',
470
- block: 'start',
471
- inline: 'nearest',
472
- });
473
-
474
- // Restore original scroll margin after a delay
475
- setTimeout(() => {
476
- desktopAnchor.style.scrollMarginTop =
477
- originalScrollMargin;
478
- }, 1000);
479
- } catch (e) {
480
- // Fallback to manual calculation
481
- const headerHeight = 56;
482
- const rect = desktopAnchor.getBoundingClientRect();
483
- const scrollTop =
484
- window.scrollY + rect.top - headerHeight;
485
-
486
- // Ensure scrollTop is not negative
487
- const finalScrollTop = Math.max(0, scrollTop);
488
-
489
- window.scrollTo({
490
- top: finalScrollTop,
491
- behavior: 'auto',
492
- });
493
- }
494
-
495
- setTimeout(() => {
496
- let topLevelTitle = null;
497
- if (typeof window !== 'undefined') {
498
- topLevelTitle = sessionStorage.getItem(
499
- 'subNav-topLevelTitle'
500
- );
501
- if (topLevelTitle) {
502
- try {
503
- topLevelTitle = JSON.parse(topLevelTitle);
504
- } catch {}
505
- }
506
- }
507
- window.dispatchEvent(
508
- new CustomEvent('sectionVisible', {
509
- detail: {
510
- title:
511
- topLevelTitle ||
512
- desktopAnchor.textContent?.trim(),
513
- id: desktopAnchor.id,
514
- },
515
- })
516
- );
517
- }, 500);
518
- }
519
- }
520
- }
521
- }, 350); // Wait for navigation state to settle
522
- }
523
-
524
- // Dispatch custom event for layout transition
525
- window.dispatchEvent(
526
- new CustomEvent('layoutTransition', {
527
- detail: { isMobile },
528
- })
529
- );
530
- }
531
-
532
- lastWidth = currentWidth;
533
- }, 250); // Debounce delay
534
- });
535
- }
536
- </script>
@@ -1,17 +0,0 @@
1
- ---
2
- let gtmCode = '';
3
- if (Astro.props.gtmID) {
4
- // Adds Google Tag Manager and DAP analytics for GSA/TTS (required for federal sites)
5
- gtmCode = `<!-- Google tag (gtag.js) -->
6
- <script async src="https://www.googletagmanager.com/gtag/js?id=${Astro.props.gtmID}"></script>
7
- <script>
8
- window.dataLayer = window.dataLayer || [];
9
- function gtag(){dataLayer.push(arguments);}
10
- gtag('js', new Date());
11
- gtag('config', '${Astro.props.gtmID}');
12
- </script>
13
- <script id="_fed_an_ua_tag" async src="https://dap.digitalgov.gov/Universal-Federated-Analytics-Min.js?agency=GSA&subagency=TTS"></script>`;
14
- }
15
- ---
16
-
17
- <Fragment set:html={gtmCode} />
@@ -1,101 +0,0 @@
1
- ---
2
- import { siteName } from '../constants';
3
- const { title, description, openGraphImage, gtmID } = Astro.props;
4
- import { getUrlFromBase } from '@gsa-tts/graymatter-ui/helpers';
5
- import GoogleTagManager from './GoogleTagManager.astro';
6
- import { fontPreloads } from './fonts';
7
-
8
- // Default values for Open Graph
9
- const defaultDescription =
10
- 'Accelerating trusted AI adoption across government. Three powerful AI tools. Industry-leading models. One integrated platform.';
11
- const defaultOgImage = getUrlFromBase('/common/assets/images/og-preview.webp');
12
-
13
- // Use computed variables instead of direct props to avoid Vite 7 import analysis issues
14
- const pageTitle = title ? `${title} | USAi` : 'USAi';
15
- const gtmIDValue = gtmID;
16
- const ogTitle = title || siteName;
17
- const ogDescription = description || defaultDescription;
18
- const ogImage = openGraphImage || defaultOgImage;
19
- const siteUrl = Astro.site || Astro.url.origin;
20
- const canonicalUrl = new URL(Astro.url.pathname, siteUrl).href;
21
- ---
22
-
23
- <meta name="viewport" content="width=device-width" />
24
- <title>{pageTitle}</title>
25
- <script is:inline>
26
- if ('performance' in window) {
27
- performance.mark('usai:documenthead:start');
28
- }
29
- </script>
30
- {gtmIDValue && <GoogleTagManager gtmID={gtmIDValue} />}
31
- <script is:inline src={`${getUrlFromBase('uswds/js/uswds-init.min.js')}`}
32
- ></script>
33
-
34
- {
35
- fontPreloads.map(href => (
36
- <link rel="preload" href={href} as="font" type="font/woff2" crossorigin />
37
- ))
38
- }
39
-
40
- <style is:global>
41
- :root {
42
- --ai-font-family-sans: 'Archivo', sans-serif;
43
- --ai-font-family-monospace: 'Space Mono', monospace;
44
- }
45
-
46
- body {
47
- font-family: var(--ai-font-family-sans);
48
- }
49
- </style>
50
- <script>
51
- import '../../static/uswds/js/uswds.js';
52
- </script>
53
-
54
- <!-- Open Graph -->
55
- <meta property="og:type" content="website" />
56
- <meta property="og:url" content={canonicalUrl} />
57
- <meta property="og:title" content={ogTitle} />
58
- <meta property="og:description" content={ogDescription} />
59
- <meta property="og:image" content={ogImage} />
60
- <meta property="og:site_name" content={ogTitle} />
61
-
62
- <meta property="twitter:card" content="summary_large_image" />
63
- <meta property="twitter:url" content={canonicalUrl} />
64
- <meta property="twitter:title" content={ogTitle} />
65
- <meta property="twitter:description" content={ogDescription} />
66
- <meta property="twitter:image" content={ogImage} />
67
-
68
- <!-- Additional meta tags -->
69
- <meta name="description" content={ogDescription} />
70
- <link rel="canonical" href={canonicalUrl} />
71
-
72
- <link
73
- rel="icon"
74
- sizes="any"
75
- href=`${getUrlFromBase('/common/assets/images/favicon.ico')}`
76
- media="(prefers-color-scheme: light)"
77
- />
78
- <link
79
- rel="icon"
80
- type="image/svg+xml"
81
- href=`${getUrlFromBase('/common/assets/images/favicon-light.svg')}`
82
- media="(prefers-color-scheme: light)"
83
- />
84
- <link
85
- rel="icon"
86
- sizes="any"
87
- href=`${getUrlFromBase('/common/assets/images/favicon-dark.ico')}`
88
- media="(prefers-color-scheme: dark)"
89
- />
90
- <link
91
- rel="icon"
92
- type="image/svg+xml"
93
- href=`${getUrlFromBase('/common/assets/images/favicon-dark.svg')}`
94
- media="(prefers-color-scheme: dark)"
95
- />
96
- <!-- This belongs at the very bottom of the head -->
97
- <script is:inline>
98
- if ('performance' in window) {
99
- performance.mark('usai:documenthead:end');
100
- }
101
- </script>
@@ -1,17 +0,0 @@
1
- // Centralized font imports and preload URLs.
2
- import '@fontsource/archivo/400.css';
3
- import '@fontsource/archivo/500.css';
4
- import '@fontsource/archivo/600.css';
5
- import '@fontsource/space-mono/400.css';
6
-
7
- import archivo400Url from '@fontsource/archivo/files/archivo-latin-400-normal.woff2?url';
8
- import archivo500Url from '@fontsource/archivo/files/archivo-latin-500-normal.woff2?url';
9
- import archivo600Url from '@fontsource/archivo/files/archivo-latin-600-normal.woff2?url';
10
- import spaceMono400Url from '@fontsource/space-mono/files/space-mono-latin-400-normal.woff2?url';
11
-
12
- export const fontPreloads = [
13
- archivo400Url,
14
- archivo500Url,
15
- archivo600Url,
16
- spaceMono400Url,
17
- ];
File without changes