+ [class.col-7]="inverted && isVertical"
+ [class.col-md-8]="inverted && isVertical"
+ [class.col-lg-9]="inverted && isVertical"
+ [class.col-8]="!inverted && isVertical"
+ [class.col-md-9]="!inverted && isVertical">
@if (tabs) {
@for (tab of tabs; track tab.id) {
diff --git a/projects/design-angular-kit/src/lib/components/core/tab/tab-container/tab-container.component.spec.ts b/projects/design-angular-kit/src/lib/components/core/tab/tab-container/tab-container.component.spec.ts
index cf1591117..756bf6756 100644
--- a/projects/design-angular-kit/src/lib/components/core/tab/tab-container/tab-container.component.spec.ts
+++ b/projects/design-angular-kit/src/lib/components/core/tab/tab-container/tab-container.component.spec.ts
@@ -18,4 +18,43 @@ describe('ItTabContainerComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
+
+ describe('isVertical', () => {
+ it('should return false when vertical is not set', () => {
+ expect(component.isVertical).toBe(false);
+ });
+
+ it('should return true when vertical is set without breakpoint', () => {
+ component.vertical = true;
+ expect(component.isVertical).toBe(true);
+ });
+
+ it('should accept verticalBreakpoint input', () => {
+ component.vertical = true;
+ component.verticalBreakpoint = 'md';
+ expect(component.verticalBreakpoint).toBe('md');
+ });
+
+ it('should be responsive when vertical and verticalBreakpoint are both set', () => {
+ component.vertical = true;
+ component.verticalBreakpoint = 'md';
+ // After ngAfterViewInit, isVertical should reflect the media query state
+ component.ngAfterViewInit();
+ // The value depends on the test viewport, but the getter should not throw
+ expect(typeof component.isVertical).toBe('boolean');
+ });
+ });
+
+ describe('cleanup', () => {
+ it('should not throw on destroy with responsive breakpoint', () => {
+ component.vertical = true;
+ component.verticalBreakpoint = 'lg';
+ component.ngAfterViewInit();
+ expect(() => component.ngOnDestroy()).not.toThrow();
+ });
+
+ it('should not throw on destroy without responsive breakpoint', () => {
+ expect(() => component.ngOnDestroy()).not.toThrow();
+ });
+ });
});
diff --git a/projects/design-angular-kit/src/lib/components/core/tab/tab-container/tab-container.component.ts b/projects/design-angular-kit/src/lib/components/core/tab/tab-container/tab-container.component.ts
index 610bb5591..301adf394 100644
--- a/projects/design-angular-kit/src/lib/components/core/tab/tab-container/tab-container.component.ts
+++ b/projects/design-angular-kit/src/lib/components/core/tab/tab-container/tab-container.component.ts
@@ -19,6 +19,17 @@ import { NgTemplateOutlet } from '@angular/common';
import { ItIconComponent } from '../../../utils/icon/icon.component';
import { inputToBoolean } from '../../../../utils/coercion';
+/** Bootstrap Italia breakpoint names and their minimum widths in px. */
+export type BootstrapBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
+
+const BREAKPOINT_PX: Record
= {
+ sm: 576,
+ md: 768,
+ lg: 992,
+ xl: 1200,
+ xxl: 1400,
+};
+
@Component({
selector: 'it-tab-container',
templateUrl: './tab-container.component.html',
@@ -48,10 +59,26 @@ export class ItTabContainerComponent extends ItAbstractComponent implements OnDe
@Input({ transform: inputToBoolean }) cards?: boolean;
/**
- * Show vertical navigation
+ * Show vertical navigation.
+ * When used together with `verticalBreakpoint`, this acts as the initial state
+ * and becomes responsive — vertical above the breakpoint, horizontal below it.
*/
@Input({ transform: inputToBoolean }) vertical?: boolean;
+ /**
+ * When set, the tab container switches from vertical to horizontal layout
+ * below the specified Bootstrap breakpoint.
+ * Requires `vertical` to be set to `true`.
+ *
+ * @example
+ * ```html
+ *
+ *
+ *
+ * ```
+ */
+ @Input() verticalBreakpoint?: BootstrapBreakpoint;
+
/**
* The tab position
*/
@@ -75,7 +102,18 @@ export class ItTabContainerComponent extends ItAbstractComponent implements OnDe
@Output() tabAdded = new EventEmitter();
+ /** Computed vertical state (respects responsive breakpoint). */
+ get isVertical(): boolean {
+ if (this.verticalBreakpoint && this.vertical) {
+ return this._isVerticalResponsive;
+ }
+ return !!this.vertical;
+ }
+
private tabSubscriptions?: Array;
+ private _mediaQueryList?: MediaQueryList;
+ private _mediaHandler?: (e: MediaQueryListEvent) => void;
+ private _isVerticalResponsive = true;
constructor() {
super();
@@ -84,6 +122,8 @@ export class ItTabContainerComponent extends ItAbstractComponent implements OnDe
override ngAfterViewInit(): void {
super.ngAfterViewInit();
+ this._setupResponsiveBreakpoint();
+
this.tabs?.changes
.pipe(
// When tabs changes (dynamic add/remove)
@@ -119,6 +159,7 @@ export class ItTabContainerComponent extends ItAbstractComponent implements OnDe
ngOnDestroy(): void {
this.tabSubscriptions?.forEach(sub => sub.unsubscribe());
+ this._teardownResponsiveBreakpoint();
}
onTab(tab: ItTabItemComponent) {
@@ -133,4 +174,32 @@ export class ItTabContainerComponent extends ItAbstractComponent implements OnDe
$event.preventDefault();
this.tabAdded.emit();
}
+
+ private _setupResponsiveBreakpoint(): void {
+ if (!this.verticalBreakpoint || !this.vertical) {
+ return;
+ }
+
+ const minWidth = BREAKPOINT_PX[this.verticalBreakpoint];
+ if (typeof window === 'undefined' || !window.matchMedia) {
+ return;
+ }
+
+ this._mediaQueryList = window.matchMedia(`(min-width: ${minWidth}px)`);
+ this._isVerticalResponsive = this._mediaQueryList.matches;
+
+ this._mediaHandler = (e: MediaQueryListEvent) => {
+ this._isVerticalResponsive = e.matches;
+ this._changeDetectorRef.detectChanges();
+ };
+ this._mediaQueryList.addEventListener('change', this._mediaHandler);
+ }
+
+ private _teardownResponsiveBreakpoint(): void {
+ if (this._mediaQueryList && this._mediaHandler) {
+ this._mediaQueryList.removeEventListener('change', this._mediaHandler);
+ this._mediaQueryList = undefined;
+ this._mediaHandler = undefined;
+ }
+ }
}
diff --git a/src/app/tabs/tabs-examples/tabs-examples.component.tpl b/src/app/tabs/tabs-examples/tabs-examples.component.tpl
index ea4a70f76..cb2399764 100644
--- a/src/app/tabs/tabs-examples/tabs-examples.component.tpl
+++ b/src/app/tabs/tabs-examples/tabs-examples.component.tpl
@@ -25,3 +25,16 @@
+
+
+{% set htmlResponsive %}
+ {% include "../tabs-responsive-example/tabs-responsive-example.component.html" %}
+{% endset %}
+
+{% set typescriptResponsive %}
+ {% include "../tabs-responsive-example/tabs-responsive-example.component.ts" %}
+{% endset %}
+
+
+
+
diff --git a/src/app/tabs/tabs-responsive-example/tabs-responsive-example.component.html b/src/app/tabs/tabs-responsive-example/tabs-responsive-example.component.html
new file mode 100644
index 000000000..0a64cb578
--- /dev/null
+++ b/src/app/tabs/tabs-responsive-example/tabs-responsive-example.component.html
@@ -0,0 +1,26 @@
+Vertical responsive (breakpoint md)
+Le tab diventano verticali sopra il breakpoint md (768px) e orizzontali sotto.
+
+
+
+ Contenuto del primo tab — verticale sopra md, orizzontale sotto.
+
+
+ Contenuto del secondo tab.
+
+
+ Contenuto del terzo tab.
+
+
+
+Vertical responsive (breakpoint lg)
+Breakpoint lg (992px): verticale sopra, orizzontale sotto.
+
+
+
+ Layout verticale sopra lg.
+
+
+ Layout orizzontale sotto lg.
+
+
diff --git a/src/app/tabs/tabs-responsive-example/tabs-responsive-example.component.ts b/src/app/tabs/tabs-responsive-example/tabs-responsive-example.component.ts
new file mode 100644
index 000000000..f7fec5b60
--- /dev/null
+++ b/src/app/tabs/tabs-responsive-example/tabs-responsive-example.component.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'it-tabs-responsive-example',
+ templateUrl: './tabs-responsive-example.component.html',
+ standalone: false,
+})
+export class TabsResponsiveExampleComponent {}
diff --git a/src/app/tabs/tabs.module.ts b/src/app/tabs/tabs.module.ts
index 823407357..f2afe2ea6 100644
--- a/src/app/tabs/tabs.module.ts
+++ b/src/app/tabs/tabs.module.ts
@@ -9,9 +9,16 @@ import { TabsExampleComponent } from './tabs-example/tabs-example.component';
import { TabsExamplesComponent } from './tabs-examples/tabs-examples.component';
import { TabsIndexComponent } from './tabs-index/tabs-index.component';
import { TabsDynamicExampleComponent } from './tabs-dynamic-example/tabs-dynamic-example.component';
+import { TabsResponsiveExampleComponent } from './tabs-responsive-example/tabs-responsive-example.component';
@NgModule({
imports: [CommonModule, FormsModule, ReactiveFormsModule, SharedModule, TabsRoutingModule],
- declarations: [TabsExampleComponent, TabsExamplesComponent, TabsIndexComponent, TabsDynamicExampleComponent],
+ declarations: [
+ TabsExampleComponent,
+ TabsExamplesComponent,
+ TabsIndexComponent,
+ TabsDynamicExampleComponent,
+ TabsResponsiveExampleComponent,
+ ],
})
export class TabsModule {}