# Table

- Entry point: `@koobiq/components/table`
- NgModule: `KbqTableModule`
- Page: https://koobiq.io/en/components/table/overview

## Overview

A table is similar to a grid but simpler in structure. Users cannot sort columns or change their widths. Only standard HTML table capabilities are available.

**Example: Table** (`table-overview`)

```ts table-overview-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqTableModule } from '@koobiq/components/table';

@Component({
    selector: 'table-overview-example',
    imports: [
        KbqTableModule
    ],
    template: `
        <table kbq-table style="margin-bottom: 32px">
            <thead>
                <tr>
                    <th>Client</th>
                    <th>Event</th>
                    <th>Registered</th>
                </tr>
            </thead>
            <tbody>
                @for (row of rows; track $index) {
                    <tr>
                        <td>{{ row.client }}</td>
                        <td>{{ row.event }}</td>
                        <td>{{ row.registered }}</td>
                    </tr>
                }
            </tbody>
        </table>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TableOverviewExample {
    protected readonly rows = [
        { client: 'Company 1', event: 'Identity Theft', registered: '8 Jul, 14:31' },
        { client: 'Company 2', event: 'DDoS', registered: '4 Dec, 16:11' },
        { client: 'Company 1', event: 'HIPS Alert', registered: '4 Dec, 16:11' },
        { client: 'Company 3', event: 'Spam Attack', registered: '4 Dec, 16:11' },
        { client: 'Company 2', event: 'Vulnerability Exploitation', registered: '4 Dec, 16:11' },
        { client: 'Company 1', event: 'Complex Attack', registered: '4 Dec, 16:11' }
    ];
}
```

By default, text in cells wraps to a new line:

**Example: Table full width** (`table-full-width`)

```ts table-full-width-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqTableModule } from '@koobiq/components/table';

@Component({
    selector: 'table-full-width-example',
    imports: [
        KbqTableModule
    ],
    templateUrl: 'table-full-width-example.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TableFullWidthExample {}
```

```html table-full-width-example.html
<table kbq-table style="margin-bottom: 32px; width: 100%">
    <thead>
        <tr>
            <th style="min-width: 140px">Incident</th>
            <th>Description, or a long header text that wraps to the next line</th>
            <th>Type</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>INC–20180823–05</td>
            <td>
                One or more failed attempts to open a VPN connection from 10.0.210.126 to host 10.0.221.202 within one
                hour
            </td>
            <td>EXT. Use of phishing resources (phishingAttacks)</td>
        </tr>
        <tr>
            <td>INC–20180601–5</td>
            <td>10.0.64.224: average memory usage over the last 5 minutes: 49% in use</td>
            <td>EXT. IMSI change on the SIM card, IMEI change on the phone (sim)</td>
        </tr>
        <tr>
            <td>INC–20180926–31</td>
            <td>
                Zone example.com was transferred from the domain DNS server ns1.example.com to the untrusted host
                10.0.0.2 over an AXFR request
            </td>
            <td>INT. Use of malicious software (malware)</td>
        </tr>
        <tr>
            <td>INC–20190622–238</td>
            <td>An attempt to list the processes running on host ns1.example.com was detected</td>
            <td>EXT. Hosting of a malicious resource on the Internet (maliciousResources)</td>
        </tr>
    </tbody>
</table>
```

### Borders

Use `border` to draw a line under every row.

In most cases, borders should be avoided, but sometimes they help support the layout when a table has gaps due to cell content varying between long and short text.

Borders are also useful in tables with complex structures — for example, when rows are grouped or some cells are merged vertically.

**Example: Table with borders** (`table-with-borders`)

```ts table-with-borders-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqTableModule } from '@koobiq/components/table';

@Component({
    selector: 'table-with-borders-example',
    imports: [
        KbqTableModule
    ],
    template: `
        <table style="margin-bottom: 32px; width: 100%" kbq-table [border]="true">
            <thead>
                <tr>
                    <th>File</th>
                    <th style="width: 110px">Owner</th>
                    <th style="width: 110px">Modified</th>
                    <th style="width: 50px">Size</th>
                </tr>
            </thead>
            <tbody>
                @for (row of rows; track row.name) {
                    <tr>
                        <td>{{ row.name }}</td>
                        <td>{{ row.owner }}</td>
                        <td>{{ row.modified }}</td>
                        <td>{{ row.size }}</td>
                    </tr>
                }
            </tbody>
        </table>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TableWithBordersExample {
    protected readonly rows = [
        { name: '000.txt', owner: 'User 1', modified: '27 May 2024', size: '17 KB' },
        { name: 'The Big Book of Poems and Fairy Tales', owner: 'User 2', modified: '1 Dec 2023', size: '998 KB' },
        { name: 'notes.doc', owner: 'User 3', modified: '7 Mar 1993', size: '502 KB' },
        { name: 'recipe.txt', owner: 'User 4', modified: '24 Aug 2022', size: '128 KB' }
    ];
}
```

### Disable hover

Use `disableHover` to remove the background color change on row hover. This is useful when rows are not interactive and the hover highlight would be misleading.

**Example: Table without hover** (`table-disable-hover`)

```ts table-disable-hover-example.ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { KbqTableModule } from '@koobiq/components/table';

@Component({
    selector: 'table-disable-hover-example',
    imports: [KbqTableModule],
    template: `
        <table kbq-table disableHover>
            <thead>
                <tr>
                    <th>File</th>
                    <th>Owner</th>
                    <th>Modified</th>
                </tr>
            </thead>
            <tbody>
                @for (row of rows; track row.name) {
                    <tr>
                        <td>{{ row.name }}</td>
                        <td>{{ row.owner }}</td>
                        <td>{{ row.modified }}</td>
                    </tr>
                }
            </tbody>
        </table>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TableDisableHoverExample {
    protected readonly rows = [
        { name: 'document.txt', owner: 'User 1', modified: '27 May 2024' },
        { name: 'report-2023.pdf', owner: 'User 2', modified: '1 Dec 2023' },
        { name: 'notes.doc', owner: 'User 3', modified: '7 Mar 2024' },
        { name: 'archive.zip', owner: 'User 4', modified: '24 Aug 2022' }
    ];
}
```

### Sticky header

If a table has a lot of rows, use `stickyHeader` to keep the column names visible while you scroll down. This way you always see what each column means, even after scrolling.

**Example: Table with sticky header** (`table-sticky-header`)

```ts table-sticky-header-example.ts
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { KbqLuxonDateModule } from '@koobiq/angular-luxon-adapter/adapter';
import { DateAdapter, DateFormatter, KbqRelativeShortDatePipe } from '@koobiq/components/core';
import { KbqTableModule } from '@koobiq/components/table';
import { DateTime } from 'luxon';

@Component({
    selector: 'table-sticky-header-example',
    imports: [KbqTableModule, KbqLuxonDateModule, KbqRelativeShortDatePipe],
    template: `
        <div style="max-height: 240px; overflow: auto;">
            <table kbq-table stickyHeader style="width: 100%">
                <thead>
                    <tr>
                        <th>File</th>
                        <th>Owner</th>
                        <th>Modified</th>
                    </tr>
                </thead>
                <tbody>
                    @for (row of rows; track row.name) {
                        <tr>
                            <td>{{ row.name }}</td>
                            <td>{{ row.owner }}</td>
                            <td>{{ row.modified | kbqRelativeShortDate }}</td>
                        </tr>
                    }
                </tbody>
            </table>
        </div>
    `,
    providers: [
        { provide: DateFormatter, deps: [DateAdapter] }
    ],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TableStickyHeaderExample {
    readonly adapter = inject<DateAdapter<DateTime>>(DateAdapter);
    readonly value = this.adapter.today();

    protected readonly rows = [
        { name: 'document.txt', owner: 'User 1', modified: this.value },
        { name: 'report-2023.pdf', owner: 'User 2', modified: this.value },
        { name: 'notes.doc', owner: 'User 3', modified: this.value },
        { name: 'archive.zip', owner: 'User 4', modified: this.value },
        { name: 'presentation.pptx', owner: 'User 5', modified: this.value },
        { name: 'budget.xlsx', owner: 'User 6', modified: this.value },
        { name: 'photo.png', owner: 'User 7', modified: this.value },
        { name: 'contract.pdf', owner: 'User 8', modified: this.value },
        { name: 'summary.doc', owner: 'User 9', modified: this.value },
        { name: 'backup.zip', owner: 'User 10', modified: this.value }
    ];
}
```

The pinned header is opaque and covers the top of the scroll area. When focus moves to a control above the visible part, the browser scrolls it to that top edge — under the header.

The table accounts for the header height on its own cells and the controls inside them. If the scroll container holds other content as well, set `scroll-padding-block-start` on the container to the header height.

The header color comes from `--kbq-table-sticky-header-background`. It defaults to the page background. For a table on a card, in a modal or in a sidepanel, point the token at that surface:

```scss
--kbq-table-sticky-header-background: var(--kbq-background-card);
```

## API

### Components

#### KbqTable (component)

A styling layer over a native `<table>`: it applies the library's spacing, typography and colors and
leaves the semantics to the platform. Sorting, resizing and virtual scrolling are out of scope — only
standard HTML table capabilities are available.

```ts
@Component({ selector: 'table[kbq-table]', exportAs: 'kbqTable' })
class KbqTable {
    readonly border = input<boolean>(false);
    readonly disableHover = input<boolean>(false);
    readonly stickyHeader = input<boolean>(false);
}
```

- `[border]`: `boolean` — Whether a line is drawn under every body row. Toggles the `kbq-table_bordered` class. Defaults to `false`.
- `[disableHover]`: `boolean` — Whether the background change on row hover is suppressed. Toggles the `kbq-table_disable-hover`
  class. Use it when rows are not interactive and the highlight would be misleading. Defaults to `false`.
- `[stickyHeader]`: `boolean` — Whether the header stays visible while the table scrolls. Toggles the `kbq-table_sticky-header`
  class. Defaults to `false`.
