diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index 242ee3c0b..4ce5a5086 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -9,6 +9,8 @@ import {
Title,
} from "@storybook/blocks";
import { TEDI_TRANSLATION_DEFAULT_TOKEN } from "../tedi/tokens/translation.token";
+import { TEDI_THEME_DEFAULT_TOKEN } from "../tedi/tokens/theme.token";
+import { THEME_FALLBACK_VALUE } from "../tedi/services/theme/theme.service";
export const globalTypes = {
theme: {
@@ -62,7 +64,10 @@ const preview: Preview = {
decorators: [
themeDecorator,
applicationConfig({
- providers: [{ provide: TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: "et" }],
+ providers: [
+ { provide: TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: "et" },
+ { provide: TEDI_THEME_DEFAULT_TOKEN, useValue: THEME_FALLBACK_VALUE },
+ ],
}),
],
parameters: {
diff --git a/package-lock.json b/package-lock.json
index 27a49a042..68668ef3e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,7 @@
"name": "@tedi-design-system/angular",
"version": "0.0.0-semantic-version",
"dependencies": {
- "@tedi-design-system/core": "^6.0.1"
+ "@tedi-design-system/core": "^6.1.2"
},
"devDependencies": {
"@angular-devkit/core": "19.2.15",
@@ -9972,9 +9972,9 @@
}
},
"node_modules/@tedi-design-system/core": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/@tedi-design-system/core/-/core-6.0.1.tgz",
- "integrity": "sha512-SgWbcIofn/LSzGbHYPYZD7i3PPdeL/qTaq99QT+RY6i9ISYViHQcrtNDiLZogx5KrREl/mQcrl3sLZNwmU6bDg==",
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/@tedi-design-system/core/-/core-6.1.2.tgz",
+ "integrity": "sha512-6kBr4pJ5KL1gfZYJd9WjTejAtnF4hJkCgZ4JR7B4UDnbKC7a7STyJC/umDkNbsZ3Xw2tLDZX1ocwkVhgcxjC0Q==",
"engines": {
"node": ">=24.0.0",
"npm": ">=11.0.0"
diff --git a/package.json b/package.json
index 38208c54e..45a3177ac 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
"ngx-float-ui": "^19.0.1 || ^20.0.0 || ^21.0.0"
},
"dependencies": {
- "@tedi-design-system/core": "^6.0.1"
+ "@tedi-design-system/core": "^6.1.2"
},
"devDependencies": {
"@angular-devkit/core": "19.2.15",
diff --git a/public/header-logo-white.svg b/public/header-logo-white.svg
new file mode 100644
index 000000000..58f4f664f
--- /dev/null
+++ b/public/header-logo-white.svg
@@ -0,0 +1,5 @@
+
diff --git a/tedi/components/base/text/text.component.ts b/tedi/components/base/text/text.component.ts
index f3fda95ba..c917963a4 100644
--- a/tedi/components/base/text/text.component.ts
+++ b/tedi/components/base/text/text.component.ts
@@ -14,6 +14,7 @@ export type TextModifiers =
| "h6"
| "normal"
| "small"
+ | "extra-small"
| "bold"
| "thin"
| "italic"
@@ -45,7 +46,8 @@ export type TextColor =
| "warning"
| "danger"
| "info"
- | "neutral";
+ | "neutral"
+ | "inherit";
@Component({
selector: "[tedi-text]",
@@ -53,7 +55,7 @@ export type TextColor =
templateUrl: "./text.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
- "[class]": "classes()"
+ "[class]": "classes()",
},
})
export class TextComponent {
@@ -81,7 +83,6 @@ export class TextComponent {
: modifiersValue
? [modifiersValue]
: [];
-
modifierClasses.forEach((modifier) => {
if (this.isHeadingModifier(modifier)) {
diff --git a/tedi/components/layout/header/header-actions/header-actions.component.scss b/tedi/components/layout/header/header-actions/header-actions.component.scss
index 960482b43..4c063dd7d 100644
--- a/tedi/components/layout/header/header-actions/header-actions.component.scss
+++ b/tedi/components/layout/header/header-actions/header-actions.component.scss
@@ -2,26 +2,23 @@
.tedi-header-actions {
display: flex;
+ gap: var(--layout-header-items-right-gutter-x);
align-items: center;
height: 100%;
+ margin-left: auto;
> * {
display: flex;
align-items: center;
height: 100%;
- &:not(:first-child) {
- padding-left: var(--layout-header-items-right-gutter-x);
- border-left: var(--tedi-borders-01) solid var(--general-border-primary);
- }
-
- &:not(:last-child) {
- padding-right: var(--layout-header-items-right-gutter-x);
- }
-
&:has(.tedi-header-login__button--mobile),
&:has(.tedi-header-profile--mobile) {
padding-left: 0;
}
+
+ @include breakpoints.media-breakpoint-only(md) {
+ max-height: 2.75rem;
+ }
}
}
diff --git a/tedi/components/layout/header/header-bottom/header-bottom.component.scss b/tedi/components/layout/header/header-bottom/header-bottom.component.scss
new file mode 100644
index 000000000..0d9d6bbd0
--- /dev/null
+++ b/tedi/components/layout/header/header-bottom/header-bottom.component.scss
@@ -0,0 +1,13 @@
+@use "@tedi-design-system/core/bootstrap-utility/breakpoints";
+
+.tedi-header-bottom {
+ display: block;
+ padding: var(--layout-grid-gutters-12) var(--layout-page-spacing-x);
+ background: var(--general-surface-primary);
+ border-top: var(--tedi-borders-01) solid var(--general-border-primary);
+ border-bottom: var(--tedi-borders-01) solid var(--general-border-primary);
+
+ @include breakpoints.media-breakpoint-up(md) {
+ display: none;
+ }
+}
diff --git a/tedi/components/layout/header/header-bottom/header-bottom.component.spec.ts b/tedi/components/layout/header/header-bottom/header-bottom.component.spec.ts
new file mode 100644
index 000000000..1cbb8592a
--- /dev/null
+++ b/tedi/components/layout/header/header-bottom/header-bottom.component.spec.ts
@@ -0,0 +1,46 @@
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { Component } from "@angular/core";
+import { HeaderBottomComponent } from "./header-bottom.component";
+
+describe("HeaderBottomComponent", () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [HeaderBottomComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(HeaderBottomComponent);
+ fixture.detectChanges();
+ });
+
+ it("should create the component", () => {
+ expect(fixture.componentInstance).toBeTruthy();
+ });
+
+ it("should apply the host class", () => {
+ expect(fixture.nativeElement.classList).toContain("tedi-header-bottom");
+ });
+
+ it("should project children content", () => {
+ @Component({
+ standalone: true,
+ imports: [HeaderBottomComponent],
+ template: `
+
+ hello
+
+ `,
+ })
+ class HostComponent {}
+
+ const hostFixture = TestBed.createComponent(HostComponent);
+ hostFixture.detectChanges();
+
+ const projected = hostFixture.nativeElement.querySelector(
+ "[data-testid='projected']",
+ );
+ expect(projected).toBeTruthy();
+ expect(projected.textContent).toBe("hello");
+ });
+});
diff --git a/tedi/components/layout/header/header-bottom/header-bottom.component.ts b/tedi/components/layout/header/header-bottom/header-bottom.component.ts
new file mode 100644
index 000000000..2b60b1770
--- /dev/null
+++ b/tedi/components/layout/header/header-bottom/header-bottom.component.ts
@@ -0,0 +1,35 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ ViewEncapsulation,
+} from "@angular/core";
+
+/**
+ * Mobile-only secondary row rendered below the main header bar.
+ *
+ * Typical use: a compact search bar or mobile-specific navigation that only
+ * appears below the `md` breakpoint. Above `md` the component is hidden via
+ * CSS, so consumers can include it unconditionally — desktop users won't see it.
+ *
+ * @example
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+@Component({
+ selector: "tedi-header-bottom",
+ standalone: true,
+ template: "",
+ styleUrl: "./header-bottom.component.scss",
+ encapsulation: ViewEncapsulation.None,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: {
+ "class": "tedi-header-bottom",
+ },
+})
+export class HeaderBottomComponent {}
diff --git a/tedi/components/layout/header/header-bottom/index.ts b/tedi/components/layout/header/header-bottom/index.ts
new file mode 100644
index 000000000..c31822bea
--- /dev/null
+++ b/tedi/components/layout/header/header-bottom/index.ts
@@ -0,0 +1 @@
+export * from "./header-bottom.component";
diff --git a/tedi/components/layout/header/header-content/header-content.component.scss b/tedi/components/layout/header/header-content/header-content.component.scss
index 266fe91ce..c79dccc60 100644
--- a/tedi/components/layout/header/header-content/header-content.component.scss
+++ b/tedi/components/layout/header/header-content/header-content.component.scss
@@ -1,23 +1,41 @@
-tedi-header-content {
+.tedi-header-content {
display: flex;
+ flex: 1 0 0;
gap: var(--layout-header-items-center-gutter-x);
align-items: center;
+ &--flex-start {
+ justify-content: flex-start;
+ }
+
+ &--center {
+ justify-content: center;
+ }
+
+ &--space-between {
+ justify-content: space-between;
+ }
+
a,
.tedi-link {
- color: var(--general-text-primary);
+ color: var(--header-link-default);
- &:hover {
- color: var(--general-text-secondary);
+ &:hover:not(:disabled, [aria-disabled="true"]) {
+ color: var(--header-link-hover);
}
- &:active {
- color: var(--general-text-tertiary);
+ &:active:not(:disabled, [aria-disabled="true"]) {
+ color: var(--header-link-active);
}
- &:focus-visible {
- outline: var(--tedi-borders-02) solid var(--tedi-primary-500);
+ &:focus-visible:not(:disabled) {
+ outline: var(--tedi-borders-02) solid var(--header-link-focus);
outline-offset: var(--tedi-borders-01);
}
}
+
+ > * {
+ display: flex;
+ gap: var(--layout-header-items-center-gutter-x);
+ }
}
diff --git a/tedi/components/layout/header/header-content/header-content.component.spec.ts b/tedi/components/layout/header/header-content/header-content.component.spec.ts
new file mode 100644
index 000000000..7cb277e33
--- /dev/null
+++ b/tedi/components/layout/header/header-content/header-content.component.spec.ts
@@ -0,0 +1,61 @@
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { HeaderContentAlignment, HeaderContentComponent } from "./header-content.component";
+
+describe("HeaderContentComponent", () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [HeaderContentComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(HeaderContentComponent);
+ fixture.detectChanges();
+ });
+
+ it("should create the component", () => {
+ expect(fixture.componentInstance).toBeTruthy();
+ });
+
+ it("should apply the base host class", () => {
+ expect(fixture.nativeElement.classList).toContain("tedi-header-content");
+ });
+
+ it("should default to the center alignment modifier", () => {
+ expect(fixture.nativeElement.classList).toContain(
+ "tedi-header-content--center",
+ );
+ });
+
+ it.each(["flex-start", "center", "space-between"])(
+ "should apply the %s alignment modifier when set",
+ (alignment) => {
+ fixture.componentRef.setInput("alignment", alignment);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.classList).toContain(
+ `tedi-header-content--${alignment}`,
+ );
+ },
+ );
+
+ it("should swap the modifier class when alignment changes", () => {
+ fixture.componentRef.setInput("alignment", "space-between");
+ fixture.detectChanges();
+ expect(fixture.nativeElement.classList).toContain(
+ "tedi-header-content--space-between",
+ );
+ expect(fixture.nativeElement.classList).not.toContain(
+ "tedi-header-content--center",
+ );
+
+ fixture.componentRef.setInput("alignment", "flex-start");
+ fixture.detectChanges();
+ expect(fixture.nativeElement.classList).toContain(
+ "tedi-header-content--flex-start",
+ );
+ expect(fixture.nativeElement.classList).not.toContain(
+ "tedi-header-content--space-between",
+ );
+ });
+});
diff --git a/tedi/components/layout/header/header-content/header-content.component.ts b/tedi/components/layout/header/header-content/header-content.component.ts
index 6e7fc1609..7b1f8b0f0 100644
--- a/tedi/components/layout/header/header-content/header-content.component.ts
+++ b/tedi/components/layout/header/header-content/header-content.component.ts
@@ -1,11 +1,34 @@
-import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ ViewEncapsulation,
+ computed,
+ input,
+} from "@angular/core";
+
+export type HeaderContentAlignment = "flex-start" | "center" | "space-between";
@Component({
- selector: 'tedi-header-content',
+ selector: "tedi-header-content",
standalone: true,
template: "",
- styleUrl: './header-content.component.scss',
+ styleUrl: "./header-content.component.scss",
encapsulation: ViewEncapsulation.None,
- changeDetection: ChangeDetectionStrategy.OnPush
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: {
+ "[class]": "classes()",
+ },
})
-export class HeaderContentComponent {}
+export class HeaderContentComponent {
+ /**
+ * Controls the horizontal alignment of the projected content (`justify-content`).
+ * Useful when the center area mixes nav links with another component (e.g. a
+ * ``) and you want to push them apart or align them to one edge.
+ * @default "center"
+ */
+ readonly alignment = input("center");
+
+ protected readonly classes = computed(
+ () => `tedi-header-content tedi-header-content--${this.alignment()}`,
+ );
+}
diff --git a/tedi/components/layout/header/header-language/header-language.component.html b/tedi/components/layout/header/header-language/header-language.component.html
index f0ca1e360..f5722fa80 100644
--- a/tedi/components/layout/header/header-language/header-language.component.html
+++ b/tedi/components/layout/header/header-language/header-language.component.html
@@ -5,18 +5,28 @@
class="tedi-header-language__label"
>{{ "header.select-lang" | tediTranslate }}
-
+
+
-
-
-
-
-
-
-
-
-} @else {
-
-
-}
+
+ @if (isSmall()) {
+
+ } @else {
+
+ }
+
+
+
+
+
+ @if (showLabel()) {
+
+
+ }
+
@if (modalOpen()) {
-