# Topbar

- Entry point: `@koobiq/components/top-bar`
- NgModule: `KbqTopBarModule`
- Page: https://koobiq.io/en/components/top-bar/overview

## Overview

Topbar is a toolbar that always remains visible on the page and provides quick access to navigation and controls.

**Example: TopBar** (`top-bar-overview`)

```ts top-bar-overview-example.ts
import { BreakpointObserver } from '@angular/cdk/layout';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { KbqButtonColor, KbqButtonModule, KbqButtonStyleInput, KbqButtonStyles } from '@koobiq/components/button';
import { KbqComponentColors, PopUpPlacements } from '@koobiq/components/core';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { KbqTopBarModule } from '@koobiq/components/top-bar';
import { map } from 'rxjs/operators';

type ExampleAction = {
    id: string;
    icon?: string;
    text?: string;
    action?: () => void;
    style: KbqButtonStyleInput;
    color: KbqButtonColor;
};

@Component({
    selector: 'top-bar-overview-example',
    imports: [
        KbqTopBarModule,
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule,
        KbqDropdownModule,
        KbqOverflowItemsModule
    ],
    template: `
        <kbq-top-bar>
            <div
                class="layout-row layout-align-center-center layout-padding-top-3xs layout-padding-bottom-3xs"
                kbqTopBarContainer
                placement="start"
            >
                <div class="layout-row layout-margin-right-l flex-none">
                    <img alt="example icon" src="https://koobiq.io/assets/example-icon.svg" width="24" height="24" />
                </div>
                <div class="kbq-title kbq-truncate-line">Dashboards</div>
            </div>

            <div kbqTopBarSpacer></div>

            <div kbqTopBarContainer placement="end">
                <div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
                    @for (action of actions; track action.id) {
                        <button
                            kbq-button
                            [kbqOverflowItem]="action.id"
                            [kbqStyle]="action.style"
                            [color]="action.color"
                            [kbqPlacement]="PopUpPlacements.Bottom"
                            [kbqTooltipArrow]="false"
                            [kbqTooltipDisabled]="isDesktop()"
                            [kbqTooltip]="action.text || action.id"
                        >
                            @if (action.icon) {
                                <i kbq-icon="" [class]="action.icon"></i>
                            }
                            @if ((action.text && isDesktop()) || (!action.icon && action.text)) {
                                {{ action.text }}
                            }
                        </button>
                    }

                    <div kbqOverflowItemsResult>
                        <button
                            kbq-button
                            aria-label="More"
                            [kbqStyle]="KbqButtonStyles.Transparent"
                            [color]="KbqComponentColors.Contrast"
                            [kbqDropdownTriggerFor]="appDropdown"
                        >
                            <i kbq-icon="kbq-ellipsis-horizontal_16"></i>
                        </button>

                        <kbq-dropdown #appDropdown="kbqDropdown">
                            @for (action of actions; track action.id) {
                                @if (kbqOverflowItems.hiddenItemIDs().has(action.id)) {
                                    <button kbq-dropdown-item>
                                        {{ action.text || action.id }}
                                    </button>
                                }
                            }
                        </kbq-dropdown>
                    </div>
                </div>
            </div>
        </kbq-top-bar>
    `,
    styles: `
        :host {
            .kbq-top-bar-container_start {
                --kbq-top-bar-container-start-min-width: 115px;
            }
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TopBarOverviewExample {
    readonly isDesktop = toSignal(
        inject(BreakpointObserver)
            .observe('(min-width: 900px)')
            .pipe(
                takeUntilDestroyed(),
                map(({ matches }) => matches)
            ),
        { initialValue: true }
    );

    protected readonly actions: ExampleAction[] = [
        {
            id: '1',
            color: KbqComponentColors.Contrast,
            style: KbqButtonStyles.Transparent,
            icon: 'kbq-list_16',
            text: 'List'
        },
        {
            id: '2',
            color: KbqComponentColors.Contrast,
            style: KbqButtonStyles.Transparent,
            icon: 'kbq-filter_16',
            text: 'Filter'
        },
        {
            id: '3',
            color: KbqComponentColors.Contrast,
            style: '',
            icon: 'kbq-plus_16',
            text: 'Create dashboard'
        }
    ];

    protected readonly KbqComponentColors = KbqComponentColors;
    protected readonly KbqButtonStyles = KbqButtonStyles;
    protected readonly PopUpPlacements = PopUpPlacements;
}
```

Depending on the interface requirements, it may include a logo, a title, breadcrumbs, action buttons, and other elements.

### Page title

This content option is suitable for initial screens when there is no need to display the navigation path.

**Example: TopBar** (`top-bar-overview`)

```ts top-bar-overview-example.ts
import { BreakpointObserver } from '@angular/cdk/layout';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { KbqButtonColor, KbqButtonModule, KbqButtonStyleInput, KbqButtonStyles } from '@koobiq/components/button';
import { KbqComponentColors, PopUpPlacements } from '@koobiq/components/core';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { KbqTopBarModule } from '@koobiq/components/top-bar';
import { map } from 'rxjs/operators';

type ExampleAction = {
    id: string;
    icon?: string;
    text?: string;
    action?: () => void;
    style: KbqButtonStyleInput;
    color: KbqButtonColor;
};

@Component({
    selector: 'top-bar-overview-example',
    imports: [
        KbqTopBarModule,
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule,
        KbqDropdownModule,
        KbqOverflowItemsModule
    ],
    template: `
        <kbq-top-bar>
            <div
                class="layout-row layout-align-center-center layout-padding-top-3xs layout-padding-bottom-3xs"
                kbqTopBarContainer
                placement="start"
            >
                <div class="layout-row layout-margin-right-l flex-none">
                    <img alt="example icon" src="https://koobiq.io/assets/example-icon.svg" width="24" height="24" />
                </div>
                <div class="kbq-title kbq-truncate-line">Dashboards</div>
            </div>

            <div kbqTopBarSpacer></div>

            <div kbqTopBarContainer placement="end">
                <div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
                    @for (action of actions; track action.id) {
                        <button
                            kbq-button
                            [kbqOverflowItem]="action.id"
                            [kbqStyle]="action.style"
                            [color]="action.color"
                            [kbqPlacement]="PopUpPlacements.Bottom"
                            [kbqTooltipArrow]="false"
                            [kbqTooltipDisabled]="isDesktop()"
                            [kbqTooltip]="action.text || action.id"
                        >
                            @if (action.icon) {
                                <i kbq-icon="" [class]="action.icon"></i>
                            }
                            @if ((action.text && isDesktop()) || (!action.icon && action.text)) {
                                {{ action.text }}
                            }
                        </button>
                    }

                    <div kbqOverflowItemsResult>
                        <button
                            kbq-button
                            aria-label="More"
                            [kbqStyle]="KbqButtonStyles.Transparent"
                            [color]="KbqComponentColors.Contrast"
                            [kbqDropdownTriggerFor]="appDropdown"
                        >
                            <i kbq-icon="kbq-ellipsis-horizontal_16"></i>
                        </button>

                        <kbq-dropdown #appDropdown="kbqDropdown">
                            @for (action of actions; track action.id) {
                                @if (kbqOverflowItems.hiddenItemIDs().has(action.id)) {
                                    <button kbq-dropdown-item>
                                        {{ action.text || action.id }}
                                    </button>
                                }
                            }
                        </kbq-dropdown>
                    </div>
                </div>
            </div>
        </kbq-top-bar>
    `,
    styles: `
        :host {
            .kbq-top-bar-container_start {
                --kbq-top-bar-container-start-min-width: 115px;
            }
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TopBarOverviewExample {
    readonly isDesktop = toSignal(
        inject(BreakpointObserver)
            .observe('(min-width: 900px)')
            .pipe(
                takeUntilDestroyed(),
                map(({ matches }) => matches)
            ),
        { initialValue: true }
    );

    protected readonly actions: ExampleAction[] = [
        {
            id: '1',
            color: KbqComponentColors.Contrast,
            style: KbqButtonStyles.Transparent,
            icon: 'kbq-list_16',
            text: 'List'
        },
        {
            id: '2',
            color: KbqComponentColors.Contrast,
            style: KbqButtonStyles.Transparent,
            icon: 'kbq-filter_16',
            text: 'Filter'
        },
        {
            id: '3',
            color: KbqComponentColors.Contrast,
            style: '',
            icon: 'kbq-plus_16',
            text: 'Create dashboard'
        }
    ];

    protected readonly KbqComponentColors = KbqComponentColors;
    protected readonly KbqButtonStyles = KbqButtonStyles;
    protected readonly PopUpPlacements = PopUpPlacements;
}
```

If you need to display the number of objects on the current page, you can do so by showing a special counter next to the page title:

**Example: TopBar With Title And Counter** (`top-bar-title-counter`)

```ts top-bar-title-counter-example.ts
import { BreakpointObserver } from '@angular/cdk/layout';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { KbqButtonColor, KbqButtonModule, KbqButtonStyleInput, KbqButtonStyles } from '@koobiq/components/button';
import { KbqComponentColors, KbqFormattersModule, PopUpPlacements } from '@koobiq/components/core';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { KbqTopBarModule } from '@koobiq/components/top-bar';
import { map } from 'rxjs/operators';

type ExampleAction = {
    id: string;
    icon?: string;
    text?: string;
    action?: () => void;
    style: KbqButtonStyleInput;
    color: KbqButtonColor;
    alwaysVisible?: boolean;
};

@Component({
    selector: 'top-bar-title-counter-example',
    imports: [
        KbqTopBarModule,
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule,
        KbqDropdownModule,
        KbqOverflowItemsModule,
        KbqFormattersModule
    ],
    template: `
        <kbq-top-bar>
            <div
                class="layout-row layout-align-center-center layout-padding-top-3xs layout-padding-bottom-3xs"
                kbqTopBarContainer
                placement="start"
            >
                <div class="layout-row layout-margin-right-l flex-none">
                    <img alt="example icon" src="https://koobiq.io/assets/example-icon.svg" width="24" height="24" />
                </div>
                <div class="kbq-title kbq-truncate-line example-kbq-top-bar__title">
                    <span class="kbq-truncate-line layout-margin-right-xs">Dashboards</span>

                    <span class="example-kbq-top-bar__counter">{{ 13294 | kbqNumber: '' : 'en-US' }}</span>
                </div>
            </div>

            <div kbqTopBarSpacer></div>

            <div kbqTopBarContainer placement="end">
                <div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
                    @for (action of actions; track action.id) {
                        <button
                            kbq-button
                            [kbqOverflowItem]="action.id"
                            [kbqStyle]="action.style"
                            [color]="action.color"
                            [kbqPlacement]="PopUpPlacements.Bottom"
                            [kbqTooltipArrow]="false"
                            [kbqTooltipDisabled]="isDesktop()"
                            [kbqTooltip]="action.text || action.id.toString()"
                            [alwaysVisible]="action?.alwaysVisible"
                        >
                            @if (action.icon) {
                                <i kbq-icon="" [class]="action.icon"></i>
                            }
                            @if ((action.text && isDesktop()) || (!action.icon && action.text)) {
                                {{ action.text }}
                            }
                        </button>
                    }

                    <div kbqOverflowItemsResult>
                        <button
                            kbq-button
                            aria-label="More"
                            [kbqStyle]="KbqButtonStyles.Transparent"
                            [color]="KbqComponentColors.Contrast"
                            [kbqDropdownTriggerFor]="appDropdown"
                        >
                            <i kbq-icon="kbq-ellipsis-horizontal_16"></i>
                        </button>

                        <kbq-dropdown #appDropdown="kbqDropdown">
                            @for (action of actions; track action.id) {
                                @if (kbqOverflowItems.hiddenItemIDs().has(action.id)) {
                                    <button kbq-dropdown-item>
                                        {{ action.text || action.id }}
                                    </button>
                                }
                            }
                        </kbq-dropdown>
                    </div>
                </div>
            </div>
        </kbq-top-bar>
    `,
    styles: `
        :host {
            .kbq-top-bar-container_start {
                --kbq-top-bar-container-start-min-width: 160px;
            }
        }

        .example-kbq-top-bar__counter {
            color: var(--kbq-foreground-contrast-tertiary);
        }

        .example-kbq-top-bar__title {
            display: inline-flex;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TopBarTitleCounterExample {
    readonly isDesktop = toSignal(
        inject(BreakpointObserver)
            .observe('(min-width: 900px)')
            .pipe(
                takeUntilDestroyed(),
                map(({ matches }) => matches)
            ),
        { initialValue: true }
    );

    protected readonly actions: ExampleAction[] = [
        {
            id: '1',
            color: KbqComponentColors.Contrast,
            style: KbqButtonStyles.Transparent,
            icon: 'kbq-list_16',
            text: 'List'
        },
        {
            id: '2',
            color: KbqComponentColors.Contrast,
            style: KbqButtonStyles.Transparent,
            icon: 'kbq-filter_16',
            text: 'Filter'
        },
        {
            id: '3',
            color: KbqComponentColors.Contrast,
            style: '',
            icon: 'kbq-plus_16',
            text: 'Create dashboard',
            alwaysVisible: true
        }
    ];

    protected readonly KbqComponentColors = KbqComponentColors;
    protected readonly KbqButtonStyles = KbqButtonStyles;
    protected readonly PopUpPlacements = PopUpPlacements;
}
```

### Breadcrumbs

For internal pages of a specific module, using [Breadcrumbs](https://koobiq.io/en/components/breadcrumbs.md) is an excellent option. This helps users navigate the application more easily.

**Example: TopBar Breadcrumbs** (`top-bar-breadcrumbs`)

```ts top-bar-breadcrumbs-example.ts
import { BreakpointObserver } from '@angular/cdk/layout';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { RouterLink } from '@angular/router';
import { kbqBreadcrumbsConfigurationProvider, KbqBreadcrumbsModule } from '@koobiq/components/breadcrumbs';
import { KbqButtonModule, KbqButtonStyles } from '@koobiq/components/button';
import { KbqComponentColors, PopUpPlacements } from '@koobiq/components/core';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { KbqTopBarModule } from '@koobiq/components/top-bar';
import { map } from 'rxjs/operators';

@Component({
    selector: 'top-bar-breadcrumbs-example',
    imports: [
        RouterLink,
        KbqTopBarModule,
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule,
        KbqBreadcrumbsModule,
        KbqDropdownModule,
        KbqOverflowItemsModule
    ],
    template: `
        <kbq-top-bar>
            <div class="layout-align-center-center" kbqTopBarContainer placement="start">
                <div class="layout-row layout-margin-right-m flex-none">
                    <img alt="example icon" src="https://koobiq.io/assets/example-icon.svg" width="24" height="24" />
                </div>
                <nav class="flex" kbq-breadcrumbs size="big">
                    <kbq-breadcrumb-item text="Dashboards" routerLink="./dashboards" />
                    <kbq-breadcrumb-item text="MEIS Dashboard" routerLink="./dashboards/dashboard123" />
                </nav>
            </div>
            <div kbqTopBarSpacer></div>
            <div kbqTopBarContainer placement="end">
                <div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
                    <button
                        kbqOverflowItem="0"
                        kbqTooltip="Filter"
                        kbq-button
                        aria-label="Filter"
                        [kbqStyle]="KbqButtonStyles.Transparent"
                        [color]="KbqComponentColors.Contrast"
                        [kbqPlacement]="PopUpPlacements.Bottom"
                        [kbqTooltipArrow]="false"
                    >
                        <i kbq-icon="kbq-filter_16"></i>
                    </button>

                    <button
                        kbqOverflowItem="1"
                        kbqTooltip="Share"
                        kbq-button
                        [kbqStyle]="KbqButtonStyles.Filled"
                        [color]="KbqComponentColors.ContrastFade"
                        [kbqTooltipDisabled]="isDesktop()"
                        [kbqPlacement]="PopUpPlacements.Bottom"
                        [kbqTooltipArrow]="false"
                    >
                        @if (isDesktop()) {
                            Share
                        } @else {
                            <i kbq-icon="kbq-arrow-up-from-rectangle_16"></i>
                        }
                    </button>

                    <div kbqOverflowItemsResult>
                        <button
                            kbq-button
                            aria-label="More"
                            [kbqStyle]="KbqButtonStyles.Transparent"
                            [color]="KbqComponentColors.Contrast"
                            [kbqDropdownTriggerFor]="appDropdown"
                        >
                            <i kbq-icon="kbq-ellipsis-horizontal_16"></i>
                        </button>

                        <kbq-dropdown #appDropdown="kbqDropdown">
                            @if (kbqOverflowItems.hiddenItemIDs().has('0')) {
                                <button kbq-dropdown-item>Filter</button>
                            }
                            @if (kbqOverflowItems.hiddenItemIDs().has('1')) {
                                <button kbq-dropdown-item>Share</button>
                            }
                        </kbq-dropdown>
                    </div>
                </div>
            </div>
        </kbq-top-bar>
    `,
    styles: `
        :host {
            .kbq-top-bar-container_start {
                --kbq-top-bar-container-start-min-width: 238px;
            }
        }
    `,
    providers: [kbqBreadcrumbsConfigurationProvider({ firstItemNegativeMargin: false })],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TopBarBreadcrumbsExample {
    readonly isDesktop = toSignal(
        inject(BreakpointObserver)
            .observe('(min-width: 900px)')
            .pipe(
                takeUntilDestroyed(),
                map(({ matches }) => matches)
            ),
        { initialValue: true }
    );

    protected readonly KbqComponentColors = KbqComponentColors;
    protected readonly KbqButtonStyles = KbqButtonStyles;
    protected readonly PopUpPlacements = PopUpPlacements;
}
```

### Action buttons

On the right side of the toolbar, there is a dedicated area for placing any actions that need to be displayed on the current page.

We recommend using the following set of actions (from left to right):

- Indicators (e.g., data refresh indicator)
- A group of icon buttons (e.g., filters)
- Frequently used actions as buttons (e.g., "Add...", "Share")
- Additional actions in a dropdown menu, where all secondary actions related to the current page can be placed.

**Example: TopBar Actions** (`top-bar-actions`)

```ts top-bar-actions-example.ts
import { BreakpointObserver } from '@angular/cdk/layout';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { KbqButtonColor, KbqButtonModule, KbqButtonStyleInput, KbqButtonStyles } from '@koobiq/components/button';
import { KbqComponentColors, PopUpPlacements } from '@koobiq/components/core';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { KbqTopBarModule } from '@koobiq/components/top-bar';
import { map } from 'rxjs/operators';

type ExampleAction = {
    id: string;
    icon?: string;
    text?: string;
    action?: () => void;
    style: KbqButtonStyleInput;
    color: KbqButtonColor;
    alwaysVisible?: boolean;
};

@Component({
    selector: 'top-bar-actions-example',
    imports: [
        KbqTopBarModule,
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule,
        KbqDropdownModule,
        KbqOverflowItemsModule
    ],
    template: `
        <kbq-top-bar>
            <div
                class="layout-row layout-align-center-center layout-padding-top-3xs layout-padding-bottom-3xs kbq-title kbq-truncate-line"
                kbqTopBarContainer
                placement="start"
            >
                <span class="kbq-truncate-line">Actions</span>
            </div>

            <div kbqTopBarSpacer></div>

            <div kbqTopBarContainer placement="end">
                <div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
                    @for (action of actions; track action.id) {
                        <button
                            kbq-button
                            [kbqOverflowItem]="action.id"
                            [alwaysVisible]="action?.alwaysVisible"
                            [kbqStyle]="action.style"
                            [color]="action.color"
                            [kbqPlacement]="PopUpPlacements.Bottom"
                            [kbqTooltipArrow]="false"
                            [kbqTooltipDisabled]="canTooltipBeAppied(action)"
                            [kbqTooltip]="action.text || action.id"
                        >
                            @if (action.icon) {
                                <i kbq-icon="" [class]="action.icon"></i>
                            }
                            @if (canTooltipBeAppied(action)) {
                                {{ action.text }}
                            }
                        </button>
                    }

                    <div kbqOverflowItemsResult>
                        <button
                            kbq-button
                            aria-label="More"
                            [kbqStyle]="KbqButtonStyles.Transparent"
                            [color]="KbqComponentColors.Contrast"
                            [kbqDropdownTriggerFor]="appDropdown"
                        >
                            <i kbq-icon="kbq-ellipsis-horizontal_16"></i>
                        </button>

                        <kbq-dropdown #appDropdown="kbqDropdown">
                            @for (action of actions; track action.id) {
                                @if (kbqOverflowItems.hiddenItemIDs().has(action.id)) {
                                    <button kbq-dropdown-item>
                                        {{ action.text || action.id }}
                                    </button>
                                }
                            }
                        </kbq-dropdown>
                    </div>
                </div>
            </div>
        </kbq-top-bar>
    `,
    styles: `
        .kbq-overflow-items {
            max-width: 400px;
        }

        :host {
            .kbq-top-bar-container_start {
                --kbq-top-bar-container-start-min-width: 55px;
            }
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TopBarActionsExample {
    readonly isDesktop = toSignal(
        inject(BreakpointObserver)
            .observe('(min-width: 900px)')
            .pipe(
                takeUntilDestroyed(),
                map(({ matches }) => matches)
            ),
        { initialValue: true }
    );

    readonly actions: ExampleAction[] = [
        {
            id: 'refresh',
            icon: 'kbq-arrow-rotate-right_16',
            text: 'Reload',
            color: KbqComponentColors.Contrast,
            style: KbqButtonStyles.Transparent
        },
        {
            id: 'Search',
            icon: 'kbq-magnifying-glass_16',
            color: KbqComponentColors.Contrast,
            style: KbqButtonStyles.Transparent
        },
        { id: 'List', icon: 'kbq-list_16', color: KbqComponentColors.Contrast, style: KbqButtonStyles.Transparent },
        { id: 'Filter', icon: 'kbq-filter_16', color: KbqComponentColors.Contrast, style: KbqButtonStyles.Transparent },
        { id: 'Action', text: 'Action', color: KbqComponentColors.ContrastFade, style: '' },
        {
            id: 'primary-action',
            text: 'Primary action',
            color: KbqComponentColors.Contrast,
            style: '',
            alwaysVisible: true
        },
        ...Array.from({ length: 3 }, (_, i): ExampleAction => ({
            id: `action${i + 7}`,
            text: `Action ${i + 7}`,
            color: KbqComponentColors.ContrastFade,
            style: ''
        }))
    ];

    // only show tooltip on button icon
    canTooltipBeAppied(action: ExampleAction): boolean {
        return (!!action.text && this.isDesktop()) || (!action.icon && !!action.text);
    }

    protected readonly KbqComponentColors = KbqComponentColors;
    protected readonly KbqButtonStyles = KbqButtonStyles;
    protected readonly PopUpPlacements = PopUpPlacements;
}
```

### Responsive mode

Internal elements can adjust to the toolbar size.

The minimum allowed spacing between the left side and the right side with actions is **80px** and is defined using the CSS variable `--kbq-top-bar-spacer-min-width`.

#### Breadcrumbs variant

When the panel is compressed, the breadcrumbs will adjust as follows:

**Example: Top Bar Breadcrumbs Adaptive** (`top-bar-breadcrumbs-adaptive`)

```ts top-bar-breadcrumbs-adaptive-example.ts
import { BreakpointObserver } from '@angular/cdk/layout';
import { ChangeDetectionStrategy, Component, computed, inject, InjectionToken } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { RouterLink } from '@angular/router';
import { kbqBreadcrumbsConfigurationProvider, KbqBreadcrumbsModule } from '@koobiq/components/breadcrumbs';
import { KbqButtonModule, KbqButtonStyles } from '@koobiq/components/button';
import { KBQ_LOCALE_SERVICE, KbqComponentColors, PopUpPlacements } from '@koobiq/components/core';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { KbqTopBarModule } from '@koobiq/components/top-bar';
import { of } from 'rxjs';
import { map } from 'rxjs/operators';

interface ExampleLocalizedText {
    breadcrumbShortening: string;
    leftmostHidden: string;
    currentItemTruncation: string;
    rightSideCompression: string;
}

const ExampleLocalizedData = new InjectionToken<Record<string | 'default', ExampleLocalizedText>>(
    'ExampleLocalizedData',
    {
        factory: () => ({
            'ru-RU': {
                breadcrumbShortening:
                    'За основу берется автоматическое сокращение хлебных крошек, когда средние пункты скрываются при отсутствии свободного пространства.',
                leftmostHidden:
                    'Если пространство становится еще уже, то скрывается и левый крайний уровень у хлебных крошек, оставляя видимым только крайний правый уровень.',
                currentItemTruncation:
                    'Минимальная ширина левой стороны зависит от заголовка текущего пункта, который может быть обрезан до 3 символов с добавлением трех точек (…).',
                rightSideCompression:
                    'После достижения минимальной ширины у левой стороны можно приступить к сжатию правой стороны с действиями. '
            } satisfies ExampleLocalizedText,
            default: {
                breadcrumbShortening:
                    'The automatic breadcrumb shortening is used as the basis, where middle items are hidden when there is not enough free space.',
                leftmostHidden:
                    'If the space becomes even narrower, the leftmost breadcrumb level is also hidden, leaving only the rightmost level visible.',
                currentItemTruncation:
                    'The minimum width of the left side depends on the title of the current item, which can be truncated to 3 characters with an ellipsis (…).',
                rightSideCompression:
                    'After reaching the minimum width on the left side, the compression of the right side with actions can begin.'
            } satisfies ExampleLocalizedText
        })
    }
);

@Component({
    selector: 'example-top-bar-breadcrumbs',
    imports: [
        KbqTopBarModule,
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule,
        KbqDropdownModule,
        KbqOverflowItemsModule,
        KbqBreadcrumbsModule,
        RouterLink
    ],
    template: `
        <kbq-top-bar withShadow>
            <div class="layout-align-center-center" kbqTopBarContainer placement="start">
                <div class="layout-row layout-margin-right-m flex-none">
                    <img alt="example icon" src="https://koobiq.io/assets/example-icon.svg" width="24" height="24" />
                </div>
                <nav kbq-breadcrumbs size="big">
                    <kbq-breadcrumb-item text="Main" routerLink="./main" />
                    <kbq-breadcrumb-item text="Section" routerLink="./main/sections" />
                    <kbq-breadcrumb-item routerLink="./main/sections/details" text="Details">
                        <a
                            *kbqBreadcrumbView
                            class="kbq-truncate-line"
                            routerLink="./main/sections/page/details"
                            tabindex="-1"
                        >
                            <button disabled aria-current="page" kbq-button kbqBreadcrumb>
                                <span>Details</span>
                                <i
                                    kbq-icon="kbq-circle-info_16"
                                    kbqTooltip="Info"
                                    [kbqPlacement]="PopUpPlacements.Bottom"
                                    [kbqTooltipArrow]="false"
                                ></i>
                            </button>
                        </a>
                    </kbq-breadcrumb-item>
                </nav>
            </div>
            <div kbqTopBarSpacer></div>
            <div kbqTopBarContainer placement="end">
                <div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
                    <button
                        kbqOverflowItem="filter"
                        kbqTooltip="Filter"
                        kbq-button
                        aria-label="Filter"
                        [kbqStyle]="KbqButtonStyles.Transparent"
                        [color]="KbqComponentColors.Contrast"
                        [kbqPlacement]="PopUpPlacements.Bottom"
                        [kbqTooltipArrow]="false"
                    >
                        <i kbq-icon="kbq-filter_16"></i>
                    </button>

                    <button
                        kbqOverflowItem="share"
                        kbqTooltip="Share"
                        kbq-button
                        [kbqStyle]="KbqButtonStyles.Filled"
                        [color]="KbqComponentColors.ContrastFade"
                        [kbqTooltipDisabled]="isDesktop()"
                        [kbqPlacement]="PopUpPlacements.Bottom"
                        [kbqTooltipArrow]="false"
                    >
                        @if (isDesktop()) {
                            Share
                        } @else {
                            <i kbq-icon="kbq-arrow-up-from-rectangle_16"></i>
                        }
                    </button>

                    <div kbqOverflowItemsResult>
                        <button
                            kbq-button
                            aria-label="More"
                            [kbqStyle]="KbqButtonStyles.Transparent"
                            [color]="KbqComponentColors.Contrast"
                            [kbqDropdownTriggerFor]="appDropdown"
                        >
                            <i kbq-icon="kbq-ellipsis-horizontal_16"></i>
                        </button>

                        <kbq-dropdown #appDropdown="kbqDropdown">
                            @if (kbqOverflowItems.hiddenItemIDs().has('filter')) {
                                <button kbq-dropdown-item>Filter</button>
                            }
                            @if (kbqOverflowItems.hiddenItemIDs().has('share')) {
                                <button kbq-dropdown-item>Share</button>
                            }
                        </kbq-dropdown>
                    </div>
                </div>
            </div>
        </kbq-top-bar>
    `,
    styles: `
        :host {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 72px;
            max-width: 100%;
            min-width: 110px;
            container-type: inline-size;

            .kbq-top-bar {
                width: 100%;
            }

            .kbq-top-bar-container_start {
                --kbq-top-bar-container-start-min-width: 180px;
            }
        }

        .example-kbq-top-bar__title {
            display: inline-flex;
        }
    `,
    providers: [kbqBreadcrumbsConfigurationProvider({ firstItemNegativeMargin: false })],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExampleTopBarBreadcrumbs {
    readonly isDesktop = toSignal(
        inject(BreakpointObserver)
            .observe('(min-width: 900px)')
            .pipe(
                takeUntilDestroyed(),
                map(({ matches }) => matches)
            ),
        { initialValue: true }
    );

    protected readonly KbqComponentColors = KbqComponentColors;
    protected readonly KbqButtonStyles = KbqButtonStyles;
    protected readonly PopUpPlacements = PopUpPlacements;
}

@Component({
    selector: 'top-bar-breadcrumbs-adaptive-example',
    imports: [ExampleTopBarBreadcrumbs],
    template: `
        <div class="layout-margin-bottom-l">
            {{ text().breadcrumbShortening }}
        </div>
        <example-top-bar-breadcrumbs />

        <div class="layout-margin-top-3xl layout-margin-bottom-l">
            {{ text().leftmostHidden }}
        </div>
        <example-top-bar-breadcrumbs [style.width.px]="500" />

        <div class="layout-margin-top-3xl layout-margin-bottom-l">
            {{ text().currentItemTruncation }}
        </div>
        <example-top-bar-breadcrumbs [style.width.px]="400" />

        <div class="layout-margin-top-3xl layout-margin-bottom-l">
            {{ text().rightSideCompression }}
        </div>
        <example-top-bar-breadcrumbs [style.width.px]="342" />
    `,
    styles: `
        div {
            color: var(--kbq-foreground-contrast-secondary);
            margin: var(--kbq-size-s) var(--kbq-size-s) 0;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TopBarBreadcrumbsAdaptiveExample {
    protected readonly data = inject(ExampleLocalizedData);
    protected readonly localeId = toSignal(inject(KBQ_LOCALE_SERVICE, { optional: true })?.changes || of(''));

    protected readonly text = computed(() => {
        const localeId = this.localeId();

        return (localeId && this.data[localeId]) || this.data.default;
    });
}
```

A more detailed explanation of breadcrumb compression is provided on the [Breadcrumbs](https://koobiq.io/en/components/breadcrumbs.md) page.

#### Page title variant

The variant using only the page title will adjust as follows:

**Example: Top Bar Title And Counter Adaptive** (`top-bar-title-counter-adaptive`)

```ts top-bar-title-counter-adaptive-example.ts
import { BreakpointObserver } from '@angular/cdk/layout';
import { ChangeDetectionStrategy, Component, computed, inject, InjectionToken } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { KbqButtonColor, KbqButtonModule, KbqButtonStyleInput, KbqButtonStyles } from '@koobiq/components/button';
import { KBQ_LOCALE_SERVICE, KbqComponentColors, KbqFormattersModule, PopUpPlacements } from '@koobiq/components/core';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { KbqTopBarModule } from '@koobiq/components/top-bar';
import { of } from 'rxjs';
import { map } from 'rxjs/operators';

interface ExampleLocalizedText {
    fullDisplay: string;
    truncatedTitle: string;
    compressRight: string;
}

const ExampleLocalizedData = new InjectionToken<Record<string | 'default', ExampleLocalizedText>>(
    'ExampleLocalizedData',
    {
        factory: () => ({
            'ru-RU': {
                fullDisplay: 'Когда есть свободное пространство заголовок и действия отображаются полностью',
                truncatedTitle:
                    'Минимальная ширина левой стороны зависит от заголовка страницы, который может быть обрезан до 3 символов с добавлением трех точек (…).',
                compressRight:
                    'После достижения минимальной ширины у левой стороны можно приступить к сжатию правой стороны с действиями. '
            } satisfies ExampleLocalizedText,
            default: {
                fullDisplay: 'When there is free space, the title and actions are fully displayed.',
                truncatedTitle:
                    'The minimum width of the left side depends on the page title, which can be truncated to 3 characters with the addition of three dots (…).',
                compressRight:
                    'After reaching the minimum width of the left side, you can start compressing the right side with the actions.'
            } satisfies ExampleLocalizedText
        })
    }
);

type ExampleAction = {
    id: string;
    icon?: string;
    text?: string;
    action?: () => void;
    style: KbqButtonStyleInput;
    color: KbqButtonColor;
};

@Component({
    selector: 'example-top-bar',
    imports: [
        KbqTopBarModule,
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule,
        KbqDropdownModule,
        KbqOverflowItemsModule,
        KbqFormattersModule
    ],
    template: `
        <kbq-top-bar withShadow>
            <div
                class="layout-row layout-align-center-center layout-padding-top-3xs layout-padding-bottom-3xs"
                kbqTopBarContainer
                placement="start"
            >
                <div class="layout-row layout-margin-right-l flex-none">
                    <img alt="example icon" src="https://koobiq.io/assets/example-icon.svg" width="24" height="24" />
                </div>
                <div class="kbq-title kbq-truncate-line example-kbq-top-bar__title">
                    <span class="kbq-truncate-line layout-margin-right-xs">Page Header</span>

                    <span class="example-kbq-top-bar__counter">{{ 10 | kbqNumber: '' : 'en-US' }}</span>
                </div>
            </div>

            <div kbqTopBarSpacer></div>

            <div kbqTopBarContainer placement="end">
                <div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
                    @for (action of actions; track action.id) {
                        <button
                            kbq-button
                            [kbqOverflowItem]="action.id"
                            [kbqStyle]="action.style"
                            [color]="action.color"
                            [kbqPlacement]="PopUpPlacements.Bottom"
                            [kbqTooltipArrow]="false"
                            [kbqTooltipDisabled]="isDesktop()"
                            [kbqTooltip]="action.text || action.id"
                        >
                            @if (action.icon) {
                                <i kbq-icon="" [class]="action.icon"></i>
                            }
                            @if ((action.text && isDesktop()) || (!action.icon && action.text)) {
                                {{ action.text }}
                            }
                        </button>
                    }

                    <div kbqOverflowItemsResult>
                        <button
                            kbq-button
                            aria-label="More"
                            [kbqStyle]="KbqButtonStyles.Transparent"
                            [color]="KbqComponentColors.Contrast"
                            [kbqDropdownTriggerFor]="appDropdown"
                        >
                            <i kbq-icon="kbq-ellipsis-horizontal_16"></i>
                        </button>

                        <kbq-dropdown #appDropdown="kbqDropdown">
                            @for (action of actions; track action.id) {
                                @if (kbqOverflowItems.hiddenItemIDs().has(action.id)) {
                                    <button kbq-dropdown-item>
                                        {{ action.text || action.id }}
                                    </button>
                                }
                            }
                        </kbq-dropdown>
                    </div>
                </div>
            </div>
        </kbq-top-bar>
    `,
    styles: `
        :host {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 72px;
            max-width: 100%;
            min-width: 110px;
            container-type: inline-size;

            .kbq-top-bar {
                width: 100%;
            }

            .kbq-top-bar-container_start {
                --kbq-top-bar-container-start-min-width: 115px;
            }

            .kbq-overflow-items {
                min-width: 32px;
            }
        }

        .example-kbq-top-bar__counter {
            color: var(--kbq-foreground-contrast-tertiary);
        }

        .example-kbq-top-bar__title {
            display: inline-flex;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExampleTopBar {
    readonly isDesktop = toSignal(
        inject(BreakpointObserver)
            .observe('(min-width: 900px)')
            .pipe(
                takeUntilDestroyed(),
                map(({ matches }) => matches)
            ),
        { initialValue: true }
    );

    readonly actions: ExampleAction[] = [
        { id: 'filter', icon: 'kbq-filter_16', color: KbqComponentColors.Contrast, style: KbqButtonStyles.Transparent },
        { id: 'button1', text: 'Add object', color: KbqComponentColors.Contrast, style: '' },
        { id: 'button2', text: 'Button 2', color: KbqComponentColors.ContrastFade, style: '' }
    ];

    protected readonly KbqComponentColors = KbqComponentColors;
    protected readonly KbqButtonStyles = KbqButtonStyles;
    protected readonly PopUpPlacements = PopUpPlacements;
}

@Component({
    selector: 'top-bar-title-counter-adaptive-example',
    imports: [ExampleTopBar],
    template: `
        <div class="example-text layout-margin-bottom-l">
            {{ text().fullDisplay }}
        </div>
        <example-top-bar [style.width.px]="680" />

        <div class="example-text layout-margin-bottom-l layout-margin-top-3xl">
            {{ text().truncatedTitle }}
        </div>
        <example-top-bar [style.width.px]="437" />

        <div class="example-text layout-margin-bottom-l layout-margin-top-3xl">
            {{ text().compressRight }}
        </div>
        <example-top-bar [style.width.px]="314" />
    `,
    styles: `
        :host .example-text {
            color: var(--kbq-foreground-contrast-secondary);
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TopBarTitleCounterAdaptiveExample {
    protected readonly data = inject(ExampleLocalizedData);
    protected readonly localeId = toSignal(inject(KBQ_LOCALE_SERVICE, { optional: true })?.changes || of(''));

    protected readonly text = computed(() => {
        const localeId = this.localeId();

        return (localeId && this.data[localeId]) || this.data.default;
    });
}
```

#### Scroll behavior

The toolbar can remain fixed while scrolling the page.

**Example: TopBar Overflow** (`top-bar-overflow`)

```ts top-bar-overflow-example.ts
import { BreakpointObserver } from '@angular/cdk/layout';
import { CdkScrollable } from '@angular/cdk/overlay';
import {
    AfterViewInit,
    ChangeDetectionStrategy,
    Component,
    inject,
    signal,
    viewChild,
    WritableSignal
} from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { KbqBadgeModule } from '@koobiq/components/badge';
import { KbqButtonColor, KbqButtonModule, KbqButtonStyleInput, KbqButtonStyles } from '@koobiq/components/button';
import { KbqComponentColors, KbqFormattersModule, PopUpPlacements } from '@koobiq/components/core';
import { KbqDd, KbqDl, KbqDt } from '@koobiq/components/dl';
import { KbqDropdownModule } from '@koobiq/components/dropdown';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqOverflowItemsModule } from '@koobiq/components/overflow-items';
import { KbqNativeScrollbar } from '@koobiq/components/scrollbar';
import { KbqToolTipModule } from '@koobiq/components/tooltip';
import { KbqTopBarModule } from '@koobiq/components/top-bar';
import { auditTime, map } from 'rxjs/operators';

type ExampleAction = {
    id: string;
    icon?: string;
    text?: string;
    action?: () => void;
    style: KbqButtonStyleInput;
    color: KbqButtonColor;
    alwaysVisible?: boolean;
};

@Component({
    selector: 'top-bar-overflow-example',
    imports: [
        CdkScrollable,
        KbqTopBarModule,
        KbqButtonModule,
        KbqToolTipModule,
        KbqIconModule,
        KbqDl,
        KbqDt,
        KbqDd,
        KbqBadgeModule,
        KbqDropdownModule,
        KbqOverflowItemsModule,
        KbqNativeScrollbar,
        KbqFormattersModule
    ],
    template: `
        <kbq-top-bar [withShadow]="hasOverflow()">
            <div class="layout-align-center-center" kbqTopBarContainer placement="start">
                <div class="kbq-title example-kbq-top-bar__title">
                    <span class="kbq-truncate-line layout-margin-right-xs">Page</span>

                    <span class="example-kbq-top-bar__counter">{{ 13294 | kbqNumber: '' : 'en-US' }}</span>
                </div>
            </div>
            <div kbqTopBarSpacer></div>
            <div kbqTopBarContainer placement="end">
                <div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
                    @for (action of actions; track action.id) {
                        <button
                            kbq-button
                            [kbqOverflowItem]="action.id"
                            [kbqStyle]="action.style"
                            [color]="action.color"
                            [kbqPlacement]="PopUpPlacements.Bottom"
                            [kbqTooltipArrow]="false"
                            [kbqTooltipDisabled]="canTooltipBeAppied(action)"
                            [kbqTooltip]="action.text || action.id"
                            [alwaysVisible]="action?.alwaysVisible"
                        >
                            @if (action.icon) {
                                <i kbq-icon="" [class]="action.icon"></i>
                            }
                            @if (canTooltipBeAppied(action)) {
                                {{ action.text }}
                            }
                        </button>
                    }

                    <div kbqOverflowItemsResult>
                        <button
                            kbq-button
                            aria-label="More"
                            [kbqStyle]="KbqButtonStyles.Transparent"
                            [color]="KbqComponentColors.Contrast"
                            [kbqDropdownTriggerFor]="appDropdown"
                        >
                            <i kbq-icon="kbq-ellipsis-horizontal_16"></i>
                        </button>

                        <kbq-dropdown #appDropdown="kbqDropdown">
                            @for (action of actions; track action.id) {
                                @if (kbqOverflowItems.hiddenItemIDs().has(action.id)) {
                                    <button kbq-dropdown-item>
                                        {{ action.text || action.id }}
                                    </button>
                                }
                            }
                        </kbq-dropdown>
                    </div>
                </div>
            </div>
        </kbq-top-bar>
        <div kbqNativeScrollbar class="overflow-content-example" cdkScrollable>
            <div style="height: 600px">
                <kbq-dl>
                    <kbq-dt>Description</kbq-dt>
                    <kbq-dd>
                        NASA describes a case where a person accidentally ended up in an area close to a vacuum
                        (pressure below 1 Pa) due to an air leak from a spacesuit.
                    </kbq-dd>

                    <kbq-dt>Status</kbq-dt>
                    <kbq-dd><kbq-badge>Badge</kbq-badge></kbq-dd>

                    <kbq-dt>Additional Information</kbq-dt>
                    <kbq-dd>
                        Space is an endless expanse full of mysteries and wonders. It begins beyond our atmosphere and
                        stretches to the farthest corners of the Universe. In space, there are billions of stars,
                        planets, and galaxies, each with its own unique story and characteristics. Exploring space helps
                        us better understand not only the Universe itself but also our place within it.
                        <br />
                        <br />
                        One of the most fascinating aspects of space is its infinity. Scientists still cannot determine
                        exactly where the Universe ends. There are theories that it may be infinite or have certain
                        boundaries. This opens up many questions about what lies beyond the visible universe and what
                        forms of life might exist on other planets. Space research also plays a crucial role in the
                        development of technologies on Earth. Many inventions we use in everyday life were developed
                        thanks to space programs. For example, satellite navigation, communication systems, and even
                        some medical technologies have their roots in space exploration. This shows how studying space
                        can benefit humanity as a whole. Finally, space inspires creativity and dreams. Since childhood,
                        we have looked at the stars and wondered what might exist beyond our planet. Movies, books, and
                        art often draw inspiration from space, creating images that capture the imagination. This drive
                        for exploration and discovery makes space not only a scientific but also a cultural part of our
                        lives.
                    </kbq-dd>
                </kbq-dl>
            </div>
        </div>
    `,
    styles: `
        :host {
            display: flex;
            flex-direction: column;
            height: 400px;
            border-radius: var(--kbq-size-border-radius);
            border: 1px solid var(--kbq-line-contrast-less);
            background: var(--kbq-background-bg);

            .kbq-top-bar {
                border-radius: var(--kbq-size-border-radius) var(--kbq-size-border-radius) 0 0;
            }
        }

        .overflow-content-example {
            height: 100%;
            overflow-y: auto;
            scroll-behavior: smooth;
            padding: 0 var(--kbq-size-xxl);
        }

        .example-kbq-top-bar__counter {
            color: var(--kbq-foreground-contrast-tertiary);
            flex: 1;
        }

        .example-kbq-top-bar__title {
            display: inline-flex;
            white-space: nowrap;
            min-width: 100px;
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TopBarOverflowExample implements AfterViewInit {
    readonly isDesktop = toSignal(
        inject(BreakpointObserver)
            .observe('(min-width: 768px)')
            .pipe(
                takeUntilDestroyed(),
                map(({ matches }) => matches)
            ),
        { initialValue: true }
    );

    protected readonly scrollable = viewChild.required(CdkScrollable);

    readonly hasOverflow: WritableSignal<boolean | undefined> = signal(false);

    readonly actions: ExampleAction[] = [
        {
            id: 'search',
            icon: 'kbq-magnifying-glass_16',
            color: KbqComponentColors.Contrast,
            style: KbqButtonStyles.Transparent
        },
        { id: 'list', icon: 'kbq-list_16', color: KbqComponentColors.Contrast, style: KbqButtonStyles.Transparent },
        { id: 'filter', icon: 'kbq-filter_16', color: KbqComponentColors.Contrast, style: KbqButtonStyles.Transparent },
        { id: 'button1', text: 'Add object', color: KbqComponentColors.Contrast, style: '', alwaysVisible: true },
        { id: 'button2', text: 'Button', color: KbqComponentColors.ContrastFade, style: '' }
    ];

    protected readonly PopUpPlacements = PopUpPlacements;
    protected readonly KbqComponentColors = KbqComponentColors;
    protected readonly KbqButtonStyles = KbqButtonStyles;

    ngAfterViewInit() {
        this.scrollable()
            .elementScrolled()
            .pipe(auditTime(300))
            .subscribe(() => this.hasOverflow.set(this.scrollable().measureScrollOffset('top') > 0));
    }

    // only show tooltip on button icon
    canTooltipBeAppied(action: ExampleAction): boolean {
        return (!!action.text && this.isDesktop()) || (!action.icon && !!action.text);
    }
}
```

The example above keeps the toolbar outside the scrolling area, which needs no configuration at all. To make
the toolbar itself stick, set `--kbq-top-bar-inset-block-start: 0` on it and put it inside the scrolling element:
`--kbq-top-bar-position` is already `sticky`, but a sticky box is only offset once it has an inset, so with
the default `auto` the toolbar lays out exactly like `position: relative`.

Panels opened from the page content paint above the toolbar. To have them slide under it, see
[Panels under a sticky header](https://koobiq.io/en/components/core.md#panels-under-a-sticky-header).

### CSS variables

The inputs cover the shadow and the accessible name; everything else is configured through custom
properties set on `kbq-top-bar` itself. The defaults are declared on the element, so a value set on an ancestor
does not reach it.

| Variable                                  | Default                                    | Description                                                                                                                          |
| ----------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `--kbq-top-bar-z-index`                   | `990`                                      | Layer of the toolbar: above page content and the `kbqOverlayLayer` layer (`980`), below the CDK overlay container (`1000`).          |
| `--kbq-top-bar-position`                  | `sticky`                                   | `position` of the host. On its own it changes nothing — see `--kbq-top-bar-inset-block-start`.                                       |
| `--kbq-top-bar-inset-block-start`         | `auto`                                     | Distance a sticky toolbar keeps from the top of its scrolling ancestor. `auto` imposes no constraint, so the toolbar does not stick. |
| `--kbq-top-bar-padding-vertical`          | `var(--kbq-size-m)`                        | Vertical padding of the toolbar.                                                                                                     |
| `--kbq-top-bar-padding-horizontal`        | `var(--kbq-size-xxl)`                      | Horizontal padding of the toolbar.                                                                                                   |
| `--kbq-top-bar-border-radius`             | `0`                                        | Corner radius of the toolbar.                                                                                                        |
| `--kbq-top-bar-spacer-min-width`          | `80px`                                     | Width of `[kbqTopBarSpacer]`, the guaranteed clearance between the two sides.                                                        |
| `--kbq-top-bar-container-start-min-width` | `0`                                        | Width the `start` container never shrinks below. Set it whenever the left-hand content has to stay readable in a narrow toolbar.     |
| `--kbq-top-bar-container-start-gap`       | `0`                                        | Gap between the elements of the `start` container.                                                                                   |
| `--kbq-top-bar-container-end-gap`         | `var(--kbq-size-s)`                        | Gap between the elements of the `end` container, and between the overflowing action buttons.                                         |
| `--kbq-top-bar-background`                | `var(--kbq-background-bg)`                 | Background of the toolbar.                                                                                                           |
| `--kbq-top-bar-shadow-bottom`             | `var(--kbq-shadow-overflow-normal-bottom)` | Shadow drawn while `withShadow` is set.                                                                                              |
| `--kbq-top-bar-shadow-transition`         | `ease-out 300ms`                           | Transition of that shadow.                                                                                                           |

### Accessibility

`kbq-top-bar` ships without a landmark role on purpose: the same toolbar is the page header in one place and
a panel header in another, and a page may expose only one `banner`. Pick the semantics at the call site.

When the toolbar is the page header, give it the banner landmark — wrapping it in a `<header>` that is not
nested in `<main>`, `<article>` or `<section>` does the same thing:

```html
<kbq-top-bar role="banner">…</kbq-top-bar>
```

Whenever a page renders more than one toolbar, name each of them so assistive technology can tell them apart:

```html
<kbq-top-bar role="banner" aria-label="Dashboards">…</kbq-top-bar>
```

Use `role="toolbar"` only if the toolbar implements roving focus itself: a toolbar is expected to be a single
tab stop whose controls are reached with the arrow keys. Without that, leave the buttons as ordinary tab
stops. The icon-only buttons a toolbar usually carries — the overflow trigger included — need an `aria-label`
of their own, which the toolbar cannot supply.

## API

### Components

#### KbqTopBar (component)

Toolbar row for the top of a page or a panel. It expects two `[kbqTopBarContainer]` slots —
`placement="start"` and `placement="end"` — with an optional `[kbqTopBarSpacer]` between them.

The host carries no landmark role on purpose: the same bar is page chrome in one place and a panel
header in another, and a page may hold only one `banner`. Add `role="banner"` (or wrap the bar in a
`<header>`) where it is the page header.

```ts
@Component({ selector: 'kbq-top-bar' })
class KbqTopBar {
    readonly withShadow = input<boolean>(false);
    readonly ariaLabel = input<string | null>(null, {
        alias: 'aria-label'
    });
}
```

- `[withShadow]`: `boolean` — Applies the `kbq-top-bar_with-shadow` modifier, which draws the bottom shadow used to signal that
  page content is scrolled under the bar.
- `[aria-label]`: `string | null` — Accessible name of the bar. Set it alongside a landmark role whenever a page renders more than one
  bar, so assistive technology can tell them apart.

### Directives

#### KbqTopBarContainer (directive)

Marks a slot inside `kbq-top-bar` and applies `kbq-top-bar-container_start` or
`kbq-top-bar-container_end` according to `placement`.

```ts
@Directive({ selector: '[kbqTopBarContainer]' })
class KbqTopBarContainer {
    readonly placement = input.required<'start' | 'end'>();
}
```

- `[placement]`: `'start' | 'end'` — Side of the bar the slot occupies. Required; the accepted values are `start` and `end`.

#### KbqTopBarSpacer (directive)

Separator between the two `[kbqTopBarContainer]` slots of a `kbq-top-bar`. It reserves
`--kbq-top-bar-spacer-min-width` of guaranteed clearance, so the two sides never collide.

```ts
@Directive({ selector: '[kbqTopBarSpacer]' })
class KbqTopBarSpacer {}
```
