@gsa-tts/graymatter-ui 0.4.19 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/graymatter-ui.js +4306 -3193
- package/dist/graymatter-ui.umd.cjs +53 -52
- package/package.json +4 -4
- package/src/components/ApiDocSubNavMenu.svelte +7 -3
- package/src/components/Button.svelte +4 -2
- package/src/components/Button.webcomponent.svelte +19 -5
- package/src/components/DesktopHeader.svelte +11 -13
- package/src/components/DesktopSideNav.svelte +118 -91
- package/src/components/DesktopSideNav.webcomponent.svelte +3 -3
- package/src/components/Logo.svelte +5 -5
- package/src/components/MobileBottomNav.svelte +25 -20
- package/src/components/MobileSideMenu.svelte +5 -5
- package/src/components/MobileSideMenu.webcomponent.svelte +2 -4
- package/src/components/MobileTopNav.svelte +22 -27
- package/src/components/NavigationInitializer.svelte +7 -4
- package/src/components/ProfileMenu.svelte +27 -18
- package/src/components/UsaBanner.svelte +3 -3
- package/src/components/icons/ApiIcon.svelte +1 -1
- package/src/components/icons/ChatIcon.svelte +1 -1
- package/src/components/icons/ConsoleIcon.svelte +1 -1
- package/src/components/icons/DiscoverIcon.svelte +1 -1
- package/src/components/icons/HelpIcon.svelte +1 -1
- package/src/components/icons/MenuIcon.svelte +15 -4
|
@@ -7,14 +7,13 @@
|
|
|
7
7
|
import Logo from './Logo.svelte';
|
|
8
8
|
import MenuIcon from './icons/MenuIcon.svelte';
|
|
9
9
|
import HelpIcon from './icons/HelpIcon.svelte';
|
|
10
|
-
import { onMount } from 'svelte';
|
|
11
10
|
|
|
12
11
|
let { profileMenuData, logoLinkUrl, logoLinkLabel, supplementalMenuData } =
|
|
13
12
|
$props();
|
|
14
13
|
|
|
15
14
|
import ProfileMenu from './ProfileMenu.svelte';
|
|
16
15
|
|
|
17
|
-
const helpUrl = supplementalMenuData?.help?.url?.trim();
|
|
16
|
+
const helpUrl = $derived(() => supplementalMenuData?.help?.url?.trim());
|
|
18
17
|
|
|
19
18
|
// Check if we're on the help page by comparing URLs (help is not a nav/subnav item)
|
|
20
19
|
function getPathname(url: string, baseOrigin: string) {
|
|
@@ -26,7 +25,8 @@
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
function checkIsHelpPage(): boolean {
|
|
29
|
-
|
|
28
|
+
const url = helpUrl();
|
|
29
|
+
if (!url || url === '#' || url.startsWith('#')) {
|
|
30
30
|
return false;
|
|
31
31
|
}
|
|
32
32
|
const location = (
|
|
@@ -35,33 +35,30 @@
|
|
|
35
35
|
}
|
|
36
36
|
).location;
|
|
37
37
|
if (location?.origin && location?.pathname) {
|
|
38
|
-
const helpPath = getPathname(
|
|
38
|
+
const helpPath = getPathname(url, location.origin);
|
|
39
39
|
return helpPath !== null && location.pathname === helpPath;
|
|
40
40
|
}
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const isHelpSelected = $derived(checkIsHelpPage());
|
|
44
|
+
const isHelpSelected = $derived(() => checkIsHelpPage());
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
$effect(() => {
|
|
47
|
+
if (typeof window === 'undefined') return;
|
|
47
48
|
// Listen for section visibility changes to update header
|
|
48
49
|
const handleSectionVisible = (event: Event) => {
|
|
49
50
|
const customEvent = event as CustomEvent;
|
|
50
51
|
if (customEvent.detail && customEvent.detail.title) {
|
|
51
52
|
selectedSubNavItemTitle.set(customEvent.detail.title);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
57
|
-
}
|
|
53
|
+
sessionStorage.setItem(
|
|
54
|
+
'subNav-selectedTitle',
|
|
55
|
+
JSON.stringify(customEvent.detail.title)
|
|
56
|
+
);
|
|
58
57
|
}
|
|
59
58
|
};
|
|
60
59
|
|
|
61
60
|
// Set default header based on first submenu item
|
|
62
61
|
const setDefaultHeaderFromFirstSubmenu = () => {
|
|
63
|
-
if (typeof window === 'undefined') return;
|
|
64
|
-
|
|
65
62
|
const mainContent = document.getElementById('main-content');
|
|
66
63
|
if (!mainContent) return;
|
|
67
64
|
|
|
@@ -75,12 +72,10 @@
|
|
|
75
72
|
// Only set if no header is currently set
|
|
76
73
|
if (!$selectedSubNavItemTitle) {
|
|
77
74
|
selectedSubNavItemTitle.set(firstSectionTitle);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
);
|
|
83
|
-
}
|
|
75
|
+
sessionStorage.setItem(
|
|
76
|
+
'subNav-selectedTitle',
|
|
77
|
+
JSON.stringify(firstSectionTitle)
|
|
78
|
+
);
|
|
84
79
|
}
|
|
85
80
|
}
|
|
86
81
|
};
|
|
@@ -98,7 +93,7 @@
|
|
|
98
93
|
// Listen for navigation changes to update help button state
|
|
99
94
|
const handleNavigationChange = () => {
|
|
100
95
|
// Force reactivity update by accessing the derived value
|
|
101
|
-
void isHelpSelected;
|
|
96
|
+
void isHelpSelected();
|
|
102
97
|
};
|
|
103
98
|
|
|
104
99
|
window.addEventListener('popstate', handleNavigationChange);
|
|
@@ -115,7 +110,7 @@
|
|
|
115
110
|
|
|
116
111
|
function handleHelpClick(event: MouseEvent) {
|
|
117
112
|
// If it's a hash link, prevent default to stay on same page
|
|
118
|
-
if (helpUrl === '#' || helpUrl?.startsWith('#')) {
|
|
113
|
+
if (helpUrl() === '#' || helpUrl()?.startsWith('#')) {
|
|
119
114
|
event.preventDefault();
|
|
120
115
|
}
|
|
121
116
|
}
|
|
@@ -145,18 +140,18 @@
|
|
|
145
140
|
<span class="brand-text">{$selectedSubNavItemTitle || ''}</span>
|
|
146
141
|
</div>
|
|
147
142
|
|
|
148
|
-
{#if helpUrl}
|
|
143
|
+
{#if helpUrl()}
|
|
149
144
|
<a
|
|
150
145
|
class="help-button"
|
|
151
|
-
class:selected={isHelpSelected}
|
|
152
|
-
aria-current={isHelpSelected ? 'page' : undefined}
|
|
153
|
-
href={helpUrl}
|
|
146
|
+
class:selected={isHelpSelected()}
|
|
147
|
+
aria-current={isHelpSelected() ? 'page' : undefined}
|
|
148
|
+
href={helpUrl()}
|
|
154
149
|
title="Help"
|
|
155
150
|
aria-label="Help"
|
|
156
151
|
onclick={handleHelpClick}
|
|
157
152
|
>
|
|
158
153
|
<span class="icon-container">
|
|
159
|
-
<HelpIcon isSelected={isHelpSelected} />
|
|
154
|
+
<HelpIcon isSelected={isHelpSelected()} />
|
|
160
155
|
</span>
|
|
161
156
|
</a>
|
|
162
157
|
{/if}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onMount } from 'svelte';
|
|
3
2
|
import {
|
|
4
3
|
navigationStore,
|
|
5
4
|
type NavigationItem,
|
|
6
5
|
} from '../stores/navigationStore.js';
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
interface Props {
|
|
8
|
+
item: NavigationItem;
|
|
9
|
+
autoExpand?: boolean;
|
|
10
|
+
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
let { item, autoExpand = false }: Props = $props();
|
|
13
|
+
|
|
14
|
+
$effect(() => {
|
|
12
15
|
if (item) {
|
|
13
16
|
navigationStore.setSelectedItem(item);
|
|
14
17
|
// Auto-expand for non-discover pages on desktop
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onMount } from 'svelte';
|
|
3
|
-
|
|
4
2
|
/**
|
|
5
3
|
* ProfileMenu Component - Event-Driven Architecture with Shadow DOM Support
|
|
6
4
|
*
|
|
@@ -71,14 +69,22 @@
|
|
|
71
69
|
* Array of menu items to render in the profile menu.
|
|
72
70
|
* Each item: { label: string, icon: string, action: string }
|
|
73
71
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
interface Props {
|
|
73
|
+
menuItems?: Array<{ label: string; icon: string; action: string }>;
|
|
74
|
+
userMeta: { initials: string };
|
|
75
|
+
ariaLabel?: string;
|
|
76
|
+
menuAlign?: 'desktop' | 'mobile';
|
|
77
|
+
}
|
|
79
78
|
|
|
80
|
-
let
|
|
81
|
-
|
|
79
|
+
let {
|
|
80
|
+
menuItems = [],
|
|
81
|
+
userMeta,
|
|
82
|
+
ariaLabel = 'Open profile menu',
|
|
83
|
+
menuAlign = 'desktop',
|
|
84
|
+
}: Props = $props();
|
|
85
|
+
|
|
86
|
+
const menuId = `profile-menu-${Math.random().toString(36).slice(2, 10)}`;
|
|
87
|
+
let menuOpen = $state(false);
|
|
82
88
|
function toggleMenu() {
|
|
83
89
|
menuOpen = !menuOpen;
|
|
84
90
|
}
|
|
@@ -118,12 +124,14 @@
|
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
let profileButton
|
|
127
|
+
let profileButton = $state<HTMLButtonElement | null>(null);
|
|
122
128
|
|
|
123
129
|
// For keyboard navigation, keep refs to all menu item buttons
|
|
124
|
-
let menuItemButtons
|
|
125
|
-
let btns
|
|
126
|
-
|
|
130
|
+
let menuItemButtons = $state<HTMLButtonElement[]>([]);
|
|
131
|
+
let btns = $state<(HTMLButtonElement | null)[]>([]);
|
|
132
|
+
$effect(() => {
|
|
133
|
+
menuItemButtons = btns.filter(Boolean) as HTMLButtonElement[];
|
|
134
|
+
});
|
|
127
135
|
|
|
128
136
|
function handleMenuKeydown(e: KeyboardEvent, idx: number) {
|
|
129
137
|
if (e.key === 'ArrowDown') {
|
|
@@ -149,7 +157,8 @@
|
|
|
149
157
|
}
|
|
150
158
|
}
|
|
151
159
|
|
|
152
|
-
|
|
160
|
+
$effect(() => {
|
|
161
|
+
if (typeof window === 'undefined') return;
|
|
153
162
|
window.addEventListener('keydown', handleGlobalEscape, true);
|
|
154
163
|
return () => {
|
|
155
164
|
window.removeEventListener('keydown', handleGlobalEscape, true);
|
|
@@ -165,8 +174,8 @@
|
|
|
165
174
|
aria-haspopup="menu"
|
|
166
175
|
aria-expanded={menuOpen}
|
|
167
176
|
aria-controls={menuId}
|
|
168
|
-
|
|
169
|
-
|
|
177
|
+
onclick={toggleMenu}
|
|
178
|
+
onblur={handleBlur}
|
|
170
179
|
bind:this={profileButton}
|
|
171
180
|
>
|
|
172
181
|
<div class="user-profile-container">
|
|
@@ -189,12 +198,12 @@
|
|
|
189
198
|
<button
|
|
190
199
|
type="button"
|
|
191
200
|
class="menu-item"
|
|
192
|
-
|
|
201
|
+
onclick={() => {
|
|
193
202
|
executeMenuAction(item.action);
|
|
194
203
|
closeMenu();
|
|
195
204
|
}}
|
|
196
205
|
bind:this={btns[idx]}
|
|
197
|
-
|
|
206
|
+
onkeydown={e => handleMenuKeydown(e, idx)}
|
|
198
207
|
role="menuitem"
|
|
199
208
|
>
|
|
200
209
|
{#if item.icon}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
widescreen: 'ai-maxw-widescreen',
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
const mappedSize = sizeMap[maxWidth] || sizeMap.none;
|
|
16
|
+
const mappedSize = $derived(() => sizeMap[maxWidth] || sizeMap.none);
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<section
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
class="usa-banner__header ai-banner__header"
|
|
27
27
|
aria-label="USA banner"
|
|
28
28
|
>
|
|
29
|
-
<div class="usa-banner__inner {mappedSize}">
|
|
29
|
+
<div class="usa-banner__inner {mappedSize()}">
|
|
30
30
|
<div class="grid-col-auto">
|
|
31
31
|
<img
|
|
32
32
|
aria-hidden="true"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
</div>
|
|
55
55
|
</header>
|
|
56
56
|
<div
|
|
57
|
-
class="usa-banner__content usa-accordion__content ai-banner__content {mappedSize}"
|
|
57
|
+
class="usa-banner__content usa-accordion__content ai-banner__content {mappedSize()}"
|
|
58
58
|
id="gov-banner-default"
|
|
59
59
|
>
|
|
60
60
|
<div class="grid-row grid-gap-lg">
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
interface Props {
|
|
3
|
+
isDisabled?: boolean;
|
|
4
|
+
isHovered?: boolean;
|
|
5
|
+
isOpen?: boolean;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
isDisabled = false,
|
|
11
|
+
isHovered = false,
|
|
12
|
+
isOpen = false,
|
|
13
|
+
// eslint-disable-next-line svelte/valid-compile -- allow passthrough svg props.
|
|
14
|
+
...restProps
|
|
15
|
+
}: Props = $props();
|
|
5
16
|
</script>
|
|
6
17
|
|
|
7
18
|
<svg
|
|
@@ -10,7 +21,7 @@
|
|
|
10
21
|
height="24"
|
|
11
22
|
viewBox="0 0 24 24"
|
|
12
23
|
fill="none"
|
|
13
|
-
{
|
|
24
|
+
{...restProps}
|
|
14
25
|
>
|
|
15
26
|
{#if isDisabled}
|
|
16
27
|
<!-- Disabled state -->
|