@dataengineeringformachinelearning/viking-ui 1.0.1 → 3.0.0-alpha.2

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.
Files changed (129) hide show
  1. package/README.md +180 -37
  2. package/dist/deml-components.css +1122 -0
  3. package/dist/design-tokens.css +688 -0
  4. package/dist/elements/button/viking-button.d.ts +14 -0
  5. package/dist/elements/card/viking-card.d.ts +8 -0
  6. package/dist/elements/core/dom.d.ts +4 -0
  7. package/dist/elements/core/styles.d.ts +2 -0
  8. package/dist/elements/modal/viking-modal.d.ts +15 -0
  9. package/dist/fonts/inter/InterVariable-Italic.woff2 +0 -0
  10. package/dist/fonts/inter/InterVariable.woff2 +0 -0
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.js +4051 -0
  13. package/dist/lib/core/brand-icons.d.ts +29 -0
  14. package/dist/lib/core/icons.d.ts +132 -0
  15. package/dist/lib/core/integration-brand-icons.d.ts +18 -0
  16. package/dist/lib/core/lucide-paths.generated.d.ts +90 -0
  17. package/dist/lib/site-drakkar/site-drakkar.config.d.ts +50 -0
  18. package/dist/lib/site-drakkar/suite-search-items.d.ts +17 -0
  19. package/dist/viking-components.css +1837 -0
  20. package/dist/viking-tokens.json +318 -0
  21. package/dist/viking-ui-elements.js +1750 -0
  22. package/dist/viking-ui.css +1 -0
  23. package/dist/web/badge/viking-badge-wc.d.ts +28 -0
  24. package/dist/web/button/viking-button-wc.d.ts +28 -0
  25. package/dist/web/callout/viking-callout-wc.d.ts +27 -0
  26. package/dist/web/card/viking-card-wc.d.ts +29 -0
  27. package/dist/web/core/base.d.ts +12 -0
  28. package/dist/web/core/dom.d.ts +11 -0
  29. package/dist/web/core/icons-inline.d.ts +7 -0
  30. package/dist/web/core/styles.d.ts +12 -0
  31. package/dist/web/core/types.d.ts +10 -0
  32. package/dist/web/field/viking-field-wc.d.ts +32 -0
  33. package/dist/web/index.d.ts +15 -0
  34. package/dist/web/input/viking-input-wc.d.ts +32 -0
  35. package/dist/web/modal/viking-modal-wc.d.ts +50 -0
  36. package/dist/web/search-palette/viking-search-palette-wc.d.ts +64 -0
  37. package/dist/web/select/viking-select-wc.d.ts +48 -0
  38. package/dist/web/suite-header/viking-suite-header-wc.d.ts +36 -0
  39. package/dist/web/suite-search-palette/viking-suite-search-palette-wc.d.ts +39 -0
  40. package/dist/web/theme-toggle/viking-theme-toggle-wc.d.ts +19 -0
  41. package/dist/web/types.d.ts +40 -0
  42. package/dist/web-components/index.d.ts +1 -0
  43. package/dist/web-components.js +1750 -0
  44. package/dist/widget.js +1182 -0
  45. package/package.json +89 -30
  46. package/src/assets/fonts/inter/InterVariable-Italic.woff2 +0 -0
  47. package/src/assets/fonts/inter/InterVariable.woff2 +0 -0
  48. package/src/elements/button/viking-button.ts +201 -0
  49. package/src/elements/card/viking-card.ts +60 -0
  50. package/src/elements/core/dom.ts +33 -0
  51. package/src/elements/core/styles.ts +28 -0
  52. package/src/elements/modal/viking-modal.ts +154 -0
  53. package/src/index.ts +1 -0
  54. package/src/lib/core/brand-icons.ts +62 -0
  55. package/src/lib/core/icons.ts +206 -0
  56. package/src/lib/core/integration-brand-icons.ts +77 -0
  57. package/src/lib/core/lucide-paths.generated.ts +130 -0
  58. package/src/lib/site-drakkar/site-drakkar.config.ts +295 -0
  59. package/src/lib/site-drakkar/suite-search-items.ts +246 -0
  60. package/src/styles/_buttons.scss +301 -0
  61. package/src/styles/_component-primitives.scss +183 -0
  62. package/src/styles/_drakkar-buttons.scss +85 -0
  63. package/src/styles/_fonts.scss +22 -0
  64. package/src/styles/_forms.scss +66 -0
  65. package/src/styles/_input.scss +106 -0
  66. package/src/styles/_layout-enforcement.scss +166 -0
  67. package/src/styles/_layout-rhythm.scss +158 -0
  68. package/src/styles/_legacy-aliases.scss +212 -0
  69. package/src/styles/_series-colors.scss +24 -0
  70. package/src/styles/_static-primitives.scss +857 -0
  71. package/src/styles/_typography.scss +87 -0
  72. package/src/styles/_variables.scss +527 -0
  73. package/src/styles/components/a11y.scss +90 -0
  74. package/src/styles/components/badges.scss +102 -0
  75. package/src/styles/components/extracted-utilities.scss +459 -0
  76. package/src/styles/components/footer.scss +155 -0
  77. package/src/styles/components/icon-heading.scss +47 -0
  78. package/src/styles/components/icon-inline.scss +78 -0
  79. package/src/styles/components/layout.scss +59 -0
  80. package/src/styles/components/utilities.scss +131 -0
  81. package/src/styles/components/viking-inspired.scss +204 -0
  82. package/src/styles/components/whitepaper-cta.scss +130 -0
  83. package/src/styles/components-bundle.scss +9 -0
  84. package/src/styles/components.scss +175 -0
  85. package/src/styles/deml-components.scss +10 -0
  86. package/src/styles/index.scss +8 -0
  87. package/src/styles/layout-shell.scss +39 -11
  88. package/src/styles/page-shell.scss +663 -33
  89. package/src/styles/static-navbar.scss +862 -146
  90. package/src/styles/surfaces/backend.scss +142 -0
  91. package/src/styles/surfaces/docs-global.scss +73 -0
  92. package/src/styles/surfaces/docs-shell.scss +412 -0
  93. package/src/styles/surfaces/docs-showcase.scss +1051 -0
  94. package/src/styles/surfaces/marketing-cta.scss +62 -0
  95. package/src/styles/surfaces/marketing-docs-bento.scss +67 -0
  96. package/src/styles/surfaces/marketing-global.scss +748 -0
  97. package/src/styles/surfaces/marketing-landing.scss +1260 -0
  98. package/src/styles/surfaces/marketing-publication.scss +535 -0
  99. package/src/styles/surfaces/marketing-theme.scss +14 -0
  100. package/src/styles/surfaces/swagger-ui.scss +921 -0
  101. package/src/styles/tokens-export.scss +18 -0
  102. package/src/styles/tokens.scss +5 -0
  103. package/src/styles/viking-ui-bundle.scss +21 -2
  104. package/src/styles/viking-ui.scss +213 -51
  105. package/src/tokens/viking-tokens.json +318 -0
  106. package/src/web/badge/viking-badge-wc.ts +116 -0
  107. package/src/web/button/viking-button-wc.ts +165 -0
  108. package/src/web/callout/viking-callout-wc.ts +105 -0
  109. package/src/web/card/viking-card-wc.ts +66 -0
  110. package/src/web/core/base.ts +56 -0
  111. package/src/web/core/dom.ts +53 -0
  112. package/src/web/core/icons-inline.ts +44 -0
  113. package/src/web/core/styles.ts +1138 -0
  114. package/src/web/core/types.ts +19 -0
  115. package/src/web/field/viking-field-wc.ts +154 -0
  116. package/src/web/index.ts +73 -0
  117. package/src/web/input/viking-input-wc.ts +211 -0
  118. package/src/web/modal/viking-modal-wc.ts +187 -0
  119. package/src/web/search-palette/viking-search-palette-wc.ts +494 -0
  120. package/src/web/select/viking-select-wc.ts +218 -0
  121. package/src/web/suite-header/viking-suite-header-wc.ts +714 -0
  122. package/src/web/suite-search-palette/viking-suite-search-palette-wc.ts +234 -0
  123. package/src/web/theme-toggle/viking-theme-toggle-wc.ts +139 -0
  124. package/src/web/types.ts +37 -0
  125. package/src/web-components/index.ts +1 -0
  126. package/fesm2022/dataengineeringformachinelearning-viking-ui.mjs +0 -5949
  127. package/fesm2022/dataengineeringformachinelearning-viking-ui.mjs.map +0 -1
  128. package/types/dataengineeringformachinelearning-viking-ui.d.ts +0 -1374
  129. package/viking.manifest.json +0 -189
@@ -0,0 +1,714 @@
1
+ import {
2
+ DEFAULT_SITE_URLS,
3
+ type SiteDrakkarContext,
4
+ type SiteUrls,
5
+ resolveBrandHref,
6
+ resolveNavHref,
7
+ SITE_NAV_LINKS,
8
+ visibleNavLinks,
9
+ } from "../../lib/site-drakkar/site-drakkar.config";
10
+ import { attachShadowStyles, readBoolAttr } from "../core/base";
11
+ import {
12
+ defineCustomElement,
13
+ escapeHtml,
14
+ HTMLElementBase,
15
+ modKeyLabel,
16
+ } from "../core/dom";
17
+ import { renderInlineIcon } from "../core/icons-inline";
18
+ import {
19
+ registerVikingSuiteSearchPaletteWc,
20
+ type VikingSuiteSearchPaletteWc,
21
+ } from "../suite-search-palette/viking-suite-search-palette-wc";
22
+ import { registerVikingThemeToggleWc } from "../theme-toggle/viking-theme-toggle-wc";
23
+
24
+ const VALID_CONTEXTS = new Set<SiteDrakkarContext>([
25
+ "app",
26
+ "marketing",
27
+ "backend",
28
+ "docs",
29
+ ]);
30
+
31
+ const VIKING_SUITE_HEADER_STYLES = `
32
+ :host {
33
+ display: block;
34
+ position: sticky;
35
+ top: 0;
36
+ z-index: var(--viking-z-sticky, 50);
37
+ color: var(--viking-text);
38
+ font-family: var(--viking-font-family);
39
+ }
40
+
41
+ * {
42
+ box-sizing: border-box;
43
+ }
44
+
45
+ .suite-header {
46
+ width: 100%;
47
+ min-height: var(--viking-navbar-height, var(--viking-space-8));
48
+ background: var(--viking-surface);
49
+ border-bottom: var(--viking-border-width, 1px) solid var(--viking-border);
50
+ box-shadow: var(--viking-shadow-xs);
51
+ isolation: isolate;
52
+ }
53
+
54
+ .suite-header__bar {
55
+ display: grid;
56
+ grid-template-columns: minmax(max-content, auto) minmax(0, 1fr) max-content;
57
+ align-items: center;
58
+ gap: var(--viking-space-1);
59
+ width: 100%;
60
+ max-width: var(--viking-container-max-width);
61
+ min-height: var(--viking-navbar-height, var(--viking-space-8));
62
+ margin-inline: auto;
63
+ padding-inline: var(--viking-page-gutter);
64
+ }
65
+
66
+ .suite-header__brand {
67
+ display: inline-flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ gap: var(--viking-space-1);
71
+ min-width: 0;
72
+ min-height: var(--viking-control-height);
73
+ color: var(--viking-text);
74
+ text-decoration: none;
75
+ border-radius: var(--viking-radius);
76
+ }
77
+
78
+ .suite-header__brand:focus-visible,
79
+ .suite-header__link:focus-visible,
80
+ .suite-header__icon-button:focus-visible,
81
+ .suite-header__auth-link:focus-visible,
82
+ .suite-header__user-trigger:focus-visible,
83
+ .suite-header__menu-link:focus-visible,
84
+ .suite-header__menu-button:focus-visible {
85
+ outline: var(--viking-ring-width) solid var(--viking-ring);
86
+ outline-offset: var(--viking-ring-offset);
87
+ }
88
+
89
+ .suite-header__mark {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ width: var(--viking-space-4);
94
+ height: var(--viking-space-4);
95
+ color: var(--viking-accent);
96
+ flex: 0 0 auto;
97
+ }
98
+
99
+ .suite-header__lockup {
100
+ display: none;
101
+ min-width: 0;
102
+ color: var(--viking-text);
103
+ font-size: var(--viking-font-size-sm);
104
+ font-weight: var(--viking-font-weight-bold);
105
+ line-height: var(--viking-line-height-tight);
106
+ letter-spacing: var(--viking-letter-spacing-caps);
107
+ text-transform: uppercase;
108
+ white-space: nowrap;
109
+ }
110
+
111
+ .suite-header__nav {
112
+ display: none;
113
+ align-items: center;
114
+ justify-content: center;
115
+ gap: var(--viking-space-half);
116
+ min-width: 0;
117
+ overflow: hidden;
118
+ }
119
+
120
+ .suite-header__link {
121
+ display: inline-flex;
122
+ align-items: center;
123
+ justify-content: center;
124
+ gap: var(--viking-space-half);
125
+ min-width: 0;
126
+ height: var(--viking-control-height);
127
+ padding-inline: var(--viking-space-1-5);
128
+ color: var(--viking-text-muted);
129
+ border-radius: var(--viking-radius) var(--viking-radius) 0 0;
130
+ font-size: var(--viking-font-size-sm);
131
+ font-weight: var(--viking-font-weight-semibold);
132
+ line-height: var(--viking-line-height-none);
133
+ letter-spacing: var(--viking-letter-spacing-caps);
134
+ text-decoration: none;
135
+ text-transform: uppercase;
136
+ white-space: nowrap;
137
+ transition: var(--viking-transition-interactive);
138
+ }
139
+
140
+ .suite-header__link:hover,
141
+ .suite-header__link[aria-current='page'] {
142
+ color: var(--viking-text);
143
+ background: var(--viking-surface-alt);
144
+ }
145
+
146
+ .suite-header__actions {
147
+ display: inline-flex;
148
+ align-items: center;
149
+ justify-content: flex-end;
150
+ gap: var(--viking-space-1);
151
+ min-width: 0;
152
+ }
153
+
154
+ .suite-header__icon-button,
155
+ .suite-header__menu-button,
156
+ .suite-header__user-trigger,
157
+ .suite-header__auth-link {
158
+ min-height: var(--viking-control-height);
159
+ border: var(--viking-border-width, 1px) solid var(--viking-border-strong);
160
+ border-radius: var(--viking-radius);
161
+ background: var(--viking-surface);
162
+ color: var(--viking-text);
163
+ box-shadow: var(--viking-shadow-sm);
164
+ cursor: pointer;
165
+ font-family: inherit;
166
+ transition: var(--viking-transition-interactive);
167
+ -webkit-tap-highlight-color: transparent;
168
+ }
169
+
170
+ .suite-header__icon-button,
171
+ .suite-header__menu-button {
172
+ display: inline-flex;
173
+ align-items: center;
174
+ justify-content: center;
175
+ width: var(--viking-control-height);
176
+ min-width: var(--viking-control-height);
177
+ padding: 0;
178
+ }
179
+
180
+ .suite-header__icon-button:hover,
181
+ .suite-header__menu-button:hover,
182
+ .suite-header__user-trigger:hover,
183
+ .suite-header__auth-link:hover {
184
+ border-color: var(--viking-accent-strong);
185
+ background: var(--viking-surface-alt);
186
+ box-shadow: var(--viking-shadow-md);
187
+ }
188
+
189
+ .suite-header__auth-link {
190
+ display: none;
191
+ align-items: center;
192
+ justify-content: center;
193
+ gap: var(--viking-space-half);
194
+ min-width: max-content;
195
+ padding-inline: var(--viking-space-2);
196
+ background: var(--viking-accent);
197
+ border-color: var(--viking-accent);
198
+ color: var(--viking-accent-content);
199
+ font-size: var(--viking-font-size-sm);
200
+ font-weight: var(--viking-font-weight-bold);
201
+ line-height: var(--viking-line-height-none);
202
+ letter-spacing: var(--viking-letter-spacing-caps);
203
+ text-decoration: none;
204
+ text-transform: uppercase;
205
+ }
206
+
207
+ .suite-header__auth-link:hover {
208
+ background: var(--viking-accent-hover);
209
+ color: var(--viking-accent-content);
210
+ }
211
+
212
+ .suite-header__user {
213
+ position: relative;
214
+ display: none;
215
+ }
216
+
217
+ .suite-header__user-trigger {
218
+ display: inline-flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ gap: var(--viking-space-1);
222
+ min-width: max-content;
223
+ padding-inline: var(--viking-space-1);
224
+ }
225
+
226
+ .suite-header__avatar {
227
+ display: inline-flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ width: var(--viking-space-3);
231
+ height: var(--viking-space-3);
232
+ border-radius: var(--viking-radius-full);
233
+ background: var(--viking-accent-soft);
234
+ color: var(--viking-accent-strong);
235
+ }
236
+
237
+ .suite-header__user-text {
238
+ display: none;
239
+ max-width: var(--viking-space-16);
240
+ overflow: hidden;
241
+ color: var(--viking-text);
242
+ font-size: var(--viking-font-size-sm);
243
+ font-weight: var(--viking-font-weight-semibold);
244
+ line-height: var(--viking-line-height-tight);
245
+ text-overflow: ellipsis;
246
+ white-space: nowrap;
247
+ }
248
+
249
+ .suite-header__menu {
250
+ position: absolute;
251
+ inset-block-start: calc(100% + var(--viking-space-1));
252
+ inset-inline-end: 0;
253
+ display: none;
254
+ min-width: var(--viking-space-24);
255
+ padding: var(--viking-space-1);
256
+ background: var(--viking-surface);
257
+ border: var(--viking-border-width, 1px) solid var(--viking-border-strong);
258
+ border-radius: var(--viking-radius);
259
+ box-shadow: var(--viking-shadow-lg);
260
+ }
261
+
262
+ .suite-header__user[data-open='true'] .suite-header__menu {
263
+ display: grid;
264
+ gap: var(--viking-space-half);
265
+ }
266
+
267
+ .suite-header__menu-label {
268
+ padding: var(--viking-space-1);
269
+ color: var(--viking-text-muted);
270
+ font-size: var(--viking-font-size-xs);
271
+ line-height: var(--viking-line-height-tight);
272
+ }
273
+
274
+ .suite-header__menu-label strong {
275
+ display: block;
276
+ color: var(--viking-text);
277
+ font-size: var(--viking-font-size-sm);
278
+ }
279
+
280
+ .suite-header__menu-link,
281
+ .suite-header__menu-button {
282
+ display: inline-flex;
283
+ align-items: center;
284
+ justify-content: flex-start;
285
+ gap: var(--viking-space-1);
286
+ width: 100%;
287
+ min-height: var(--viking-control-height);
288
+ padding-inline: var(--viking-space-1);
289
+ background: transparent;
290
+ border: 0;
291
+ border-radius: var(--viking-radius-sm);
292
+ box-shadow: none;
293
+ color: var(--viking-text);
294
+ font: inherit;
295
+ text-align: start;
296
+ text-decoration: none;
297
+ }
298
+
299
+ .suite-header__menu-link:hover,
300
+ .suite-header__menu-button:hover {
301
+ background: var(--viking-surface-alt);
302
+ }
303
+
304
+ .suite-header__mobile {
305
+ display: none;
306
+ width: 100%;
307
+ max-width: var(--viking-container-max-width);
308
+ margin-inline: auto;
309
+ padding: 0 var(--viking-page-gutter) var(--viking-space-2);
310
+ }
311
+
312
+ .suite-header[data-menu-open='true'] .suite-header__mobile {
313
+ display: grid;
314
+ gap: var(--viking-space-1);
315
+ }
316
+
317
+ .suite-header__mobile .suite-header__link,
318
+ .suite-header__mobile .suite-header__auth-link,
319
+ .suite-header__mobile .suite-header__menu-button {
320
+ display: inline-flex;
321
+ justify-content: flex-start;
322
+ width: 100%;
323
+ border-radius: var(--viking-radius);
324
+ }
325
+
326
+ .suite-header__mobile .suite-header__auth-link {
327
+ justify-content: center;
328
+ margin-block-start: var(--viking-space-1);
329
+ }
330
+
331
+ viking-suite-command-palette {
332
+ display: contents;
333
+ }
334
+
335
+ @media (min-width: 520px) {
336
+ .suite-header__lockup,
337
+ .suite-header__user-text {
338
+ display: inline;
339
+ }
340
+ }
341
+
342
+ @media (min-width: 768px) {
343
+ .suite-header__bar {
344
+ padding-inline: var(--viking-page-gutter-lg);
345
+ }
346
+
347
+ .suite-header__nav {
348
+ display: flex;
349
+ }
350
+
351
+ .suite-header__auth-link,
352
+ .suite-header__user {
353
+ display: inline-flex;
354
+ }
355
+
356
+ .suite-header__menu-button {
357
+ display: none;
358
+ }
359
+ }
360
+ `;
361
+
362
+ const detectContext = (): SiteDrakkarContext => {
363
+ const explicit = document.documentElement.getAttribute("data-deml-context");
364
+ if (explicit && VALID_CONTEXTS.has(explicit as SiteDrakkarContext)) {
365
+ return explicit as SiteDrakkarContext;
366
+ }
367
+
368
+ const host = window.location.hostname;
369
+ if (host.startsWith("ui.")) {
370
+ return "docs";
371
+ }
372
+ if (host.startsWith("backend.")) {
373
+ return "backend";
374
+ }
375
+ if (host.includes("deml.app")) {
376
+ return "app";
377
+ }
378
+ return "marketing";
379
+ };
380
+
381
+ const readContext = (el: HTMLElement): SiteDrakkarContext => {
382
+ const raw = el.getAttribute("context");
383
+ if (raw && VALID_CONTEXTS.has(raw as SiteDrakkarContext)) {
384
+ return raw as SiteDrakkarContext;
385
+ }
386
+ return detectContext();
387
+ };
388
+
389
+ const readUrls = (el: HTMLElement): SiteUrls => {
390
+ const env = (globalThis as { __DEML?: Partial<SiteUrls> }).__DEML ?? {};
391
+ return {
392
+ app: el.getAttribute("app-url") ?? env.app ?? DEFAULT_SITE_URLS.app,
393
+ marketing:
394
+ el.getAttribute("marketing-url") ??
395
+ env.marketing ??
396
+ DEFAULT_SITE_URLS.marketing,
397
+ backend:
398
+ el.getAttribute("backend-url") ??
399
+ env.backend ??
400
+ DEFAULT_SITE_URLS.backend,
401
+ };
402
+ };
403
+
404
+ const isCurrentHref = (href: string): boolean => {
405
+ try {
406
+ const url = new URL(href, window.location.origin);
407
+ return (
408
+ url.origin === window.location.origin &&
409
+ url.pathname === window.location.pathname
410
+ );
411
+ } catch {
412
+ return false;
413
+ }
414
+ };
415
+
416
+ /**
417
+ * Unified framework-agnostic DEML suite header.
418
+ * Tag: `viking-suite-header`
419
+ *
420
+ * @attr context - Surface: app | marketing | backend | docs (auto-detected when omitted)
421
+ * @attr authenticated - Render the user menu instead of Sign In
422
+ * @attr user-name - Display name for the authenticated user menu
423
+ * @attr user-email - Secondary user menu label
424
+ * @attr sign-in-href - Fallback href for static Sign In navigation
425
+ */
426
+ export class VikingSuiteHeaderWc extends HTMLElementBase {
427
+ static readonly tag = "viking-suite-header";
428
+
429
+ static get observedAttributes(): string[] {
430
+ return [
431
+ "context",
432
+ "app-url",
433
+ "marketing-url",
434
+ "backend-url",
435
+ "authenticated",
436
+ "user-name",
437
+ "user-email",
438
+ "sign-in-href",
439
+ "dashboard-href",
440
+ ];
441
+ }
442
+
443
+ private readonly shadow: ShadowRoot;
444
+ private menuOpen = false;
445
+ private userMenuOpen = false;
446
+ private paletteEl: VikingSuiteSearchPaletteWc | null = null;
447
+
448
+ constructor() {
449
+ super();
450
+ this.shadow = this.attachShadow({ mode: "open" });
451
+ attachShadowStyles(this.shadow, VIKING_SUITE_HEADER_STYLES);
452
+ }
453
+
454
+ connectedCallback(): void {
455
+ registerVikingThemeToggleWc();
456
+ registerVikingSuiteSearchPaletteWc();
457
+ this.render();
458
+ document.addEventListener("click", this.onDocumentClick);
459
+ }
460
+
461
+ disconnectedCallback(): void {
462
+ document.removeEventListener("click", this.onDocumentClick);
463
+ }
464
+
465
+ attributeChangedCallback(): void {
466
+ if (this.isConnected) {
467
+ this.render();
468
+ }
469
+ }
470
+
471
+ openSearch(): void {
472
+ this.paletteEl?.openPalette();
473
+ this.dispatchEvent(
474
+ new CustomEvent("viking-search-open", { bubbles: true, composed: true }),
475
+ );
476
+ }
477
+
478
+ private readonly onDocumentClick = (event: MouseEvent): void => {
479
+ if (event.composedPath().includes(this)) {
480
+ return;
481
+ }
482
+ if (this.menuOpen || this.userMenuOpen) {
483
+ this.menuOpen = false;
484
+ this.userMenuOpen = false;
485
+ this.syncOpenState();
486
+ }
487
+ };
488
+
489
+ private readonly onSearchClick = (): void => {
490
+ this.openSearch();
491
+ };
492
+
493
+ private readonly onMenuClick = (): void => {
494
+ this.menuOpen = !this.menuOpen;
495
+ this.syncOpenState();
496
+ };
497
+
498
+ private readonly onUserClick = (): void => {
499
+ this.userMenuOpen = !this.userMenuOpen;
500
+ this.syncOpenState();
501
+ };
502
+
503
+ private readonly onSignInClick = (event: Event): void => {
504
+ const signInEvent = new CustomEvent("viking-sign-in", {
505
+ bubbles: true,
506
+ cancelable: true,
507
+ composed: true,
508
+ detail: event,
509
+ });
510
+ this.dispatchEvent(signInEvent);
511
+ if (signInEvent.defaultPrevented) {
512
+ event.preventDefault();
513
+ }
514
+ };
515
+
516
+ private readonly onSignOutClick = (): void => {
517
+ this.userMenuOpen = false;
518
+ this.menuOpen = false;
519
+ this.syncOpenState();
520
+ this.dispatchEvent(
521
+ new CustomEvent("viking-sign-out", { bubbles: true, composed: true }),
522
+ );
523
+ };
524
+
525
+ private syncOpenState(): void {
526
+ const header = this.shadow.querySelector<HTMLElement>(".suite-header");
527
+ const menuButton =
528
+ this.shadow.querySelector<HTMLButtonElement>("[data-menu-toggle]");
529
+ const user = this.shadow.querySelector<HTMLElement>(".suite-header__user");
530
+ const userButton =
531
+ this.shadow.querySelector<HTMLButtonElement>("[data-user-toggle]");
532
+ header?.setAttribute("data-menu-open", String(this.menuOpen));
533
+ menuButton?.setAttribute("aria-expanded", String(this.menuOpen));
534
+ user?.setAttribute("data-open", String(this.userMenuOpen));
535
+ userButton?.setAttribute("aria-expanded", String(this.userMenuOpen));
536
+ }
537
+
538
+ private renderNavLinks(
539
+ context: SiteDrakkarContext,
540
+ urls: SiteUrls,
541
+ authenticated: boolean,
542
+ ): string {
543
+ return visibleNavLinks(SITE_NAV_LINKS, authenticated)
544
+ .map((link) => {
545
+ const href = resolveNavHref(link, context, urls);
546
+ const current = isCurrentHref(href) ? ' aria-current="page"' : "";
547
+ return `
548
+ <a class="suite-header__link" href="${escapeHtml(href)}"${current}>
549
+ ${renderInlineIcon(link.icon, 16, "suite-header__link-icon")}
550
+ <span>${escapeHtml(link.label)}</span>
551
+ </a>
552
+ `;
553
+ })
554
+ .join("");
555
+ }
556
+
557
+ private renderAuth(
558
+ urls: SiteUrls,
559
+ authenticated: boolean,
560
+ mobile = false,
561
+ ): string {
562
+ if (!authenticated) {
563
+ const signInHref =
564
+ this.getAttribute("sign-in-href") ?? `${urls.app}/login`;
565
+ return `
566
+ <a class="suite-header__auth-link" href="${escapeHtml(signInHref)}" data-sign-in>
567
+ <span>Sign In</span>
568
+ ${renderInlineIcon("arrow-right", 16, "suite-header__auth-icon")}
569
+ </a>
570
+ `;
571
+ }
572
+
573
+ if (mobile) {
574
+ return `
575
+ <a class="suite-header__menu-link" href="${escapeHtml(this.getDashboardHref(urls))}">
576
+ ${renderInlineIcon("home", 16, "suite-header__menu-icon")}
577
+ <span>Dashboard</span>
578
+ </a>
579
+ <button class="suite-header__menu-button" type="button" data-sign-out>
580
+ ${renderInlineIcon("log-out", 16, "suite-header__menu-icon")}
581
+ <span>Sign Out</span>
582
+ </button>
583
+ `;
584
+ }
585
+
586
+ const userName = this.getAttribute("user-name") ?? "Account";
587
+ const userEmail = this.getAttribute("user-email") ?? "";
588
+ return `
589
+ <div class="suite-header__user" data-open="${String(this.userMenuOpen)}">
590
+ <button
591
+ class="suite-header__user-trigger"
592
+ type="button"
593
+ aria-haspopup="menu"
594
+ aria-expanded="${String(this.userMenuOpen)}"
595
+ data-user-toggle
596
+ >
597
+ <span class="suite-header__avatar" aria-hidden="true">
598
+ ${renderInlineIcon("user", 16, "suite-header__avatar-icon")}
599
+ </span>
600
+ <span class="suite-header__user-text">${escapeHtml(userName)}</span>
601
+ ${renderInlineIcon("chevron-down", 16, "suite-header__chevron")}
602
+ </button>
603
+ <div class="suite-header__menu" role="menu">
604
+ <div class="suite-header__menu-label">
605
+ <strong>${escapeHtml(userName)}</strong>
606
+ ${userEmail ? `<span>${escapeHtml(userEmail)}</span>` : ""}
607
+ </div>
608
+ <a class="suite-header__menu-link" role="menuitem" href="${escapeHtml(
609
+ this.getDashboardHref(urls),
610
+ )}">
611
+ ${renderInlineIcon("home", 16, "suite-header__menu-icon")}
612
+ <span>Dashboard</span>
613
+ </a>
614
+ <button class="suite-header__menu-button" type="button" role="menuitem" data-sign-out>
615
+ ${renderInlineIcon("log-out", 16, "suite-header__menu-icon")}
616
+ <span>Sign Out</span>
617
+ </button>
618
+ </div>
619
+ </div>
620
+ `;
621
+ }
622
+
623
+ private getDashboardHref(urls: SiteUrls): string {
624
+ return this.getAttribute("dashboard-href") ?? `${urls.app}/dashboard`;
625
+ }
626
+
627
+ private render(): void {
628
+ const context = readContext(this);
629
+ const urls = readUrls(this);
630
+ const authenticated = readBoolAttr(this, "authenticated");
631
+ const navLinks = this.renderNavLinks(context, urls, authenticated);
632
+ const brandHref = resolveBrandHref(context, urls);
633
+ const shortcut = `${modKeyLabel()}K`;
634
+
635
+ this.shadow.innerHTML = `
636
+ <header class="suite-header" data-menu-open="${String(this.menuOpen)}">
637
+ <div class="suite-header__bar">
638
+ <a class="suite-header__brand" href="${escapeHtml(
639
+ brandHref,
640
+ )}" aria-label="Data Engineering for Machine Learning home">
641
+ <span class="suite-header__mark" aria-hidden="true">
642
+ ${renderInlineIcon("drakkar", 28, "suite-header__brand-icon")}
643
+ </span>
644
+ <span class="suite-header__lockup">DEML</span>
645
+ </a>
646
+
647
+ <nav class="suite-header__nav" aria-label="Main navigation">
648
+ ${navLinks}
649
+ </nav>
650
+
651
+ <div class="suite-header__actions">
652
+ <button
653
+ class="suite-header__icon-button"
654
+ type="button"
655
+ aria-label="Open command palette (${shortcut})"
656
+ title="Open command palette (${shortcut})"
657
+ data-search-trigger
658
+ >
659
+ ${renderInlineIcon("search", 18, "suite-header__action-icon")}
660
+ </button>
661
+ ${this.renderAuth(urls, authenticated)}
662
+ <viking-theme-toggle-wc></viking-theme-toggle-wc>
663
+ <button
664
+ class="suite-header__menu-button"
665
+ type="button"
666
+ aria-label="Toggle navigation menu"
667
+ aria-expanded="${String(this.menuOpen)}"
668
+ data-menu-toggle
669
+ >
670
+ ${renderInlineIcon(this.menuOpen ? "x" : "menu", 18, "suite-header__action-icon")}
671
+ </button>
672
+ </div>
673
+ </div>
674
+
675
+ <nav class="suite-header__mobile" aria-label="Mobile navigation">
676
+ ${navLinks}
677
+ ${this.renderAuth(urls, authenticated, true)}
678
+ </nav>
679
+ </header>
680
+ <viking-suite-command-palette
681
+ context="${context}"
682
+ app-url="${escapeHtml(urls.app)}"
683
+ marketing-url="${escapeHtml(urls.marketing)}"
684
+ backend-url="${escapeHtml(urls.backend)}"
685
+ global-shortcut
686
+ ></viking-suite-command-palette>
687
+ `;
688
+
689
+ this.paletteEl = this.shadow.querySelector("viking-suite-command-palette");
690
+ this.shadow
691
+ .querySelector("[data-search-trigger]")
692
+ ?.addEventListener("click", this.onSearchClick);
693
+ this.shadow
694
+ .querySelector("[data-menu-toggle]")
695
+ ?.addEventListener("click", this.onMenuClick);
696
+ this.shadow
697
+ .querySelector("[data-user-toggle]")
698
+ ?.addEventListener("click", this.onUserClick);
699
+ this.shadow
700
+ .querySelectorAll("[data-sign-in]")
701
+ .forEach((control) =>
702
+ control.addEventListener("click", this.onSignInClick),
703
+ );
704
+ this.shadow
705
+ .querySelectorAll("[data-sign-out]")
706
+ .forEach((control) =>
707
+ control.addEventListener("click", this.onSignOutClick),
708
+ );
709
+ }
710
+ }
711
+
712
+ export const registerVikingSuiteHeaderWc = (): void => {
713
+ defineCustomElement(VikingSuiteHeaderWc.tag, VikingSuiteHeaderWc);
714
+ };