File

src/components/sq-tabs/sq-tabs.component.ts

Description

Represents a tab container component for managing a collection of tabs.

Look the link about the component in original framework and the appearance

See https://css.squidit.com.br/components/tabs

Example :
<sq-tabs [lineStyle]="true" (tabChange)="handleTabChange($event)">
  <sq-tab [title]="'Tab 1'" (whenOpen)="handleTabOpen()">Tab 1 Content</sq-tab>
  <sq-tab [title]="'Tab 2'">Tab 2 Content</sq-tab>
  <!-- Add more sq-tab elements as needed -->
</sq-tabs>

Implements

AfterViewInit AfterViewChecked OnDestroy

Metadata

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(cdr: ChangeDetectorRef)

Creates an instance of SqTabsComponent.

Parameters :
Name Type Optional Description
cdr ChangeDetectorRef No
  • The ChangeDetectorRef service for manual change detection control.

Inputs

customClass
Type : string
Default value : ''

Custom CSS class for the input element.

height
Type : string

The height of the tab container.

hideHtmlForInactives
Type : boolean
Default value : false

Flag to hide HTML content for inactive tabs.

lineStyle
Type : boolean
Default value : false

Flag to indicate whether to display a line-style indicator for the selected tab.

margin
Type : string
Default value : '0 auto'

The margin of the tab container.

maxWidth
Type : string
Default value : 'initial'

The maximum width of the tab container.

sm
Type : boolean
Default value : true

Flag to indicate to use small size for tabs header.

tabWidth
Type : string
Default value : ''

The width of individual tabs.

Outputs

tabChange
Type : EventEmitter<literal type>

Event emitted when a tab is changed.

Methods

ngAfterViewChecked
ngAfterViewChecked()

Angular lifecycle hook called after the view has been checked. Updates the total tab count if it has changed.

Returns : void
ngAfterViewInit
ngAfterViewInit()

Angular lifecycle hook called after component's view has been initialized. Sets up initial tab selection and subscriptions.

Returns : void
ngOnDestroy
ngOnDestroy()

Angular lifecycle hook called when the component is destroyed. Cleans up subscriptions to prevent memory leaks.

Returns : void
selectTab
selectTab(tab: SqTabComponent, index: number)

Selects a tab by making it active.

Parameters :
Name Type Optional Description
tab SqTabComponent No
  • The tab to be selected.
index number No
  • The index of the selected tab.
Private setupTabsSubscription
setupTabsSubscription()

Sets up subscription to track changes in the tabs QueryList. Updates the total tab count when tabs are added/removed.

Returns : void

Properties

Private destroy$
Default value : new Subject<void>()

Subject used to manage component lifecycle and unsubscribe from observables.

memoizedTabWidth
Default value : useMemo((tabWidth: string, lineStyle: boolean): string => { if (tabWidth) { return tabWidth; } if (lineStyle) { return 'fit-content'; } return 'initial'; })

Memoized function to determine tab width based on conditions.

Example :
                the provided tabWidth if it exists,
                otherwise returns 'initial'.
Parameters :
Name Description
tabWidth
  • The width of the tab.
lineStyle
  • A flag to determine if line style is applied.
tabs
Type : QueryList<SqTabComponent>
Default value : [] as unknown as QueryList<SqTabComponent>
Decorators :
@ContentChildren(SqTabComponent)

A query list of SqTabComponent elements representing the tabs.

total
Type : number
Default value : 1

The total number of tabs in the container.

import {
  AfterViewChecked,
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChildren,
  EventEmitter,
  Input,
  OnDestroy,
  Output,
  QueryList,
} from '@angular/core';
import { NgClass, NgStyle } from '@angular/common';
import { useMemo } from '../../helpers/memo.helper';
import { SqTabComponent } from './sq-tab/sq-tab.component';
import { Subject, takeUntil } from 'rxjs';
import { SqLoaderComponent } from '../sq-loader/sq-loader.component';
import { UniversalSafePipe } from '../../pipes/universal-safe/universal-safe.pipe';

/**
 * Represents a tab container component for managing a collection of tabs.
 *
 * Look the link about the component in original framework and the appearance
 *
 * @see {@link https://css.squidit.com.br/components/tabs}
 *
 * @example
 * <sq-tabs [lineStyle]="true" (tabChange)="handleTabChange($event)">
 *   <sq-tab [title]="'Tab 1'" (whenOpen)="handleTabOpen()">Tab 1 Content</sq-tab>
 *   <sq-tab [title]="'Tab 2'">Tab 2 Content</sq-tab>
 *   <!-- Add more sq-tab elements as needed -->
 * </sq-tabs>
 *
 * @implements {AfterViewInit}
 * @implements {AfterViewChecked}
 */
@Component({
  selector: 'sq-tabs',
  templateUrl: './sq-tabs.component.html',
  styleUrls: ['./sq-tabs.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  imports: [NgClass, NgStyle, SqLoaderComponent, UniversalSafePipe],
})
export class SqTabsComponent implements AfterViewInit, AfterViewChecked, OnDestroy {
  /**
   * A query list of `SqTabComponent` elements representing the tabs.
   */
  @ContentChildren(SqTabComponent) tabs: QueryList<SqTabComponent> = [] as unknown as QueryList<SqTabComponent>;

  /**
   * Custom CSS class for the input element.
   */
  @Input() customClass = '';

  /**
   * The height of the tab container.
   * @default undefined
   */
  @Input() height?: string;

  /**
   * The maximum width of the tab container.
   * @default 'initial'
   */
  @Input() maxWidth = 'initial';

  /**
   * The margin of the tab container.
   * @default '0 auto'
   */
  @Input() margin = '0 auto';

  /**
   * Flag to indicate whether to display a line-style indicator for the selected tab.
   * @default false
   */
  @Input() lineStyle = false;

  /**
   * The width of individual tabs.
   * @default ''
   */
  @Input() tabWidth = '';

  /**
   * Flag to indicate to use small size for tabs header.
   * @default true
   */
  @Input() sm = true;

  /**
   * Flag to hide HTML content for inactive tabs.
   * @default false
   */
  @Input() hideHtmlForInactives = false;

  /**
   * Event emitted when a tab is changed.
   * @eventProperty
   */
  @Output() tabChange: EventEmitter<{ tab: SqTabComponent; index: number }> = new EventEmitter();

  /**
   * The total number of tabs in the container.
   */
  total = 1;

  /**
   * Subject used to manage component lifecycle and unsubscribe from observables.
   * @private
   */
  private destroy$ = new Subject<void>();

  /**
   * Creates an instance of SqTabsComponent.
   * @param cdr - The ChangeDetectorRef service for manual change detection control.
   */
  constructor(private cdr: ChangeDetectorRef) {}

  /**
   * Angular lifecycle hook called after component's view has been initialized.
   * Sets up initial tab selection and subscriptions.
   */
  ngAfterViewInit() {
    this.setupTabsSubscription();

    const activeTab = {
      tab: this.tabs.find((tab: { active: any }) => tab.active),
      index: this.tabs.toArray().findIndex((tab: { active: any }) => tab.active),
    };

    /**
     * setTimeout sem delay para:
     *  - Colocar a execução no final da fila de eventos (microtask queue)
     *  - Evitar ExpressionChangedAfterItHasBeenCheckedError
     *  - Manter tempo de resposta instantâneo (sem delay artificial)
     */
    setTimeout(() => {
      if (activeTab.tab?.title) {
        this.selectTab(activeTab.tab, activeTab.index);
      } else if (this.tabs.first) {
        this.selectTab(this.tabs.first, 0);
      }

      this.total = this.tabs.toArray().length || 1;
      this.cdr.markForCheck();
    });
  }

  /**
   * Angular lifecycle hook called after the view has been checked.
   * Updates the total tab count if it has changed.
   */
  ngAfterViewChecked(): void {
    if (this.tabs.toArray().length !== this.total) {
      this.total = this.tabs.toArray().length || 1;
      this.cdr.markForCheck();
    }
  }

  /**
   * Angular lifecycle hook called when the component is destroyed.
   * Cleans up subscriptions to prevent memory leaks.
   */
  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  /**
   * Selects a tab by making it active.
   *
   * @param {SqTabComponent} tab - The tab to be selected.
   * @param {number} index - The index of the selected tab.
   * @returns {null} - Always returns null (no explicit return value).
   */
  selectTab(tab: SqTabComponent, index: number): null {
    if (tab?.disabled || tab?.loading) {
      return null;
    }

    this.tabs.toArray().forEach((tabItem: { active: boolean; hideHtml: boolean }) => {
      tabItem.active = false;
      if (this.hideHtmlForInactives) {
        tabItem.hideHtml = true;
      }
    });

    if (tab) {
      tab.active = true;
      tab.hideHtml = false;

      this.tabChange.emit({ tab, index });

      if (tab.whenOpen) {
        tab.whenOpen.emit();
      }
    }

    this.cdr.markForCheck();
    return null;
  }

  /**
   * Memoized function to determine tab width based on conditions.
   *
   * @param {string} tabWidth - The width of the tab.
   * @param {boolean} lineStyle - A flag to determine if line style is applied.
   * @returns {string} - Returns 'fit-content' if lineStyle is true,
   *                     the provided tabWidth if it exists,
   *                     otherwise returns 'initial'.
   */
  memoizedTabWidth = useMemo((tabWidth: string, lineStyle: boolean): string => {
    if (tabWidth) {
      return tabWidth;
    }
    if (lineStyle) {
      return 'fit-content';
    }
    return 'initial';
  });

  /**
   * Sets up subscription to track changes in the tabs QueryList.
   * Updates the total tab count when tabs are added/removed.
   * @private
   */
  private setupTabsSubscription(): void {
    this.tabs.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
      this.total = this.tabs.toArray().length || 1;
      this.cdr.markForCheck();
    });
  }
}
<div class="tabs-squid">
  <div class="tabs-container">
    <div
      class="wrapper {{ customClass }}"
      [ngStyle]="{
        width: memoizedTabWidth(tabWidth, lineStyle),
      }"
    >
      <ul
        class="tabs-header tabs-center tabs-width"
        #tabsHeaderContainer
        [ngClass]="{
          'line-style': lineStyle,
        }"
        [ngStyle]="{
          'max-width': maxWidth,
          margin: margin,
        }"
      >
        @for (tab of tabs; track tab; let i = $index) {
          <li
            (click)="!tab?.disabled ? selectTab(tab, i) : null"
            [ngClass]="{
              disabled: !tab?.title || tab?.disabled,
              loading: tab?.title && tab.loading,
              active: tab?.active,
              sm: sm,
            }"
            [ngStyle]="{
              background: tab?.active && !lineStyle ? tab?.color : null,
            }"
            #thisTab
          >
            @if (!tab.loading) {
              <span
                [ngStyle]="{
                  color: tab?.active && !lineStyle ? tab?.textColor : null,
                }"
                [innerHtml]="tab?.title || '' | universalSafe"
              ></span>
            }
            @if (tab.loading) {
              <sq-loader></sq-loader>
            }
          </li>
        }
      </ul>
    </div>
  </div>
  <ng-content></ng-content>
</div>

./sq-tabs.component.scss

:host {
  display: block;
  width: 100%;
  .wrapper {
    position: relative;
    justify-content: center;
    display: flex;
    align-items: center;
  }
  .tabs-header {
    position: relative;
    scroll-behavior: smooth;
    background: var(--transparent);
    li {
      min-width: min-content;
    }
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""