@gsa-tts/graymatter-ui 0.4.17 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsa-tts/graymatter-ui",
3
- "version": "0.4.17",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -53,13 +53,13 @@
53
53
  "@types/unist": "^3.0.3",
54
54
  "@uswds/compile": "1.2.2",
55
55
  "@vitest/coverage-istanbul": "^3.2.4",
56
- "astro": "^5.13.7",
56
+ "astro": "^5.15.9",
57
57
  "astro-purgecss": "^5.2.2",
58
58
  "eslint": "^9.21.0",
59
59
  "gulp": "^5.0.0",
60
- "svelte": "^5.35.4",
60
+ "svelte": "^5.53.5",
61
61
  "typescript": "5.7.3",
62
- "vite": "^6.3.5",
62
+ "vite": "^6.4.1",
63
63
  "vitest": "^3.0.7",
64
64
  "@gsa-tts/graymatter-eslint": "0.0.2",
65
65
  "@gsa-tts/graymatter-typescript-config": "0.0.2",
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, onMount } from 'svelte';
2
+ import { createEventDispatcher } from 'svelte';
3
3
  import { subNavReady } from '../stores/subNavReadyStore';
4
4
  import type { SubNavData, SubNavItem } from '../types/subnav.ts';
5
5
  import ApiDocSubNavList from './ApiDocSubNavList.svelte';
6
6
 
7
- const { data = null } = $props();
7
+ let { data = null } = $props();
8
8
  // TODO: remove legacy /api/documentation path once all consuming apps migrate
9
9
  const API_DOC_PATHS = ['/api-documentation', '/api/documentation'];
10
10
 
@@ -187,6 +187,18 @@
187
187
  let currentSelectedId = $state<string | null>(null);
188
188
  let hydrated = $state(false);
189
189
  let pageLoadComplete = $state(false);
190
+ let hasMounted = $state(false);
191
+ let scrollTimeoutId: ReturnType<typeof setTimeout> | null = null;
192
+
193
+ function queueScrollToAnchor(hash: string, delay: number) {
194
+ if (scrollTimeoutId) {
195
+ clearTimeout(scrollTimeoutId);
196
+ }
197
+ scrollTimeoutId = setTimeout(() => {
198
+ scrollTimeoutId = null;
199
+ scrollToAnchorWithOffset(hash);
200
+ }, delay);
201
+ }
190
202
 
191
203
  const effectiveSelectedId = $derived(() => {
192
204
  if (!hydrated || !data) return null;
@@ -257,7 +269,10 @@
257
269
  }
258
270
  });
259
271
 
260
- onMount(() => {
272
+ $effect(() => {
273
+ if (hasMounted) return;
274
+ hasMounted = true;
275
+
261
276
  hydrated = true;
262
277
 
263
278
  if (!data) return;
@@ -296,13 +311,20 @@
296
311
  if (targetItem && targetItem.href && targetItem.href.includes('#')) {
297
312
  const hash = targetItem.href.split('#')[1];
298
313
  if (hash) {
299
- setTimeout(() => scrollToAnchorWithOffset(hash), 100);
314
+ queueScrollToAnchor(hash, 100);
300
315
  }
301
316
  }
302
317
 
303
318
  // Mark page load as complete
304
319
  pageLoadComplete = true;
305
320
  subNavReady.set(true);
321
+
322
+ return () => {
323
+ if (scrollTimeoutId) {
324
+ clearTimeout(scrollTimeoutId);
325
+ scrollTimeoutId = null;
326
+ }
327
+ };
306
328
  });
307
329
 
308
330
  function handleItemClick(event: Event, item: SubNavItem) {
@@ -332,9 +354,7 @@
332
354
  window.location.hash = hash;
333
355
  if (window.innerWidth > 640) {
334
356
  // Desktop: use delay for consistent timing
335
- setTimeout(() => {
336
- scrollToAnchorWithOffset(hash);
337
- }, 350);
357
+ queueScrollToAnchor(hash, 350);
338
358
  } else {
339
359
  // Mobile: immediate scroll without delay
340
360
  window.dispatchEvent(
@@ -382,6 +402,7 @@
382
402
 
383
403
  // Utility: Scroll to anchor with header offset
384
404
  function scrollToAnchorWithOffset(anchorId: string) {
405
+ if (typeof document === 'undefined') return;
385
406
  const mainContent = document.getElementById('main-content');
386
407
  if (mainContent) {
387
408
  const anchor = mainContent.querySelector(
@@ -28,6 +28,7 @@
28
28
  type?: 'button' | 'submit' | 'reset';
29
29
  onclick?: (event: MouseEvent) => void;
30
30
  class?: string;
31
+ children?: import('svelte').Snippet;
31
32
  [key: `data-${string}`]: string;
32
33
  [key: `aria-${string}`]: string;
33
34
  }
@@ -42,6 +43,7 @@
42
43
  size = 'medium',
43
44
  type = 'button',
44
45
  class: className = '',
46
+ children,
45
47
  // eslint-disable-next-line svelte/valid-compile
46
48
  ...props
47
49
  }: Props = $props();
@@ -71,11 +73,11 @@
71
73
 
72
74
  {#if href}
73
75
  <a class={mergedClasses()} {href} class:disabled {onclick} {...props}>
74
- <slot />
76
+ {@render children?.()}
75
77
  </a>
76
78
  {:else}
77
79
  <button class={mergedClasses()} {type} {disabled} {onclick} {...props}>
78
- <slot />
80
+ {@render children?.()}
79
81
  </button>
80
82
  {/if}
81
83
 
@@ -16,17 +16,32 @@
16
16
  import Button from './Button.svelte';
17
17
 
18
18
  // Props for the web component
19
+ interface Props {
20
+ variant?: 'primary' | 'secondary' | 'unstyled';
21
+ theme?: 'default' | 'inverted';
22
+ href?: string;
23
+ disabled?: boolean;
24
+ size?: 'small' | 'medium' | 'large';
25
+ type?: 'button' | 'submit' | 'reset';
26
+ onclick?: (event: MouseEvent) => void;
27
+ class?: string;
28
+ children?: import('svelte').Snippet;
29
+ [key: `data-${string}`]: string;
30
+ [key: `aria-${string}`]: string;
31
+ }
32
+
19
33
  let {
20
34
  variant = 'primary',
21
35
  theme = 'default',
22
- href = null,
36
+ href = undefined,
23
37
  disabled = false,
24
38
  size = 'medium',
25
39
  type = 'button',
26
40
  onclick,
27
41
  class: className = '',
42
+ children,
28
43
  ...props
29
- } = $props();
44
+ }: Props = $props();
30
45
  </script>
31
46
 
32
47
  <Button
@@ -37,8 +52,7 @@
37
52
  {size}
38
53
  {type}
39
54
  {onclick}
55
+ {children}
40
56
  class={className}
41
57
  {...props}
42
- >
43
- <slot />
44
- </Button>
58
+ ></Button>
@@ -4,11 +4,10 @@
4
4
  isExpanded,
5
5
  selectedSubNavItemTitle,
6
6
  } from '../stores/navigationStore.js';
7
- import { onMount } from 'svelte';
8
7
 
9
- const { customHeaderText = '' } = $props();
8
+ let { customHeaderText = '' } = $props();
10
9
 
11
- let isLayoutTransitioning = false;
10
+ let isLayoutTransitioning = $state(false);
12
11
 
13
12
  const showHeader = $derived(
14
13
  () =>
@@ -21,20 +20,19 @@
21
20
  () => customHeaderText || $selectedSubNavItemTitle
22
21
  );
23
22
 
24
- onMount(() => {
23
+ $effect(() => {
24
+ if (typeof window === 'undefined') return;
25
25
  const handleSectionVisible = (event: Event) => {
26
- if (typeof window !== 'undefined' && window.innerWidth <= 640) {
26
+ if (window.innerWidth <= 640) {
27
27
  return;
28
28
  }
29
29
  let topLevelTitle = null;
30
- if (typeof window !== 'undefined') {
31
- topLevelTitle = sessionStorage.getItem('subNav-topLevelTitle');
32
- if (topLevelTitle) {
33
- try {
34
- topLevelTitle = JSON.parse(topLevelTitle);
35
- } catch {
36
- /* ignore parse error */
37
- }
30
+ topLevelTitle = sessionStorage.getItem('subNav-topLevelTitle');
31
+ if (topLevelTitle) {
32
+ try {
33
+ topLevelTitle = JSON.parse(topLevelTitle);
34
+ } catch {
35
+ /* ignore parse error */
38
36
  }
39
37
  }
40
38
  if (typeof topLevelTitle === 'string' && topLevelTitle.trim() !== '') {
@@ -1,9 +1,14 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * Controls which nav item is selected on initial server-side render (SSR) or first paint.
4
- * - For the Discover page, set ssrSelectedItem="discover".
5
- * - For other pages, set ssrSelectedItem to the corresponding nav key (e.g., "api", "chat", "console").
6
- * - If not provided, the navigation will default to expanded/open on SSR.
3
+ * Initial selection for SSR/first paint.
4
+ * - Discover page: set ssrSelectedItem="discover".
5
+ * - Other pages: set ssrSelectedItem to the nav key (e.g., "api", "chat", "console").
6
+ * - Omit to default to expanded/open on SSR.
7
+ *
8
+ * controlledSelectedItem overrides the nav store after hydration.
9
+ * - Use for web component integrations where bundle/app stores can diverge.
10
+ * - In Svelte/SvelteKit apps, prefer NavigationInitializer and omit
11
+ * controlledSelectedItem to keep store-driven behavior.
7
12
  */
8
13
  import { onMount } from 'svelte';
9
14
  import {
@@ -21,8 +26,9 @@
21
26
  import ProfileMenu from './ProfileMenu.svelte';
22
27
  import HelpIcon from './icons/HelpIcon.svelte';
23
28
 
24
- const {
29
+ let {
25
30
  ssrSelectedItem = undefined,
31
+ controlledSelectedItem = undefined,
26
32
  onNavToggle = undefined,
27
33
  onNavItemClick = undefined,
28
34
  showAppIcons = true,
@@ -33,9 +39,10 @@
33
39
  consoleUrl,
34
40
  discoverUrl,
35
41
  supplementalMenuData,
42
+ children,
36
43
  } = $props();
37
44
 
38
- const helpUrl = supplementalMenuData?.help?.url?.trim();
45
+ const helpUrl = $derived(() => supplementalMenuData?.help?.url?.trim());
39
46
 
40
47
  // Check if we're on the help page by comparing URLs (help is not a nav/subnav item)
41
48
  function getPathname(url: string, baseOrigin: string) {
@@ -47,23 +54,24 @@
47
54
  }
48
55
 
49
56
  function checkIsHelpPage(): boolean {
50
- if (!helpUrl || helpUrl === '#' || helpUrl.startsWith('#')) {
57
+ const url = helpUrl();
58
+ if (!url || url === '#' || url.startsWith('#')) {
51
59
  return false;
52
60
  }
53
61
  const location = (
54
62
  globalThis as { location?: { pathname?: string; origin?: string } }
55
63
  ).location;
56
64
  if (location?.origin && location?.pathname) {
57
- const helpPath = getPathname(helpUrl, location.origin);
65
+ const helpPath = getPathname(url, location.origin);
58
66
  return helpPath !== null && location.pathname === helpPath;
59
67
  }
60
68
  return false;
61
69
  }
62
70
 
63
- const isHelpSelected = $derived(checkIsHelpPage());
71
+ const isHelpSelected = $derived(() => checkIsHelpPage());
64
72
 
65
- let hydrated = false;
66
- let navContainer: HTMLDivElement;
73
+ let hydrated = $state(false);
74
+ let navContainer = $state<HTMLDivElement | null>(null);
67
75
  let hoveredItem = $state<string | null>(null);
68
76
  let menuButtonHovered = $state(false);
69
77
  let canHover = $state(false);
@@ -79,8 +87,16 @@
79
87
  return hydrated ? hydratedValue : ssrValue;
80
88
  }
81
89
 
90
+ const selectedForUi = $derived(() => controlledSelectedItem ?? $selectedItem);
91
+ const ssrSelectedForUi = $derived(
92
+ () => controlledSelectedItem ?? ssrSelectedItem
93
+ );
94
+ const ssrSelected = $derived(() => ssrSelectedForUi());
95
+ const ssrIsDiscover = $derived(() => ssrSelected() === 'discover');
96
+ const isMenuDisabledForUi = $derived(() => selectedForUi() === 'discover');
97
+
82
98
  function isNavSelected(key: NavigationItem) {
83
- return ssrOrHydrated($selectedItem === key, ssrSelectedItem === key);
99
+ return ssrOrHydrated(selectedForUi() === key, ssrSelectedForUi() === key);
84
100
  }
85
101
 
86
102
  function isNavHovered(key: string) {
@@ -88,31 +104,36 @@
88
104
  }
89
105
 
90
106
  const sideNavExpanded = $derived(() =>
91
- ssrOrHydrated(
92
- $isExpanded,
93
- ssrSelectedItem ? ssrSelectedItem !== 'discover' : true
94
- )
107
+ ssrOrHydrated($isExpanded, ssrSelected() ? !ssrIsDiscover() : true)
95
108
  );
96
109
  const menuBtnDisabled = $derived(() =>
97
110
  ssrOrHydrated(
98
- $isMenuButtonDisabled,
99
- ssrSelectedItem ? ssrSelectedItem === 'discover' : false
111
+ isMenuDisabledForUi(),
112
+ ssrSelected() ? ssrIsDiscover() : false
100
113
  )
101
114
  );
102
115
  const menuBtnOpen = $derived(() =>
103
- ssrOrHydrated(
104
- $isExpanded,
105
- ssrSelectedItem ? ssrSelectedItem !== 'discover' : true
106
- )
116
+ ssrOrHydrated($isExpanded, ssrSelected() ? !ssrIsDiscover() : true)
107
117
  );
108
118
  const menuBtnHovered = $derived(
109
119
  () => canHover && menuButtonHovered && !menuBtnDisabled()
110
120
  );
111
121
 
112
122
  onMount(() => {
123
+ if (typeof window === 'undefined') return;
124
+
113
125
  hydrated = true;
126
+
127
+ if (
128
+ controlledSelectedItem === undefined &&
129
+ ssrSelectedItem &&
130
+ $selectedItem === null
131
+ ) {
132
+ navigationStore.setSelectedItem(ssrSelectedItem);
133
+ }
114
134
  // Remove the CSS class since reactive state takes over
115
135
  document.documentElement.classList.remove('nav-initially-expanded');
136
+
116
137
  // Remove the custom property since reactive CSS takes over
117
138
  document.documentElement.style.removeProperty('--initial-main-top-padding');
118
139
  // Add hydrated class to re-enable transitions
@@ -121,106 +142,120 @@
121
142
  }
122
143
 
123
144
  // Detect if device supports hover (desktop only)
124
- if (typeof window !== 'undefined') {
125
- const mq = window.matchMedia('(hover: hover)');
145
+ const mq =
146
+ typeof window.matchMedia === 'function'
147
+ ? window.matchMedia('(hover: hover)')
148
+ : null;
149
+ const handleHoverChange = (e: MediaQueryListEvent) => {
150
+ canHover = e.matches;
151
+ };
152
+ if (mq) {
126
153
  canHover = mq.matches;
127
- mq.addEventListener('change', e => {
128
- canHover = e.matches;
129
- });
154
+ mq.addEventListener('change', handleHoverChange);
155
+ } else {
156
+ canHover = false;
157
+ }
130
158
 
131
- // Listen for layout transitions
132
- const handleLayoutTransition = (event: Event) => {
133
- const customEvent = event as CustomEvent;
134
- const { isMobile } = customEvent.detail;
159
+ // Listen for layout transitions
160
+ const handleLayoutTransition = (event: Event) => {
161
+ const customEvent = event as CustomEvent;
162
+ const { isMobile } = customEvent.detail;
135
163
 
136
- if (isMobile) {
137
- // Switching to mobile: close navigation
138
- navigationStore.setExpanded(false);
164
+ if (isMobile) {
165
+ // Switching to mobile: close navigation
166
+ navigationStore.setExpanded(false);
167
+ } else {
168
+ // Switching to desktop: open navigation if on non-discover page
169
+ const currentItem = selectedForUi();
170
+
171
+ if (currentItem && currentItem !== 'discover') {
172
+ navigationStore.setExpanded(true);
139
173
  } else {
140
- // Switching to desktop: open navigation if on non-discover page
141
- const currentItem = $selectedItem;
142
-
143
- if (currentItem && currentItem !== 'discover') {
144
- navigationStore.setExpanded(true);
145
- } else {
146
- navigationStore.setExpanded(false);
147
- }
148
- }
149
- };
150
-
151
- window.addEventListener('layoutTransition', handleLayoutTransition);
152
-
153
- // Add Escape key handler for nav collapse, but ignore if focus is in profile context menu
154
- const handleEscape = (e: KeyboardEvent) => {
155
- const active = document.activeElement;
156
- if (
157
- e.key === 'Escape' &&
158
- $isExpanded &&
159
- !(active && active.closest('.profile-context-menu'))
160
- ) {
161
174
  navigationStore.setExpanded(false);
162
175
  }
163
- };
164
- window.addEventListener('keydown', handleEscape);
176
+ }
177
+ };
165
178
 
166
- // Custom event system for external control
167
- const handleDesktopNavControlEvent = (event: Event) => {
168
- const customEvent = event as CustomEvent;
169
- const { action, isOpen: eventIsOpen } = customEvent.detail;
179
+ window.addEventListener('layoutTransition', handleLayoutTransition);
170
180
 
171
- if (action === 'toggle') {
172
- navigationStore.toggleExpanded();
173
- } else if (action === 'open') {
174
- navigationStore.setExpanded(true);
175
- } else if (action === 'close') {
176
- navigationStore.setExpanded(false);
177
- } else if (action === 'set' && typeof eventIsOpen === 'boolean') {
178
- navigationStore.setExpanded(eventIsOpen);
179
- }
180
- };
181
+ // Add Escape key handler for nav collapse, but ignore if focus is in profile context menu
182
+ const handleEscape = (e: KeyboardEvent) => {
183
+ const active = document.activeElement;
184
+ const profileMenuOpen = Boolean(
185
+ document.querySelector('.profile-context-menu')
186
+ );
187
+ if (
188
+ e.key === 'Escape' &&
189
+ $isExpanded &&
190
+ !profileMenuOpen &&
191
+ !(active && active.closest('.profile-context-menu'))
192
+ ) {
193
+ navigationStore.setExpanded(false);
194
+ }
195
+ };
196
+ window.addEventListener('keydown', handleEscape);
197
+
198
+ // Custom event system for external control
199
+ const handleDesktopNavControlEvent = (event: Event) => {
200
+ const customEvent = event as CustomEvent;
201
+ const { action, isOpen: eventIsOpen } = customEvent.detail;
202
+
203
+ if (action === 'toggle') {
204
+ navigationStore.toggleExpanded();
205
+ } else if (action === 'open') {
206
+ navigationStore.setExpanded(true);
207
+ } else if (action === 'close') {
208
+ navigationStore.setExpanded(false);
209
+ } else if (action === 'set' && typeof eventIsOpen === 'boolean') {
210
+ navigationStore.setExpanded(eventIsOpen);
211
+ }
212
+ };
213
+
214
+ // Listen for custom desktop navigation control events
215
+ window.addEventListener(
216
+ 'graymatter-desktop-nav-control',
217
+ handleDesktopNavControlEvent
218
+ );
181
219
 
182
- // Listen for custom desktop navigation control events
183
- window.addEventListener(
220
+ eventListenerCleanup = () => {
221
+ window.removeEventListener(
184
222
  'graymatter-desktop-nav-control',
185
223
  handleDesktopNavControlEvent
186
224
  );
187
-
188
- eventListenerCleanup = () => {
189
- window.removeEventListener(
190
- 'graymatter-desktop-nav-control',
191
- handleDesktopNavControlEvent
192
- );
193
- };
194
-
195
- // Listen for navigation changes to update help button state
196
- const handleNavigationChange = () => {
197
- // Force reactivity update by accessing the derived value
198
- // Accessing isHelpSelected triggers reactivity
199
- void isHelpSelected;
200
- };
201
-
202
- window.addEventListener('popstate', handleNavigationChange);
203
-
204
- return () => {
205
- window.removeEventListener('layoutTransition', handleLayoutTransition);
206
- window.removeEventListener('keydown', handleEscape);
207
- window.removeEventListener('popstate', handleNavigationChange);
208
- if (eventListenerCleanup) {
209
- eventListenerCleanup();
210
- }
211
- };
212
- }
225
+ };
226
+
227
+ // Listen for navigation changes to update help button state
228
+ const handleNavigationChange = () => {
229
+ // Force reactivity update by accessing the derived value
230
+ // Accessing isHelpSelected triggers reactivity
231
+ void isHelpSelected();
232
+ };
233
+
234
+ window.addEventListener('popstate', handleNavigationChange);
235
+
236
+ return () => {
237
+ window.removeEventListener('layoutTransition', handleLayoutTransition);
238
+ window.removeEventListener('keydown', handleEscape);
239
+ window.removeEventListener('popstate', handleNavigationChange);
240
+ if (mq) {
241
+ mq.removeEventListener('change', handleHoverChange);
242
+ }
243
+ if (eventListenerCleanup) {
244
+ eventListenerCleanup();
245
+ }
246
+ };
213
247
  });
214
248
 
215
249
  function handleHelpClick(event: MouseEvent) {
216
250
  // If it's a hash link, prevent default to stay on same page
217
- if (helpUrl === '#' || helpUrl?.startsWith('#')) {
251
+ const url = helpUrl();
252
+ if (url === '#' || url?.startsWith('#')) {
218
253
  event.preventDefault();
219
254
  }
220
255
  }
221
256
 
222
257
  function toggleNav() {
223
- if ($isMenuButtonDisabled) return;
258
+ if (menuBtnDisabled()) return;
224
259
  navigationStore.toggleExpanded();
225
260
  onNavToggle?.({ expanded: $isExpanded });
226
261
  }
@@ -231,7 +266,9 @@
231
266
  sessionStorage.setItem('navigatingToMainNav', 'true');
232
267
  }
233
268
 
234
- navigationStore.setSelectedItem(item);
269
+ if (controlledSelectedItem === undefined) {
270
+ navigationStore.setSelectedItem(item);
271
+ }
235
272
 
236
273
  // Clear header state for main navigation items (including discover)
237
274
  if (item !== 'discover') {
@@ -470,16 +507,16 @@
470
507
 
471
508
  {#if profileMenuData}
472
509
  <div class="nav-footer" part="nav-footer">
473
- {#if helpUrl}
510
+ {#if helpUrl()}
474
511
  <div
475
512
  class="nav-footer-icon-container"
476
513
  part="nav-footer-icon-container"
477
514
  >
478
515
  <a
479
516
  class="help-link"
480
- class:selected={isHelpSelected}
481
- aria-current={isHelpSelected ? 'page' : undefined}
482
- href={helpUrl}
517
+ class:selected={isHelpSelected()}
518
+ aria-current={isHelpSelected() ? 'page' : undefined}
519
+ href={helpUrl()}
483
520
  part="help-link"
484
521
  title="Help"
485
522
  aria-label="Help"
@@ -487,7 +524,7 @@
487
524
  >
488
525
  <div class="icon-container" part="help-icon-container">
489
526
  <div class="icon-wrapper">
490
- <HelpIcon isSelected={isHelpSelected} />
527
+ <HelpIcon isSelected={isHelpSelected()} />
491
528
  </div>
492
529
  </div>
493
530
  </a>
@@ -513,7 +550,7 @@
513
550
  {#if sideNavExpanded()}
514
551
  <div class="drawer-content" part="drawer-content">
515
552
  <nav aria-label="Sub navigation" part="sub-navigation">
516
- <slot />
553
+ {@render children?.()}
517
554
  </nav>
518
555
  </div>
519
556
  {/if}
@@ -10,6 +10,7 @@
10
10
  // Props for the web component
11
11
  let {
12
12
  ssrSelectedItem,
13
+ controlledSelectedItem,
13
14
  onNavToggle,
14
15
  onNavItemClick,
15
16
  showAppIcons = true,
@@ -20,11 +21,13 @@
20
21
  consoleUrl,
21
22
  discoverUrl,
22
23
  supplementalMenuData,
24
+ children,
23
25
  } = $props();
24
26
  </script>
25
27
 
26
28
  <DesktopSideNav
27
29
  {ssrSelectedItem}
30
+ {controlledSelectedItem}
28
31
  {onNavToggle}
29
32
  {onNavItemClick}
30
33
  {showAppIcons}
@@ -35,6 +38,5 @@
35
38
  {consoleUrl}
36
39
  {discoverUrl}
37
40
  {supplementalMenuData}
38
- >
39
- <slot />
40
- </DesktopSideNav>
41
+ {children}
42
+ />
@@ -8,9 +8,9 @@
8
8
  ...restProps
9
9
  } = $props();
10
10
 
11
- const logoColor = isInverted
12
- ? 'var(--ai-color-white)'
13
- : 'var(--ai-color-black)';
11
+ const logoColor = $derived(() =>
12
+ isInverted ? 'var(--ai-color-white)' : 'var(--ai-color-black)'
13
+ );
14
14
  </script>
15
15
 
16
16
  {#if !showServiceMark}
@@ -20,7 +20,7 @@
20
20
  viewBox="0 0 102 30"
21
21
  fill="none"
22
22
  xmlns="http://www.w3.org/2000/svg"
23
- style="fill: {logoColor};"
23
+ style="fill: {logoColor()};"
24
24
  {...restProps}
25
25
  >
26
26
  <g>
@@ -44,7 +44,7 @@
44
44
  viewBox="0 0 75 20"
45
45
  fill="none"
46
46
  xmlns="http://www.w3.org/2000/svg"
47
- style="fill: {logoColor};"
47
+ style="fill: {logoColor()};"
48
48
  {...restProps}
49
49
  >
50
50
  <path