Skip to content

feat(header): update Header component against Figma #312#440

Open
ly-tempel-bitweb wants to merge 4 commits into
rcfrom
feat/312-update-header-against-figma
Open

feat(header): update Header component against Figma #312#440
ly-tempel-bitweb wants to merge 4 commits into
rcfrom
feat/312-update-header-against-figma

Conversation

@ly-tempel-bitweb
Copy link
Copy Markdown
Contributor

@ly-tempel-bitweb ly-tempel-bitweb commented May 15, 2026

Migration guide

Breaking changes

HeaderRoleComponentrole input renamed to label
The input that previously held the role text (e.g. "Roll:") is now called label to align with React's Header.Role API and to free the role attribute from being reflected onto the host element (axe was flagging non-ARIA values like "Asutus" / "Roll:"). The content-projection slot for richer markup (e.g. a <tedi-tag>) is unchanged: [tedi-header-role-title].

<!-- Before -->
<tedi-header-role role="Roll:" ...></tedi-header-role>
<!-- After -->
<tedi-header-role label="Roll:" ...></tedi-header-role>

HeaderRoleComponentRepresentative.id is now required
Each representative must carry a stable id. It's used for selection comparison and for re-syncing the current representative when the representatives list changes. The previous shape (only name / description / optional icon) won't compile.

// Before
const reps: Representative[] = [
  { name: "Mari Maasikas", description: "49504080934" },
];
// After
const reps: Representative[] = [
  { id: "1", name: "Mari Maasikas", description: "49504080934" },
];

The Representative.icon type also widened from string to string | RepresentativeIcon — that one is additive (the old icon: 'person' form still works, you can now also pass { name: 'person', size: 18 }).

HeaderProfileComponentname input renamed to label
For the same reason as HeaderRoleComponent and for parity with React.

<!-- Before -->
<tedi-header-profile [name]="userName">...</tedi-header-profile>
<!-- After -->
<tedi-header-profile [label]="userName">...</tedi-header-profile>

HeaderProfileComponentshowDropdown input renamed to showPopover, default changed
The Breakpoint input controlling the desktop popover threshold is now showPopover (matching React) and defaults to 'lg' instead of undefined.

<!-- Before -->
<tedi-header-profile [showDropdown]="'lg'">...</tedi-header-profile>
<!-- After -->
<tedi-header-profile [showPopover]="'lg'">...</tedi-header-profile>
<!-- Or omit — 'lg' is now the default -->
<tedi-header-profile>...</tedi-header-profile>

HeaderLogoutComponent — content projection removed; use label input instead
The component previously rendered projected children inside its label span. It's now input-driven (matching React) and falls back to translation keys when no label is provided.

<!-- Before -->
<tedi-header-logout>Log out</tedi-header-logout>
<!-- After -->
<tedi-header-logout label="Log out"></tedi-header-logout>
<!-- Or omit — falls back to `header.logout` / `header.logout-small` -->
<tedi-header-logout />

Translation key rename: header.login-mobileheader.login.mobile
The mobile-variant translation keys now use a dot separator: header.login.mobile, header.logout.mobile, header.profile.mobile. Consumers with custom Estonian/English/Russian overrides for header.login-mobile (Angular pre-PR) need to rename to header.login.mobile. The bundled values are unchanged.

Translation value change: header.profile
The bundled value changed from Profiil / Profile / Профиль to Minu profiil / My profile / Мой профиль to match React. A new key header.profile.mobile was added with the old short values for the compact mobile trigger. Consumers who ship their own translation overrides for header.profile should review whether they want the longer or shorter text — the rename below is automatic in the bundled translations, but custom overrides keep their existing values.

HeaderLogin mobile breakpoint shifted from sm to md
The compact/icon-only login variant now activates below the md breakpoint instead of below sm. Visual change rather than API change — verify any responsive design that depended on the old threshold.


Not breaking — worth knowing

  • New components: HeaderLogoComponent, HeaderBottomComponent, HeaderMobileButtonComponent, plus directives HeaderLogoDarkDirective (tediHeaderLogoDark slot) and HeaderRoleTitleDirective ([tedi-header-role-title] slot).
  • New output: HeaderRoleComponent.roleSelectionToggle (fires boolean when the role popover/mobile collapse opens or closes; complements the existing currentRepresentativeChange from model.required<Representative>()).
  • New inputs on HeaderLoginComponent and HeaderLogoutComponent: size, label, href.
  • HeaderContentComponent gained an alignment input ('flex-start' | 'center' | 'space-between', default 'center'); the default of center may visually shift content compared to the previous no-flex template.
  • Per-breakpoint inputs on HeaderProfile, HeaderLogin, HeaderLogout via the existing BreakpointInputs<T> pattern. Each accepts xs / sm / md / lg / xl / xxl inputs that override label / size / showPopover at the matching breakpoint:
    <tedi-header-profile [showLabel]="true" [md]="{ label: 'Mari Maasikas' }" />
    Same shape LinkComponent already uses, mirrors React's BreakpointSupport<T>.
  • Fixed aria-controls="" axe violation on every [tedi-popover-trigger] (HeaderProfile, HeaderLanguage, HeaderRole popovers). The attribute is now omitted until the floating-UI container has a real id, instead of binding to an empty string.

Summary by CodeRabbit

  • New Features

    • Added many header pieces: mobile search (modal/inline), mobile header button, header bottom row, responsive/logo dark variant, role selector, profile/login/logout controls, and a standalone header search component with configurable inputs.
  • Bug Fixes

    • Popover trigger accessibility now reflects actual open/closed state.
  • Style & UX Improvements

    • Refined header spacing, alignment, responsive behaviors, icon transitions, and updated header translations.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 995f1202-2b50-454b-9816-804d26e39d86

📥 Commits

Reviewing files that changed from the base of the PR and between 4fdbfbc and 90698de.

📒 Files selected for processing (5)
  • tedi/components/layout/header/header-login/header-login.component.spec.ts
  • tedi/components/layout/header/header-profile/header-profile.component.html
  • tedi/components/layout/header/header-profile/header-profile.component.scss
  • tedi/components/layout/header/header-role/header-role.component.ts
  • tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.scss
✅ Files skipped from review due to trivial changes (1)
  • tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.scss
🚧 Files skipped from review as they are similar to previous changes (3)
  • tedi/components/layout/header/header-profile/header-profile.component.scss
  • tedi/components/layout/header/header-role/header-role.component.ts
  • tedi/components/layout/header/header-login/header-login.component.spec.ts

📝 Walkthrough

Walkthrough

Header subsystem: adds Storybook theme provider; introduces HeaderMobileButton, HeaderLogo (+dark directive), HeaderBottom, HeaderSearch; refactors HeaderLogin/HeaderLogout/Profile/Role to breakpoint-aware, input-driven APIs; updates header templates, styles, popover reactive state, translations, and Storybook stories/tests.

Changes

Support Infrastructure & Foundation

Layer / File(s) Summary
Storybook theme provider & package bump
.storybook/preview.tsx, package.json
Adds TEDI_THEME_DEFAULT_TOKEN provider with THEME_FALLBACK_VALUE to Storybook applicationConfig and bumps @tedi-design-system/core peer range to ^6.1.2.
Translations and small text types
tedi/services/translation/translations.ts, tedi/components/base/text/text.component.ts
Updates header translation keys (login/logout/profile/search/role-switch) and extends TextModifiers with extra-small and TextColor with neutral and inherit.
Popover reactive state & trigger ARIA
tedi/components/overlay/popover/*, tedi/components/overlay/popover/popover-trigger/*
Adds isOpen signal to PopoverComponent, sets it on show/hide, applies host tedi-popover class, updates PopoverTriggerDirective ARIA bindings to use popover.isOpen() and `popover.containerId()

New Header Foundation Components

Layer / File(s) Summary
HeaderMobileButton
tedi/components/layout/header/header-mobile-button/*
New standalone mobile header button component supporting icon, optional extra-small label, link vs button rendering, selected/disabled states, ARIA passthrough, styles and unit tests; re-exported via index.
HeaderLogo + dark directive
tedi/components/layout/header/header-logo/*
HeaderLogoComponent with href and showLogo inputs, dark-variant projection via HeaderLogoDarkDirective, ThemeService integration, styles and tests; added to header barrel.
HeaderBottom layout
tedi/components/layout/header/header-bottom/*
New HeaderBottomComponent (standalone) for a mobile-only secondary header row with SCSS and tests; barrel re-export added.
HeaderSearch (modal/inline)
tedi/components/layout/header/header-search/*
New HeaderSearchComponent with mobileVariant (modal

Header Action Buttons Refactoring

Layer / File(s) Summary
HeaderLogin
tedi/components/layout/header/header-login/*
Refactors to input-driven API (size, label, href) with breakpoint-aware isSmall (md), renders desktop anchor/button or mobile HeaderMobileButton, updates tests and removes prior translation-tracking fields.
HeaderLogout
tedi/components/layout/header/header-logout/*
Refactors selector to tedi-header-logout, adds inputs (size, label, href), breakpoint-aware isSmall, resolvedLabel translation fallback, static host class, and updates template/SCSS/tests.

Header Profile & Role Components

Layer / File(s) Summary
HeaderProfile
tedi/components/layout/header/header-profile/*
Replaces name/showDropdown with label/showPopover/noStyle/size and breakpoint override inputs; integrates HeaderMobileButton; computes resolvedLabel, isSmall, buttonVariant using showLabel; updates modal scroll-lock behavior and tests.
HeaderRole + Title directive
tedi/components/layout/header/header-role/*
Adds HeaderRoleTitleDirective, extends Representative with id, description, icon (string

Header Layout & Styling System

Layer / File(s) Summary
Header structural template & styles
tedi/components/layout/header/header.component.*, tedi/components/layout/header/header-actions/*
Wraps header content in tedi-header__main and tedi-header__main--content, adds tedi-header-bottom projection, moves box-shadow and content layout to root/main content blocks, updates header-actions to use gap + right alignment, and caps header-actions max-height at md breakpoint.
HeaderContent alignment & link colors
tedi/components/layout/header/header-content/*
Adds HeaderContentAlignment input and host-class modifiers (--flex-start, --center, --space-between), updates link color variables to header-specific tokens, and ensures child elements use flex + gap; includes tests.
Language, role, sidenav, link styles
tedi/components/layout/header/header-language/*, tedi/components/layout/header/header-role/*, tedi/components/layout/sidenav/*, tedi/components/navigation/link/link.component.scss
Updates language chevron transition/rotation keyed on aria-expanded, refactors header-role styles and transitions, tweaks sidenav toggle icon color and font-family defaults for links and sidenav titles.
Storybook stories expansion
tedi/components/layout/header/header.stories.ts
Expands header stories with new subcomponents/directives, story-local helpers (translate pipe, theme toggle, responsive-logo), richer argTypes and many story scenarios demonstrating new APIs and behaviors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • mart-sessman
  • airikej
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/312-update-header-against-figma

@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

❌ Patch coverage is 98.71795% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...di/components/layout/header/header-bottom/index.ts 0.00% 1 Missing ⚠️
...onents/layout/header/header-mobile-button/index.ts 0.00% 1 Missing ⚠️
...di/components/layout/header/header-search/index.ts 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tedi/components/layout/header/header-profile/header-profile.component.spec.ts (1)

15-22: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing TediTranslationService mock in TestBed providers.

The coding guidelines require providing both TediTranslationService mock and TEDI_TRANSLATION_DEFAULT_TOKEN in test setup for components using translations. Currently, only the token is provided (line 19), but the service mock is missing from the providers array. The test on lines 75-77 spies on component.translationService, indicating the service is expected to be available. Providing a mock upfront ensures consistent, isolated test behavior rather than relying on per-test spies or real service instances.

As per coding guidelines, tedi/**/*.component.spec.ts: Provide TediTranslationService mock and TEDI_TRANSLATION_DEFAULT_TOKEN in test setup for tests using translated components.

🧪 Proposed fix to add TediTranslationService mock
+import { TediTranslationService } from "../../../../services/translation/translation.service";
+
 describe("HeaderProfileComponent", () => {
   let fixture: ComponentFixture<HeaderProfileComponent>;
   let component: HeaderProfileComponent;
   let documentMock: Document;
+  let translationServiceMock: jest.Mocked<TediTranslationService>;
 
   beforeEach(() => {
     documentMock = document;
+    translationServiceMock = {
+      translate: jest.fn((key: string) => `__${key}__`),
+    } as unknown as jest.Mocked<TediTranslationService>;
 
     TestBed.configureTestingModule({
       imports: [HeaderProfileComponent],
       providers: [
         { provide: DOCUMENT, useValue: documentMock },
         { provide: TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: "et" },
+        { provide: TediTranslationService, useValue: translationServiceMock },
       ],
       schemas: [NO_ERRORS_SCHEMA],
     });

Then simplify the spy in the resolvedLabel test:

     it("falls back to the `header.profile` translation key when `label` is empty", () => {
-      const translate = jest
-        .spyOn(component.translationService, "translate")
-        .mockImplementation(((...args: unknown[]) => `__${args[0]}__`) as unknown as typeof component.translationService.translate);
-
       fixture.componentRef.setInput("label", "");
       fixture.detectChanges();
 
       expect(component.resolvedLabel()).toBe("__header.profile__");
-      expect(translate).toHaveBeenCalledWith("header.profile");
+      expect(translationServiceMock.translate).toHaveBeenCalledWith("header.profile");
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tedi/components/layout/header/header-profile/header-profile.component.spec.ts`
around lines 15 - 22, Add a mock provider for TediTranslationService to the
TestBed.configureTestingModule providers list alongside the existing
TEDI_TRANSLATION_DEFAULT_TOKEN so the component has a predictable translation
service instance; update the providers array to include a mock object (or
jasmine.createSpyObj) for TediTranslationService and remove or simplify per-test
spies that access component.translationService (e.g., the spy in the
resolvedLabel test) so tests use the injected mock consistently.
tedi/components/layout/header/header-language/header-language.component.spec.ts (1)

25-31: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Include TEDI_TRANSLATION_DEFAULT_TOKEN in TestBed providers.

This spec exercises a translated component; add the default translation token alongside the service mock to match required test setup.

Proposed patch
 import { HeaderLanguageComponent, HeaderLanguage } from './header-language.component';
 import {
   Language,
   TediTranslationService,
+  TEDI_TRANSLATION_DEFAULT_TOKEN,
 } from '../../../../services/translation/translation.service';
@@
       providers: [
-        { provide: TediTranslationService, useValue: mockTranslationService }
+        { provide: TediTranslationService, useValue: mockTranslationService },
+        { provide: TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: 'et' },
       ],

As per coding guidelines, “Provide TediTranslationService mock and TEDI_TRANSLATION_DEFAULT_TOKEN in test setup for tests using translated components.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tedi/components/layout/header/header-language/header-language.component.spec.ts`
around lines 25 - 31, The TestBed setup for HeaderLanguageComponent is missing
the TEDI_TRANSLATION_DEFAULT_TOKEN provider; update the providers array in the
TestBed.configureTestingModule call to include { provide:
TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: /* default token value or mock */ }
alongside the existing { provide: TediTranslationService, useValue:
mockTranslationService } so the translated component has the required default
token during tests.
🧹 Nitpick comments (5)
tedi/components/layout/header/header-role/header-role.component.ts (1)

140-144: ⚖️ Poor tradeoff

Consider using afterNextRender for focus timing.

The setTimeout without a delay relies on the next event loop tick to ensure the search input is rendered. While this typically works, Angular's afterNextRender API provides more explicit timing control for post-render operations.

♻️ Alternative implementation
-import { _IdGenerator } from "@angular/cdk/a11y";
+import { _IdGenerator } from "@angular/cdk/a11y";
+import { afterNextRender } from "@angular/core";
-    effect(() => {
-      if (this.popover()?.isOpen() && this.showInput()) {
-        setTimeout(() => this.searchInput()?.nativeElement.focus());
-      }
-    });
+    effect(() => {
+      if (this.popover()?.isOpen() && this.showInput()) {
+        afterNextRender(() => {
+          this.searchInput()?.nativeElement.focus();
+        });
+      }
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tedi/components/layout/header/header-role/header-role.component.ts` around
lines 140 - 144, The effect callback currently uses setTimeout(() =>
this.searchInput()?.nativeElement.focus()) to wait for the input to be rendered;
replace that timing hack with Angular's afterNextRender to run the focus after
the next render cycle. Import afterNextRender from `@angular/core` and inside
effect() where you check this.popover()?.isOpen() && this.showInput(), call
afterNextRender(() => this.searchInput()?.nativeElement.focus()) instead of
setTimeout; keep the same null-safe calls to popover, showInput and searchInput
so behavior is preserved.
tedi/components/layout/header/header-profile/header-profile.component.ts (1)

110-116: 💤 Low value

Verify resolvedLabel usage aligns with component behavior.

The resolvedLabel computed property returns translationService.translate("header.profile") when label() is empty, but the desktop button template (in the HTML file) only displays the label when label() is truthy. This means resolvedLabel() is only used by the mobile button, which is correct. However, the naming might be misleading since "resolved" implies it's used everywhere.

Consider renaming to mobileLabel or adding a JSDoc comment clarifying it's primarily for the mobile button variant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tedi/components/layout/header/header-profile/header-profile.component.ts`
around lines 110 - 116, resolvedLabel returns a fallback translation for mobile
use but the desktop template ignores it, so rename the computed property or
document its intent: update the computed property name from resolvedLabel to
mobileLabel (and update all template and TS references) OR add a brief JSDoc
above resolvedLabel clarifying “used only by the mobile button; desktop uses
label() directly,” and ensure translationService.translate("header.profile")
remains the fallback; reference the computed symbol resolvedLabel/mobileLabel,
the label() accessor, and translationService.translate when making the change.
tedi/components/layout/header/header.component.scss (1)

3-3: ⚡ Quick win

Remove fallback value from CSS variable usage

var(--tedi-alpha-20, rgb(0 0 0 / 20%)) violates the SCSS rule disallowing fallback values in var().

Proposed fix
-  box-shadow: 0 1px 5px 0 var(--tedi-alpha-20, rgb(0 0 0 / 20%));
+  box-shadow: 0 1px 5px 0 var(--tedi-alpha-20);

As per coding guidelines: “Do not use fallback values in CSS var() functions — write var(--token-name) without fallback values”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tedi/components/layout/header/header.component.scss` at line 3, The CSS uses
a fallback in the var() call for the box-shadow: replace the fallback usage
var(--tedi-alpha-20, rgb(0 0 0 / 20%)) with a bare variable reference
var(--tedi-alpha-20) so the SCSS rule is satisfied; update the box-shadow
declaration that references --tedi-alpha-20 to remove the fallback and ensure
the design token is defined elsewhere.
tedi/components/layout/header/header-logout/header-logout.component.ts (1)

37-37: 💤 Low value

Consider making breakpointService private for consistency.

Line 36 marks translationService as private, and breakpointService is only used internally. For consistency and encapsulation, consider marking it private readonly.

♻️ Proposed refactor
-  breakpointService = inject(BreakpointService);
+  private readonly breakpointService = inject(BreakpointService);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tedi/components/layout/header/header-logout/header-logout.component.ts` at
line 37, The BreakpointService instance assigned to breakpointService should be
made private and readonly for consistency and encapsulation: change the injected
property breakpointService (type BreakpointService) to private readonly, similar
to translationService, since breakpointService is only used internally within
HeaderLogoutComponent; update any references accordingly if needed.
tedi/components/layout/header/header-login/header-login.component.spec.ts (1)

68-68: ⚡ Quick win

Use setInput for consistency with other tests.

For consistency with the rest of the test suite (lines 95, 124), use fixture.componentRef.setInput('label', 'Sign in') instead of direct property assignment.

♻️ Proposed refactor
-    fixture.componentInstance.label = "Sign in";
+    fixture.componentRef.setInput("label", "Sign in");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tedi/components/layout/header/header-login/header-login.component.spec.ts` at
line 68, Replace the direct property assignment to the test component instance
(fixture.componentInstance.label = "Sign in") with the consistent test API by
calling fixture.componentRef.setInput('label', 'Sign in'); locate the occurrence
in header-login.component.spec.ts where fixture.componentInstance is used and
change it to use fixture.componentRef.setInput so it matches other tests that
set inputs via setInput.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@tedi/components/layout/header/header-language/header-language.component.scss`:
- Around line 10-17: The SCSS is styling the Angular element selector
tedi-icon[name="expand_more"] directly; instead add a host CSS class (e.g.,
tedi-header-language__chevron) to the icon in the header-language template and
update the SCSS selectors to target that class under [tedi-popover-trigger] and
[tedi-popover-trigger][aria-expanded="true"] (replace
tedi-icon[name="expand_more"] with .tedi-header-language__chevron in both
rules). Ensure the template adds the class to the icon host and the SCSS uses
the new class for margin, transition and transform.

In `@tedi/components/layout/header/header-login/header-login.component.html`:
- Line 17: In header-login.component.html, update the button element used for
the header login (the <button> with tedi-button and class
"tedi-header-login__button") to include an explicit type="button" attribute so
it does not trigger form submission when placed inside a form; locate the button
in the header-login template and add the type attribute to the element that uses
the tedi-button directive/class.

In `@tedi/components/layout/header/header-profile/header-profile.component.ts`:
- Around line 54-92: Update the public changelog and migration guide to call out
the input API changes for HeaderProfileComponent: state that the inputs name and
showDropdown were removed and list the new inputs label, showPopover, noStyle,
and size (reference the component inputs label, showPopover, noStyle, size in
header-profile.component.ts); provide a short migration snippet and recommended
replacements (e.g., replace name with label or elsewhere, replace showDropdown
logic with showPopover/modal behavior) and mark this as a breaking change in the
release notes with the component name and version bump.

In `@tedi/components/layout/header/header-role/header-role.component.ts`:
- Around line 39-55: Representative.id was made required which breaks callers;
update documentation and add a runtime compatibility fix: document the breaking
change in release notes/migration guide and, in the component that consumes the
type (e.g., HeaderRoleComponent handling the representatives input and selection
logic such as the code paths that sync selection or call
setSelectedRepresentative), detect missing id values and synthesize a stable id
(for example from name or index or a UUID) before using them for
comparison/selection, or revert the type to keep id optional until consumers are
migrated. Ensure references to the Representative type and its id field are
updated accordingly.

In `@tedi/components/layout/header/header-search/header-search.component.ts`:
- Around line 103-107: The effect that currently closes the modal when not
mobile leaves modalOpen true when the variant flips away from "modal", causing a
stale reopen later; update the effect (the reactive effect using this.isMobile()
and this.modalOpen()) to also reset this.modalOpen to false whenever the
mobileVariant is not "modal" (e.g., check this.mobileVariant() !== "modal" or
add a helper like isModalVariant()), ensuring modalOpen is explicitly set false
when variant changes away from modal so it cannot reopen unexpectedly; reference
the existing effect, isMobile(), modalOpen, and mobileVariant symbols when
making the change.

In `@tedi/components/layout/header/header.stories.ts`:
- Around line 1004-1005: In the WithSearch2 story update the two tedi-search
instances so they do not share the same inputId ("search-4"); give each a unique
id (e.g., change the second tedi-search inputId to "search-5") to avoid
duplicate-id accessibility and interaction conflicts—update the inputId
attributes on the tedi-search elements referenced in the WithSearch2 story (also
update the corresponding second occurrence noted near the other tedi-search in
the same story).

In `@tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.scss`:
- Around line 15-17: Replace the element selector styling for tedi-icon with a
class-based selector and add that class to the component template/host;
specifically, remove the direct selector "tedi-icon { ... }" in
sidenav-toggle.component.scss and use a dedicated class (e.g.,
.sidenav-toggle__icon) so the rule targets .sidenav-toggle__icon { color:
var(--button-main-primary-text-default); }, and update the sidenav-toggle
component template/host to apply that class to the tedi-icon element or host
element so the style continues to apply.

In `@tedi/components/overlay/popover/popover.component.scss`:
- Around line 8-11: Replace the element selector styling "tedi-popover { ... }"
with a host class selector (e.g. ".tedi-popover { display: inline-flex;
align-items: center; }") and ensure the Popover component adds that class to its
host (add host: { class: 'tedi-popover' } in the `@Component` decorator or add the
class to the host element). Update popover.component.scss to target
".tedi-popover" instead of the element selector and add the host class in the
popover component (e.g., PopoverComponent) so the styles apply via the host
class.

---

Outside diff comments:
In
`@tedi/components/layout/header/header-language/header-language.component.spec.ts`:
- Around line 25-31: The TestBed setup for HeaderLanguageComponent is missing
the TEDI_TRANSLATION_DEFAULT_TOKEN provider; update the providers array in the
TestBed.configureTestingModule call to include { provide:
TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: /* default token value or mock */ }
alongside the existing { provide: TediTranslationService, useValue:
mockTranslationService } so the translated component has the required default
token during tests.

In
`@tedi/components/layout/header/header-profile/header-profile.component.spec.ts`:
- Around line 15-22: Add a mock provider for TediTranslationService to the
TestBed.configureTestingModule providers list alongside the existing
TEDI_TRANSLATION_DEFAULT_TOKEN so the component has a predictable translation
service instance; update the providers array to include a mock object (or
jasmine.createSpyObj) for TediTranslationService and remove or simplify per-test
spies that access component.translationService (e.g., the spy in the
resolvedLabel test) so tests use the injected mock consistently.

---

Nitpick comments:
In `@tedi/components/layout/header/header-login/header-login.component.spec.ts`:
- Line 68: Replace the direct property assignment to the test component instance
(fixture.componentInstance.label = "Sign in") with the consistent test API by
calling fixture.componentRef.setInput('label', 'Sign in'); locate the occurrence
in header-login.component.spec.ts where fixture.componentInstance is used and
change it to use fixture.componentRef.setInput so it matches other tests that
set inputs via setInput.

In `@tedi/components/layout/header/header-logout/header-logout.component.ts`:
- Line 37: The BreakpointService instance assigned to breakpointService should
be made private and readonly for consistency and encapsulation: change the
injected property breakpointService (type BreakpointService) to private
readonly, similar to translationService, since breakpointService is only used
internally within HeaderLogoutComponent; update any references accordingly if
needed.

In `@tedi/components/layout/header/header-profile/header-profile.component.ts`:
- Around line 110-116: resolvedLabel returns a fallback translation for mobile
use but the desktop template ignores it, so rename the computed property or
document its intent: update the computed property name from resolvedLabel to
mobileLabel (and update all template and TS references) OR add a brief JSDoc
above resolvedLabel clarifying “used only by the mobile button; desktop uses
label() directly,” and ensure translationService.translate("header.profile")
remains the fallback; reference the computed symbol resolvedLabel/mobileLabel,
the label() accessor, and translationService.translate when making the change.

In `@tedi/components/layout/header/header-role/header-role.component.ts`:
- Around line 140-144: The effect callback currently uses setTimeout(() =>
this.searchInput()?.nativeElement.focus()) to wait for the input to be rendered;
replace that timing hack with Angular's afterNextRender to run the focus after
the next render cycle. Import afterNextRender from `@angular/core` and inside
effect() where you check this.popover()?.isOpen() && this.showInput(), call
afterNextRender(() => this.searchInput()?.nativeElement.focus()) instead of
setTimeout; keep the same null-safe calls to popover, showInput and searchInput
so behavior is preserved.

In `@tedi/components/layout/header/header.component.scss`:
- Line 3: The CSS uses a fallback in the var() call for the box-shadow: replace
the fallback usage var(--tedi-alpha-20, rgb(0 0 0 / 20%)) with a bare variable
reference var(--tedi-alpha-20) so the SCSS rule is satisfied; update the
box-shadow declaration that references --tedi-alpha-20 to remove the fallback
and ensure the design token is defined elsewhere.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 919bfca0-677b-4f71-9cc6-240126019107

📥 Commits

Reviewing files that changed from the base of the PR and between a575670 and b52d923.

⛔ Files ignored due to path filters (1)
  • public/header-logo-white.svg is excluded by !**/*.svg
📒 Files selected for processing (57)
  • .storybook/preview.tsx
  • tedi/components/base/text/text.component.ts
  • tedi/components/layout/header/header-actions/header-actions.component.scss
  • tedi/components/layout/header/header-bottom/header-bottom.component.scss
  • tedi/components/layout/header/header-bottom/header-bottom.component.spec.ts
  • tedi/components/layout/header/header-bottom/header-bottom.component.ts
  • tedi/components/layout/header/header-bottom/index.ts
  • tedi/components/layout/header/header-content/header-content.component.scss
  • tedi/components/layout/header/header-content/header-content.component.spec.ts
  • tedi/components/layout/header/header-content/header-content.component.ts
  • tedi/components/layout/header/header-language/header-language.component.html
  • tedi/components/layout/header/header-language/header-language.component.scss
  • tedi/components/layout/header/header-language/header-language.component.spec.ts
  • tedi/components/layout/header/header-login/header-login.component.html
  • tedi/components/layout/header/header-login/header-login.component.scss
  • tedi/components/layout/header/header-login/header-login.component.spec.ts
  • tedi/components/layout/header/header-login/header-login.component.ts
  • tedi/components/layout/header/header-logo/header-logo-dark.directive.ts
  • tedi/components/layout/header/header-logo/header-logo.component.html
  • tedi/components/layout/header/header-logo/header-logo.component.scss
  • tedi/components/layout/header/header-logo/header-logo.component.spec.ts
  • tedi/components/layout/header/header-logo/header-logo.component.ts
  • tedi/components/layout/header/header-logout/header-logout.component.html
  • tedi/components/layout/header/header-logout/header-logout.component.scss
  • tedi/components/layout/header/header-logout/header-logout.component.spec.ts
  • tedi/components/layout/header/header-logout/header-logout.component.ts
  • tedi/components/layout/header/header-mobile-button/header-mobile-button.component.html
  • tedi/components/layout/header/header-mobile-button/header-mobile-button.component.scss
  • tedi/components/layout/header/header-mobile-button/header-mobile-button.component.spec.ts
  • tedi/components/layout/header/header-mobile-button/header-mobile-button.component.ts
  • tedi/components/layout/header/header-mobile-button/index.ts
  • tedi/components/layout/header/header-profile/header-profile.component.html
  • tedi/components/layout/header/header-profile/header-profile.component.scss
  • tedi/components/layout/header/header-profile/header-profile.component.spec.ts
  • tedi/components/layout/header/header-profile/header-profile.component.ts
  • tedi/components/layout/header/header-role/header-role-title.directive.ts
  • tedi/components/layout/header/header-role/header-role.component.html
  • tedi/components/layout/header/header-role/header-role.component.scss
  • tedi/components/layout/header/header-role/header-role.component.spec.ts
  • tedi/components/layout/header/header-role/header-role.component.ts
  • tedi/components/layout/header/header-search/header-search.component.html
  • tedi/components/layout/header/header-search/header-search.component.scss
  • tedi/components/layout/header/header-search/header-search.component.spec.ts
  • tedi/components/layout/header/header-search/header-search.component.ts
  • tedi/components/layout/header/header-search/index.ts
  • tedi/components/layout/header/header.component.html
  • tedi/components/layout/header/header.component.scss
  • tedi/components/layout/header/header.stories.ts
  • tedi/components/layout/header/index.ts
  • tedi/components/layout/sidenav/sidenav-item/sidenav-item.component.scss
  • tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.html
  • tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.scss
  • tedi/components/navigation/link/link.component.scss
  • tedi/components/overlay/popover/popover-trigger/popover-trigger.directive.ts
  • tedi/components/overlay/popover/popover.component.scss
  • tedi/components/overlay/popover/popover.component.ts
  • tedi/services/translation/translations.ts
💤 Files with no reviewable changes (1)
  • tedi/components/layout/header/header-login/header-login.component.scss

Comment thread tedi/components/layout/header/header-language/header-language.component.scss Outdated
Comment thread tedi/components/layout/header/header-login/header-login.component.html Outdated
Comment on lines +54 to +92
/**
* Custom label text for the profile button. When provided, used as-is — not
* translated. When omitted or empty, the desktop trigger renders as an
* icon-only button (no visible label) and the mobile trigger falls back to
* the `header.profile` translation key.
*/
label = input("");
/**
* Defines the breakpoint from which the profile menu is displayed as a popover.
* Below this breakpoint, it is rendered as a modal.
*
* @default 'lg'
*/
showPopover = input<Breakpoint>("lg");

/**
* Removes default item styles from the mobile modal content. When `true`,
* projected children render without the padding, border, or background that
* `<tedi-header-profile>` normally applies to each direct child of the
* modal. Use when the content requires custom item styling.
*
* Only affects the mobile/modal branch. The desktop popover uses
* `<tedi-popover-content>`'s own styling and is unaffected.
*
* @default false
*/
noStyle = input<boolean>(false);

/**
* Visual size of the profile button.
* - `'small'` renders a compact icon-and-caption button (the
* `<tedi-header-mobile-button>` variant).
* - `'default'` renders a button with the user's name + icon.
*
* When left unset, the size is chosen automatically based on the viewport:
* `'small'` below the `md` breakpoint, `'default'` from `md` up. Pass an
* explicit value to force a variant regardless of viewport.
*/
size = input<HeaderProfileSize>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Document the breaking changes in the public API.

The component's input API has significantly changed:

  • Removed: name input
  • Removed: showDropdown input
  • Added: label, showPopover, noStyle, size inputs

These are breaking changes for consumers. Ensure they are documented in release notes or migration guides.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tedi/components/layout/header/header-profile/header-profile.component.ts`
around lines 54 - 92, Update the public changelog and migration guide to call
out the input API changes for HeaderProfileComponent: state that the inputs name
and showDropdown were removed and list the new inputs label, showPopover,
noStyle, and size (reference the component inputs label, showPopover, noStyle,
size in header-profile.component.ts); provide a short migration snippet and
recommended replacements (e.g., replace name with label or elsewhere, replace
showDropdown logic with showPopover/modal behavior) and mark this as a breaking
change in the release notes with the component name and version bump.

Comment on lines 39 to 55
export type Representative = {
/**
* Stable identifier. Required for selection comparison and for re-syncing
* the current representative when the `representatives` input changes.
*/
id: string;
name: string;
icon?: string;
/**
* Icon shown next to the representative. Accepts either a Material Icon name
* as a string (`'person'`) for the common case, or a full
* `RepresentativeIcon` object (`{ name: 'person', size: 18 }`) when an
* explicit size is needed. The two forms are equivalent when only `name` is
* relevant.
*/
icon?: string | RepresentativeIcon;
description?: string;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Breaking change: Representative.id is now required.

The Representative type now requires an id field. This is a breaking change for consumers who may be passing representative objects without an id. Ensure this change is documented in release notes or migration guides.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tedi/components/layout/header/header-role/header-role.component.ts` around
lines 39 - 55, Representative.id was made required which breaks callers; update
documentation and add a runtime compatibility fix: document the breaking change
in release notes/migration guide and, in the component that consumes the type
(e.g., HeaderRoleComponent handling the representatives input and selection
logic such as the code paths that sync selection or call
setSelectedRepresentative), detect missing id values and synthesize a stable id
(for example from name or index or a UUID) before using them for
comparison/selection, or revert the type to keep id optional until consumers are
migrated. Ensure references to the Representative type and its id field are
updated accordingly.

Comment thread tedi/components/layout/header/header.stories.ts
Comment thread tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.scss Outdated
Comment thread tedi/components/overlay/popover/popover.component.scss Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tedi/components/layout/header/header-login/header-login.component.spec.ts`:
- Around line 40-45: The test setup for HeaderLoginComponent configures a mocked
TediTranslationService but omits providing TEDI_TRANSLATION_DEFAULT_TOKEN; add a
provider entry { provide: TEDI_TRANSLATION_DEFAULT_TOKEN, useValue: <appropriate
default> } to the TestBed.configureTestingModule providers alongside the mocked
TediTranslationService (and ensure the token import is present) so the
translated component test follows repo conventions.

In `@tedi/components/layout/header/header-role/header-role.component.ts`:
- Line 2: The import of the private CDK symbol `_IdGenerator` must be removed
and replaced with a stable ID generation approach: remove `_IdGenerator` usage
in header-role.component.ts and implement either a simple module-level counter
or a small injectable service (e.g., HeaderIdService or a file-level counter
like nextHeaderRoleId) to produce deterministic IDs for the component; update
any usages of `_IdGenerator` in the component class (e.g., constructor or
methods that call it) to call your new generator service/function and ensure IDs
remain unique across instances.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8bf5ec04-87ff-45e1-affd-b2ec9076cdd8

📥 Commits

Reviewing files that changed from the base of the PR and between b52d923 and 4fdbfbc.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • package.json
  • tedi/components/layout/header/header-language/header-language.component.html
  • tedi/components/layout/header/header-language/header-language.component.scss
  • tedi/components/layout/header/header-language/header-language.component.spec.ts
  • tedi/components/layout/header/header-login/header-login.component.html
  • tedi/components/layout/header/header-login/header-login.component.spec.ts
  • tedi/components/layout/header/header-login/header-login.component.ts
  • tedi/components/layout/header/header-logout/header-logout.component.spec.ts
  • tedi/components/layout/header/header-logout/header-logout.component.ts
  • tedi/components/layout/header/header-profile/header-profile.component.html
  • tedi/components/layout/header/header-profile/header-profile.component.scss
  • tedi/components/layout/header/header-profile/header-profile.component.spec.ts
  • tedi/components/layout/header/header-profile/header-profile.component.ts
  • tedi/components/layout/header/header-role/header-role.component.html
  • tedi/components/layout/header/header-role/header-role.component.scss
  • tedi/components/layout/header/header-role/header-role.component.spec.ts
  • tedi/components/layout/header/header-role/header-role.component.ts
  • tedi/components/layout/header/header-search/header-search.component.spec.ts
  • tedi/components/layout/header/header-search/header-search.component.ts
  • tedi/components/layout/header/header.component.scss
  • tedi/components/layout/header/header.stories.ts
  • tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.html
  • tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.scss
  • tedi/components/overlay/popover/popover.component.scss
  • tedi/components/overlay/popover/popover.component.ts
  • tedi/services/translation/translations.ts
✅ Files skipped from review due to trivial changes (5)
  • package.json
  • tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.scss
  • tedi/components/layout/sidenav/sidenav-toggle/sidenav-toggle.component.html
  • tedi/components/layout/header/header.component.scss
  • tedi/components/layout/header/header-language/header-language.component.html
🚧 Files skipped from review as they are similar to previous changes (15)
  • tedi/components/layout/header/header-profile/header-profile.component.spec.ts
  • tedi/components/layout/header/header-language/header-language.component.spec.ts
  • tedi/components/layout/header/header-language/header-language.component.scss
  • tedi/components/overlay/popover/popover.component.scss
  • tedi/components/layout/header/header-login/header-login.component.ts
  • tedi/components/layout/header/header-search/header-search.component.spec.ts
  • tedi/components/layout/header/header-logout/header-logout.component.spec.ts
  • tedi/services/translation/translations.ts
  • tedi/components/layout/header/header-role/header-role.component.spec.ts
  • tedi/components/layout/header/header-logout/header-logout.component.ts
  • tedi/components/layout/header/header-role/header-role.component.html
  • tedi/components/layout/header/header-search/header-search.component.ts
  • tedi/components/layout/header/header-profile/header-profile.component.scss
  • tedi/components/layout/header/header-role/header-role.component.scss
  • tedi/components/layout/header/header.stories.ts

Comment thread tedi/components/layout/header/header-role/header-role.component.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant