@gsa-tts/graymatter-ui 0.0.2 → 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.
- package/index.ts +2 -0
- package/package.json +8 -5
- package/src/components/DesktopHeader.svelte +89 -0
- package/src/components/DesktopSideNav.svelte +375 -0
- package/src/components/GoogleTagManager.astro +16 -0
- package/src/components/Logo.svelte +8 -7
- package/src/components/MobileBottomNav.svelte +99 -0
- package/src/components/MobileSideMenu.svelte +170 -0
- package/src/components/MobileTopNav.svelte +101 -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/layouts/AppLayout.astro +1 -1
- package/src/layouts/BaseLayout.astro +4 -0
- package/src/layouts/GlobalAppLayout.astro +585 -0
- package/src/stores/navigationStore.ts +153 -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 +76 -0
- package/src/styles/components/globalAppNav.css +548 -0
- package/assets/images/gsai-logo-black.svg +0 -18
- package/assets/images/gsai-logo-white.svg +0 -18
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Store for selected subnav item id and title (for all apps)
|
|
2
|
+
import { writable, type Writable, derived } from 'svelte/store';
|
|
3
|
+
|
|
4
|
+
export type NavigationItem = 'discover' | 'chat' | 'console' | 'api' | null;
|
|
5
|
+
|
|
6
|
+
const browser = typeof window !== 'undefined';
|
|
7
|
+
|
|
8
|
+
// Persistent subnav item id and title in sessionStorage
|
|
9
|
+
function getSessionValue<T>(key: string, defaultValue: T): T {
|
|
10
|
+
if (browser && typeof sessionStorage !== 'undefined') {
|
|
11
|
+
const stored = sessionStorage.getItem(key);
|
|
12
|
+
if (stored !== null) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(stored) as T;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.warn(`Failed to parse stored value for ${key}:`, e);
|
|
17
|
+
return defaultValue;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return defaultValue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createSessionStore<T>(key: string, defaultValue: T): Writable<T> {
|
|
25
|
+
const initialValue = getSessionValue(key, defaultValue);
|
|
26
|
+
const store = writable<T>(initialValue);
|
|
27
|
+
|
|
28
|
+
if (browser) {
|
|
29
|
+
store.subscribe((value: T) => {
|
|
30
|
+
try {
|
|
31
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.warn(`Failed to persist ${key} to sessionStorage:`, e);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return store;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const selectedSubNavItemId: Writable<string | null> = createSessionStore(
|
|
42
|
+
'subNav-selectedId',
|
|
43
|
+
null
|
|
44
|
+
);
|
|
45
|
+
export const selectedSubNavItemTitle: Writable<string | null> =
|
|
46
|
+
createSessionStore('subNav-selectedTitle', '');
|
|
47
|
+
|
|
48
|
+
function getInitialValue<T>(key: string, defaultValue: T): T {
|
|
49
|
+
if (browser && typeof localStorage !== 'undefined') {
|
|
50
|
+
const stored = localStorage.getItem(key);
|
|
51
|
+
if (stored !== null) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(stored) as T;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.warn(`Failed to parse stored value for ${key}:`, e);
|
|
56
|
+
return defaultValue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return defaultValue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createPersistentStore<T>(key: string, defaultValue: T): Writable<T> {
|
|
64
|
+
const initialValue = getInitialValue(key, defaultValue);
|
|
65
|
+
const store = writable<T>(initialValue);
|
|
66
|
+
|
|
67
|
+
if (browser) {
|
|
68
|
+
store.subscribe((value: T) => {
|
|
69
|
+
try {
|
|
70
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.warn(`Failed to persist ${key} to localStorage:`, e);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return store;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const isExpanded: Writable<boolean> = createPersistentStore(
|
|
81
|
+
'globalSideNav-isExpanded',
|
|
82
|
+
false
|
|
83
|
+
);
|
|
84
|
+
export const selectedItem: Writable<NavigationItem> = createPersistentStore(
|
|
85
|
+
'globalSideNav-selectedItem',
|
|
86
|
+
null
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Derived store to determine if menu button should be disabled
|
|
90
|
+
export const isMenuButtonDisabled = derived(
|
|
91
|
+
selectedItem,
|
|
92
|
+
$selectedItem => $selectedItem === 'discover'
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
export interface NavigationStore {
|
|
96
|
+
toggleExpanded: () => void;
|
|
97
|
+
setExpanded: (value: boolean) => void;
|
|
98
|
+
setSelectedItem: (item: NavigationItem) => void;
|
|
99
|
+
handleLayoutTransition: (isMobile: boolean) => void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const navigationStore: NavigationStore = {
|
|
103
|
+
toggleExpanded: (): void => {
|
|
104
|
+
isExpanded.update((expanded: boolean) => !expanded);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
setExpanded: (value: boolean): void => {
|
|
108
|
+
isExpanded.set(value);
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
setSelectedItem: (item: NavigationItem): void => {
|
|
112
|
+
selectedItem.set(item);
|
|
113
|
+
if (item === 'discover') {
|
|
114
|
+
isExpanded.set(false);
|
|
115
|
+
} else {
|
|
116
|
+
// For non-discover items (API, Console, Chat), ALWAYS expand on desktop
|
|
117
|
+
if (browser && window.innerWidth > 640) {
|
|
118
|
+
isExpanded.set(true);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
handleLayoutTransition: (isMobile: boolean): void => {
|
|
124
|
+
console.log('[NavigationStore] handleLayoutTransition called:', {
|
|
125
|
+
isMobile,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (isMobile) {
|
|
129
|
+
// Switching to mobile: always close navigation
|
|
130
|
+
console.log('[NavigationStore] Closing navigation for mobile');
|
|
131
|
+
isExpanded.set(false);
|
|
132
|
+
} else {
|
|
133
|
+
// Switching to desktop: open navigation if on non-discover page
|
|
134
|
+
const currentItem = getInitialValue('globalSideNav-selectedItem', null);
|
|
135
|
+
console.log(
|
|
136
|
+
'[NavigationStore] Desktop transition - current item:',
|
|
137
|
+
currentItem
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (currentItem && currentItem !== 'discover') {
|
|
141
|
+
console.log(
|
|
142
|
+
'[NavigationStore] Opening navigation for desktop (non-discover page)'
|
|
143
|
+
);
|
|
144
|
+
isExpanded.set(true);
|
|
145
|
+
} else {
|
|
146
|
+
console.log(
|
|
147
|
+
'[NavigationStore] Keeping navigation closed for desktop (discover page or no selection)'
|
|
148
|
+
);
|
|
149
|
+
isExpanded.set(false);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@layer designsystem, theme, buttons;
|
|
1
|
+
@layer designsystem, theme, buttons, appnav, applayout;
|
|
2
2
|
@import '../../../static/uswds/styles/styles.css' layer(designsystem);
|
|
3
3
|
@import '../../../node_modules/@gsa-tts/graymatter-style-tokens/dist/css/variables.css'
|
|
4
4
|
layer(theme);
|
|
@@ -6,3 +6,10 @@
|
|
|
6
6
|
@import 'typography.css' layer(theme);
|
|
7
7
|
@import '../utilities/layout.css' layer(theme);
|
|
8
8
|
@import '../components/heroCards.css' layer(theme);
|
|
9
|
+
@import '../components/globalAppNav.css' layer(appnav);
|
|
10
|
+
@import '../components/globalAppLayout.css' layer(applayout);
|
|
11
|
+
|
|
12
|
+
/* Ensure anchor targets are not hidden behind fixed header */
|
|
13
|
+
[id] {
|
|
14
|
+
scroll-margin-top: 56px;
|
|
15
|
+
}
|
|
@@ -9,43 +9,44 @@
|
|
|
9
9
|
|
|
10
10
|
h1,
|
|
11
11
|
.ai-heading-size-1 {
|
|
12
|
-
font-size: clamp(var(--ai-size-
|
|
12
|
+
font-size: clamp(var(--ai-size-30), 5vw, var(--ai-size-32));
|
|
13
13
|
font-weight: var(--ai-font-weight-semibold);
|
|
14
|
-
margin-bottom: var(--ai-size-
|
|
14
|
+
margin-bottom: var(--ai-size-20);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
h2,
|
|
18
18
|
.ai-heading-size-2 {
|
|
19
|
-
font-size:
|
|
20
|
-
|
|
19
|
+
font-size: var(--ai-size-26);
|
|
20
|
+
font-weight: var(--ai-font-weight-semibold);
|
|
21
|
+
margin-bottom: var(--ai-size-16);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
h3,
|
|
24
25
|
.ai-heading-size-3 {
|
|
25
|
-
font-size:
|
|
26
|
-
font-weight: var(--ai-font-weight-
|
|
27
|
-
margin-bottom: var(--ai-size-
|
|
26
|
+
font-size: var(--ai-size-24);
|
|
27
|
+
font-weight: var(--ai-font-weight-semibold);
|
|
28
|
+
margin-bottom: var(--ai-size-16);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
h4,
|
|
31
32
|
.ai-heading-size-4 {
|
|
32
|
-
font-size: var(--ai-size-
|
|
33
|
-
font-weight: var(--ai-font-weight-
|
|
34
|
-
margin-bottom: var(--ai-size-
|
|
33
|
+
font-size: var(--ai-size-22);
|
|
34
|
+
font-weight: var(--ai-font-weight-semibold);
|
|
35
|
+
margin-bottom: var(--ai-size-10);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
h5,
|
|
38
39
|
.ai-heading-size-5 {
|
|
39
40
|
font-size: var(--ai-size-22);
|
|
40
41
|
font-weight: var(--ai-font-weight-normal);
|
|
41
|
-
margin-bottom: var(--ai-size-
|
|
42
|
+
margin-bottom: var(--ai-size-10);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
h6,
|
|
45
46
|
.ai-heading-size-6 {
|
|
46
|
-
font-size: var(--ai-size-
|
|
47
|
-
font-weight: var(--ai-font-weight-
|
|
48
|
-
margin-bottom: var(--ai-size-
|
|
47
|
+
font-size: var(--ai-size-18);
|
|
48
|
+
font-weight: var(--ai-font-weight-semibold);
|
|
49
|
+
margin-bottom: var(--ai-size-6);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
pre {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
.app {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
height: 100vh;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.main-content-mobile {
|
|
8
|
+
flex: 1;
|
|
9
|
+
padding: var(--initial-main-top-padding, calc(3.5rem + 1rem)) 1rem
|
|
10
|
+
var(--initial-main-bottom-padding, calc(5rem + 1rem)) 1rem;
|
|
11
|
+
overflow-y: auto;
|
|
12
|
+
display: block;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.layout-container {
|
|
16
|
+
display: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.main-container {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
flex: 1;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
position: relative;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.app main {
|
|
28
|
+
flex: 1;
|
|
29
|
+
padding: var(--initial-main-top-padding, calc(3.5rem + 1rem)) 1rem
|
|
30
|
+
var(--initial-main-bottom-padding, calc(5rem + 1rem)) 1rem;
|
|
31
|
+
overflow-y: auto;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@media (--ai-size-breakpoint-tablet) {
|
|
35
|
+
.main-content-mobile {
|
|
36
|
+
display: none;
|
|
37
|
+
}
|
|
38
|
+
.layout-container {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex: 1;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
flex-direction: row;
|
|
43
|
+
}
|
|
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
|
+
.main-container {
|
|
56
|
+
flex: 1 1 0%;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
position: relative;
|
|
59
|
+
z-index: 1;
|
|
60
|
+
}
|
|
61
|
+
.app main {
|
|
62
|
+
padding: var(--ai-size-40) var(--ai-size-32) var(--ai-size-8) 4.875rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
:global(.side-nav-container.expanded) ~ .main-container main {
|
|
66
|
+
padding-top: var(--ai-size-8);
|
|
67
|
+
}
|
|
68
|
+
:global(.side-nav-container),
|
|
69
|
+
:global(.side-nav-container .expanded-content) {
|
|
70
|
+
transition: none;
|
|
71
|
+
}
|
|
72
|
+
:global(body) {
|
|
73
|
+
overflow-y: hidden;
|
|
74
|
+
height: 100%;
|
|
75
|
+
}
|
|
76
|
+
}
|