@dataengineeringformachinelearning/viking-ui 3.0.0-alpha.2 → 4.0.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.
Files changed (198) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +55 -12
  3. package/dist/LICENSE +201 -0
  4. package/dist/README.md +251 -0
  5. package/dist/fesm2022/dataengineeringformachinelearning-viking-ui.mjs +13751 -0
  6. package/dist/fesm2022/dataengineeringformachinelearning-viking-ui.mjs.map +1 -0
  7. package/dist/index.js +1 -4051
  8. package/dist/package.json +87 -0
  9. package/dist/scripts/build-web-components.mjs +37 -0
  10. package/dist/src/tokens/series-presets.ts +15 -0
  11. package/dist/src/tokens/tailwind.preset.js +209 -0
  12. package/dist/src/tokens/viking-tokens.json +318 -0
  13. package/dist/src/web/badge/viking-badge-wc.ts +116 -0
  14. package/dist/src/web/button/viking-button-wc.ts +165 -0
  15. package/dist/src/web/callout/viking-callout-wc.ts +105 -0
  16. package/dist/src/web/card/viking-card-wc.ts +66 -0
  17. package/dist/src/web/core/base.ts +56 -0
  18. package/dist/src/web/core/dom.ts +53 -0
  19. package/dist/src/web/core/icons-inline.ts +44 -0
  20. package/dist/src/web/core/styles.ts +1138 -0
  21. package/dist/src/web/core/types.ts +19 -0
  22. package/dist/src/web/field/viking-field-wc.ts +154 -0
  23. package/dist/src/web/index.ts +73 -0
  24. package/dist/src/web/input/viking-input-wc.ts +211 -0
  25. package/dist/src/web/modal/viking-modal-wc.ts +187 -0
  26. package/dist/src/web/search-palette/viking-search-palette-wc.ts +494 -0
  27. package/dist/src/web/select/viking-select-wc.ts +218 -0
  28. package/dist/src/web/suite-header/viking-suite-header-wc.ts +714 -0
  29. package/dist/src/web/suite-search-palette/viking-suite-search-palette-wc.ts +234 -0
  30. package/dist/src/web/theme-toggle/viking-theme-toggle-wc.ts +139 -0
  31. package/dist/src/web/types.ts +37 -0
  32. package/dist/src/web/web-components.spec.ts +297 -0
  33. package/dist/types/dataengineeringformachinelearning-viking-ui.d.ts +2658 -0
  34. package/dist/viking.manifest.json +271 -0
  35. package/dist/web-components.js +2427 -126
  36. package/package.json +25 -11
  37. package/src/assets/drakkar-favicon.svg +17 -0
  38. package/src/core/brand/deml-lockup.svg +5 -0
  39. package/src/core/brand/deml-mark-compact.svg +4 -0
  40. package/src/core/brand/deml-mark-filled.svg +3 -0
  41. package/src/core/brand/deml-mark-outline.svg +4 -0
  42. package/src/core/brand-icons.ts +62 -0
  43. package/src/core/icons.spec.ts +52 -0
  44. package/src/core/icons.ts +206 -0
  45. package/src/core/integration-brand-icons.ts +77 -0
  46. package/src/core/lucide-paths.generated.ts +130 -0
  47. package/src/core/types.ts +85 -0
  48. package/src/core/uid.ts +7 -0
  49. package/src/lib/accordion/accordion.ts +126 -0
  50. package/src/lib/app-header/app-header.scss +274 -0
  51. package/src/lib/app-header/app-header.ts +43 -0
  52. package/src/lib/app-sidebar/app-sidebar.scss +415 -0
  53. package/src/lib/app-sidebar/app-sidebar.ts +29 -0
  54. package/src/lib/auth-panel/auth-panel.spec.ts +132 -0
  55. package/src/lib/auth-panel/auth-panel.ts +228 -0
  56. package/src/lib/autocomplete/autocomplete.ts +223 -0
  57. package/src/lib/avatar/avatar.ts +126 -0
  58. package/src/lib/badge/badge.ts +182 -0
  59. package/src/lib/bar/bar.ts +31 -0
  60. package/src/lib/brand/brand.ts +60 -0
  61. package/src/lib/breadcrumbs/breadcrumbs.ts +81 -0
  62. package/src/lib/button/button-group.ts +40 -0
  63. package/src/lib/button/button.scss +96 -0
  64. package/src/lib/button/button.ts +98 -0
  65. package/src/lib/calendar/calendar.ts +253 -0
  66. package/src/lib/callout/callout.ts +211 -0
  67. package/src/lib/card/card-title.spec.ts +27 -0
  68. package/src/lib/card/card-title.ts +50 -0
  69. package/src/lib/card/card.ts +170 -0
  70. package/src/lib/carousel/carousel.ts +206 -0
  71. package/src/lib/chart/chart-zoom.spec.ts +65 -0
  72. package/src/lib/chart/chart-zoom.ts +71 -0
  73. package/src/lib/chart/chart.ts +1257 -0
  74. package/src/lib/chart-card-header/chart-card-header.ts +80 -0
  75. package/src/lib/chart-empty-state/chart-empty-state.ts +146 -0
  76. package/src/lib/chart-panel/chart-panel.ts +133 -0
  77. package/src/lib/checkbox/checkbox.ts +139 -0
  78. package/src/lib/color-picker/color-picker.ts +148 -0
  79. package/src/lib/command/command.ts +260 -0
  80. package/src/lib/composer/composer.ts +180 -0
  81. package/src/lib/confirm-dialog/confirm-dialog.ts +163 -0
  82. package/src/lib/confirm-dialog/dialog.service.ts +79 -0
  83. package/src/lib/context/context.ts +75 -0
  84. package/src/lib/core/cva.ts +33 -0
  85. package/src/lib/date-picker/date-picker.ts +139 -0
  86. package/src/lib/dropdown/dropdown.ts +188 -0
  87. package/src/lib/editor/editor.ts +170 -0
  88. package/src/lib/empty-state/empty-state.ts +100 -0
  89. package/src/lib/fab/fab.ts +76 -0
  90. package/src/lib/field/field.ts +111 -0
  91. package/src/lib/file-upload/file-upload.ts +208 -0
  92. package/src/lib/footer/footer.scss +155 -0
  93. package/src/lib/footer/footer.ts +28 -0
  94. package/src/lib/form-section/form-section.ts +200 -0
  95. package/src/lib/forms.spec.ts +92 -0
  96. package/src/lib/gauge-arc/gauge-arc.ts +85 -0
  97. package/src/lib/hud-panel/hud-panel.ts +27 -0
  98. package/src/lib/icon/icon.ts +284 -0
  99. package/src/lib/icon-badge/icon-badge.ts +87 -0
  100. package/src/lib/icon-heading/icon-heading.spec.ts +32 -0
  101. package/src/lib/icon-heading/icon-heading.ts +38 -0
  102. package/src/lib/icon-text/icon-text.ts +120 -0
  103. package/src/lib/input/input.ts +119 -0
  104. package/src/lib/kanban/kanban.ts +211 -0
  105. package/src/lib/kbd/kbd.ts +32 -0
  106. package/src/lib/label/label.ts +39 -0
  107. package/src/lib/loading-overlay/loading-overlay.ts +80 -0
  108. package/src/lib/menubar/menubar.ts +77 -0
  109. package/src/lib/metric-card/metric-card.ts +33 -0
  110. package/src/lib/modal/modal.ts +207 -0
  111. package/src/lib/native-select/native-select.ts +124 -0
  112. package/src/lib/navbar/navbar.ts +124 -0
  113. package/src/lib/navigation-menu/navigation-menu.ts +116 -0
  114. package/src/lib/otp-input/otp-input.ts +164 -0
  115. package/src/lib/page-back-link/page-back-link.ts +74 -0
  116. package/src/lib/page-header/page-header.ts +70 -0
  117. package/src/lib/pagination/pagination.ts +157 -0
  118. package/src/lib/pillbox/pillbox.ts +157 -0
  119. package/src/lib/popover/popover.ts +92 -0
  120. package/src/lib/profile/profile.ts +73 -0
  121. package/src/lib/progress/progress.ts +123 -0
  122. package/src/lib/radio/radio.ts +132 -0
  123. package/src/lib/ring-gauge/ring-gauge.ts +75 -0
  124. package/src/lib/scroll-area/scroll-area.ts +57 -0
  125. package/src/lib/search-palette/search-palette.scss +288 -0
  126. package/src/lib/search-palette/search-palette.ts +114 -0
  127. package/src/lib/select/select.ts +292 -0
  128. package/src/lib/separator/separator.ts +59 -0
  129. package/src/lib/sheet/sheet.ts +192 -0
  130. package/src/lib/sidebar-nav/sidebar-nav.scss +218 -0
  131. package/src/lib/sidebar-nav/sidebar-nav.ts +158 -0
  132. package/src/lib/site-drakkar/site-footer.ts +129 -0
  133. package/src/lib/site-drakkar/site-navbar.scss +2 -0
  134. package/src/lib/site-drakkar/site-navbar.ts +267 -0
  135. package/src/lib/site-drakkar/suite-search-items.spec.ts +61 -0
  136. package/src/lib/skeleton/skeleton.ts +51 -0
  137. package/src/lib/slider/slider.ts +133 -0
  138. package/src/lib/spinner/spinner.ts +75 -0
  139. package/src/lib/status-metric-row/status-metric-row.ts +74 -0
  140. package/src/lib/suite-search-palette/suite-search-palette.ts +95 -0
  141. package/src/lib/switch/switch.ts +131 -0
  142. package/src/lib/table/table.ts +137 -0
  143. package/src/lib/tabs/tab-panel.ts +61 -0
  144. package/src/lib/tabs/tab.ts +107 -0
  145. package/src/lib/tabs/tabs.ts +109 -0
  146. package/src/lib/textarea/textarea.ts +103 -0
  147. package/src/lib/theme-toggle/theme-toggle.spec.ts +40 -0
  148. package/src/lib/theme-toggle/theme-toggle.ts +39 -0
  149. package/src/lib/time-picker/time-picker.ts +197 -0
  150. package/src/lib/timeline/timeline.ts +152 -0
  151. package/src/lib/toast/toast.ts +196 -0
  152. package/src/lib/toggle/toggle-group.ts +60 -0
  153. package/src/lib/toggle/toggle.ts +101 -0
  154. package/src/lib/tooltip/tooltip.ts +119 -0
  155. package/src/lib/typography/heading.ts +52 -0
  156. package/src/lib/typography/text.ts +51 -0
  157. package/src/lib/uptime-bar/uptime-bar.ts +55 -0
  158. package/src/lib/verification-code-field/verification-code-field.ts +149 -0
  159. package/src/lib/whitepaper-cta/whitepaper-cta.scss +130 -0
  160. package/src/lib/whitepaper-cta/whitepaper-cta.ts +78 -0
  161. package/src/lib/wizard/wizard.ts +80 -0
  162. package/src/public-api.ts +172 -0
  163. package/src/tokens/series-presets.ts +15 -0
  164. package/src/tokens/tailwind.preset.js +209 -0
  165. package/src/viking-ui.spec.ts +416 -0
  166. package/src/web/web-components.spec.ts +297 -0
  167. package/dist/elements/button/viking-button.d.ts +0 -14
  168. package/dist/elements/card/viking-card.d.ts +0 -8
  169. package/dist/elements/core/dom.d.ts +0 -4
  170. package/dist/elements/core/styles.d.ts +0 -2
  171. package/dist/elements/modal/viking-modal.d.ts +0 -15
  172. package/dist/index.d.ts +0 -1
  173. package/dist/lib/core/brand-icons.d.ts +0 -29
  174. package/dist/lib/core/icons.d.ts +0 -132
  175. package/dist/lib/core/integration-brand-icons.d.ts +0 -18
  176. package/dist/lib/core/lucide-paths.generated.d.ts +0 -90
  177. package/dist/lib/site-drakkar/site-drakkar.config.d.ts +0 -50
  178. package/dist/lib/site-drakkar/suite-search-items.d.ts +0 -17
  179. package/dist/web/badge/viking-badge-wc.d.ts +0 -28
  180. package/dist/web/button/viking-button-wc.d.ts +0 -28
  181. package/dist/web/callout/viking-callout-wc.d.ts +0 -27
  182. package/dist/web/card/viking-card-wc.d.ts +0 -29
  183. package/dist/web/core/base.d.ts +0 -12
  184. package/dist/web/core/dom.d.ts +0 -11
  185. package/dist/web/core/icons-inline.d.ts +0 -7
  186. package/dist/web/core/styles.d.ts +0 -12
  187. package/dist/web/core/types.d.ts +0 -10
  188. package/dist/web/field/viking-field-wc.d.ts +0 -32
  189. package/dist/web/index.d.ts +0 -15
  190. package/dist/web/input/viking-input-wc.d.ts +0 -32
  191. package/dist/web/modal/viking-modal-wc.d.ts +0 -50
  192. package/dist/web/search-palette/viking-search-palette-wc.d.ts +0 -64
  193. package/dist/web/select/viking-select-wc.d.ts +0 -48
  194. package/dist/web/suite-header/viking-suite-header-wc.d.ts +0 -36
  195. package/dist/web/suite-search-palette/viking-suite-search-palette-wc.d.ts +0 -39
  196. package/dist/web/theme-toggle/viking-theme-toggle-wc.d.ts +0 -19
  197. package/dist/web/types.d.ts +0 -40
  198. package/dist/web-components/index.d.ts +0 -1
@@ -0,0 +1,129 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ input,
5
+ output,
6
+ } from "@angular/core";
7
+ import { RouterLink } from "@angular/router";
8
+ import { VikingFooter } from "../footer/footer";
9
+ import {
10
+ cookieSettingsHref,
11
+ DEFAULT_SITE_URLS,
12
+ isAppRouterPath,
13
+ resolveFooterHref,
14
+ SITE_FOOTER_COLUMNS,
15
+ SiteDrakkarContext,
16
+ SiteFooterLink,
17
+ SiteUrls,
18
+ } from "./site-drakkar.config";
19
+
20
+ /**
21
+ * viking-site-footer — canonical site footer shared across DEML surfaces.
22
+ */
23
+ @Component({
24
+ selector: "viking-site-footer",
25
+ changeDetection: ChangeDetectionStrategy.OnPush,
26
+ imports: [RouterLink, VikingFooter],
27
+ template: `
28
+ <viking-footer>
29
+ <nav
30
+ vikingFooterDirectory
31
+ class="viking-footer-directory footer-directory"
32
+ aria-label="Footer Directory"
33
+ >
34
+ @for (column of columns; track column.title) {
35
+ <div class="footer-column">
36
+ <h3 class="footer-column-title">{{ column.title }}</h3>
37
+ <ul class="footer-list">
38
+ @for (link of column.links; track link.label) {
39
+ <li>
40
+ @if (link.action === "cookie-settings") {
41
+ @if (context() === "marketing") {
42
+ <a href="#" (click)="onCookieSettings($event)">{{
43
+ link.label
44
+ }}</a>
45
+ } @else {
46
+ <a [href]="cookieSettingsUrl()">{{ link.label }}</a>
47
+ }
48
+ } @else if (link.action === "bug-report") {
49
+ @if (context() === "app") {
50
+ <a href="#" (click)="onBugReport($event)">{{
51
+ link.label
52
+ }}</a>
53
+ } @else {
54
+ <a [href]="resolveHref(link)">{{ link.label }}</a>
55
+ }
56
+ } @else if (context() === "app" && isAppRoute(link)) {
57
+ <a [routerLink]="link.appHref">{{ link.label }}</a>
58
+ } @else {
59
+ <a [href]="resolveHref(link)">{{ link.label }}</a>
60
+ }
61
+ </li>
62
+ }
63
+ </ul>
64
+ </div>
65
+ }
66
+ </nav>
67
+
68
+ <ng-container vikingFooterBottom>
69
+ <div class="footer-badges-top">
70
+ <span
71
+ class="usa-badge viking-usa-badge"
72
+ id="usa-badge"
73
+ (mouseenter)="usaBadgeHover.emit($event)"
74
+ >
75
+ <span
76
+ class="usa-badge-icon viking-usa-badge-icon"
77
+ aria-hidden="true"
78
+ >🇺🇸</span
79
+ >
80
+ <span>Made in the U.S.A</span>
81
+ </span>
82
+ </div>
83
+
84
+ <div class="footer-compliance-row">
85
+ <div class="copyright-info">
86
+ <span class="copyright-text">
87
+ Copyright © {{ year() }} Data Engineering for Machine Learning by
88
+ <a
89
+ href="https://joealongi.dev/"
90
+ target="_blank"
91
+ rel="noopener noreferrer"
92
+ >Joe Alongi</a
93
+ >. All rights reserved.
94
+ </span>
95
+ </div>
96
+ </div>
97
+ </ng-container>
98
+ </viking-footer>
99
+ `,
100
+ })
101
+ export class VikingSiteFooter {
102
+ readonly context = input<SiteDrakkarContext>("app");
103
+ readonly urls = input<SiteUrls>(DEFAULT_SITE_URLS);
104
+ readonly year = input<number>(new Date().getFullYear());
105
+
106
+ readonly cookieSettings = output<Event>();
107
+ readonly bugReport = output<Event>();
108
+ readonly usaBadgeHover = output<MouseEvent>();
109
+
110
+ protected readonly columns = SITE_FOOTER_COLUMNS;
111
+
112
+ protected readonly cookieSettingsUrl = () => cookieSettingsHref(this.urls());
113
+
114
+ protected resolveHref = (link: SiteFooterLink): string =>
115
+ resolveFooterHref(link, this.context(), this.urls());
116
+
117
+ protected isAppRoute = (link: SiteFooterLink): boolean =>
118
+ !link.action && isAppRouterPath(link.appHref);
119
+
120
+ protected onCookieSettings(event: Event): void {
121
+ event.preventDefault();
122
+ this.cookieSettings.emit(event);
123
+ }
124
+
125
+ protected onBugReport(event: Event): void {
126
+ event.preventDefault();
127
+ this.bugReport.emit(event);
128
+ }
129
+ }
@@ -0,0 +1,2 @@
1
+ // Navbar styles live in static-navbar.scss (global viking-ui.css + app styles.scss).
2
+ // ViewEncapsulation.None — do not @use static-navbar here (duplicates ~46kB into component budget).
@@ -0,0 +1,267 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ ViewEncapsulation,
5
+ computed,
6
+ input,
7
+ output,
8
+ signal,
9
+ } from "@angular/core";
10
+ import { RouterLink, RouterLinkActive } from "@angular/router";
11
+ import { VikingButton } from "../button/button";
12
+ import { VikingIcon } from "../icon/icon";
13
+ import { VikingThemeToggle } from "../theme-toggle/theme-toggle";
14
+ import {
15
+ DEFAULT_SITE_URLS,
16
+ isAppRouterPath,
17
+ resolveBrandHref,
18
+ resolveNavHref,
19
+ SITE_NAV_LINKS,
20
+ SiteDrakkarContext,
21
+ SiteUrls,
22
+ visibleNavLinks,
23
+ } from "./site-drakkar.config";
24
+
25
+ /**
26
+ * viking-site-navbar — canonical site header shared across DEML surfaces.
27
+ * Uses `.navbar` markup + viking-ui.css (same structure as marketing/backend static HTML).
28
+ */
29
+ @Component({
30
+ selector: "viking-site-navbar",
31
+ changeDetection: ChangeDetectionStrategy.OnPush,
32
+ encapsulation: ViewEncapsulation.None,
33
+ imports: [
34
+ RouterLink,
35
+ RouterLinkActive,
36
+ VikingButton,
37
+ VikingIcon,
38
+ VikingThemeToggle,
39
+ ],
40
+ styleUrl: "./site-navbar.scss",
41
+ template: `
42
+ <header class="navbar">
43
+ <div class="navbar-content">
44
+ <div class="navbar-left">
45
+ @if (context() === "app") {
46
+ <a
47
+ [href]="brandHref()"
48
+ class="navbar-brand"
49
+ aria-label="Go to marketing homepage"
50
+ (click)="onBrandClick($event)"
51
+ >
52
+ <viking-icon
53
+ name="drakkar"
54
+ [size]="28"
55
+ color="accent"
56
+ class="brand-icon navbar-logo"
57
+ />
58
+ </a>
59
+ } @else {
60
+ <a
61
+ [href]="brandHref()"
62
+ class="navbar-brand"
63
+ aria-label="Go to homepage"
64
+ >
65
+ <viking-icon
66
+ name="drakkar"
67
+ [size]="28"
68
+ color="accent"
69
+ class="brand-icon navbar-logo"
70
+ />
71
+ </a>
72
+ }
73
+ </div>
74
+
75
+ <nav class="navbar-center desktop-nav" aria-label="Main navigation">
76
+ @for (link of navLinks(); track link.id) {
77
+ @if (context() === "app" && isAppRouterPath(link.appHref)) {
78
+ <a
79
+ [routerLink]="link.appHref"
80
+ routerLinkActive="active"
81
+ [routerLinkActiveOptions]="{
82
+ exact: link.appHref === '/explore',
83
+ }"
84
+ class="nav-btn"
85
+ >
86
+ <viking-icon [name]="link.icon" [size]="16" />
87
+ <span>{{ link.label }}</span>
88
+ </a>
89
+ } @else {
90
+ <a [href]="resolveHref(link)" class="nav-btn">
91
+ <viking-icon [name]="link.icon" [size]="16" />
92
+ <span>{{ link.label }}</span>
93
+ </a>
94
+ }
95
+ }
96
+ </nav>
97
+
98
+ <div class="navbar-right">
99
+ @if (showSearch()) {
100
+ <div class="navbar-search" role="search">
101
+ <viking-button
102
+ variant="outline"
103
+ [square]="true"
104
+ [compact]="true"
105
+ icon="search"
106
+ label="Open search (⌘K)"
107
+ (pressed)="openSearch()"
108
+ />
109
+ </div>
110
+ }
111
+
112
+ <div class="desktop-auth">
113
+ @if (!isAuthenticated()) {
114
+ <viking-button
115
+ variant="primary"
116
+ icon="arrow-right"
117
+ (pressed)="login.emit()"
118
+ >
119
+ Sign In
120
+ </viking-button>
121
+ } @else {
122
+ <viking-button
123
+ variant="primary"
124
+ icon="home"
125
+ [href]="urls().app + '/dashboard'"
126
+ >
127
+ Dashboard
128
+ </viking-button>
129
+ <viking-button variant="ghost" (pressed)="logout.emit()"
130
+ >Sign Out</viking-button
131
+ >
132
+ }
133
+ </div>
134
+
135
+ <viking-theme-toggle
136
+ [theme]="theme()"
137
+ (toggle)="themeToggle.emit()"
138
+ />
139
+
140
+ <viking-button
141
+ variant="outline"
142
+ [square]="true"
143
+ class="menu-toggle-btn"
144
+ [icon]="mobileMenuOpen() ? 'x' : 'menu'"
145
+ [label]="
146
+ mobileMenuOpen()
147
+ ? 'Close navigation menu'
148
+ : 'Toggle navigation menu'
149
+ "
150
+ (pressed)="toggleMobileMenu()"
151
+ />
152
+ </div>
153
+ </div>
154
+
155
+ <nav
156
+ class="mobile-menu"
157
+ [class.open]="mobileMenuOpen()"
158
+ aria-label="Mobile navigation"
159
+ >
160
+ @for (link of navLinks(); track link.id + "-mobile") {
161
+ @if (context() === "app" && isAppRouterPath(link.appHref)) {
162
+ <a
163
+ [routerLink]="link.appHref"
164
+ routerLinkActive="active"
165
+ [routerLinkActiveOptions]="{ exact: link.appHref === '/explore' }"
166
+ class="mobile-nav-btn"
167
+ (click)="closeMobileMenu()"
168
+ >
169
+ <viking-icon [name]="link.icon" [size]="16" />
170
+ <span>{{ link.label }}</span>
171
+ </a>
172
+ } @else {
173
+ <a
174
+ [href]="resolveHref(link)"
175
+ class="mobile-nav-btn"
176
+ (click)="closeMobileMenu()"
177
+ >
178
+ <viking-icon [name]="link.icon" [size]="16" />
179
+ <span>{{ link.label }}</span>
180
+ </a>
181
+ }
182
+ }
183
+
184
+ <div class="mobile-divider"></div>
185
+
186
+ @if (!isAuthenticated()) {
187
+ <viking-button
188
+ variant="primary"
189
+ icon="arrow-right"
190
+ class="mobile-auth-btn"
191
+ [fullWidth]="true"
192
+ (pressed)="login.emit(); closeMobileMenu()"
193
+ >
194
+ Sign In
195
+ </viking-button>
196
+ } @else {
197
+ <viking-button
198
+ variant="primary"
199
+ icon="home"
200
+ class="mobile-auth-btn"
201
+ [fullWidth]="true"
202
+ [href]="urls().app + '/dashboard'"
203
+ (pressed)="closeMobileMenu()"
204
+ >
205
+ Dashboard
206
+ </viking-button>
207
+ <viking-button
208
+ variant="ghost"
209
+ class="mobile-auth-btn"
210
+ [fullWidth]="true"
211
+ (pressed)="logout.emit(); closeMobileMenu()"
212
+ >
213
+ Sign Out
214
+ </viking-button>
215
+ }
216
+ </nav>
217
+ </header>
218
+ `,
219
+ })
220
+ export class VikingSiteNavbar {
221
+ readonly context = input<SiteDrakkarContext>("app");
222
+ readonly urls = input<SiteUrls>(DEFAULT_SITE_URLS);
223
+ readonly isAuthenticated = input<boolean>(false);
224
+ readonly theme = input<"light" | "dark">("dark");
225
+ readonly showSearch = input<boolean>(false);
226
+
227
+ readonly login = output<void>();
228
+ readonly logout = output<void>();
229
+ readonly themeToggle = output<void>();
230
+ readonly searchOpen = output<void>();
231
+
232
+ protected readonly mobileMenuOpen = signal(false);
233
+
234
+ protected readonly navLinks = computed(() =>
235
+ visibleNavLinks(SITE_NAV_LINKS, this.isAuthenticated()),
236
+ );
237
+
238
+ protected readonly brandHref = computed(() =>
239
+ resolveBrandHref(this.context(), this.urls()),
240
+ );
241
+
242
+ protected resolveHref = (link: (typeof SITE_NAV_LINKS)[number]): string =>
243
+ resolveNavHref(link, this.context(), this.urls());
244
+
245
+ protected isAppRouterPath = isAppRouterPath;
246
+
247
+ protected onBrandClick(event: MouseEvent): void {
248
+ event.preventDefault();
249
+ window.location.href = this.brandHref();
250
+ }
251
+
252
+ protected toggleMobileMenu(): void {
253
+ this.mobileMenuOpen.update((open) => !open);
254
+ }
255
+
256
+ protected closeMobileMenu(): void {
257
+ this.mobileMenuOpen.set(false);
258
+ }
259
+
260
+ protected openSearch(): void {
261
+ this.searchOpen.emit();
262
+ const widgets = (
263
+ globalThis as { DemlWidgets?: { openSearch?: () => void } }
264
+ ).DemlWidgets;
265
+ widgets?.openSearch?.();
266
+ }
267
+ }
@@ -0,0 +1,61 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildSuiteSearchItems } from "./suite-search-items";
3
+
4
+ const urls = {
5
+ app: "https://deml.app",
6
+ marketing: "https://dataengineeringformachinelearning.com",
7
+ backend: "https://backend.deml.app",
8
+ };
9
+
10
+ describe("buildSuiteSearchItems", () => {
11
+ it("includes platform navigation and legal links for marketing", () => {
12
+ const items = buildSuiteSearchItems("marketing", urls);
13
+ const titles = items.map((item) => item.title);
14
+
15
+ expect(titles).toContain("Documentation");
16
+ expect(titles).toContain("Whitepaper");
17
+ expect(titles).toContain("Book");
18
+ expect(titles).toContain("Privacy Policy");
19
+ expect(titles).toContain("Terms of Service");
20
+ expect(titles).toContain("SOC2 Compliance");
21
+ expect(titles).toContain("Viking-UI Components");
22
+ });
23
+
24
+ it("resolves app routes for deml.app context", () => {
25
+ const items = buildSuiteSearchItems("app", urls);
26
+ const dashboard = items.find((item) => item.title === "Dashboard");
27
+
28
+ expect(dashboard?.href).toBe("/dashboard");
29
+ expect(items.some((item) => item.title === "Billing & subscription")).toBe(
30
+ true,
31
+ );
32
+ });
33
+
34
+ it("matches keyword search metadata", () => {
35
+ const items = buildSuiteSearchItems("marketing", urls);
36
+ const privacy = items.find((item) => item.title === "Privacy Policy");
37
+
38
+ expect(
39
+ privacy?.keywords?.some((keyword) => keyword.includes("privacy")),
40
+ ).toBe(true);
41
+ });
42
+
43
+ it("includes Viking-UI docs entries for docs context", () => {
44
+ const items = buildSuiteSearchItems("docs", urls, {
45
+ docsOrigin: "https://ui.dataengineeringformachinelearning.com",
46
+ });
47
+ const titles = items.map((item) => item.title);
48
+
49
+ expect(titles).toContain("Components");
50
+ expect(titles).toContain("Playground");
51
+ expect(titles).toContain("Whitepaper");
52
+ expect(items.find((item) => item.title === "Components")?.href).toBe(
53
+ "https://ui.dataengineeringformachinelearning.com/components",
54
+ );
55
+ });
56
+
57
+ it("includes Settings shortcut for app context", () => {
58
+ const items = buildSuiteSearchItems("app", urls);
59
+ expect(items.some((item) => item.title === "Settings")).toBe(true);
60
+ });
61
+ });
@@ -0,0 +1,51 @@
1
+ import { ChangeDetectionStrategy, Component, input } from "@angular/core";
2
+
3
+ /**
4
+ * viking-skeleton — loading placeholder.
5
+ */
6
+ @Component({
7
+ selector: "viking-skeleton",
8
+ changeDetection: ChangeDetectionStrategy.OnPush,
9
+ host: {
10
+ "aria-hidden": "true",
11
+ "[class.viking-skeleton-circle]": "shape() === 'circle'",
12
+ "[style.width]": "width()",
13
+ "[style.height]": "height()",
14
+ },
15
+ template: ``,
16
+ styles: [
17
+ `
18
+ :host {
19
+ display: block;
20
+ border-radius: var(--viking-radius-sm);
21
+ background: linear-gradient(
22
+ 100deg,
23
+ var(--viking-surface-alt) 38%,
24
+ color-mix(
25
+ in srgb,
26
+ var(--viking-text-muted) 10%,
27
+ var(--viking-surface-alt)
28
+ )
29
+ 50%,
30
+ var(--viking-surface-alt) 62%
31
+ );
32
+ background-size: 200% 100%;
33
+ animation: viking-shimmer 1.6s ease-in-out infinite;
34
+ }
35
+ :host(.viking-skeleton-circle) {
36
+ border-radius: var(--viking-radius-pill);
37
+ }
38
+ @media (prefers-reduced-motion: reduce) {
39
+ :host {
40
+ animation: none;
41
+ background: var(--viking-surface-alt);
42
+ }
43
+ }
44
+ `,
45
+ ],
46
+ })
47
+ export class VikingSkeleton {
48
+ readonly shape = input<"line" | "rect" | "circle">("line");
49
+ readonly width = input<string>("100%");
50
+ readonly height = input<string>("var(--viking-font-size-sm)");
51
+ }
@@ -0,0 +1,133 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ computed,
5
+ input,
6
+ model,
7
+ } from "@angular/core";
8
+ import { VikingControl, provideVikingCva } from "../core/cva";
9
+
10
+ /**
11
+ * viking-slider — range slider.
12
+ * ControlValueAccessor-compatible.
13
+ */
14
+ @Component({
15
+ selector: "viking-slider",
16
+ providers: [provideVikingCva(VikingSlider)],
17
+ changeDetection: ChangeDetectionStrategy.OnPush,
18
+ template: `
19
+ <div class="viking-slider-header">
20
+ @if (label()) {
21
+ <span class="viking-slider-label">{{ label() }}</span>
22
+ }
23
+ @if (showValue()) {
24
+ <output class="viking-slider-value">{{ value() }}</output>
25
+ }
26
+ </div>
27
+ <input
28
+ type="range"
29
+ [min]="min()"
30
+ [max]="max()"
31
+ [step]="step()"
32
+ [value]="value()"
33
+ [disabled]="disabled() || formDisabled()"
34
+ [attr.aria-label]="label() || 'Slider'"
35
+ [style.--viking-slider-fill.%]="fillPercent()"
36
+ (input)="onInput($event)"
37
+ (blur)="onTouched()"
38
+ />
39
+ `,
40
+ styles: [
41
+ `
42
+ :host {
43
+ display: block;
44
+ font-family: var(--viking-font-family);
45
+ }
46
+ .viking-slider-header {
47
+ display: flex;
48
+ justify-content: space-between;
49
+ gap: var(--viking-space-2);
50
+ margin-bottom: calc(var(--viking-space-1) / 2);
51
+ font-size: var(--viking-font-size);
52
+ }
53
+ .viking-slider-label {
54
+ color: var(--viking-text);
55
+ font-weight: 500;
56
+ }
57
+ .viking-slider-value {
58
+ color: var(--viking-text-muted);
59
+ font-variant-numeric: tabular-nums;
60
+ }
61
+ input[type="range"] {
62
+ appearance: none;
63
+ width: 100%;
64
+ height: var(--viking-space-1);
65
+ border-radius: var(--viking-radius-pill);
66
+ border: 1px solid var(--viking-border);
67
+ background: linear-gradient(
68
+ to right,
69
+ var(--viking-accent) var(--viking-slider-fill, 0%),
70
+ var(--viking-surface-alt) var(--viking-slider-fill, 0%)
71
+ );
72
+ cursor: pointer;
73
+ margin: var(--viking-space-1) 0;
74
+ }
75
+ input[type="range"]:disabled {
76
+ opacity: var(--viking-state-disabled-opacity);
77
+ cursor: not-allowed;
78
+ }
79
+ input[type="range"]::-webkit-slider-thumb {
80
+ appearance: none;
81
+ width: var(--viking-space-2);
82
+ height: var(--viking-space-2);
83
+ border-radius: var(--viking-radius-pill);
84
+ background: var(--viking-surface);
85
+ border: 2px solid var(--viking-accent);
86
+ box-shadow: var(--viking-shadow-sm);
87
+ transition: transform 0.15s ease;
88
+ }
89
+ input[type="range"]::-webkit-slider-thumb:hover {
90
+ transform: scale(1.15);
91
+ }
92
+ input[type="range"]::-moz-range-thumb {
93
+ width: var(--viking-space-2);
94
+ height: var(--viking-space-2);
95
+ border-radius: var(--viking-radius-pill);
96
+ background: var(--viking-surface);
97
+ border: 2px solid var(--viking-accent);
98
+ box-shadow: var(--viking-shadow-sm);
99
+ }
100
+ input[type="range"]:focus-visible {
101
+ outline: var(--viking-ring-width) solid var(--viking-ring);
102
+ outline-offset: var(--viking-ring-offset);
103
+ }
104
+ `,
105
+ ],
106
+ })
107
+ export class VikingSlider extends VikingControl<number> {
108
+ readonly value = model<number>(0);
109
+ readonly min = input<number>(0);
110
+ readonly max = input<number>(100);
111
+ readonly step = input<number>(1);
112
+ readonly label = input<string>("");
113
+ readonly showValue = input<boolean>(true);
114
+ readonly disabled = input<boolean>(false);
115
+
116
+ protected readonly fillPercent = computed(() => {
117
+ const range = this.max() - this.min();
118
+ if (range <= 0) {
119
+ return 0;
120
+ }
121
+ return ((this.value() - this.min()) / range) * 100;
122
+ });
123
+
124
+ writeValue(value: number): void {
125
+ this.value.set(Number(value ?? 0));
126
+ }
127
+
128
+ protected onInput = (event: Event): void => {
129
+ const next = Number((event.target as HTMLInputElement).value);
130
+ this.value.set(next);
131
+ this.onChange(next);
132
+ };
133
+ }