@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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
export let isInverted: boolean = false;
|
|
3
3
|
export let width: number | undefined;
|
|
4
4
|
export let height: number | undefined;
|
|
5
|
-
const defaultWidth =
|
|
6
|
-
const defaultHeight =
|
|
5
|
+
const defaultWidth = 102;
|
|
6
|
+
const defaultHeight = 30;
|
|
7
7
|
width = width || defaultWidth;
|
|
8
8
|
height = height || defaultHeight;
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<svg
|
|
14
14
|
{width}
|
|
15
15
|
{height}
|
|
16
|
-
viewBox="0 0
|
|
16
|
+
viewBox="0 0 102 30"
|
|
17
17
|
fill="none"
|
|
18
18
|
xmlns="http://www.w3.org/2000/svg"
|
|
19
19
|
style="fill: {logoColor};"
|
|
@@ -21,14 +21,15 @@
|
|
|
21
21
|
>
|
|
22
22
|
<g>
|
|
23
23
|
<path
|
|
24
|
-
d="
|
|
24
|
+
d="M57.5791 20.4177C57.5791 15.5645 53.7309 11.6187 48.9988 11.6187H40.188C38.6688 11.6187 37.4304 10.3567 37.4304 8.80197C37.4304 7.24729 38.6658 5.98522 40.188 5.98522H56.2373V0H38.8757C34.1466 0 30.2983 3.94877 30.2983 8.80197C30.2983 13.6552 34.1466 17.6039 38.8757 17.6039H47.6865C49.2057 17.6039 50.4442 18.866 50.4442 20.4207C50.4442 21.9754 49.2087 23.2374 47.6865 23.2374H31.0284V29.2197H48.9988C53.7309 29.2197 57.5791 25.2709 57.5791 20.4207V20.4177Z"
|
|
25
25
|
/>
|
|
26
26
|
<path
|
|
27
|
-
d="
|
|
27
|
+
d="M82.1466 24.7153L82.188 24.7685L83.6747 29.2197H90.6914L80.917 0H68.3525L58.5781 29.2167H65.5949L67.0993 24.7153H82.1466ZM69.186 18.4818L73.3653 5.98522H75.9013L75.919 6.03547L80.1161 18.5793H69.1535L69.186 18.4818Z"
|
|
28
28
|
/>
|
|
29
|
-
<path d="M104.809 0.478271H99.8516V32.3468H104.809V0.478271Z" />
|
|
30
29
|
<path
|
|
31
|
-
d="
|
|
30
|
+
d="M27.198 0H20.5626V15.8158C20.5626 21.4345 16.9626 23.4355 13.5961 23.4355C9.03842 23.4355 6.62956 20.799 6.62956 15.8158V0H0V17.0069C0 20.6985 1.46305 24.0532 4.11724 26.4532C6.64729 28.7409 10.0138 30 13.599 30C20.3586 30 27.198 25.5369 27.198 17.0039V0Z"
|
|
32
31
|
/>
|
|
32
|
+
<path d="M101.708 9.78915H93.8662V29.2197H101.708V9.78915Z" />
|
|
33
|
+
<path d="M101.823 0H93.8662V5.98227H101.823V0Z" />
|
|
33
34
|
</g>
|
|
34
35
|
</svg>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<svelte:options customElement={{
|
|
2
|
+
tag: 'graymatter-logo',
|
|
3
|
+
props: {
|
|
4
|
+
isInverted: { reflect: true, type: 'Boolean', attribute: 'is-inverted' },
|
|
5
|
+
width: { reflect: true, type: 'Number', attribute: 'width' },
|
|
6
|
+
height: { reflect: true, type: 'Number', attribute: 'height' }
|
|
7
|
+
}
|
|
8
|
+
}} />
|
|
9
|
+
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import Logo from './Logo.svelte';
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
isInverted = false,
|
|
15
|
+
width = undefined,
|
|
16
|
+
height = undefined,
|
|
17
|
+
...props
|
|
18
|
+
} = $props();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<Logo
|
|
22
|
+
{isInverted}
|
|
23
|
+
{width}
|
|
24
|
+
{height}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { selectedItem, navigationStore } from '../stores/navigationStore.js';
|
|
3
|
+
import type { NavigationItem } from '../stores/navigationStore.js';
|
|
4
|
+
import DiscoverIcon from './icons/DiscoverIcon.svelte';
|
|
5
|
+
import ChatIcon from './icons/ChatIcon.svelte';
|
|
6
|
+
import ConsoleIcon from './icons/ConsoleIcon.svelte';
|
|
7
|
+
import ApiIcon from './icons/ApiIcon.svelte';
|
|
8
|
+
import { getAppUrls } from '../utils/getAppUrls';
|
|
9
|
+
|
|
10
|
+
export let onNavItemClick: ((data: { item: NavigationItem; expanded: boolean }) => void) | undefined = undefined;
|
|
11
|
+
|
|
12
|
+
const urls = getAppUrls();
|
|
13
|
+
|
|
14
|
+
const navItems = [
|
|
15
|
+
{ id: 'discover', label: 'Discover', icon: DiscoverIcon, href: urls.discover },
|
|
16
|
+
{ id: 'chat', label: 'Chat', icon: ChatIcon, href: urls.chat },
|
|
17
|
+
{ id: 'console', label: 'Console', icon: ConsoleIcon, href: urls.console },
|
|
18
|
+
{ id: 'api', label: 'API', icon: ApiIcon, href: urls.api }
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
function handleNavItemClick(item: NavigationItem) {
|
|
22
|
+
// Set navigation flag immediately to prevent header restoration
|
|
23
|
+
if (item !== 'discover' && typeof window !== 'undefined') {
|
|
24
|
+
sessionStorage.setItem('navigatingToMainNav', 'true');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
navigationStore.setSelectedItem(item);
|
|
28
|
+
|
|
29
|
+
// Clear header state for main navigation items (including discover)
|
|
30
|
+
if (item !== 'discover') {
|
|
31
|
+
try {
|
|
32
|
+
import('@gsa-tts/graymatter-ui/stores/navigationStore').then(({ selectedSubNavItemTitle }) => {
|
|
33
|
+
// Clear the store value immediately
|
|
34
|
+
selectedSubNavItemTitle.set('');
|
|
35
|
+
if (typeof window !== 'undefined') {
|
|
36
|
+
sessionStorage.removeItem('subNav-selectedTitle');
|
|
37
|
+
sessionStorage.removeItem('subNav-selectedId');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Set default header from first submenu item after a short delay
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
const mainContent = document.getElementById('main-content');
|
|
43
|
+
if (mainContent) {
|
|
44
|
+
const firstSection = mainContent.querySelector('h2[id], h3[id], h4[id], h5[id], h6[id]');
|
|
45
|
+
if (firstSection && firstSection.textContent) {
|
|
46
|
+
const firstSectionTitle = firstSection.textContent.trim();
|
|
47
|
+
selectedSubNavItemTitle.set(firstSectionTitle);
|
|
48
|
+
if (typeof window !== 'undefined') {
|
|
49
|
+
sessionStorage.setItem('subNav-selectedTitle', JSON.stringify(firstSectionTitle));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, 200); // Delay to ensure page content is loaded
|
|
54
|
+
}).catch((err) => {
|
|
55
|
+
console.log('[MobileBottomNav] Error clearing header state:', err);
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.log('[MobileBottomNav] Import failed for clearing state:', err);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Clear header state for Discover page
|
|
62
|
+
try {
|
|
63
|
+
import('@gsa-tts/graymatter-ui/stores/navigationStore').then(({ selectedSubNavItemTitle }) => {
|
|
64
|
+
selectedSubNavItemTitle.set('');
|
|
65
|
+
if (typeof window !== 'undefined') {
|
|
66
|
+
sessionStorage.removeItem('subNav-selectedTitle');
|
|
67
|
+
sessionStorage.removeItem('subNav-selectedId');
|
|
68
|
+
}
|
|
69
|
+
}).catch((err) => {
|
|
70
|
+
console.log('[MobileBottomNav] Error clearing header state for Discover:', err);
|
|
71
|
+
});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.log('[MobileBottomNav] Import failed for clearing Discover state:', err);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onNavItemClick?.({ item, expanded: false });
|
|
78
|
+
}
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<nav class="mobile-bottom-nav" part="mobile-bottom-nav">
|
|
82
|
+
{#each navItems as navItem}
|
|
83
|
+
<a
|
|
84
|
+
href={navItem.href}
|
|
85
|
+
class="nav-item"
|
|
86
|
+
class:active={$selectedItem === navItem.id}
|
|
87
|
+
on:click={() => handleNavItemClick(navItem.id)}
|
|
88
|
+
part="nav-item"
|
|
89
|
+
>
|
|
90
|
+
<div class="icon">
|
|
91
|
+
<svelte:component
|
|
92
|
+
this={navItem.icon}
|
|
93
|
+
isSelected={$selectedItem === navItem.id}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
<span class="label">{navItem.label}</span>
|
|
97
|
+
</a>
|
|
98
|
+
{/each}
|
|
99
|
+
</nav>
|
|
100
|
+
|
|
101
|
+
<style>
|
|
102
|
+
.nav-item {
|
|
103
|
+
flex: 1;
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
padding: var(--ai-size-4);
|
|
109
|
+
text-decoration: none;
|
|
110
|
+
color: var(--ai-color-neutral-600);
|
|
111
|
+
transition: color 0.2s ease;
|
|
112
|
+
height: 100%;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.nav-item.active {
|
|
116
|
+
color: var(--ai-color-neutral-900);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.nav-item.active .icon {
|
|
120
|
+
background-color: var(--ai-color-neutral-200);
|
|
121
|
+
border-radius: var(--ai-size-8);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.nav-item:hover {
|
|
125
|
+
color: var(--ai-color-neutral-700);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.icon {
|
|
129
|
+
width: var(--ai-size-24);
|
|
130
|
+
height: var(--ai-size-24);
|
|
131
|
+
margin-bottom: var(--ai-size-2);
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
justify-content: center;
|
|
135
|
+
padding: var(--ai-size-2);
|
|
136
|
+
transition: background-color 0.2s ease;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.label {
|
|
140
|
+
font-size: 0.6875rem; /* 11px */
|
|
141
|
+
font-weight: 500;
|
|
142
|
+
line-height: 1;
|
|
143
|
+
text-align: center;
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
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 } = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<MobileBottomNav
|
|
13
|
+
{onNavItemClick}
|
|
14
|
+
/>
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { selectedItem, isExpanded, navigationStore } from '../stores/navigationStore.js';
|
|
4
|
+
import Logo from './Logo.svelte';
|
|
5
|
+
import CloseIcon from './icons/CloseIcon.svelte';
|
|
6
|
+
|
|
7
|
+
let sideMenuElement: HTMLDivElement;
|
|
8
|
+
let pendingMobileHash: string | null = null;
|
|
9
|
+
|
|
10
|
+
function handleMobileAnchorNavigate(event: Event) {
|
|
11
|
+
const customEvent = event as CustomEvent;
|
|
12
|
+
pendingMobileHash = customEvent.detail.hash;
|
|
13
|
+
closeMobileMenu();
|
|
14
|
+
// Scroll immediately without delay
|
|
15
|
+
if (pendingMobileHash) {
|
|
16
|
+
const el = document.getElementById(pendingMobileHash);
|
|
17
|
+
if (el) {
|
|
18
|
+
// Check if the anchor exists in mobile content
|
|
19
|
+
const mobileContent = document.getElementById('main-content-mobile');
|
|
20
|
+
const mobileAnchor = mobileContent ? mobileContent.querySelector(`[id="${pendingMobileHash}"]`) : null;
|
|
21
|
+
|
|
22
|
+
if (mobileAnchor && mobileContent) {
|
|
23
|
+
// For mobile: show section title in both header and content
|
|
24
|
+
const rect = mobileAnchor.getBoundingClientRect();
|
|
25
|
+
const mobileRect = mobileContent.getBoundingClientRect();
|
|
26
|
+
const scrollTop = mobileContent.scrollTop + rect.top - mobileRect.top;
|
|
27
|
+
mobileContent.scrollTo({ top: scrollTop, behavior: 'auto' });
|
|
28
|
+
|
|
29
|
+
// Update header after scroll completes
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
window.dispatchEvent(new CustomEvent('sectionVisible', {
|
|
32
|
+
detail: {
|
|
33
|
+
title: mobileAnchor.textContent?.trim(),
|
|
34
|
+
id: mobileAnchor.id
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
37
|
+
}, 100);
|
|
38
|
+
} else {
|
|
39
|
+
// Fallback to desktop content
|
|
40
|
+
let scrollableParent = el.parentElement;
|
|
41
|
+
while (scrollableParent && scrollableParent !== document.body) {
|
|
42
|
+
const style = window.getComputedStyle(scrollableParent);
|
|
43
|
+
if (style.overflowY === 'auto' || style.overflowY === 'scroll') {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
scrollableParent = scrollableParent.parentElement;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (scrollableParent && scrollableParent !== document.body) {
|
|
50
|
+
const desktopMain = document.getElementById('main-content');
|
|
51
|
+
if (desktopMain) {
|
|
52
|
+
el.scrollIntoView({ behavior: 'auto', block: 'start' });
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
window.dispatchEvent(new CustomEvent('sectionVisible', {
|
|
55
|
+
detail: {
|
|
56
|
+
title: el.textContent?.trim(),
|
|
57
|
+
id: el.id
|
|
58
|
+
}
|
|
59
|
+
}));
|
|
60
|
+
}, 100);
|
|
61
|
+
} else {
|
|
62
|
+
el.scrollIntoView({ behavior: 'auto', block: 'start' });
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
el.scrollIntoView({ behavior: 'auto', block: 'start' });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
pendingMobileHash = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
onMount(() => {
|
|
74
|
+
// Listen for the custom event globally
|
|
75
|
+
window.addEventListener('mobileAnchorNavigate', handleMobileAnchorNavigate);
|
|
76
|
+
|
|
77
|
+
const handleCloseMobileMenu = () => {
|
|
78
|
+
closeMobileMenu();
|
|
79
|
+
};
|
|
80
|
+
window.addEventListener('closeMobileMenu', handleCloseMobileMenu);
|
|
81
|
+
|
|
82
|
+
const handleLayoutTransition = (event: Event) => {
|
|
83
|
+
const customEvent = event as CustomEvent;
|
|
84
|
+
const { isMobile } = customEvent.detail;
|
|
85
|
+
|
|
86
|
+
if (isMobile) {
|
|
87
|
+
navigationStore.setExpanded(false);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
window.addEventListener('layoutTransition', handleLayoutTransition);
|
|
91
|
+
|
|
92
|
+
const handleEscape = (e: KeyboardEvent) => {
|
|
93
|
+
if (e.key === 'Escape' && $isExpanded) {
|
|
94
|
+
closeMobileMenu();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
window.addEventListener('keydown', handleEscape);
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
window.removeEventListener('mobileAnchorNavigate', handleMobileAnchorNavigate);
|
|
101
|
+
window.removeEventListener('closeMobileMenu', handleCloseMobileMenu);
|
|
102
|
+
window.removeEventListener('layoutTransition', handleLayoutTransition);
|
|
103
|
+
window.removeEventListener('keydown', handleEscape);
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
function closeMobileMenu() {
|
|
108
|
+
navigationStore.setExpanded(false);
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<!-- Mobile overlay -->
|
|
113
|
+
{#if $isExpanded}
|
|
114
|
+
<div
|
|
115
|
+
class="mobile-overlay"
|
|
116
|
+
role="button"
|
|
117
|
+
tabindex="0"
|
|
118
|
+
aria-label="Close navigation menu (Escape key supported)"
|
|
119
|
+
part="mobile-overlay"
|
|
120
|
+
on:click={closeMobileMenu}
|
|
121
|
+
on:keydown={(e) => {
|
|
122
|
+
if (e.key === 'Enter' || e.key === ' ') closeMobileMenu();
|
|
123
|
+
}}
|
|
124
|
+
></div>
|
|
125
|
+
{/if}
|
|
126
|
+
|
|
127
|
+
<!-- Mobile side menu -->
|
|
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">
|
|
131
|
+
<Logo width={75} height={22} />
|
|
132
|
+
</div>
|
|
133
|
+
<button class="close-button" on:click={closeMobileMenu} aria-label="Close navigation menu" part="close-button">
|
|
134
|
+
<CloseIcon />
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div class="menu-content" part="menu-content">
|
|
139
|
+
<!-- Show submenu items if available and not on discover -->
|
|
140
|
+
{#if $selectedItem && $selectedItem !== 'discover'}
|
|
141
|
+
<div class="subnav-section" part="subnav-section">
|
|
142
|
+
<slot />
|
|
143
|
+
</div>
|
|
144
|
+
{:else}
|
|
145
|
+
<!-- Show message when no submenu is available -->
|
|
146
|
+
<div class="no-subnav-message" part="no-subnav-message">
|
|
147
|
+
<p>Select a section from the bottom navigation</p>
|
|
148
|
+
</div>
|
|
149
|
+
{/if}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<style>
|
|
154
|
+
.no-subnav-message {
|
|
155
|
+
padding: var(--ai-size-16);
|
|
156
|
+
text-align: center;
|
|
157
|
+
color: var(--ai-color-steel-600);
|
|
158
|
+
font-size: var(--ai-size-14);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.no-subnav-message p {
|
|
162
|
+
margin: 0;
|
|
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
|
+
}
|
|
225
|
+
</style>
|