diff --git a/packages/components-dev/table/module.ts b/packages/components-dev/table/module.ts index cf0925fb4f..9abf5593ad 100644 --- a/packages/components-dev/table/module.ts +++ b/packages/components-dev/table/module.ts @@ -2,14 +2,15 @@ import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/ import { KbqButtonModule } from '@koobiq/components/button'; import { KbqComponentColors } from '@koobiq/components/core'; import { KbqTableModule } from '@koobiq/components/table'; -import { TableDisableHoverExample } from 'packages/docs-examples/components/table'; +import { TableDisableHoverExample, TableStickyHeaderExample } from 'packages/docs-examples/components/table'; @Component({ selector: 'dev-examples', - imports: [TableDisableHoverExample], + imports: [TableDisableHoverExample, TableStickyHeaderExample], template: `
+ `, changeDetection: ChangeDetectionStrategy.OnPush }) diff --git a/packages/components/table/__screenshots__/01-dark.png b/packages/components/table/__screenshots__/01-dark.png index 6c937b0713..ea86ee1db2 100644 Binary files a/packages/components/table/__screenshots__/01-dark.png and b/packages/components/table/__screenshots__/01-dark.png differ diff --git a/packages/components/table/__screenshots__/01-light.png b/packages/components/table/__screenshots__/01-light.png index 3fd4b6ca93..b0421e4060 100644 Binary files a/packages/components/table/__screenshots__/01-light.png and b/packages/components/table/__screenshots__/01-light.png differ diff --git a/packages/components/table/_table-theme.scss b/packages/components/table/_table-theme.scss index c2654f9bbd..bbcc1e78dc 100644 --- a/packages/components/table/_table-theme.scss +++ b/packages/components/table/_table-theme.scss @@ -28,6 +28,10 @@ } } + &.kbq-table_sticky-header > thead > tr > th { + background-color: var(--kbq-background-bg); + } + &.kbq-table_bordered { & > tbody > tr { & th, diff --git a/packages/components/table/e2e.playwright-spec.ts b/packages/components/table/e2e.playwright-spec.ts index 600668f89e..335a1788a5 100644 --- a/packages/components/table/e2e.playwright-spec.ts +++ b/packages/components/table/e2e.playwright-spec.ts @@ -7,6 +7,9 @@ test.describe('KbqTableModule', () => { test('states', async ({ page }) => { await page.goto('/E2eTableStates'); + await getComponent(page) + .getByTestId('e2eTableStickyHeader') + .evaluate((el) => (el.scrollTop = 60)); await expect(getComponent(page)).toHaveScreenshot('01-light.png'); await e2eEnableDarkTheme(page); await expect(getComponent(page)).toHaveScreenshot('01-dark.png'); diff --git a/packages/components/table/e2e.ts b/packages/components/table/e2e.ts index acdc1a6ddb..4354c602d5 100644 --- a/packages/components/table/e2e.ts +++ b/packages/components/table/e2e.ts @@ -158,6 +158,28 @@ import { KbqTableModule } from '@koobiq/components/table'; + + +
+ + + + @for (th of [0, 1, 2, 3]; track $index) { + + } + + + + @for (tr of [0, 1, 2, 3, 4, 5]; track $index) { + + @for (td of [0, 1, 2, 3]; track $index) { + + } + + } + +
Sticky
Cell
+
`, styles: ` :host { diff --git a/packages/components/table/table.component.ts b/packages/components/table/table.component.ts index 8d3f9aaa9c..cfa6a13dbd 100644 --- a/packages/components/table/table.component.ts +++ b/packages/components/table/table.component.ts @@ -18,13 +18,15 @@ import { KbqButton } from '@koobiq/components/button'; host: { class: 'kbq-table', '[class.kbq-table_bordered]': 'border()', - '[class.kbq-table_disable-hover]': 'disableHover()' + '[class.kbq-table_disable-hover]': 'disableHover()', + '[class.kbq-table_sticky-header]': 'stickyHeader()' }, exportAs: 'kbqTable' }) export class KbqTable { readonly border = input(false, { transform: booleanAttribute }); readonly disableHover = input(false, { transform: booleanAttribute }); + readonly stickyHeader = input(false, { transform: booleanAttribute }); } @Directive({ diff --git a/packages/components/table/table.en.md b/packages/components/table/table.en.md index 5322caaaf1..c4ac14ded6 100644 --- a/packages/components/table/table.en.md +++ b/packages/components/table/table.en.md @@ -19,3 +19,9 @@ Borders are also useful in tables with complex structures — for example, when 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. + +### 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. + + diff --git a/packages/components/table/table.ru.md b/packages/components/table/table.ru.md index 2df8528cae..c76c09272d 100644 --- a/packages/components/table/table.ru.md +++ b/packages/components/table/table.ru.md @@ -19,3 +19,9 @@ Если строки не являются интерактивными, подсветка при наведении может вводить в заблуждение. Атрибут `disableHover` позволяет убрать этот эффект. + +### Закрепленная шапка + +Если в таблице много строк, используйте атрибут `stickyHeader`, чтобы названия столбцов оставались видимыми при прокрутке. + + diff --git a/packages/components/table/table.scss b/packages/components/table/table.scss index de3fb88feb..06d16547d1 100644 --- a/packages/components/table/table.scss +++ b/packages/components/table/table.scss @@ -37,6 +37,14 @@ } } +.kbq-table.kbq-table_sticky-header { + & > thead > tr > th { + position: sticky; + top: 0; + z-index: 1; + } +} + .kbq-table.kbq-table_bordered { & > tbody > tr { & th, diff --git a/packages/docs-examples/components/table/index.ts b/packages/docs-examples/components/table/index.ts index b6c5a87bda..104e1203a1 100644 --- a/packages/docs-examples/components/table/index.ts +++ b/packages/docs-examples/components/table/index.ts @@ -2,15 +2,23 @@ import { NgModule } from '@angular/core'; import { TableDisableHoverExample } from './table-disable-hover/table-disable-hover-example'; import { TableFullWidthExample } from './table-full-width/table-full-width-example'; import { TableOverviewExample } from './table-overview/table-overview-example'; +import { TableStickyHeaderExample } from './table-sticky-header/table-sticky-header-example'; import { TableWithBordersExample } from './table-with-borders/table-with-borders-example'; -export { TableDisableHoverExample, TableFullWidthExample, TableOverviewExample, TableWithBordersExample }; +export { + TableDisableHoverExample, + TableFullWidthExample, + TableOverviewExample, + TableStickyHeaderExample, + TableWithBordersExample +}; const EXAMPLES = [ TableOverviewExample, TableWithBordersExample, TableFullWidthExample, - TableDisableHoverExample + TableDisableHoverExample, + TableStickyHeaderExample ]; @NgModule({ diff --git a/packages/docs-examples/components/table/table-sticky-header/table-sticky-header-example.ts b/packages/docs-examples/components/table/table-sticky-header/table-sticky-header-example.ts new file mode 100644 index 0000000000..6717a86176 --- /dev/null +++ b/packages/docs-examples/components/table/table-sticky-header/table-sticky-header-example.ts @@ -0,0 +1,56 @@ +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'; + +/** + * @title Table with sticky header + */ +@Component({ + selector: 'table-sticky-header-example', + imports: [KbqTableModule, KbqLuxonDateModule, KbqRelativeShortDatePipe], + template: ` +
+ + + + + + + + + + @for (row of rows; track row.name) { + + + + + + } + +
FileOwnerModified
{{ row.name }}{{ row.owner }}{{ row.modified | kbqRelativeShortDate }}
+
+ `, + providers: [ + { provide: DateFormatter, deps: [DateAdapter] } + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TableStickyHeaderExample { + readonly adapter = inject>(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 } + ]; +} diff --git a/packages/docs-examples/example-module.ts b/packages/docs-examples/example-module.ts index d29ac4b48b..149f1c21fa 100644 --- a/packages/docs-examples/example-module.ts +++ b/packages/docs-examples/example-module.ts @@ -2059,6 +2059,19 @@ export const EXAMPLE_COMPONENTS: {[id: string]: LiveExample} = { "primaryFile": "file-upload-multiple-accept-validation-example.ts", "importPath": "components/file-upload" }, + "file-upload-multiple-add-strategy": { + "packagePath": "components/file-upload/file-upload-multiple-add-strategy", + "title": "File-upload multiple add strategy", + "componentName": "FileUploadMultipleAddStrategyExample", + "files": [ + "file-upload-multiple-add-strategy-example.ts" + ], + "localImportFiles": [], + "selector": "file-upload-multiple-add-strategy-example", + "additionalComponents": [], + "primaryFile": "file-upload-multiple-add-strategy-example.ts", + "importPath": "components/file-upload" + }, "file-upload-multiple-compact-overview": { "packagePath": "components/file-upload/file-upload-multiple-compact-overview", "title": "File-upload multiple compact", @@ -5640,6 +5653,19 @@ export const EXAMPLE_COMPONENTS: {[id: string]: LiveExample} = { "primaryFile": "table-overview-example.ts", "importPath": "components/table" }, + "table-sticky-header": { + "packagePath": "components/table/table-sticky-header", + "title": "Table with sticky header", + "componentName": "TableStickyHeaderExample", + "files": [ + "table-sticky-header-example.ts" + ], + "localImportFiles": [], + "selector": "table-sticky-header-example", + "additionalComponents": [], + "primaryFile": "table-sticky-header-example.ts", + "importPath": "components/table" + }, "table-with-borders": { "packagePath": "components/table/table-with-borders", "title": "Table with borders", @@ -7968,6 +7994,8 @@ return import('@koobiq/docs-examples/components/file-upload'); case 'file-upload-local-dropzone': return import('@koobiq/docs-examples/components/file-upload'); case 'file-upload-multiple-accept-validation': +return import('@koobiq/docs-examples/components/file-upload'); + case 'file-upload-multiple-add-strategy': return import('@koobiq/docs-examples/components/file-upload'); case 'file-upload-multiple-compact-overview': return import('@koobiq/docs-examples/components/file-upload'); @@ -8506,6 +8534,8 @@ return import('@koobiq/docs-examples/components/table'); case 'table-full-width': return import('@koobiq/docs-examples/components/table'); case 'table-overview': +return import('@koobiq/docs-examples/components/table'); + case 'table-sticky-header': return import('@koobiq/docs-examples/components/table'); case 'table-with-borders': return import('@koobiq/docs-examples/components/table'); diff --git a/tools/public_api_guard/components/table.api.md b/tools/public_api_guard/components/table.api.md index f14fc8a45b..9db83fd6d8 100644 --- a/tools/public_api_guard/components/table.api.md +++ b/tools/public_api_guard/components/table.api.md @@ -16,7 +16,9 @@ export class KbqTable { // (undocumented) readonly disableHover: i0.InputSignalWithTransform; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + readonly stickyHeader: i0.InputSignalWithTransform; + // (undocumented) + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; }