@gsa-tts/graymatter-ui 0.1.0 → 0.2.1
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 +4281 -0
- package/dist/graymatter-ui.umd.cjs +40 -0
- package/index.ts +1 -0
- package/package.json +9 -5
- package/src/components/Button.svelte +11 -23
- package/src/components/Button.webcomponent.svelte +44 -0
- package/src/components/DesktopHeader.svelte +30 -18
- package/src/components/DesktopHeader.webcomponent.svelte +17 -0
- package/src/components/DesktopSideNav.svelte +332 -72
- package/src/components/DesktopSideNav.webcomponent.svelte +32 -0
- package/src/components/Logo.webcomponent.svelte +26 -0
- package/src/components/MobileBottomNav.svelte +67 -15
- package/src/components/MobileBottomNav.webcomponent.svelte +18 -0
- package/src/components/MobileSideMenu.svelte +69 -14
- package/src/components/MobileSideMenu.webcomponent.svelte +11 -0
- package/src/components/MobileTopNav.svelte +137 -3
- package/src/components/MobileTopNav.webcomponent.svelte +14 -0
- package/src/index.ts +16 -0
- package/src/layouts/GlobalAppLayout.astro +24 -19
- package/src/stores/navigationStore.ts +0 -15
- package/src/styles/base/global.css +1 -2
- package/src/styles/components/globalAppLayout.css +9 -12
- package/src/styles/components/globalAppNav.css +30 -483
- package/src/utils/getAppUrls.ts +26 -0
- package/src/utils/index.ts +1 -0
- package/src/layouts/AppLayout.astro +0 -88
|
@@ -5,23 +5,30 @@
|
|
|
5
5
|
import ChatIcon from './icons/ChatIcon.svelte';
|
|
6
6
|
import ConsoleIcon from './icons/ConsoleIcon.svelte';
|
|
7
7
|
import ApiIcon from './icons/ApiIcon.svelte';
|
|
8
|
+
import { getAppUrls } from '../utils/getAppUrls';
|
|
9
|
+
import slugify from "slugify";
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
let {onNavItemClick, apiUrl, chatUrl, consoleUrl, discoverUrl}: {
|
|
12
|
+
onNavItemClick?: (data: { item: NavigationItem; expanded: boolean }) => void,
|
|
13
|
+
apiUrl?: string,
|
|
14
|
+
chatUrl?: string,
|
|
15
|
+
consoleUrl?: string,
|
|
16
|
+
discoverUrl?: string,
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
const urls = getAppUrls();
|
|
10
20
|
|
|
11
21
|
const navItems = [
|
|
12
|
-
{ id: 'discover', label: 'Discover', icon: DiscoverIcon, href:
|
|
13
|
-
{ id: 'chat', label: 'Chat', icon: ChatIcon, href:
|
|
14
|
-
{ id: 'console', label: 'Console', icon: ConsoleIcon, href:
|
|
15
|
-
{ id: 'api', label: 'API', icon: ApiIcon, href:
|
|
22
|
+
{ id: 'discover', label: 'Discover', icon: DiscoverIcon, href: discoverUrl || urls.discover },
|
|
23
|
+
{ id: 'chat', label: 'Chat', icon: ChatIcon, href: chatUrl || urls.chat },
|
|
24
|
+
{ id: 'console', label: 'Console', icon: ConsoleIcon, href: consoleUrl || urls.console },
|
|
25
|
+
{ id: 'api', label: 'API', icon: ApiIcon, href: apiUrl || urls.api }
|
|
16
26
|
] as const;
|
|
17
27
|
|
|
18
|
-
function handleNavItemClick(item: NavigationItem) {
|
|
19
|
-
console.log('[MobileBottomNav] handleNavItemClick called for:', item, 'at:', Date.now());
|
|
20
|
-
|
|
28
|
+
function handleNavItemClick(item: NavigationItem) {
|
|
21
29
|
// Set navigation flag immediately to prevent header restoration
|
|
22
30
|
if (item !== 'discover' && typeof window !== 'undefined') {
|
|
23
31
|
sessionStorage.setItem('navigatingToMainNav', 'true');
|
|
24
|
-
console.log('[MobileBottomNav] Set navigation flag for:', item, 'at:', Date.now());
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
navigationStore.setSelectedItem(item);
|
|
@@ -44,7 +51,6 @@
|
|
|
44
51
|
const firstSection = mainContent.querySelector('h2[id], h3[id], h4[id], h5[id], h6[id]');
|
|
45
52
|
if (firstSection && firstSection.textContent) {
|
|
46
53
|
const firstSectionTitle = firstSection.textContent.trim();
|
|
47
|
-
console.log('[MobileBottomNav] Setting default header for', item, ':', firstSectionTitle);
|
|
48
54
|
selectedSubNavItemTitle.set(firstSectionTitle);
|
|
49
55
|
if (typeof window !== 'undefined') {
|
|
50
56
|
sessionStorage.setItem('subNav-selectedTitle', JSON.stringify(firstSectionTitle));
|
|
@@ -79,21 +85,67 @@
|
|
|
79
85
|
}
|
|
80
86
|
</script>
|
|
81
87
|
|
|
82
|
-
<nav class="mobile-bottom-nav">
|
|
88
|
+
<nav class="mobile-bottom-nav" part="mobile-bottom-nav">
|
|
83
89
|
{#each navItems as navItem}
|
|
84
90
|
<a
|
|
85
91
|
href={navItem.href}
|
|
86
92
|
class="nav-item"
|
|
87
93
|
class:active={$selectedItem === navItem.id}
|
|
88
|
-
|
|
94
|
+
onclick={() => handleNavItemClick(navItem.id)}
|
|
95
|
+
part={`${slugify(navItem.id)}-item`}
|
|
89
96
|
>
|
|
90
97
|
<div class="icon">
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
isActive={$selectedItem === navItem.id}
|
|
98
|
+
<navItem.icon
|
|
99
|
+
isSelected={$selectedItem === navItem.id}
|
|
94
100
|
/>
|
|
95
101
|
</div>
|
|
96
102
|
<span class="label">{navItem.label}</span>
|
|
97
103
|
</a>
|
|
98
104
|
{/each}
|
|
99
105
|
</nav>
|
|
106
|
+
|
|
107
|
+
<style>
|
|
108
|
+
.nav-item {
|
|
109
|
+
flex: 1;
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
padding: var(--ai-size-4);
|
|
115
|
+
text-decoration: none;
|
|
116
|
+
color: var(--ai-color-neutral-600);
|
|
117
|
+
transition: color 0.2s ease;
|
|
118
|
+
height: 100%;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.nav-item.active {
|
|
122
|
+
color: var(--ai-color-neutral-900);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.nav-item.active .icon {
|
|
126
|
+
background-color: var(--ai-color-neutral-200);
|
|
127
|
+
border-radius: var(--ai-size-8);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.nav-item:hover {
|
|
131
|
+
color: var(--ai-color-neutral-700);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.icon {
|
|
135
|
+
width: var(--ai-size-24);
|
|
136
|
+
height: var(--ai-size-24);
|
|
137
|
+
margin-bottom: var(--ai-size-2);
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
padding: var(--ai-size-2);
|
|
142
|
+
transition: background-color 0.2s ease;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.label {
|
|
146
|
+
font-size: 0.6875rem; /* 11px */
|
|
147
|
+
font-weight: 500;
|
|
148
|
+
line-height: 1;
|
|
149
|
+
text-align: center;
|
|
150
|
+
}
|
|
151
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svelte:options customElement={{
|
|
2
|
+
tag: 'graymatter-mobile-bottom-nav'
|
|
3
|
+
}} />
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import MobileBottomNav from './MobileBottomNav.svelte';
|
|
7
|
+
|
|
8
|
+
// Props for the web component
|
|
9
|
+
let { onNavItemClick, apiUrl, chatUrl, consoleUrl, discoverUrl } = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<MobileBottomNav
|
|
13
|
+
{onNavItemClick}
|
|
14
|
+
{apiUrl}
|
|
15
|
+
{chatUrl}
|
|
16
|
+
{consoleUrl}
|
|
17
|
+
{discoverUrl}
|
|
18
|
+
/>
|
|
@@ -74,28 +74,21 @@
|
|
|
74
74
|
// Listen for the custom event globally
|
|
75
75
|
window.addEventListener('mobileAnchorNavigate', handleMobileAnchorNavigate);
|
|
76
76
|
|
|
77
|
-
// Listen for close mobile menu event
|
|
78
77
|
const handleCloseMobileMenu = () => {
|
|
79
|
-
console.log('[MobileSideMenu] Received closeMobileMenu event');
|
|
80
78
|
closeMobileMenu();
|
|
81
79
|
};
|
|
82
80
|
window.addEventListener('closeMobileMenu', handleCloseMobileMenu);
|
|
83
81
|
|
|
84
|
-
// Listen for layout transitions
|
|
85
82
|
const handleLayoutTransition = (event: Event) => {
|
|
86
83
|
const customEvent = event as CustomEvent;
|
|
87
84
|
const { isMobile } = customEvent.detail;
|
|
88
|
-
console.log('[MobileSideMenu] Layout transition detected:', { isMobile });
|
|
89
85
|
|
|
90
86
|
if (isMobile) {
|
|
91
|
-
// Switching to mobile: close navigation
|
|
92
|
-
console.log('[MobileSideMenu] Closing navigation for mobile');
|
|
93
87
|
navigationStore.setExpanded(false);
|
|
94
88
|
}
|
|
95
89
|
};
|
|
96
90
|
window.addEventListener('layoutTransition', handleLayoutTransition);
|
|
97
91
|
|
|
98
|
-
// Listen for Escape key globally to close menu
|
|
99
92
|
const handleEscape = (e: KeyboardEvent) => {
|
|
100
93
|
if (e.key === 'Escape' && $isExpanded) {
|
|
101
94
|
closeMobileMenu();
|
|
@@ -123,6 +116,7 @@
|
|
|
123
116
|
role="button"
|
|
124
117
|
tabindex="0"
|
|
125
118
|
aria-label="Close navigation menu (Escape key supported)"
|
|
119
|
+
part="mobile-overlay"
|
|
126
120
|
on:click={closeMobileMenu}
|
|
127
121
|
on:keydown={(e) => {
|
|
128
122
|
if (e.key === 'Enter' || e.key === ' ') closeMobileMenu();
|
|
@@ -131,25 +125,25 @@
|
|
|
131
125
|
{/if}
|
|
132
126
|
|
|
133
127
|
<!-- Mobile side menu -->
|
|
134
|
-
<div class="mobile-side-menu" class:open={$isExpanded} bind:this={sideMenuElement}>
|
|
135
|
-
<div class="menu-header">
|
|
136
|
-
<div class="logo-container">
|
|
128
|
+
<div class="mobile-side-menu" class:open={$isExpanded} bind:this={sideMenuElement} part="mobile-side-menu">
|
|
129
|
+
<div class="menu-header" part="menu-header">
|
|
130
|
+
<div class="logo-container" part="logo-container">
|
|
137
131
|
<Logo width={75} height={22} />
|
|
138
132
|
</div>
|
|
139
|
-
<button class="close-button" on:click={closeMobileMenu} aria-label="Close navigation menu">
|
|
133
|
+
<button class="close-button" on:click={closeMobileMenu} aria-label="Close navigation menu" part="close-button">
|
|
140
134
|
<CloseIcon />
|
|
141
135
|
</button>
|
|
142
136
|
</div>
|
|
143
137
|
|
|
144
|
-
<div class="menu-content">
|
|
138
|
+
<div class="menu-content" part="menu-content">
|
|
145
139
|
<!-- Show submenu items if available and not on discover -->
|
|
146
140
|
{#if $selectedItem && $selectedItem !== 'discover'}
|
|
147
|
-
<div class="subnav-section">
|
|
141
|
+
<div class="subnav-section" part="subnav-section">
|
|
148
142
|
<slot />
|
|
149
143
|
</div>
|
|
150
144
|
{:else}
|
|
151
145
|
<!-- Show message when no submenu is available -->
|
|
152
|
-
<div class="no-subnav-message">
|
|
146
|
+
<div class="no-subnav-message" part="no-subnav-message">
|
|
153
147
|
<p>Select a section from the bottom navigation</p>
|
|
154
148
|
</div>
|
|
155
149
|
{/if}
|
|
@@ -167,4 +161,65 @@
|
|
|
167
161
|
.no-subnav-message p {
|
|
168
162
|
margin: 0;
|
|
169
163
|
}
|
|
164
|
+
|
|
165
|
+
.mobile-side-menu {
|
|
166
|
+
position: fixed;
|
|
167
|
+
top: 0;
|
|
168
|
+
left: 0;
|
|
169
|
+
bottom: 0;
|
|
170
|
+
width: 15rem;
|
|
171
|
+
max-width: 85vw;
|
|
172
|
+
background: var(--ai-color-white);
|
|
173
|
+
transform: translateX(-100%);
|
|
174
|
+
transition: transform 0.3s ease;
|
|
175
|
+
z-index: var(--ai-layer-50);
|
|
176
|
+
flex-direction: column;
|
|
177
|
+
box-shadow: var(--ai-size-2) 0 var(--ai-size-10) #0000001a;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.mobile-side-menu.open {
|
|
181
|
+
transform: translateX(0);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.menu-header {
|
|
185
|
+
display: flex;
|
|
186
|
+
align-items: center;
|
|
187
|
+
justify-content: space-between;
|
|
188
|
+
padding: var(--ai-size-16) var(--ai-size-20);
|
|
189
|
+
border-bottom: 0.0625rem solid var(--ai-color-neutral-200);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.mobile-side-menu .logo-container {
|
|
193
|
+
display: flex;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.close-button {
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
justify-content: center;
|
|
200
|
+
width: var(--ai-size-32);
|
|
201
|
+
height: var(--ai-size-32);
|
|
202
|
+
border: none;
|
|
203
|
+
background: none;
|
|
204
|
+
color: var(--ai-color-neutral-700);
|
|
205
|
+
cursor: pointer;
|
|
206
|
+
border-radius: var(--ai-size-6);
|
|
207
|
+
transition: background-color 0.2s ease;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.close-button:hover {
|
|
211
|
+
background-color: var(--ai-color-neutral-100);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.menu-content {
|
|
215
|
+
flex: 1;
|
|
216
|
+
overflow-y: auto;
|
|
217
|
+
padding: 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.subnav-section {
|
|
221
|
+
padding: var(--ai-size-16) 0;
|
|
222
|
+
overflow-y: auto;
|
|
223
|
+
max-height: calc(100vh - 4rem); /* leave space for header/close */
|
|
224
|
+
}
|
|
170
225
|
</style>
|
|
@@ -71,13 +71,14 @@
|
|
|
71
71
|
}
|
|
72
72
|
</script>
|
|
73
73
|
|
|
74
|
-
<nav class="mobile-top-nav">
|
|
74
|
+
<nav class="mobile-top-nav" part="mobile-top-nav">
|
|
75
75
|
<button
|
|
76
76
|
class="menu-button"
|
|
77
77
|
class:disabled={$isMenuButtonDisabled}
|
|
78
78
|
disabled={$isMenuButtonDisabled}
|
|
79
79
|
on:click={toggleMobileNav}
|
|
80
|
-
aria-label="
|
|
80
|
+
aria-label="Open navigation menu"
|
|
81
|
+
part="menu-button"
|
|
81
82
|
>
|
|
82
83
|
<MenuIcon />
|
|
83
84
|
</button>
|
|
@@ -91,11 +92,144 @@
|
|
|
91
92
|
class="profile-button"
|
|
92
93
|
on:click={handleProfileClick}
|
|
93
94
|
aria-label="Open profile menu"
|
|
95
|
+
part="profile-button"
|
|
94
96
|
>
|
|
95
97
|
<div class="user-profile-container">
|
|
96
|
-
<div class="user-profile-icon">
|
|
98
|
+
<div class="user-profile-icon" part="user-profile-icon">
|
|
97
99
|
<span class="user-initials">ZW</span>
|
|
98
100
|
</div>
|
|
99
101
|
</div>
|
|
100
102
|
</button>
|
|
101
103
|
</nav>
|
|
104
|
+
|
|
105
|
+
<style>
|
|
106
|
+
.user-profile-container,
|
|
107
|
+
.user-profile-icon {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: center;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.menu-button,
|
|
114
|
+
.profile-button {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
justify-content: center;
|
|
118
|
+
border: none;
|
|
119
|
+
background: none;
|
|
120
|
+
cursor: pointer;
|
|
121
|
+
border-radius: var(--ai-size-8);
|
|
122
|
+
transition: background-color 0.2s ease;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.menu-button:disabled,
|
|
126
|
+
.menu-button.disabled {
|
|
127
|
+
cursor: not-allowed;
|
|
128
|
+
opacity: 0.5;
|
|
129
|
+
background-color: transparent;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.user-profile-icon {
|
|
133
|
+
border-radius: var(--ai-size-6);
|
|
134
|
+
background: var(--ai-color-steel-700);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.user-initials {
|
|
138
|
+
color: var(--ai-color-white);
|
|
139
|
+
text-align: center;
|
|
140
|
+
font-weight: 400;
|
|
141
|
+
line-height: 1.3;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.mobile-top-nav .menu-button {
|
|
145
|
+
width: var(--ai-size-40);
|
|
146
|
+
height: var(--ai-size-40);
|
|
147
|
+
color: var(--ai-color-neutral-700);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.menu-button:hover {
|
|
151
|
+
background-color: var(--ai-color-neutral-100);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* MobileTopNav specific disabled state */
|
|
155
|
+
.mobile-top-nav .menu-button.disabled,
|
|
156
|
+
.mobile-top-nav .menu-button:disabled {
|
|
157
|
+
cursor: not-allowed;
|
|
158
|
+
opacity: 0.5;
|
|
159
|
+
background-color: transparent;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.brand {
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
gap: var(--ai-size-12);
|
|
166
|
+
flex: 1;
|
|
167
|
+
margin-left: var(--ai-size-8);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.brand-text {
|
|
171
|
+
color: var(--ai-color-neutral-900);
|
|
172
|
+
font-size: var(--ai-size-16);
|
|
173
|
+
font-weight: 600;
|
|
174
|
+
line-height: 1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.profile-button {
|
|
178
|
+
width: var(--ai-size-40);
|
|
179
|
+
height: var(--ai-size-40);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.profile-button:hover {
|
|
183
|
+
background-color: var(--ai-color-neutral-100);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.mobile-top-nav .user-profile-icon {
|
|
187
|
+
width: var(--ai-size-32);
|
|
188
|
+
height: var(--ai-size-32);
|
|
189
|
+
border-radius: 0.3125rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.mobile-top-nav .user-initials {
|
|
193
|
+
font-size: var(--ai-size-14);
|
|
194
|
+
line-height: 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.user-profile-container {
|
|
198
|
+
width: var(--ai-size-40);
|
|
199
|
+
height: var(--ai-size-40);
|
|
200
|
+
aspect-ratio: 1/1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.user-profile-icon {
|
|
204
|
+
padding: 0.5556rem 0.4931rem 0.5694rem 0.3819rem;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.user-initials {
|
|
208
|
+
font-size: var(--ai-size-16);
|
|
209
|
+
font-style: normal;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.menu-button {
|
|
213
|
+
display: flex;
|
|
214
|
+
padding: var(--ai-size-4);
|
|
215
|
+
justify-content: center;
|
|
216
|
+
align-items: center;
|
|
217
|
+
background: none;
|
|
218
|
+
border: none;
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
opacity: 1;
|
|
221
|
+
transition: opacity 0.2s ease;
|
|
222
|
+
will-change: opacity;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.menu-button.disabled {
|
|
226
|
+
cursor: not-allowed;
|
|
227
|
+
opacity: 0.5;
|
|
228
|
+
will-change: opacity;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.menu-button:focus {
|
|
232
|
+
outline: none;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svelte:options customElement={{
|
|
2
|
+
tag: 'graymatter-mobile-top-nav'
|
|
3
|
+
}} />
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import MobileTopNav from './MobileTopNav.svelte';
|
|
7
|
+
|
|
8
|
+
// Props for the web component
|
|
9
|
+
let { onMenuToggle } = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<MobileTopNav
|
|
13
|
+
{onMenuToggle}
|
|
14
|
+
/>
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Button from './components/Button.webcomponent.svelte';
|
|
2
|
+
import DesktopHeader from './components/DesktopHeader.webcomponent.svelte';
|
|
3
|
+
import DesktopSideNav from './components/DesktopSideNav.webcomponent.svelte';
|
|
4
|
+
import Logo from './components/Logo.webcomponent.svelte';
|
|
5
|
+
import MobileBottomNav from './components/MobileBottomNav.webcomponent.svelte';
|
|
6
|
+
import MobileSideMenu from './components/MobileSideMenu.webcomponent.svelte';
|
|
7
|
+
import MobileTopNav from './components/MobileTopNav.webcomponent.svelte';
|
|
8
|
+
export {
|
|
9
|
+
Button,
|
|
10
|
+
DesktopHeader,
|
|
11
|
+
DesktopSideNav,
|
|
12
|
+
Logo,
|
|
13
|
+
MobileBottomNav,
|
|
14
|
+
MobileSideMenu,
|
|
15
|
+
MobileTopNav,
|
|
16
|
+
};
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import BaseLayout from '@gsa-tts/graymatter-ui/layouts/BaseLayout.astro';
|
|
3
3
|
import '../styles/base/global.css';
|
|
4
4
|
import { getPageTitle } from '@gsa-tts/graymatter-ui/helpers';
|
|
5
|
-
import UsaBanner from '../components/UsaBanner.astro';
|
|
6
5
|
import DesktopSideNav from '../components/DesktopSideNav.svelte';
|
|
7
6
|
import DesktopHeader from '../components/DesktopHeader.svelte';
|
|
8
7
|
import MobileTopNav from '../components/MobileTopNav.svelte';
|
|
9
8
|
import MobileBottomNav from '../components/MobileBottomNav.svelte';
|
|
10
9
|
import MobileSideMenu from '../components/MobileSideMenu.svelte';
|
|
10
|
+
import Logo from '../components/Logo.svelte';
|
|
11
11
|
|
|
12
12
|
const { componentOptions = {}, ssrSelectedItem, gtmID } = Astro.props;
|
|
13
13
|
|
|
@@ -16,34 +16,39 @@ const title = getPageTitle(Astro);
|
|
|
16
16
|
|
|
17
17
|
<BaseLayout title={title} gtmID={gtmID}>
|
|
18
18
|
<div class="app">
|
|
19
|
-
<div class="banner">
|
|
20
|
-
<UsaBanner isInverted={componentOptions.usaBanner?.theme === 'inverse'} />
|
|
21
|
-
</div>
|
|
22
|
-
<!-- Mobile Navigation -->
|
|
23
|
-
<MobileTopNav client:load />
|
|
24
|
-
<MobileSideMenu client:load>
|
|
25
|
-
<slot name="sub-nav" />
|
|
26
|
-
</MobileSideMenu>
|
|
27
|
-
<MobileBottomNav client:load />
|
|
28
|
-
|
|
29
19
|
<!-- Desktop Navigation -->
|
|
30
20
|
<div class="layout-container">
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
21
|
+
<nav aria-label="Main navigation">
|
|
22
|
+
<DesktopSideNav client:load ssrSelectedItem={ssrSelectedItem}>
|
|
23
|
+
<slot name="sub-nav" />
|
|
24
|
+
</DesktopSideNav>
|
|
25
|
+
</nav>
|
|
34
26
|
<div class="main-container">
|
|
35
|
-
<
|
|
27
|
+
<header>
|
|
28
|
+
<DesktopHeader client:load />
|
|
29
|
+
</header>
|
|
36
30
|
<main id="main-content">
|
|
37
31
|
<slot />
|
|
38
32
|
</main>
|
|
39
33
|
</div>
|
|
40
34
|
</div>
|
|
41
35
|
|
|
36
|
+
<!-- Mobile Navigation -->
|
|
37
|
+
<nav aria-label="Mobile navigation">
|
|
38
|
+
<MobileTopNav client:load />
|
|
39
|
+
<MobileSideMenu client:load>
|
|
40
|
+
<slot name="sub-nav" />
|
|
41
|
+
</MobileSideMenu>
|
|
42
|
+
<MobileBottomNav client:load />
|
|
43
|
+
</nav>
|
|
44
|
+
|
|
45
|
+
<!-- Fixed Logo (Desktop only) -->
|
|
46
|
+
<div class="logo-fixed-container display-none tablet:display-flex">
|
|
47
|
+
<Logo width={75} height={22} />
|
|
48
|
+
</div>
|
|
49
|
+
|
|
42
50
|
<!-- Mobile Main Content -->
|
|
43
|
-
<main
|
|
44
|
-
id="main-content-mobile"
|
|
45
|
-
class="main-content-mobile"
|
|
46
|
-
>
|
|
51
|
+
<main id="main-content-mobile" class="main-content-mobile">
|
|
47
52
|
<slot />
|
|
48
53
|
</main>
|
|
49
54
|
</div>
|
|
@@ -121,31 +121,16 @@ export const navigationStore: NavigationStore = {
|
|
|
121
121
|
},
|
|
122
122
|
|
|
123
123
|
handleLayoutTransition: (isMobile: boolean): void => {
|
|
124
|
-
console.log('[NavigationStore] handleLayoutTransition called:', {
|
|
125
|
-
isMobile,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
124
|
if (isMobile) {
|
|
129
|
-
// Switching to mobile: always close navigation
|
|
130
|
-
console.log('[NavigationStore] Closing navigation for mobile');
|
|
131
125
|
isExpanded.set(false);
|
|
132
126
|
} else {
|
|
133
|
-
// Switching to desktop: open navigation if on non-discover page
|
|
134
127
|
const currentItem = getInitialValue('globalSideNav-selectedItem', null);
|
|
135
|
-
console.log(
|
|
136
|
-
'[NavigationStore] Desktop transition - current item:',
|
|
137
|
-
currentItem
|
|
138
|
-
);
|
|
139
|
-
|
|
140
128
|
if (currentItem && currentItem !== 'discover') {
|
|
141
129
|
console.log(
|
|
142
130
|
'[NavigationStore] Opening navigation for desktop (non-discover page)'
|
|
143
131
|
);
|
|
144
132
|
isExpanded.set(true);
|
|
145
133
|
} else {
|
|
146
|
-
console.log(
|
|
147
|
-
'[NavigationStore] Keeping navigation closed for desktop (discover page or no selection)'
|
|
148
|
-
);
|
|
149
134
|
isExpanded.set(false);
|
|
150
135
|
}
|
|
151
136
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
@layer designsystem, theme, buttons, appnav, applayout;
|
|
2
2
|
@import '../../../static/uswds/styles/styles.css' layer(designsystem);
|
|
3
|
-
@import '
|
|
4
|
-
layer(theme);
|
|
3
|
+
@import '@gsa-tts/graymatter-style-tokens/dist/css/variables.css' layer(theme);
|
|
5
4
|
@import 'theme.css' layer(theme);
|
|
6
5
|
@import 'typography.css' layer(theme);
|
|
7
6
|
@import '../utilities/layout.css' layer(theme);
|
|
@@ -31,6 +31,14 @@
|
|
|
31
31
|
overflow-y: auto;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
.logo-fixed-container {
|
|
35
|
+
position: fixed;
|
|
36
|
+
top: 1rem;
|
|
37
|
+
left: var(--ai-size-88);
|
|
38
|
+
z-index: 10;
|
|
39
|
+
background: transparent;
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
@media (--ai-size-breakpoint-tablet) {
|
|
35
43
|
.main-content-mobile {
|
|
36
44
|
display: none;
|
|
@@ -41,22 +49,11 @@
|
|
|
41
49
|
overflow: hidden;
|
|
42
50
|
flex-direction: row;
|
|
43
51
|
}
|
|
44
|
-
.side-nav-container {
|
|
45
|
-
display: flex;
|
|
46
|
-
flex-shrink: 0;
|
|
47
|
-
position: relative;
|
|
48
|
-
z-index: 2;
|
|
49
|
-
}
|
|
50
|
-
.side-nav-container.expanded {
|
|
51
|
-
width: 12.5rem;
|
|
52
|
-
min-width: 12.5rem;
|
|
53
|
-
max-width: 12.5rem;
|
|
54
|
-
}
|
|
55
52
|
.main-container {
|
|
56
53
|
flex: 1 1 0%;
|
|
57
54
|
min-width: 0;
|
|
58
55
|
position: relative;
|
|
59
|
-
z-index: 1;
|
|
56
|
+
z-index: var(--ai-layer-1);
|
|
60
57
|
}
|
|
61
58
|
.app main {
|
|
62
59
|
padding: var(--ai-size-40) var(--ai-size-32) var(--ai-size-8) 4.875rem;
|