From 6a46379aa7ac4c755a1c54e19a9037f59c9b2f9b Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Wed, 8 Apr 2026 22:22:07 +0700 Subject: [PATCH 01/28] =?UTF-8?q?accordion:=20=D1=81=D1=82=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F,=20=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D1=81=D1=8B,=20=D0=BE=D0=B1=D1=91=D1=80=D1=82?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../accordion/accordion.component.ts | 44 ++++ .../tokens/components/accordion.ts | 30 +++ src/prime-preset/tokens/tokens.json | 2 +- .../components/accordion/accordion.stories.ts | 245 ++++++++++++++++++ .../examples/accordion-disabled.component.ts | 42 +++ .../examples/accordion-multiple.component.ts | 41 +++ 6 files changed, 403 insertions(+), 1 deletion(-) create mode 100644 src/lib/components/accordion/accordion.component.ts create mode 100644 src/prime-preset/tokens/components/accordion.ts create mode 100644 src/stories/components/accordion/accordion.stories.ts create mode 100644 src/stories/components/accordion/examples/accordion-disabled.component.ts create mode 100644 src/stories/components/accordion/examples/accordion-multiple.component.ts diff --git a/src/lib/components/accordion/accordion.component.ts b/src/lib/components/accordion/accordion.component.ts new file mode 100644 index 00000000..98ab31ea --- /dev/null +++ b/src/lib/components/accordion/accordion.component.ts @@ -0,0 +1,44 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Accordion, AccordionPanel, AccordionHeader, AccordionContent } from 'primeng/accordion'; + +export interface AccordionItem { + value: string; + header: string; + content: string; + icon?: string; + disabled?: boolean; +} + +@Component({ + selector: 'accordion', + host: { style: 'display: block' }, + standalone: true, + imports: [Accordion, AccordionPanel, AccordionHeader, AccordionContent], + template: ` + + @for (item of items; track item.value) { + + +
+ @if (item.icon) { + + } + {{ item.header }} +
+
+ {{ item.content }} +
+ } +
+ `, +}) +export class AccordionComponent { + @Input() items: AccordionItem[] = []; + @Input() multiple = false; + @Input() activeValue: string | null = '0'; + @Output() activeValueChange = new EventEmitter(); +} diff --git a/src/prime-preset/tokens/components/accordion.ts b/src/prime-preset/tokens/components/accordion.ts new file mode 100644 index 00000000..b9b38aa9 --- /dev/null +++ b/src/prime-preset/tokens/components/accordion.ts @@ -0,0 +1,30 @@ +/** + * Кастомная CSS-стилизация для компонента p-accordion. + * Подключается в map-tokens.ts: `import { accordionCss } from './tokens/components/accordion'` + */ +export const accordionCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Шрифт заголовка ─── */ + .p-accordionheader { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + } + + /* ─── Размер иконки toggle ─── */ + .p-accordionheader-toggle-icon, + .p-accordionpanel:not(.p-disabled).p-accordionpanel-active > .p-accordionheader .p-accordionheader-toggle-icon { + width: ${dt('accordion.extend.extHeader.iconSize')}; + height: ${dt('accordion.extend.extHeader.iconSize')}; + } + + /* ─── Лэйаут заголовка: иконка + текст ─── */ + .p-accordionheader > div { + display: flex; + align-items: center; + gap: ${dt('accordion.extend.extHeader.gap')}; + } + + /* ─── Размер иконки в заголовке ─── */ + .p-accordionheader > div > i { + font-size: ${dt('accordion.extend.extHeader.iconSize')}; + } +`; diff --git a/src/prime-preset/tokens/tokens.json b/src/prime-preset/tokens/tokens.json index f9ea56a8..46f6cdf6 100644 --- a/src/prime-preset/tokens/tokens.json +++ b/src/prime-preset/tokens/tokens.json @@ -1371,7 +1371,7 @@ "transitionDuration": "{controls.transitionDuration}" }, "panel": { - "borderWidth": "{borderWidth.none} {borderWidth.none} {navigation.width.200} {borderWidth.none}", + "borderWidth": "{borderWidth.none} {borderWidth.none} {navigation.width.100} {borderWidth.none}", "borderColor": "{form.borderColor}" }, "content": { diff --git a/src/stories/components/accordion/accordion.stories.ts b/src/stories/components/accordion/accordion.stories.ts new file mode 100644 index 00000000..b99f1a43 --- /dev/null +++ b/src/stories/components/accordion/accordion.stories.ts @@ -0,0 +1,245 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { AccordionComponent, AccordionItem } from '../../../lib/components/accordion/accordion.component'; +import { AccordionMultipleComponent } from './examples/accordion-multiple.component'; +import { AccordionDisabledComponent } from './examples/accordion-disabled.component'; + +const defaultItems: AccordionItem[] = [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Отправитель: ООО «Логистика+»', + }, + { + value: '1', + header: 'Маршрут доставки', + icon: 'ti ti-map-pin', + content: 'Принят в Москве 14 апр 09:15 → Сортировочный центр 14 апр 14:30 → Передан перевозчику → Прибыл в Новосибирск 15 апр 08:00 → Доставлен 15 апр 14:20', + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Стоимость доставки: 450 ₽ · НДС: 75 ₽ · Итого: 525 ₽ · Оплачено: карта *4321', + }, +]; + +const commonTemplate = ` + +`; + +const meta: Meta = { + title: 'Components/Panel/Accordion', + component: AccordionComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + AccordionComponent, + AccordionMultipleComponent, + AccordionDisabledComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Группирует контент в раскрывающиеся панели. Поддерживает одиночное и множественное раскрытие. + +\`\`\`typescript +import { AccordionModule } from 'primeng/accordion'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-accordion' }, + }, + argTypes: { + multiple: { + control: 'boolean', + description: 'Позволяет открывать несколько панелей одновременно', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + activeValue: { + control: 'text', + description: 'Значение активной панели (value из AccordionItem)', + table: { + category: 'Props', + defaultValue: { summary: "'0'" }, + type: { summary: 'string | null' }, + }, + }, + items: { + table: { disable: true }, + }, + activeValueChange: { + control: false, + description: 'Событие смены активной панели', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, + args: { + items: defaultItems, + multiple: false, + activeValue: '0', + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = ['[items]="items"']; + + if (args.multiple) parts.push(`[multiple]="true"`); + if (args.activeValue && args.activeValue !== '0') parts.push(`activeValue="${args.activeValue}"`); + + const template = ``; + + return { props: args, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Stories ────────────────────────────────────────────────────────────────── + +export const Multiple: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { multiple: true, activeValue: '0' }, + parameters: { + docs: { + description: { story: 'Режим множественного раскрытия — несколько панелей могут быть открыты одновременно.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { AccordionComponent, AccordionItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-accordion-multiple', + standalone: true, + imports: [AccordionComponent], + template: \` + + \`, +}) +export class AccordionMultipleComponent { + items: AccordionItem[] = [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места', + }, + { + value: '1', + header: 'Маршрут доставки', + icon: 'ti ti-map-pin', + content: 'Принят в Москве 14 апр 09:15 → Доставлен 15 апр 14:20', + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Итого: 525 ₽ · Оплачено: карта *4321', + }, + ]; +} + `, + }, + }, + }, +}; + +export const Disabled: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { + items: [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Отправитель: ООО «Логистика+»', + }, + { + value: '1', + header: 'Документы (недоступно)', + icon: 'ti ti-file-description', + content: 'Документация по отправлению временно недоступна.', + disabled: true, + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Стоимость доставки: 450 ₽ · НДС: 75 ₽ · Итого: 525 ₽ · Оплачено: карта *4321', + }, + ], + multiple: false, + activeValue: '0', + }, + parameters: { + docs: { + description: { story: 'Заблокированная панель — элемент недоступен для взаимодействия.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { AccordionComponent, AccordionItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-accordion-disabled', + standalone: true, + imports: [AccordionComponent], + template: \` + + \`, +}) +export class AccordionDisabledComponent { + items: AccordionItem[] = [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места', + }, + { + value: '1', + header: 'Документы (недоступно)', + icon: 'ti ti-file-description', + content: 'Документация по отправлению временно недоступна.', + disabled: true, + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Итого: 525 ₽ · Оплачено: карта *4321', + }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/accordion/examples/accordion-disabled.component.ts b/src/stories/components/accordion/examples/accordion-disabled.component.ts new file mode 100644 index 00000000..b6a16441 --- /dev/null +++ b/src/stories/components/accordion/examples/accordion-disabled.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { AccordionComponent, AccordionItem } from '../../../../lib/components/accordion/accordion.component'; + +const items: AccordionItem[] = [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Отправитель: ООО «Логистика+»', + }, + { + value: '1', + header: 'Документы (недоступно)', + icon: 'ti ti-file-description', + content: 'Документация по отправлению временно недоступна.', + disabled: true, + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Стоимость доставки: 450 ₽ · НДС: 75 ₽ · Итого: 525 ₽ · Оплачено: карта *4321', + }, +]; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-accordion-disabled', + standalone: true, + imports: [AccordionComponent], + template, + styles, +}) +export class AccordionDisabledComponent { + items = items; +} diff --git a/src/stories/components/accordion/examples/accordion-multiple.component.ts b/src/stories/components/accordion/examples/accordion-multiple.component.ts new file mode 100644 index 00000000..a6399aaa --- /dev/null +++ b/src/stories/components/accordion/examples/accordion-multiple.component.ts @@ -0,0 +1,41 @@ +import { Component } from '@angular/core'; +import { AccordionComponent, AccordionItem } from '../../../../lib/components/accordion/accordion.component'; + +const items: AccordionItem[] = [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Отправитель: ООО «Логистика+»', + }, + { + value: '1', + header: 'Маршрут доставки', + icon: 'ti ti-map-pin', + content: 'Принят в Москве 14 апр 09:15 → Сортировочный центр 14 апр 14:30 → Передан перевозчику → Прибыл в Новосибирск 15 апр 08:00 → Доставлен 15 апр 14:20', + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Стоимость доставки: 450 ₽ · НДС: 75 ₽ · Итого: 525 ₽ · Оплачено: карта *4321', + }, +]; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-accordion-multiple', + standalone: true, + imports: [AccordionComponent], + template, + styles, +}) +export class AccordionMultipleComponent { + items = items; +} From 4032f6f1b0bd2971b65f125d6ae3b3ab3fae00bc Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Thu, 9 Apr 2026 17:04:32 +0700 Subject: [PATCH 02/28] =?UTF-8?q?confirm-dialog:=20=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F,=20=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D1=81=D1=8B,=20=D0=BE=D0=B1=D1=91=D1=80?= =?UTF-8?q?=D1=82=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../confirm-dialog.component.ts | 73 +++++ src/prime-preset/map-tokens.ts | 5 + .../tokens/components/confirm-dialog.ts | 59 ++++ src/prime-preset/tokens/components/dialog.ts | 8 +- .../confirm-dialog/confirm-dialog.stories.ts | 262 ++++++++++++++++++ .../confirm-dialog-default.component.ts | 40 +++ .../confirm-dialog-severities.component.ts | 114 ++++++++ .../confirm-dialog-sizes.component.ts | 68 +++++ 8 files changed, 625 insertions(+), 4 deletions(-) create mode 100644 src/lib/components/confirm-dialog/confirm-dialog.component.ts create mode 100644 src/prime-preset/tokens/components/confirm-dialog.ts create mode 100644 src/stories/components/confirm-dialog/confirm-dialog.stories.ts create mode 100644 src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts create mode 100644 src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts create mode 100644 src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts diff --git a/src/lib/components/confirm-dialog/confirm-dialog.component.ts b/src/lib/components/confirm-dialog/confirm-dialog.component.ts new file mode 100644 index 00000000..f0cca136 --- /dev/null +++ b/src/lib/components/confirm-dialog/confirm-dialog.component.ts @@ -0,0 +1,73 @@ +import { Component, Input } from '@angular/core'; +import { ConfirmDialog } from 'primeng/confirmdialog'; +import { Button } from 'primeng/button'; +import { PrimeTemplate } from 'primeng/api'; + +export type ConfirmDialogSize = 'sm' | 'default' | 'lg' | 'xlg'; +export type ConfirmDialogSeverity = 'success' | 'info' | 'warn' | 'help' | 'danger' | 'default'; + +@Component({ + selector: 'confirm-dialog', + host: { style: 'display: contents' }, + standalone: true, + imports: [ConfirmDialog, Button, PrimeTemplate], + template: ` + + +
+
+ + {{ message.header }} +
+ +
+
+

{{ message.message }}

+
+ +
+
+ `, +}) +export class ConfirmDialogComponent { + @Input() key = ''; + @Input() size: ConfirmDialogSize = 'default'; + @Input() severity: ConfirmDialogSeverity = 'default'; + + get computedClass(): string { + const classes: string[] = []; + if (this.size === 'sm') classes.push('p-confirmdialog-sm'); + else if (this.size === 'lg') classes.push('p-confirmdialog-lg'); + else if (this.size === 'xlg') classes.push('p-confirmdialog-xlg'); + + const severityMap: Record = { + success: 'p-confirm-dialog-accept', + info: 'p-confirm-dialog-info', + warn: 'p-confirm-dialog-warn', + help: 'p-confirm-dialog-help', + danger: 'p-confirm-dialog-error', + default: '', + }; + if (severityMap[this.severity]) classes.push(severityMap[this.severity]); + + return classes.join(' '); + } +} diff --git a/src/prime-preset/map-tokens.ts b/src/prime-preset/map-tokens.ts index 6f2975f5..2b695ba7 100644 --- a/src/prime-preset/map-tokens.ts +++ b/src/prime-preset/map-tokens.ts @@ -4,6 +4,7 @@ import type { AuraBaseDesignTokens } from '@primeuix/themes/aura/base'; import tokens from './tokens/tokens.json'; import { buttonCss } from './tokens/components/button'; +import { confirmDialogCss } from './tokens/components/confirm-dialog'; import { dialogCss } from './tokens/components/dialog'; const presetTokens: Preset = { @@ -15,6 +16,10 @@ const presetTokens: Preset = { ...(tokens.components.button as unknown as ComponentsDesignTokens['button']), css: buttonCss, }, + confirmdialog: { + ...(tokens.components.confirmdialog as unknown as ComponentsDesignTokens['confirmdialog']), + css: confirmDialogCss, + }, dialog: { ...(tokens.components.dialog as unknown as ComponentsDesignTokens['dialog']), css: dialogCss, diff --git a/src/prime-preset/tokens/components/confirm-dialog.ts b/src/prime-preset/tokens/components/confirm-dialog.ts new file mode 100644 index 00000000..f0aacab7 --- /dev/null +++ b/src/prime-preset/tokens/components/confirm-dialog.ts @@ -0,0 +1,59 @@ +export const confirmDialogCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* Иконка в заголовке */ + .p-confirmdialog .p-dialog-title { + display: flex; + align-items: center; + gap: ${dt('dialog.header.gap')}; + } + + .p-confirmdialog .p-dialog-title .p-icon { + width: ${dt('confirmdialog.icon.size')}; + height: ${dt('confirmdialog.icon.size')}; + font-size: ${dt('confirmdialog.icon.size')}; + flex-shrink: 0; + } + + /* Размеры */ + .p-confirmdialog.p-dialog { + width: ${dt('overlay.width')}; + } + + .p-confirmdialog-sm.p-dialog { + width: ${dt('sizing.80x')}; + } + + .p-confirmdialog-lg.p-dialog { + width: ${dt('sizing.120x')}; + } + + .p-confirmdialog-xlg.p-dialog { + width: ${dt('sizing.128x')}; + } + + /* Цвета иконок по severity */ + .p-confirmdialog[data-pc-severity="success"] .p-dialog-title .p-icon, + .p-confirmdialog.p-confirm-dialog-accept .p-dialog-title .p-icon { + color: ${dt('confirmdialog.extend.extIcon.success')}; + } + + .p-confirmdialog[data-pc-severity="info"] .p-dialog-title .p-icon, + .p-confirmdialog.p-confirm-dialog-info .p-dialog-title .p-icon { + color: ${dt('confirmdialog.extend.extIcon.info')}; + } + + .p-confirmdialog[data-pc-severity="warn"] .p-dialog-title .p-icon, + .p-confirmdialog.p-confirm-dialog-warn .p-dialog-title .p-icon { + color: ${dt('confirmdialog.extend.extIcon.warn')}; + } + + .p-confirmdialog[data-pc-severity="help"] .p-dialog-title .p-icon, + .p-confirmdialog.p-confirm-dialog-help .p-dialog-title .p-icon { + color: ${dt('confirmdialog.extend.extIcon.help')}; + } + + .p-confirmdialog[data-pc-severity="danger"] .p-dialog-title .p-icon, + .p-confirmdialog[data-pc-severity="error"] .p-dialog-title .p-icon, + .p-confirmdialog.p-confirm-dialog-error .p-dialog-title .p-icon { + color: ${dt('confirmdialog.extend.extIcon.danger')}; + } +`; diff --git a/src/prime-preset/tokens/components/dialog.ts b/src/prime-preset/tokens/components/dialog.ts index c157e61e..7426012c 100644 --- a/src/prime-preset/tokens/components/dialog.ts +++ b/src/prime-preset/tokens/components/dialog.ts @@ -36,18 +36,18 @@ export const dialogCss = ({ dt }: { dt: (token: string) => string }): string => } .p-dialog { - width: ${dt('sizing.80x')}; + width: ${dt('overlay.width')}; } .p-dialog.p-component.p-dialog-sm { - width: ${dt('overlay.sm.width')}; + width: ${dt('sizing.80x')}; } .p-dialog.p-component.p-dialog-lg { - width: ${dt('overlay.lg.width')}; + width: ${dt('sizing.120x')}; } .p-dialog.p-component.p-dialog-xlg { - width: ${dt('overlay.xlg.width')}; + width: ${dt('sizing.128x')}; } `; diff --git a/src/stories/components/confirm-dialog/confirm-dialog.stories.ts b/src/stories/components/confirm-dialog/confirm-dialog.stories.ts new file mode 100644 index 00000000..399c7634 --- /dev/null +++ b/src/stories/components/confirm-dialog/confirm-dialog.stories.ts @@ -0,0 +1,262 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ConfirmDialogComponent } from '../../../lib/components/confirm-dialog/confirm-dialog.component'; +import { ConfirmDialogDefaultComponent } from './examples/confirm-dialog-default.component'; +import { ConfirmDialogSeveritiesComponent } from './examples/confirm-dialog-severities.component'; +import { ConfirmDialogSizesComponent } from './examples/confirm-dialog-sizes.component'; + +const meta: Meta = { + title: 'Components/Overlay/ConfirmDialog', + component: ConfirmDialogComponent, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: `Компонент для подтверждения действий пользователя. Требует подключения \`ConfirmationService\`. + +\`\`\`typescript +import { ConfirmDialogComponent } from '@cdek-it/angular-ui-kit'; +import { ConfirmationService } from 'primeng/api'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-confirmdialog' }, + }, + argTypes: { + key: { + control: 'text', + description: 'Идентификатор группы для адресной отправки сообщений.', + table: { + category: 'Props', + type: { summary: 'string' }, + }, + }, + size: { + control: 'select', + options: ['sm', 'default', 'lg', 'xlg'], + description: 'Размер диалога', + table: { + category: 'Props', + defaultValue: { summary: 'default' }, + type: { summary: "'sm' | 'default' | 'lg' | 'xlg'" }, + }, + }, + severity: { + control: 'select', + options: ['default', 'success', 'info', 'warn', 'help', 'danger'], + description: 'Цветовая схема иконки в заголовке', + table: { + category: 'Props', + defaultValue: { summary: 'default' }, + type: { summary: "'default' | 'success' | 'info' | 'warn' | 'help' | 'danger'" }, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'ConfirmDialog', + decorators: [moduleMetadata({ imports: [ConfirmDialogDefaultComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Базовый пример диалога подтверждения.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { Button } from 'primeng/button'; +import { ConfirmationService } from 'primeng/api'; +import { ConfirmDialogComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-confirm-dialog-default', + standalone: true, + imports: [ConfirmDialogComponent, Button], + providers: [ConfirmationService], + template: \` + + + \`, +}) +export class ConfirmDialogDefaultComponent { + constructor(private confirmationService: ConfirmationService) {} + + showConfirm(): void { + this.confirmationService.confirm({ + key: 'cd-default', + message: 'Вы уверены, что хотите продолжить?', + header: 'Подтверждение', + icon: 'ti ti-alert-triangle', + acceptLabel: 'Да', + rejectLabel: 'Нет', + rejectButtonProps: { + severity: 'secondary', + text: true, + }, + accept: () => {}, + reject: () => {}, + }); + } +} + `, + }, + }, + }, +}; + +// ── Severities ──────────────────────────────────────────────────────────────── + +export const Severities: Story = { + name: 'Severities', + decorators: [moduleMetadata({ imports: [ConfirmDialogSeveritiesComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Варианты диалога с различными уровнями важности: success, info, warn, help, danger.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { Button } from 'primeng/button'; +import { ConfirmationService } from 'primeng/api'; +import { ConfirmDialogComponent } from '@cdek-it/angular-ui-kit'; + +const SEVERITIES = [ + { type: 'success', buttonSeverity: 'success', icon: 'ti ti-circle-check', label: 'Успех', header: 'Успех', message: 'Операция выполнена успешно.', acceptLabel: 'OK' }, + { type: 'info', buttonSeverity: 'info', icon: 'ti ti-info-circle', label: 'Информация', header: 'Информация', message: 'Это информационное сообщение.', acceptLabel: 'Понятно' }, + { type: 'warn', buttonSeverity: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение', header: 'Предупреждение', message: 'Внимание! Это действие может иметь последствия.', acceptLabel: 'Продолжить' }, + { type: 'help', buttonSeverity: 'help', icon: 'ti ti-help-circle', label: 'Справка', header: 'Справка', message: 'Нужна помощь с этим действием?', acceptLabel: 'Да' }, + { type: 'danger', buttonSeverity: 'danger', icon: 'ti ti-circle-x', label: 'Удаление', header: 'Подтверждение', message: 'Это действие нельзя отменить. Продолжить?', acceptLabel: 'Удалить' }, +]; + +@Component({ + selector: 'app-confirm-dialog-severities', + standalone: true, + imports: [ConfirmDialogComponent, Button], + providers: [ConfirmationService], + template: \` + + + + + + +
+ @for (severity of severities; track severity.type) { + + } +
+ \`, +}) +export class ConfirmDialogSeveritiesComponent { + severities = SEVERITIES; + constructor(private confirmationService: ConfirmationService) {} + + showConfirm(severity: any): void { + this.confirmationService.confirm({ + key: 'cd-severity-' + severity.type, + message: severity.message, + header: severity.header, + icon: severity.icon, + acceptLabel: severity.acceptLabel, + rejectLabel: 'Нет', + acceptButtonProps: { severity: severity.buttonSeverity }, + rejectButtonProps: { + severity: 'secondary', + text: true, + }, + accept: () => {}, + reject: () => {}, + }); + } +} + `, + }, + }, + }, +}; + +// ── Sizes ───────────────────────────────────────────────────────────────────── + +export const Sizes: Story = { + name: 'Sizes', + decorators: [moduleMetadata({ imports: [ConfirmDialogSizesComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Доступные размеры диалога: sm, base, lg, xlg.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { Button } from 'primeng/button'; +import { ConfirmationService } from 'primeng/api'; +import { ConfirmDialogComponent } from '@cdek-it/angular-ui-kit'; + +const SIZES = [ + { key: 'sm', label: 'Small' }, + { key: 'default', label: 'Base' }, + { key: 'lg', label: 'Large' }, + { key: 'xlg', label: 'Extra Large' }, +]; + +@Component({ + selector: 'app-confirm-dialog-sizes', + standalone: true, + imports: [ConfirmDialogComponent, Button], + providers: [ConfirmationService], + template: \` + + + + + +
+ @for (size of sizes; track size.key) { + + } +
+ \`, +}) +export class ConfirmDialogSizesComponent { + sizes = SIZES; + constructor(private confirmationService: ConfirmationService) {} + + showConfirm(size: any): void { + this.confirmationService.confirm({ + key: 'cd-size-' + size.key, + message: 'Это диалог размера ' + size.label, + header: 'Подтверждение', + icon: 'ti ti-alert-triangle', + acceptLabel: 'Да', + rejectLabel: 'Нет', + rejectButtonProps: { + severity: 'secondary', + text: true, + }, + accept: () => {}, + reject: () => {}, + }); + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts new file mode 100644 index 00000000..5c812da3 --- /dev/null +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts @@ -0,0 +1,40 @@ +import { Component } from '@angular/core'; +import { Button } from 'primeng/button'; +import { ConfirmationService } from 'primeng/api'; +import { ConfirmDialogComponent } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; + +const template = ` +
+ + + +
+`; + +@Component({ + selector: 'app-confirm-dialog-default', + standalone: true, + imports: [ConfirmDialogComponent, Button], + providers: [ConfirmationService], + template, +}) +export class ConfirmDialogDefaultComponent { + constructor(private confirmationService: ConfirmationService) {} + + showConfirm(): void { + this.confirmationService.confirm({ + key: 'cd-default', + message: 'Вы уверены, что хотите продолжить?', + header: 'Подтверждение', + icon: 'ti ti-alert-triangle', + acceptLabel: 'Да', + rejectLabel: 'Нет', + rejectButtonProps: { + severity: 'secondary', + text: true, + }, + accept: () => {}, + reject: () => {}, + }); + } +} diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts new file mode 100644 index 00000000..c3cd8542 --- /dev/null +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts @@ -0,0 +1,114 @@ +import { Component } from '@angular/core'; +import { Button } from 'primeng/button'; +import { ConfirmationService } from 'primeng/api'; +import { ConfirmDialogComponent } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; + +interface SeverityItem { + type: 'success' | 'info' | 'warn' | 'help' | 'danger'; + buttonSeverity: 'success' | 'info' | 'warn' | 'help' | 'danger'; + icon: string; + label: string; + header: string; + message: string; + acceptLabel: string; +} + +const SEVERITIES: SeverityItem[] = [ + { + type: 'success', + buttonSeverity: 'success', + icon: 'ti ti-circle-check', + label: 'Успех', + header: 'Успех', + message: 'Операция выполнена успешно.', + acceptLabel: 'OK', + }, + { + type: 'info', + buttonSeverity: 'info', + icon: 'ti ti-info-circle', + label: 'Информация', + header: 'Информация', + message: 'Это информационное сообщение.', + acceptLabel: 'Понятно', + }, + { + type: 'warn', + buttonSeverity: 'warn', + icon: 'ti ti-alert-triangle', + label: 'Предупреждение', + header: 'Предупреждение', + message: 'Внимание! Это действие может иметь последствия.', + acceptLabel: 'Продолжить', + }, + { + type: 'help', + buttonSeverity: 'help', + icon: 'ti ti-help-circle', + label: 'Справка', + header: 'Справка', + message: 'Нужна помощь с этим действием?', + acceptLabel: 'Да', + }, + { + type: 'danger', + buttonSeverity: 'danger', + icon: 'ti ti-circle-x', + label: 'Удаление', + header: 'Подтверждение', + message: 'Это действие нельзя отменить. Продолжить?', + acceptLabel: 'Удалить', + }, +]; + +const template = ` +
+ + + + + + +
+ @for (severity of severities; track severity.type) { + + } +
+
+`; + +@Component({ + selector: 'app-confirm-dialog-severities', + standalone: true, + imports: [ConfirmDialogComponent, Button], + providers: [ConfirmationService], + template, +}) +export class ConfirmDialogSeveritiesComponent { + severities = SEVERITIES; + + constructor(private confirmationService: ConfirmationService) {} + + showConfirm(severity: SeverityItem): void { + this.confirmationService.confirm({ + key: 'cd-severity-' + severity.type, + message: severity.message, + header: severity.header, + icon: severity.icon, + acceptLabel: severity.acceptLabel, + rejectLabel: 'Нет', + acceptButtonProps: { severity: severity.buttonSeverity }, + rejectButtonProps: { + severity: 'secondary', + text: true, + }, + accept: () => {}, + reject: () => {}, + }); + } +} diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts new file mode 100644 index 00000000..a2dcfada --- /dev/null +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts @@ -0,0 +1,68 @@ +import { Component } from '@angular/core'; +import { Button } from 'primeng/button'; +import { ConfirmationService } from 'primeng/api'; +import { + ConfirmDialogComponent, + ConfirmDialogSize, +} from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; + +interface SizeItem { + key: ConfirmDialogSize; + label: string; +} + +const SIZES: SizeItem[] = [ + { key: 'sm', label: 'Small' }, + { key: 'default', label: 'Base' }, + { key: 'lg', label: 'Large' }, + { key: 'xlg', label: 'Extra Large' }, +]; + +const template = ` +
+ + + + + +
+ @for (size of sizes; track size.key) { + + } +
+
+`; + +@Component({ + selector: 'app-confirm-dialog-sizes', + standalone: true, + imports: [ConfirmDialogComponent, Button], + providers: [ConfirmationService], + template, +}) +export class ConfirmDialogSizesComponent { + sizes = SIZES; + + constructor(private confirmationService: ConfirmationService) {} + + showConfirm(size: SizeItem): void { + this.confirmationService.confirm({ + key: 'cd-size-' + size.key, + message: 'Это диалог размера ' + size.label, + header: 'Подтверждение', + icon: 'ti ti-alert-triangle', + acceptLabel: 'Да', + rejectLabel: 'Нет', + rejectButtonProps: { + severity: 'secondary', + text: true, + }, + accept: () => {}, + reject: () => {}, + }); + } +} From b9db1e583cec4b0cc53a559e2e6da07959bdde64 Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Thu, 9 Apr 2026 20:34:00 +0700 Subject: [PATCH 03/28] =?UTF-8?q?data-table:=20=D1=81=D1=82=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F,=20=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D1=81=D1=8B,=20=D0=BE=D0=B1=D1=91=D1=80=D1=82?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data-table/data-table.component.ts | 94 +++ src/prime-preset/map-tokens.ts | 5 + .../tokens/components/data-table.ts | 28 + .../data-table/data-table.stories.ts | 569 ++++++++++++++++++ .../examples/data-table-default.component.ts | 38 ++ .../data-table-grid-lines.component.ts | 35 ++ .../data-table-pagination.component.ts | 44 ++ .../data-table-scroll-horizontal.component.ts | 37 ++ .../data-table-scroll-vertical.component.ts | 42 ++ .../data-table-selectable.component.ts | 42 ++ ...data-table-selection-checkbox.component.ts | 42 ++ .../data-table-selection-radio.component.ts | 42 ++ .../data-table-striped-rows.component.ts | 35 ++ 13 files changed, 1053 insertions(+) create mode 100644 src/lib/components/data-table/data-table.component.ts create mode 100644 src/prime-preset/tokens/components/data-table.ts create mode 100644 src/stories/components/data-table/data-table.stories.ts create mode 100644 src/stories/components/data-table/examples/data-table-default.component.ts create mode 100644 src/stories/components/data-table/examples/data-table-grid-lines.component.ts create mode 100644 src/stories/components/data-table/examples/data-table-pagination.component.ts create mode 100644 src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts create mode 100644 src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts create mode 100644 src/stories/components/data-table/examples/data-table-selectable.component.ts create mode 100644 src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts create mode 100644 src/stories/components/data-table/examples/data-table-selection-radio.component.ts create mode 100644 src/stories/components/data-table/examples/data-table-striped-rows.component.ts diff --git a/src/lib/components/data-table/data-table.component.ts b/src/lib/components/data-table/data-table.component.ts new file mode 100644 index 00000000..798bf685 --- /dev/null +++ b/src/lib/components/data-table/data-table.component.ts @@ -0,0 +1,94 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { TableModule } from 'primeng/table'; +import { PrimeTemplate } from 'primeng/api'; + +export interface DataTableColumn { + field?: string; + header?: string; + sortable?: boolean; + style?: string; + headerStyle?: string; + selectionMode?: 'single' | 'multiple'; +} + +@Component({ + selector: 'data-table', + host: { style: 'display: block' }, + standalone: true, + imports: [TableModule, PrimeTemplate], + template: ` + + + + @for (col of columns; track $index) { + @if (col.selectionMode === 'single') { + + } @else if (col.selectionMode === 'multiple') { + + + + } @else if (col.sortable) { + + {{ col.header }} + + + } @else { + {{ col.header }} + } + } + + + + + + @for (col of columns; track $index) { + @if (col.selectionMode === 'single') { + + + + } @else if (col.selectionMode === 'multiple') { + + + + } @else { + {{ rowData[col.field] }} + } + } + + + + `, +}) +export class DataTableComponent { + @Input() value: any[] = []; + @Input() columns: DataTableColumn[] = []; + @Input() stripedRows = false; + @Input() showGridlines = false; + @Input() loading = false; + @Input() size: 'small' | 'large' | undefined = undefined; + @Input() scrollable = false; + @Input() scrollHeight = ''; + @Input() paginator = false; + @Input() rows = 5; + @Input() rowsPerPageOptions: number[] = [5, 10, 25]; + @Input() selectionMode: 'single' | 'multiple' | undefined = undefined; + @Input() selection: any = null; + @Output() selectionChange = new EventEmitter(); + @Input() dataKey = 'id'; +} diff --git a/src/prime-preset/map-tokens.ts b/src/prime-preset/map-tokens.ts index 39627587..7fbaefa1 100644 --- a/src/prime-preset/map-tokens.ts +++ b/src/prime-preset/map-tokens.ts @@ -4,6 +4,7 @@ import type { AuraBaseDesignTokens } from '@primeuix/themes/aura/base'; import tokens from './tokens/tokens.json'; import { buttonCss } from './tokens/components/button'; +import { dataTableCss } from './tokens/components/data-table'; const presetTokens: Preset = { primitive: tokens.primitive as unknown as AuraBaseDesignTokens['primitive'], @@ -14,6 +15,10 @@ const presetTokens: Preset = { ...(tokens.components.button as unknown as ComponentsDesignTokens['button']), css: buttonCss, }, + datatable: { + ...(tokens.components.datatable as unknown as ComponentsDesignTokens['datatable']), + css: dataTableCss, + }, } as ComponentsDesignTokens, }; diff --git a/src/prime-preset/tokens/components/data-table.ts b/src/prime-preset/tokens/components/data-table.ts new file mode 100644 index 00000000..f770594c --- /dev/null +++ b/src/prime-preset/tokens/components/data-table.ts @@ -0,0 +1,28 @@ +export const dataTableCss = ({ dt }: { dt: (token: string) => string }): string => ` + .p-datatable .p-datatable-thead > tr > th { + font-weight: ${dt('datatable.columnTitle.fontWeight')}; + } + + .p-datatable .p-datatable-tfoot > tr > td { + font-weight: ${dt('datatable.columnFooter.fontWeight')}; + } + + .p-datatable .p-datatable-sort-icon { + width: ${dt('datatable.sortIcon.size')}; + height: ${dt('datatable.sortIcon.size')}; + } + + .p-datatable .p-datatable-loading-icon { + width: ${dt('datatable.loadingIcon.size')}; + height: ${dt('datatable.loadingIcon.size')}; + } + + .p-datatable .p-datatable-row-toggle-button { + width: ${dt('datatable.rowToggleButton.size')}; + height: ${dt('datatable.rowToggleButton.size')}; + } + + .p-datatable .p-datatable-sortable-column:not(.p-datatable-column-sorted):hover { + color: ${dt('datatable.headerCell.hoverColor')}; + } +`; diff --git a/src/stories/components/data-table/data-table.stories.ts b/src/stories/components/data-table/data-table.stories.ts new file mode 100644 index 00000000..6ea5f7f4 --- /dev/null +++ b/src/stories/components/data-table/data-table.stories.ts @@ -0,0 +1,569 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { DataTableComponent } from '../../../lib/components/data-table/data-table.component'; +import { DataTableDefaultComponent } from './examples/data-table-default.component'; +import { DataTableStripedRowsComponent } from './examples/data-table-striped-rows.component'; +import { DataTableSelectableComponent } from './examples/data-table-selectable.component'; +import { DataTableGridLinesComponent } from './examples/data-table-grid-lines.component'; +import { DataTablePaginationComponent } from './examples/data-table-pagination.component'; +import { DataTableSelectionRadioComponent } from './examples/data-table-selection-radio.component'; +import { DataTableSelectionCheckboxComponent } from './examples/data-table-selection-checkbox.component'; +import { DataTableScrollVerticalComponent } from './examples/data-table-scroll-vertical.component'; +import { DataTableScrollHorizontalComponent } from './examples/data-table-scroll-horizontal.component'; + +const meta: Meta = { + title: 'Components/Data/DataTable', + component: DataTableComponent, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: `Таблица данных с поддержкой сортировки, пагинации, выбора строк и прокрутки. + +\`\`\`typescript +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-datatable' }, + }, + argTypes: { + value: { + control: false, + description: 'Массив данных для отображения в таблице.', + table: { + category: 'Props', + type: { summary: 'any[]' }, + }, + }, + columns: { + control: false, + description: 'Определения столбцов таблицы.', + table: { + category: 'Props', + type: { summary: 'DataTableColumn[]' }, + }, + }, + stripedRows: { + control: 'boolean', + description: 'Чередование цвета строк.', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + showGridlines: { + control: 'boolean', + description: 'Отображение сетки между ячейками.', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + loading: { + control: 'boolean', + description: 'Отображает индикатор загрузки.', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + size: { + control: 'select', + options: ['small', 'large', undefined], + description: 'Размер таблицы.', + table: { + category: 'Props', + defaultValue: { summary: 'undefined (normal)' }, + type: { summary: "'small' | 'large' | undefined" }, + }, + }, + scrollable: { + control: 'boolean', + description: 'Включает прокрутку таблицы.', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + scrollHeight: { + control: 'text', + description: 'Высота области прокрутки (например "400px"). Работает только при scrollable=true.', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + paginator: { + control: 'boolean', + description: 'Включает пагинацию.', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + rows: { + control: 'number', + description: 'Количество строк на странице (при paginator=true).', + table: { + category: 'Props', + defaultValue: { summary: '5' }, + type: { summary: 'number' }, + }, + }, + selectionMode: { + control: 'select', + options: ['single', 'multiple', undefined], + description: 'Режим выбора строк.', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: "'single' | 'multiple' | undefined" }, + }, + }, + dataKey: { + control: 'text', + description: 'Поле объекта, используемое как уникальный идентификатор строки.', + table: { + category: 'Props', + defaultValue: { summary: "'id'" }, + type: { summary: 'string' }, + }, + }, + selectionChange: { + control: false, + description: 'Событие изменения выбранных строк.', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'DataTable', + decorators: [moduleMetadata({ imports: [DataTableDefaultComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Базовая таблица отправлений с сортировкой по всем столбцам.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-data-table-default', + standalone: true, + imports: [DataTableComponent], + template: \` + + \`, +}) +export class DataTableDefaultComponent { + shipments = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + ]; + + columns: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, + ]; +} + `, + }, + }, + }, +}; + +// ── StripedRows ─────────────────────────────────────────────────────────────── + +export const StripedRows: Story = { + name: 'StripedRows', + decorators: [moduleMetadata({ imports: [DataTableStripedRowsComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Чередование цвета строк для улучшения читаемости.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-data-table-striped-rows', + standalone: true, + imports: [DataTableComponent], + template: \` + + \`, +}) +export class DataTableStripedRowsComponent { + shipments = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + ]; + + columns: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, + ]; +} + `, + }, + }, + }, +}; + +// ── Selectable ──────────────────────────────────────────────────────────────── + +export const Selectable: Story = { + name: 'Selectable', + decorators: [moduleMetadata({ imports: [DataTableSelectableComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Выбор строки кликом. Режим single — выбирается одна строка.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-data-table-selectable', + standalone: true, + imports: [DataTableComponent], + template: \` + + \`, +}) +export class DataTableSelectableComponent { + shipments = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + ]; + + columns: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, + ]; + + selected: any = null; +} + `, + }, + }, + }, +}; + +// ── GridLines ───────────────────────────────────────────────────────────────── + +export const GridLines: Story = { + name: 'GridLines', + decorators: [moduleMetadata({ imports: [DataTableGridLinesComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Сетка между ячейками для наглядного разграничения данных.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-data-table-grid-lines', + standalone: true, + imports: [DataTableComponent], + template: \` + + \`, +}) +export class DataTableGridLinesComponent { + shipments = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + ]; + + columns: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, + ]; +} + `, + }, + }, + }, +}; + +// ── Pagination ──────────────────────────────────────────────────────────────── + +export const Pagination: Story = { + name: 'Pagination', + decorators: [moduleMetadata({ imports: [DataTablePaginationComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Пагинация для больших наборов данных. Управление количеством строк на странице.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-data-table-pagination', + standalone: true, + imports: [DataTableComponent], + template: \` + + \`, +}) +export class DataTablePaginationComponent { + shipments = [...]; // массив отправлений + columns: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, + ]; +} + `, + }, + }, + }, +}; + +// ── SelectionRadio ──────────────────────────────────────────────────────────── + +export const SelectionRadio: Story = { + name: 'Row Selection: RadioButton', + decorators: [moduleMetadata({ imports: [DataTableSelectionRadioComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Выбор одной строки через радио-кнопку. Укажите selectionMode: "single" в первом столбце.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-data-table-selection-radio', + standalone: true, + imports: [DataTableComponent], + template: \` + + \`, +}) +export class DataTableSelectionRadioComponent { + shipments = [...]; // массив отправлений + columns: DataTableColumn[] = [ + { selectionMode: 'single' }, + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + ]; + selected: any = null; +} + `, + }, + }, + }, +}; + +// ── SelectionCheckbox ───────────────────────────────────────────────────────── + +export const SelectionCheckbox: Story = { + name: 'Row Selection: Checkbox', + decorators: [moduleMetadata({ imports: [DataTableSelectionCheckboxComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Множественный выбор строк через чекбоксы. Первый столбец с selectionMode: "multiple" добавляет чекбокс в заголовок.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-data-table-selection-checkbox', + standalone: true, + imports: [DataTableComponent], + template: \` + + \`, +}) +export class DataTableSelectionCheckboxComponent { + shipments = [...]; // массив отправлений + columns: DataTableColumn[] = [ + { selectionMode: 'multiple' }, + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + ]; + selected: any[] = []; +} + `, + }, + }, + }, +}; + +// ── ScrollVertical ──────────────────────────────────────────────────────────── + +export const ScrollVertical: Story = { + name: 'Scroll: Vertical', + decorators: [moduleMetadata({ imports: [DataTableScrollVerticalComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Вертикальная прокрутка с фиксированной высотой контейнера. Заголовок остаётся видимым.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-data-table-scroll-vertical', + standalone: true, + imports: [DataTableComponent], + template: \` + + \`, +}) +export class DataTableScrollVerticalComponent { + shipments = [...]; // большой массив отправлений + columns: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + ]; +} + `, + }, + }, + }, +}; + +// ── ScrollHorizontal ────────────────────────────────────────────────────────── + +export const ScrollHorizontal: Story = { + name: 'Scroll: Horizontal', + decorators: [moduleMetadata({ imports: [DataTableScrollHorizontalComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Горизонтальная прокрутка при большом количестве столбцов. Используйте style с min-width на столбцах.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-data-table-scroll-horizontal', + standalone: true, + imports: [DataTableComponent], + template: \` + + \`, +}) +export class DataTableScrollHorizontalComponent { + shipments = [...]; // массив отправлений + columns: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true, style: 'min-width: 160px' }, + { field: 'sender', header: 'Отправитель', sortable: true, style: 'min-width: 160px' }, + { field: 'destination', header: 'Назначение', sortable: true, style: 'min-width: 160px' }, + { field: 'status', header: 'Статус', sortable: true, style: 'min-width: 140px' }, + { field: 'weight', header: 'Вес, кг', sortable: true, style: 'min-width: 120px' }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true, style: 'min-width: 140px' }, + { field: 'dimensions', header: 'Габариты', sortable: false, style: 'min-width: 160px' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/data-table/examples/data-table-default.component.ts b/src/stories/components/data-table/examples/data-table-default.component.ts new file mode 100644 index 00000000..db4cdd08 --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-default.component.ts @@ -0,0 +1,38 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +const COLUMNS: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-default', + standalone: true, + imports: [DataTableComponent], + template, +}) +export class DataTableDefaultComponent { + shipments = SHIPMENTS; + columns = COLUMNS; +} diff --git a/src/stories/components/data-table/examples/data-table-grid-lines.component.ts b/src/stories/components/data-table/examples/data-table-grid-lines.component.ts new file mode 100644 index 00000000..2aa5bed3 --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-grid-lines.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +const COLUMNS: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-grid-lines', + standalone: true, + imports: [DataTableComponent], + template, +}) +export class DataTableGridLinesComponent { + shipments = SHIPMENTS; + columns = COLUMNS; +} diff --git a/src/stories/components/data-table/examples/data-table-pagination.component.ts b/src/stories/components/data-table/examples/data-table-pagination.component.ts new file mode 100644 index 00000000..275670db --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-pagination.component.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +const COLUMNS: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-pagination', + standalone: true, + imports: [DataTableComponent], + template, +}) +export class DataTablePaginationComponent { + shipments = SHIPMENTS; + columns = COLUMNS; +} diff --git a/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts b/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts new file mode 100644 index 00000000..3c6962b8 --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts @@ -0,0 +1,37 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', sender: 'Иванов И.И.', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200, dimensions: '30×20×15 см' }, + { id: 2, trackNumber: 'ЦД-00123457', sender: 'Петров П.П.', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450, dimensions: '10×10×10 см' }, + { id: 3, trackNumber: 'ЦД-00123458', sender: 'Сидоров С.С.', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100, dimensions: '50×40×30 см' }, + { id: 4, trackNumber: 'ЦД-00123459', sender: 'Козлов К.К.', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750, dimensions: '20×15×10 см' }, + { id: 5, trackNumber: 'ЦД-00123460', sender: 'Новиков Н.Н.', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800, dimensions: '40×30×20 см' }, +]; + +const COLUMNS: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true, style: 'min-width: 160px' }, + { field: 'sender', header: 'Отправитель', sortable: true, style: 'min-width: 160px' }, + { field: 'destination', header: 'Назначение', sortable: true, style: 'min-width: 160px' }, + { field: 'status', header: 'Статус', sortable: true, style: 'min-width: 140px' }, + { field: 'weight', header: 'Вес, кг', sortable: true, style: 'min-width: 120px' }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true, style: 'min-width: 140px' }, + { field: 'dimensions', header: 'Габариты', sortable: false, style: 'min-width: 160px' }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-scroll-horizontal', + standalone: true, + imports: [DataTableComponent], + template, +}) +export class DataTableScrollHorizontalComponent { + shipments = SHIPMENTS; + columns = COLUMNS; +} diff --git a/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts b/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts new file mode 100644 index 00000000..f819b9e3 --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; + +const BASE_SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1 }, +]; + +const COLUMNS: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-scroll-vertical', + standalone: true, + imports: [DataTableComponent], + template, +}) +export class DataTableScrollVerticalComponent { + shipments = [...BASE_SHIPMENTS, ...BASE_SHIPMENTS, ...BASE_SHIPMENTS]; + columns = COLUMNS; +} diff --git a/src/stories/components/data-table/examples/data-table-selectable.component.ts b/src/stories/components/data-table/examples/data-table-selectable.component.ts new file mode 100644 index 00000000..2b9eb35f --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-selectable.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +const COLUMNS: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-selectable', + standalone: true, + imports: [DataTableComponent], + template, +}) +export class DataTableSelectableComponent { + shipments = SHIPMENTS; + columns = COLUMNS; + selected: any = null; +} diff --git a/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts b/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts new file mode 100644 index 00000000..ea9120e5 --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, +]; + +const COLUMNS: DataTableColumn[] = [ + { selectionMode: 'multiple' }, + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-selection-checkbox', + standalone: true, + imports: [DataTableComponent], + template, +}) +export class DataTableSelectionCheckboxComponent { + shipments = SHIPMENTS; + columns = COLUMNS; + selected: any[] = []; +} diff --git a/src/stories/components/data-table/examples/data-table-selection-radio.component.ts b/src/stories/components/data-table/examples/data-table-selection-radio.component.ts new file mode 100644 index 00000000..fe6c4114 --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-selection-radio.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, +]; + +const COLUMNS: DataTableColumn[] = [ + { selectionMode: 'single' }, + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-selection-radio', + standalone: true, + imports: [DataTableComponent], + template, +}) +export class DataTableSelectionRadioComponent { + shipments = SHIPMENTS; + columns = COLUMNS; + selected: any = null; +} diff --git a/src/stories/components/data-table/examples/data-table-striped-rows.component.ts b/src/stories/components/data-table/examples/data-table-striped-rows.component.ts new file mode 100644 index 00000000..e86ca8dd --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-striped-rows.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +const COLUMNS: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-striped-rows', + standalone: true, + imports: [DataTableComponent], + template, +}) +export class DataTableStripedRowsComponent { + shipments = SHIPMENTS; + columns = COLUMNS; +} From 8020a49381e00613c5b11fd3709eea83be52a111 Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 13 Apr 2026 18:28:15 +0700 Subject: [PATCH 04/28] =?UTF-8?q?confirm-dialog:=20=D1=83=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20p-icon=20=D1=81?= =?UTF-8?q?=20=D0=B8=D0=BA=D0=BE=D0=BD=D0=BA=D0=B8=20=D1=81=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/components/confirm-dialog/confirm-dialog.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/confirm-dialog/confirm-dialog.component.ts b/src/lib/components/confirm-dialog/confirm-dialog.component.ts index f0cca136..4462b86c 100644 --- a/src/lib/components/confirm-dialog/confirm-dialog.component.ts +++ b/src/lib/components/confirm-dialog/confirm-dialog.component.ts @@ -16,7 +16,7 @@ export type ConfirmDialogSeverity = 'success' | 'info' | 'warn' | 'help' | 'dang
- + {{ message.header }}
- - + }

{{ message.message }}

- + @if (footerTemplate) { + + + } @else { + + }
`, @@ -51,6 +64,8 @@ export class ConfirmDialogComponent { @Input() key = ''; @Input() size: ConfirmDialogSize = 'default'; @Input() severity: ConfirmDialogSeverity = 'default'; + @Input() headerTemplate: TemplateRef | null = null; + @Input() footerTemplate: TemplateRef | null = null; get computedClass(): string { const classes: string[] = []; From 0480f7ae4a973039a405432c4a729957987da80d Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 13 Apr 2026 18:32:34 +0700 Subject: [PATCH 06/28] =?UTF-8?q?confirm-dialog:=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20ConfirmDialogService,=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D0=BD=D1=91=D0=BD=20ConfirmationService=20?= =?UTF-8?q?=D0=B2=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D1=80=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../confirm-dialog/confirm-dialog.service.ts | 16 ++++++++ .../confirm-dialog/confirm-dialog.stories.ts | 40 +++++++------------ .../confirm-dialog-default.component.ts | 11 ++--- .../confirm-dialog-severities.component.ts | 11 ++--- .../confirm-dialog-sizes.component.ts | 11 ++--- 5 files changed, 42 insertions(+), 47 deletions(-) create mode 100644 src/lib/components/confirm-dialog/confirm-dialog.service.ts diff --git a/src/lib/components/confirm-dialog/confirm-dialog.service.ts b/src/lib/components/confirm-dialog/confirm-dialog.service.ts new file mode 100644 index 00000000..1031258d --- /dev/null +++ b/src/lib/components/confirm-dialog/confirm-dialog.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { Confirmation, ConfirmationService } from 'primeng/api'; + +export type ConfirmDialogOptions = Pick< + Confirmation, + 'key' | 'message' | 'header' | 'icon' | 'acceptLabel' | 'rejectLabel' | 'accept' | 'reject' | 'acceptButtonProps' +>; + +@Injectable() +export class ConfirmDialogService { + constructor(private readonly confirmationService: ConfirmationService) {} + + confirm(options: ConfirmDialogOptions): void { + this.confirmationService.confirm(options); + } +} diff --git a/src/stories/components/confirm-dialog/confirm-dialog.stories.ts b/src/stories/components/confirm-dialog/confirm-dialog.stories.ts index 399c7634..91f44e05 100644 --- a/src/stories/components/confirm-dialog/confirm-dialog.stories.ts +++ b/src/stories/components/confirm-dialog/confirm-dialog.stories.ts @@ -11,10 +11,10 @@ const meta: Meta = { parameters: { docs: { description: { - component: `Компонент для подтверждения действий пользователя. Требует подключения \`ConfirmationService\`. + component: `Компонент для подтверждения действий пользователя. Требует подключения \`ConfirmationService\` и \`ConfirmDialogService\`. \`\`\`typescript -import { ConfirmDialogComponent } from '@cdek-it/angular-ui-kit'; +import { ConfirmDialogComponent, ConfirmDialogService } from '@cdek-it/angular-ui-kit'; import { ConfirmationService } from 'primeng/api'; \`\`\``, }, @@ -73,33 +73,29 @@ export const Default: Story = { import { Component } from '@angular/core'; import { Button } from 'primeng/button'; import { ConfirmationService } from 'primeng/api'; -import { ConfirmDialogComponent } from '@cdek-it/angular-ui-kit'; +import { ConfirmDialogComponent, ConfirmDialogService } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-confirm-dialog-default', standalone: true, imports: [ConfirmDialogComponent, Button], - providers: [ConfirmationService], + providers: [ConfirmationService, ConfirmDialogService], template: \` \`, }) export class ConfirmDialogDefaultComponent { - constructor(private confirmationService: ConfirmationService) {} + constructor(private confirmDialogService: ConfirmDialogService) {} showConfirm(): void { - this.confirmationService.confirm({ + this.confirmDialogService.confirm({ key: 'cd-default', message: 'Вы уверены, что хотите продолжить?', header: 'Подтверждение', icon: 'ti ti-alert-triangle', acceptLabel: 'Да', rejectLabel: 'Нет', - rejectButtonProps: { - severity: 'secondary', - text: true, - }, accept: () => {}, reject: () => {}, }); @@ -128,7 +124,7 @@ export const Severities: Story = { import { Component } from '@angular/core'; import { Button } from 'primeng/button'; import { ConfirmationService } from 'primeng/api'; -import { ConfirmDialogComponent } from '@cdek-it/angular-ui-kit'; +import { ConfirmDialogComponent, ConfirmDialogService } from '@cdek-it/angular-ui-kit'; const SEVERITIES = [ { type: 'success', buttonSeverity: 'success', icon: 'ti ti-circle-check', label: 'Успех', header: 'Успех', message: 'Операция выполнена успешно.', acceptLabel: 'OK' }, @@ -142,7 +138,7 @@ const SEVERITIES = [ selector: 'app-confirm-dialog-severities', standalone: true, imports: [ConfirmDialogComponent, Button], - providers: [ConfirmationService], + providers: [ConfirmationService, ConfirmDialogService], template: \` @@ -164,10 +160,10 @@ const SEVERITIES = [ }) export class ConfirmDialogSeveritiesComponent { severities = SEVERITIES; - constructor(private confirmationService: ConfirmationService) {} + constructor(private confirmDialogService: ConfirmDialogService) {} showConfirm(severity: any): void { - this.confirmationService.confirm({ + this.confirmDialogService.confirm({ key: 'cd-severity-' + severity.type, message: severity.message, header: severity.header, @@ -175,10 +171,6 @@ export class ConfirmDialogSeveritiesComponent { acceptLabel: severity.acceptLabel, rejectLabel: 'Нет', acceptButtonProps: { severity: severity.buttonSeverity }, - rejectButtonProps: { - severity: 'secondary', - text: true, - }, accept: () => {}, reject: () => {}, }); @@ -207,7 +199,7 @@ export const Sizes: Story = { import { Component } from '@angular/core'; import { Button } from 'primeng/button'; import { ConfirmationService } from 'primeng/api'; -import { ConfirmDialogComponent } from '@cdek-it/angular-ui-kit'; +import { ConfirmDialogComponent, ConfirmDialogService } from '@cdek-it/angular-ui-kit'; const SIZES = [ { key: 'sm', label: 'Small' }, @@ -220,7 +212,7 @@ const SIZES = [ selector: 'app-confirm-dialog-sizes', standalone: true, imports: [ConfirmDialogComponent, Button], - providers: [ConfirmationService], + providers: [ConfirmationService, ConfirmDialogService], template: \` @@ -236,20 +228,16 @@ const SIZES = [ }) export class ConfirmDialogSizesComponent { sizes = SIZES; - constructor(private confirmationService: ConfirmationService) {} + constructor(private confirmDialogService: ConfirmDialogService) {} showConfirm(size: any): void { - this.confirmationService.confirm({ + this.confirmDialogService.confirm({ key: 'cd-size-' + size.key, message: 'Это диалог размера ' + size.label, header: 'Подтверждение', icon: 'ti ti-alert-triangle', acceptLabel: 'Да', rejectLabel: 'Нет', - rejectButtonProps: { - severity: 'secondary', - text: true, - }, accept: () => {}, reject: () => {}, }); diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts index 5c812da3..8b7118c4 100644 --- a/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core'; import { Button } from 'primeng/button'; import { ConfirmationService } from 'primeng/api'; import { ConfirmDialogComponent } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; +import { ConfirmDialogService } from '../../../../lib/components/confirm-dialog/confirm-dialog.service'; const template = `
@@ -15,24 +16,20 @@ const template = ` selector: 'app-confirm-dialog-default', standalone: true, imports: [ConfirmDialogComponent, Button], - providers: [ConfirmationService], + providers: [ConfirmationService, ConfirmDialogService], template, }) export class ConfirmDialogDefaultComponent { - constructor(private confirmationService: ConfirmationService) {} + constructor(private confirmDialogService: ConfirmDialogService) {} showConfirm(): void { - this.confirmationService.confirm({ + this.confirmDialogService.confirm({ key: 'cd-default', message: 'Вы уверены, что хотите продолжить?', header: 'Подтверждение', icon: 'ti ti-alert-triangle', acceptLabel: 'Да', rejectLabel: 'Нет', - rejectButtonProps: { - severity: 'secondary', - text: true, - }, accept: () => {}, reject: () => {}, }); diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts index c3cd8542..41cdadcc 100644 --- a/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core'; import { Button } from 'primeng/button'; import { ConfirmationService } from 'primeng/api'; import { ConfirmDialogComponent } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; +import { ConfirmDialogService } from '../../../../lib/components/confirm-dialog/confirm-dialog.service'; interface SeverityItem { type: 'success' | 'info' | 'warn' | 'help' | 'danger'; @@ -86,16 +87,16 @@ const template = ` selector: 'app-confirm-dialog-severities', standalone: true, imports: [ConfirmDialogComponent, Button], - providers: [ConfirmationService], + providers: [ConfirmationService, ConfirmDialogService], template, }) export class ConfirmDialogSeveritiesComponent { severities = SEVERITIES; - constructor(private confirmationService: ConfirmationService) {} + constructor(private confirmDialogService: ConfirmDialogService) {} showConfirm(severity: SeverityItem): void { - this.confirmationService.confirm({ + this.confirmDialogService.confirm({ key: 'cd-severity-' + severity.type, message: severity.message, header: severity.header, @@ -103,10 +104,6 @@ export class ConfirmDialogSeveritiesComponent { acceptLabel: severity.acceptLabel, rejectLabel: 'Нет', acceptButtonProps: { severity: severity.buttonSeverity }, - rejectButtonProps: { - severity: 'secondary', - text: true, - }, accept: () => {}, reject: () => {}, }); diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts index a2dcfada..fbfb90db 100644 --- a/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts @@ -5,6 +5,7 @@ import { ConfirmDialogComponent, ConfirmDialogSize, } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; +import { ConfirmDialogService } from '../../../../lib/components/confirm-dialog/confirm-dialog.service'; interface SizeItem { key: ConfirmDialogSize; @@ -41,26 +42,22 @@ const template = ` selector: 'app-confirm-dialog-sizes', standalone: true, imports: [ConfirmDialogComponent, Button], - providers: [ConfirmationService], + providers: [ConfirmationService, ConfirmDialogService], template, }) export class ConfirmDialogSizesComponent { sizes = SIZES; - constructor(private confirmationService: ConfirmationService) {} + constructor(private confirmDialogService: ConfirmDialogService) {} showConfirm(size: SizeItem): void { - this.confirmationService.confirm({ + this.confirmDialogService.confirm({ key: 'cd-size-' + size.key, message: 'Это диалог размера ' + size.label, header: 'Подтверждение', icon: 'ti ti-alert-triangle', acceptLabel: 'Да', rejectLabel: 'Нет', - rejectButtonProps: { - severity: 'secondary', - text: true, - }, accept: () => {}, reject: () => {}, }); From b6684fd9548606493f433d0dccc8e6391294daac Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 13 Apr 2026 18:39:28 +0700 Subject: [PATCH 07/28] =?UTF-8?q?confirm-dialog:=20=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D1=81=20=D1=86=D0=B2=D0=B5=D1=82=D0=BE=D0=B2=20=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D0=BE=D0=BA=20severity,=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=20severity=20secondary=20=D1=83=20=D0=BA=D0=BD=D0=BE?= =?UTF-8?q?=D0=BF=D0=BA=D0=B8=20=D0=BE=D1=82=D0=BA=D0=BB=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../confirm-dialog.component.ts | 1 - .../tokens/components/confirm-dialog.ts | 24 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/lib/components/confirm-dialog/confirm-dialog.component.ts b/src/lib/components/confirm-dialog/confirm-dialog.component.ts index e5958374..cdb49939 100644 --- a/src/lib/components/confirm-dialog/confirm-dialog.component.ts +++ b/src/lib/components/confirm-dialog/confirm-dialog.component.ts @@ -45,7 +45,6 @@ export type ConfirmDialogSeverity = 'success' | 'info' | 'warn' | 'help' | 'dang @@ -86,8 +85,8 @@ const template = ` @Component({ selector: 'app-confirm-dialog-severities', standalone: true, - imports: [ConfirmDialogComponent, Button], - providers: [ConfirmationService, ConfirmDialogService], + imports: [ConfirmDialogComponent, ButtonComponent], + providers: [ConfirmDialogService.providers()], template, }) export class ConfirmDialogSeveritiesComponent { diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts index fbfb90db..717cba6d 100644 --- a/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts @@ -1,6 +1,5 @@ import { Component } from '@angular/core'; -import { Button } from 'primeng/button'; -import { ConfirmationService } from 'primeng/api'; +import { ButtonComponent } from '../../../../lib/components/button/button.component'; import { ConfirmDialogComponent, ConfirmDialogSize, @@ -28,11 +27,11 @@ const template = `
@for (size of sizes; track size.key) { - + (click)="showConfirm(size)" + > }
@@ -41,8 +40,8 @@ const template = ` @Component({ selector: 'app-confirm-dialog-sizes', standalone: true, - imports: [ConfirmDialogComponent, Button], - providers: [ConfirmationService, ConfirmDialogService], + imports: [ConfirmDialogComponent, ButtonComponent], + providers: [ConfirmDialogService.providers()], template, }) export class ConfirmDialogSizesComponent { From cd9f1227de328bfafc79b877eec251eda46ab616 Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Fri, 17 Apr 2026 17:02:03 +0700 Subject: [PATCH 15/28] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D1=91=D0=BD=20?= =?UTF-8?q?=D1=84=D0=B0=D0=B9=D0=BB=20claude.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/CLAUDE-v1.6.md | 751 ----------------------------------------- 1 file changed, 751 deletions(-) delete mode 100644 .claude/CLAUDE-v1.6.md diff --git a/.claude/CLAUDE-v1.6.md b/.claude/CLAUDE-v1.6.md deleted file mode 100644 index 2cf230a4..00000000 --- a/.claude/CLAUDE-v1.6.md +++ /dev/null @@ -1,751 +0,0 @@ -# CLAUDE.md — Angular UI Kit (@cdek-it/angular-ui-kit) - -Инструкции для Claude Code по генерации компонентов, обёрток и сторисов. - ---- - -## Стек и версии - -| Технология | Версия | -|----------------|---------| -| Angular | 20 | -| PrimeNG | 20 | -| Storybook | 10 | -| Tailwind CSS | 3 | -| TypeScript | 5 | - ---- - -## Структура проекта - -``` -src/ -├── lib/ -│ └── components/ -│ └── {name}/ -│ └── {name}.component.ts ← компонент-обёртка -├── stories/ -│ └── components/ -│ └── {name}/ -│ ├── {name}.stories.ts ← сторисы -│ └── examples/ ← примеры для сторисов -├── prime-preset/ -│ └── tokens/ -│ └── components/ -│ └── {name}.ts ← CSS-токены компонента -└── styles.scss ← Tailwind + иконки + шрифты -``` - ---- - -## Паттерн компонента-обёртки - -Каждый компонент — standalone Angular-компонент, оборачивающий PrimeNG. - -### Правила - -1. Файл: `src/lib/components/{name}/{name}.component.ts` -2. `selector` — с приставкой `extra-` + имя компонента строчными буквами (например `selector: 'extra-button'`) -3. Импортировать PrimeNG-компонент и указать его в `imports: []` -4. Для каждого типа Input создавать отдельный `type`-алиас -5. Все `@Input()` отражают **свой** API обёртки, не PrimeNG напрямую -6. Computed-геттеры маппят API обёртки → PrimeNG API -7. Шаблон компонента использует только геттеры, не сырые инпуты - -### Эталон — ButtonComponent - -```typescript -import { Component, Input } from '@angular/core'; -import { Button, ButtonSeverity as PrimeButtonSeverity } from 'primeng/button'; - -// Типы — отдельные алиасы, не inline union -export type ButtonVariant = 'primary' | 'secondary' | 'outlined' | 'text' | 'link'; -export type ButtonSeverity = 'success' | 'warning' | 'danger' | 'info' | null; -export type ButtonSize = 'small' | 'base' | 'large' | 'xlarge'; -export type ButtonIconPos = 'prefix' | 'postfix' | null; -export type BadgeSeverity = 'success' | 'info' | 'warning' | 'danger' | 'secondary' | 'contrast' | null; - -@Component({ - selector: 'extra-button', - standalone: true, - imports: [Button], - template: ` - - ` -}) -export class ButtonComponent { - @Input() label = 'Button'; - @Input() variant: ButtonVariant = 'primary'; - @Input() severity: ButtonSeverity = null; - @Input() size: ButtonSize = 'base'; - @Input() rounded = false; - @Input() iconPos: ButtonIconPos = null; - @Input() iconOnly = false; - @Input() icon = ''; - @Input() disabled = false; - @Input() loading = false; - @Input() badge = ''; - @Input() badgeSeverity: BadgeSeverity = null; - @Input() showBadge = false; - @Input() fluid = false; - @Input() ariaLabel: string | undefined = undefined; - @Input() autofocus = false; - @Input() tabindex: number | undefined = undefined; - @Input() text = false; - - // Геттеры — маппинг в PrimeNG API - get primeSize(): 'small' | 'large' | undefined { - if (this.size === 'small') return 'small'; - if (this.size === 'large') return 'large'; - return undefined; - } - - get primeIconPos(): 'left' | 'right' { - return this.iconPos === 'postfix' ? 'right' : 'left'; - } - - get primeSeverity(): PrimeButtonSeverity | null { - if (this.variant === 'secondary') return 'secondary'; - if (this.severity === 'warning') return 'warn'; - return this.severity; - } - - get primeBadgeSeverity() { - if (this.badgeSeverity === 'warning') return 'warn'; - return this.badgeSeverity; - } -} -``` - ---- - -## Паттерн сторисов - -### Файл: `src/stories/components/{name}/{name}.stories.ts` - -**Все тексты описаний — на русском языке.** - -### Правило: title сториса - -Формат: `Components/{Category}/{ComponentName}` - -Категории соответствуют группировке на [primeng.org](https://primeng.org/): - -| Категория | Компоненты | -|-----------|-----------------------------------------------------------------------------------------------| -| Button | Button, SpeedDial, SplitButton | -| Data | DataTable, DataView, OrderList, OrgChart, Paginator, PickList, Timeline, Tree, TreeTable | -| Form | AutoComplete, Checkbox, ColorPicker, DatePicker, InputMask, InputNumber, InputOtp, InputText, Knob, Listbox, MultiSelect, Password, RadioButton, Rating, Select, SelectButton, Slider, Textarea, ToggleButton, ToggleSwitch, TreeSelect | -| Menu | Breadcrumb, ContextMenu, Dock, Menu, Menubar, MegaMenu, PanelMenu, Steps, TabMenu, TieredMenu | -| Messages | Message, Toast | -| Misc | Avatar, Badge, BlockUI, Chip, Inplace, MeterGroup, ProgressBar, ProgressSpinner, ScrollTop, Skeleton, Tag | -| Overlay | ConfirmDialog, ConfirmPopup, Dialog, Drawer, Popover, Tooltip | -| Panel | Accordion, Card, Divider, Fieldset, Panel, ScrollPanel, Splitter, Stepper, Tabs | -| Media | Carousel, Galleria, Image, ImageCompare | - -```typescript -// ❌ Запрещено -title: 'Prime/Button' -title: 'Components/Button' - -// ✅ Правильно -title: 'Components/Button/Button' -title: 'Components/Panel/Card' -title: 'Components/Panel/Divider' -title: 'Components/Form/InputText' -``` - -### Полный шаблон сториса - -```typescript -import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; -import { XxxComponent } from '../../../lib/components/xxx/xxx.component'; - -// Расширяем тип для Events, которых нет в @Output() -type XxxArgs = XxxComponent & { onClick?: (event: MouseEvent) => void }; - -const meta: Meta = { - title: 'Components/{Category}/Xxx', - component: XxxComponent, - tags: ['autodocs'], - decorators: [ - moduleMetadata({ imports: [XxxComponent] }) - ], - parameters: { - docs: { - description: { - // 1. Одна строка — для чего компонент - // 2. Ссылка на Figma - // 3. Блок импорта - component: `Описание компонента. [Figma Design](https://www.figma.com/design/...). - -\`\`\`typescript -import { XxxModule } from 'primeng/xxx'; -\`\`\``, - }, - }, - }, - argTypes: { - // ── Props ──────────────────────────────────────────────── - propName: { - control: 'text' | 'boolean' | 'select' | 'number', - options: [...], // только для select - description: 'Описание на русском', - table: { - category: 'Props', - defaultValue: { summary: 'значение' }, - type: { summary: "'тип1' | 'тип2'" }, - }, - }, - // ── Badge ──────────────────────────────────────────────── - badge: { - // ... category: 'Badge' - }, - // ── Events ─────────────────────────────────────────────── - onClick: { - control: false, - description: 'Событие клика', - table: { - category: 'Events', - type: { summary: 'EventEmitter' }, - }, - }, - }, - args: { - // Дефолты для полей, которые нужно явно инициализировать - }, -}; - -// commonTemplate — для сторисов-вариаций (НЕ для Default) -const commonTemplate = ` - -`; - -export default meta; -type Story = StoryObj; - -// ── Default ────────────────────────────────────────────────────────────────── -// Динамический render: template генерируется из текущих args. -// Storybook Angular захватывает template как source code → -// при смене controls сниппет обновляется автоматически. - -export const Default: Story = { - name: 'Default', - render: (args) => { - const parts: string[] = []; - - if (args.label) parts.push(`label="${args.label}"`); - if (args.variant) parts.push(`variant="${args.variant}"`); - if (args.severity) parts.push(`severity="${args.severity}"`); - // ... остальные пропсы - if (args.rounded) parts.push(`[rounded]="true"`); - if (args.disabled) parts.push(`[disabled]="true"`); - - const template = parts.length - ? `` - : ``; - - return { props: args, template }; - }, - args: { label: 'Label' }, - parameters: { - docs: { - description: { - story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', - }, - }, - }, -}; - -// ── Сторисы-вариации ───────────────────────────────────────────────────────── -// Каждая сторис — ОДИН вариант компонента. -// Используют commonTemplate + props: args → controls работают. -// source.code — статичный минимальный пример. - -export const Sizes: Story = { - render: (args) => ({ props: args, template: commonTemplate }), - args: { label: 'Button', size: 'large' }, - parameters: { - docs: { - description: { story: 'Описание вариации.' }, - source: { - code: ``, - }, - }, - }, -}; -``` - ---- - -## Паттерн examples/ - -### Назначение - -Папка `src/stories/components/{name}/examples/` содержит **standalone Angular-компоненты** — каждый инкапсулирует один вариант использования компонента. -В блоке **Source** в Storybook показывается полноценный Angular-компонент (TypeScript), который пользователь библиотеки может скопировать к себе как есть. - -Это принципиально отличается от подхода в `{name}.stories.ts`, где `source.code` показывает просто HTML-шаблон. - -### Структура файла - -```typescript -import { Component } from '@angular/core'; -import { StoryObj } from '@storybook/angular'; -import { XxxComponent } from '../../../../lib/components/xxx/xxx.component'; - -// 1. Шаблон выносится в const — чтобы переиспользовать в source.code -const template = ` -
- -
-`; -const styles = ''; - -// 2. Standalone-компонент с реальным шаблоном -@Component({ - selector: 'app-xxx-variant', - standalone: true, - imports: [XxxComponent], - template, - styles, -}) -export class XxxVariantComponent {} - -// 3. StoryObj — рендерит компонент; source.code — код компонента для копирования -export const Variant: StoryObj = { - render: () => ({ - template: ``, - }), - parameters: { - docs: { - description: { story: 'Описание на русском.' }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { XxxComponent } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-xxx-variant', - standalone: true, - imports: [XxxComponent], - template: \` - - \`, -}) -export class XxxVariantComponent {} - `, - }, - }, - }, -}; -``` - -### Правила - -1. Каждый файл — **одна вариация**, один `@Component` + один `StoryObj` -2. Шаблон выносится в `const template` — чтобы использовать в `source.code` -3. `render: () => ({ template: '' })` — **только** для статичных примеров, где controls не нужны (форм-контролы с `ngModel`, группы компонентов). Для простых prop-вариаций controls не будут работать при таком подходе — см. правило ниже. -4. `source.code` содержит полный TypeScript-код компонента с импортами из `@cdek-it/angular-ui-kit` -5. Обёртка `
` — фон preview; **`p-4` не добавлять** -6. Именование файлов: `{name}-{variant}.component.ts` (например `avatar-label.component.ts`) -7. Именование selector компонента: `app-{name}-{variant}` (например `app-avatar-label`) -8. Класс компонента: `{Name}{Variant}Component` (например `AvatarLabelComponent`) - -### Когда создавать examples/ - -`examples/` создаётся **обязательно для каждого компонента** — для всех вариационных сторисов (кроме `Default`). - -`Default` (интерактивный playground) в examples/ **не дублируется** — он живёт только в `{name}.stories.ts`. - -Каждая вариационная сторис (`WithIcon`, `Removable`, `Disabled` и т.д.) имеет соответствующий файл в `examples/`. - ---- - -## Структура сторисов — обязательные разделы - -| Порядок | Сторис | Описание | -|---------|-------------|-----------------------------------------------------------------------| -| 1 | **Default** | Интерактивный playground. Динамический render. Все пропсы в Controls. | -| 2+ | Вариации | По одному варианту: Sizes, Icons, Rounded, Loading, Disabled, и т.д. | - -### Правило: один экземпляр компонента на сторис - -**Каждая сторис показывает ровно один вариант компонента. Все остальные виды компонента настраиваются с помощью пропсов через `args`.** - -В каждой вариационной сторис шаблон содержит **ровно один** экземпляр компонента. -Это правило распространяется как на сторисы в `{name}.stories.ts`, так и на компоненты в `examples/`. -Вариация демонстрируется через `args` (пропсы), а не через дублирование тегов. - -```typescript -// ❌ Запрещено — несколько экземпляров компонента в шаблоне -export const Sizes: Story = { - render: (args) => ({ - props: args, - template: ` - - - - `, - }), -}; - -// ❌ Запрещено — то же нарушение внутри examples/ -@Component({ template: ` - - - -` }) -export class TagSeveritiesComponent {} - -// ✅ Правильно — один экземпляр, вариация задаётся через args -export const Sizes: Story = { - render: (args) => ({ props: args, template: commonTemplate }), - args: { label: 'Button', size: 'large' }, -}; - -export const Severity: Story = { - render: (args) => ({ props: args, template: commonTemplate }), - args: { value: 'Success', severity: 'success' }, -}; -``` - -**Исключение**: составные компоненты (например группа радиокнопок / чекбоксов), где несколько дочерних элементов — это **сам смысл компонента**, а не визуальное перечисление вариантов. - -### Обязательные вариации для большинства компонентов -- `Sizes` — один компонент с нужным `size` в `args` -- `Icons` — один компонент с иконкой в `args` -- `Loading` — один компонент с `loading: true` в `args` -- `Rounded` — один компонент с `rounded: true` в `args` -- `Severity` — один компонент с нужным `severity` в `args` -- `Disabled` — один компонент с `disabled: true` в `args` - -### Правило: controls (пропсы) работают во всех вариационных сторисах - -**Вариационные сторисы ВСЕГДА рендерят через `commonTemplate + args`** — это единственный способ, при котором Storybook передаёт значения controls в компонент. - -`render: () => ({ template: '' })` — статичный рендер, controls **не работают**. Такой подход применим только для форм-контролов с `ngModel` и групп компонентов. - -```typescript -// ❌ Controls не работают — props не передаются в статичный компонент -export const Rounded: Story = { - render: () => ({ - template: ``, - }), -}; - -// ✅ Controls работают — props передаются через args -export const Rounded: Story = { - render: (args) => ({ props: args, template: commonTemplate }), - args: { value: 'Rounded', severity: 'success', rounded: true }, - parameters: { - docs: { - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { TagComponent } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-tag-rounded', - standalone: true, - imports: [TagComponent], - template: \\\` - - \\\`, -}) -export class TagRoundedComponent {} - `, - }, - }, - }, -}; -``` - -**Если для вариации существует example-файл:** example-компонент регистрируется в `moduleMetadata`, но сторис рендерит через `commonTemplate + args`. В `source.code` сторис показывает TypeScript-код из example-файла (дублируется вручную). - ---- - -## Ключевые технические решения - -### Почему Default story использует динамический render - -В Storybook 10 Angular `source.transform` **не реактивен** — вызывается один раз. -Единственный способ обновлять code-сниппет при смене controls: -генерировать `template` строку прямо в `render(args)`. -Storybook Angular использует `template` из render как source code. - -```typescript -// ✅ Правильно — template обновляется при смене controls -render: (args) => { - const template = ``; - return { props: args, template }; -}, - -// ❌ Неправильно — source.transform не реактивен -parameters: { - docs: { source: { transform: (src, ctx) => ... } } -} -``` - -### Почему НЕ используется самозакрывающийся тег - -Angular JIT-компилятор запрещает ``. -` - -``` - -Поиск иконок: https://tabler.io/icons - ---- - -## Стилизация компонентов - -### Порядок слоёв - -``` -Компонент-обёртка → PrimeNG (p-button) → PrimeNG Aura тема -→ Токены (src/prime-preset/tokens/components/{name}.ts) -→ Tailwind CSS -``` - -### Добавление CSS-токенов - -Файл: `src/prime-preset/tokens/components/{name}.ts` - -Структура токенов соответствует PrimeNG Aura preset. -Кастомные расширения — через префикс `--p-{name}-extend-*`. - -### Tailwind в шаблонах сторисов - -```html - -
- -
-``` - ---- - -## Референс Vue UI Kit - -Vue UI Kit (PrimeVue) — источник референса по структуре сторисов и вариациям компонентов: - -- **Репозиторий**: `~/Downloads/vue-ui-kit-3/src/plugins/prime/stories/` -- **Запущен локально**: `http://localhost:6006` - -### Что брать как референс - -| Vue файл | Что переносить в Angular | -|-----------------------------------|--------------------------------------------------| -| `Button/Button.stories.js` | argTypes, stories args, описания | -| `Button/Button.template.js` | Шаблоны вариаций (grid-матрица размеров/severity)| -| `Button/Button.mdx` | Структура документации, порядок сторисов | - -### Как адаптировать Vue → Angular - -| Vue | Angular | -|-----------------------------|----------------------------------------------| -| `v-bind="args"` | `[prop]="prop"` (через `props: args`) | -| `variant="text"` | Остаётся `variant="text"` (статичный шаблон) | -| ` } @if (!last) { - + }
@@ -82,10 +82,10 @@ export interface StepperItem {

{{ step.content }}

@if (!first) { - + } @if (!last) { - + }
From 8b16555a0ca7203dd50c413cddcfcc280bb69cd7 Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 20 Apr 2026 10:42:37 +0700 Subject: [PATCH 18/28] =?UTF-8?q?stepper:=20=D1=8D=D0=BA=D1=81=D0=BF=D0=BE?= =?UTF-8?q?=D1=80=D1=82=20=D1=82=D0=B8=D0=BF=D0=B0=20=D0=BA=D0=B0=D0=BA=20?= =?UTF-8?q?=D0=B2=20=D0=B4=D1=80=D1=83=D0=B3=D0=B8=D1=85=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/components/stepper/stepper.stories.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/stories/components/stepper/stepper.stories.ts b/src/stories/components/stepper/stepper.stories.ts index cdd962d7..b9cd7548 100644 --- a/src/stories/components/stepper/stepper.stories.ts +++ b/src/stories/components/stepper/stepper.stories.ts @@ -5,7 +5,9 @@ import { StepperLinearComponent, Linear as LinearStory } from './examples/steppe import { StepperStepsOnlyComponent, StepsOnly as StepsOnlyStory } from './examples/stepper-steps-only.component'; import { StepperErrorComponent, Error as ErrorStory } from './examples/stepper-error.component'; -const meta: Meta = { +type StepperArgs = StepperComponent; + +const meta: Meta = { title: 'Components/Stepper', component: StepperComponent, tags: ['autodocs'], @@ -82,7 +84,7 @@ import { StepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; }; export default meta; -type Story = StoryObj; +type Story = StoryObj; // ── Default (Horizontal) ───────────────────────────────────────────────────── From 654e7fb1ee2300f1e117dadf4c40c14bd8a9877d Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 20 Apr 2026 10:42:51 +0700 Subject: [PATCH 19/28] stepper: change detection = OnPush --- src/lib/components/stepper/stepper.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/components/stepper/stepper.component.ts b/src/lib/components/stepper/stepper.component.ts index b0d8e23b..2128eacf 100644 --- a/src/lib/components/stepper/stepper.component.ts +++ b/src/lib/components/stepper/stepper.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { NgClass } from '@angular/common'; import { Stepper } from 'primeng/stepper'; import { StepList } from 'primeng/stepper'; @@ -20,6 +20,7 @@ export interface StepperItem { @Component({ selector: 'stepper', standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [Stepper, StepList, Step, StepPanels, StepPanel, StepItem, ButtonComponent, NgClass], template: ` Date: Mon, 20 Apr 2026 10:43:11 +0700 Subject: [PATCH 20/28] modified .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 808f9bdc..ce8e3c03 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ src/assets/components/themes /storybook-static /debug-storybook.log /documentation.json + +.claude/* \ No newline at end of file From ca483e1a03f1191453e1e950a28d93aab7505d5a Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 20 Apr 2026 14:17:39 +0700 Subject: [PATCH 21/28] =?UTF-8?q?confirm-dialog:=20=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D1=81=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=81=D1=82=D0=B8=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ .../confirm-dialog/confirm-dialog.component.ts | 11 ++++++----- src/prime-preset/map-tokens.ts | 11 +++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 808f9bdc..ff02803d 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ src/assets/components/themes /storybook-static /debug-storybook.log /documentation.json + +.claude/* diff --git a/src/lib/components/confirm-dialog/confirm-dialog.component.ts b/src/lib/components/confirm-dialog/confirm-dialog.component.ts index 1314df12..033da08a 100644 --- a/src/lib/components/confirm-dialog/confirm-dialog.component.ts +++ b/src/lib/components/confirm-dialog/confirm-dialog.component.ts @@ -26,12 +26,13 @@ export type ConfirmDialogSeverity = 'success' | 'info' | 'warn' | 'help' | 'dang {{ message.header }} + > }
diff --git a/src/prime-preset/map-tokens.ts b/src/prime-preset/map-tokens.ts index 437e87f4..d49732b6 100644 --- a/src/prime-preset/map-tokens.ts +++ b/src/prime-preset/map-tokens.ts @@ -6,6 +6,9 @@ import tokens from './tokens/tokens.json'; import { avatarCss } from './tokens/components/avatar'; import { buttonCss } from './tokens/components/button'; import { checkboxCss } from './tokens/components/checkbox'; +import { confirmDialogCss } from './tokens/components/confirm-dialog'; +import { dialogCss } from './tokens/components/dialog'; +import { tagCss } from './tokens/components/tag'; import { tooltipCss } from './tokens/components/tooltip'; const presetTokens: Preset = { @@ -25,6 +28,14 @@ const presetTokens: Preset = { ...(tokens.components.button as unknown as ComponentsDesignTokens['button']), css: buttonCss, }, + confirmdialog: { + ...(tokens.components.confirmdialog as unknown as ComponentsDesignTokens['confirmdialog']), + css: confirmDialogCss, + }, + dialog: { + ...(tokens.components.dialog as unknown as ComponentsDesignTokens['dialog']), + css: dialogCss, + }, tag: { ...(tokens.components.tag as unknown as ComponentsDesignTokens['tag']), css: tagCss, From 952dcecc9c0570fa1742da761018321e75e998a8 Mon Sep 17 00:00:00 2001 From: "ak.dmitriev" Date: Mon, 25 May 2026 22:50:11 +0700 Subject: [PATCH 22/28] DS-517 --- .../data-table/data-table.component.ts | 4 +- src/lib/components/data-table/ng-package.json | 7 ++ src/lib/components/data-table/public_api.ts | 4 + .../data-table/data-table.stories.ts | 74 +++++++++---------- .../examples/data-table-default.component.ts | 6 +- .../data-table-grid-lines.component.ts | 6 +- .../data-table-pagination.component.ts | 8 +- .../data-table-scroll-horizontal.component.ts | 6 +- .../data-table-scroll-vertical.component.ts | 8 +- .../data-table-selectable.component.ts | 8 +- ...data-table-selection-checkbox.component.ts | 8 +- .../data-table-selection-radio.component.ts | 8 +- .../data-table-striped-rows.component.ts | 6 +- 13 files changed, 82 insertions(+), 71 deletions(-) create mode 100644 src/lib/components/data-table/ng-package.json create mode 100644 src/lib/components/data-table/public_api.ts diff --git a/src/lib/components/data-table/data-table.component.ts b/src/lib/components/data-table/data-table.component.ts index 75d3d3b5..54f5bd8c 100644 --- a/src/lib/components/data-table/data-table.component.ts +++ b/src/lib/components/data-table/data-table.component.ts @@ -12,7 +12,7 @@ export interface DataTableColumn { } @Component({ - selector: 'data-table', + selector: 'extra-data-table', host: { style: 'display: block' }, standalone: true, imports: [TableModule, PrimeTemplate], @@ -75,7 +75,7 @@ export interface DataTableColumn { `, }) -export class DataTableComponent { +export class ExtraDataTableComponent { @Input() value: any[] = []; @Input() columns: DataTableColumn[] = []; @Input() stripedRows = false; diff --git a/src/lib/components/data-table/ng-package.json b/src/lib/components/data-table/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/data-table/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/data-table/public_api.ts b/src/lib/components/data-table/public_api.ts new file mode 100644 index 00000000..8b3cbf32 --- /dev/null +++ b/src/lib/components/data-table/public_api.ts @@ -0,0 +1,4 @@ +export * from './data-table.component'; + + + diff --git a/src/stories/components/data-table/data-table.stories.ts b/src/stories/components/data-table/data-table.stories.ts index 6ea5f7f4..03d2fd92 100644 --- a/src/stories/components/data-table/data-table.stories.ts +++ b/src/stories/components/data-table/data-table.stories.ts @@ -1,5 +1,5 @@ import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; -import { DataTableComponent } from '../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent } from '../../../lib/components/data-table/data-table.component'; import { DataTableDefaultComponent } from './examples/data-table-default.component'; import { DataTableStripedRowsComponent } from './examples/data-table-striped-rows.component'; import { DataTableSelectableComponent } from './examples/data-table-selectable.component'; @@ -10,9 +10,9 @@ import { DataTableSelectionCheckboxComponent } from './examples/data-table-selec import { DataTableScrollVerticalComponent } from './examples/data-table-scroll-vertical.component'; import { DataTableScrollHorizontalComponent } from './examples/data-table-scroll-horizontal.component'; -const meta: Meta = { +const meta: Meta = { title: 'Components/Data/DataTable', - component: DataTableComponent, + component: ExtraDataTableComponent, tags: ['autodocs'], parameters: { docs: { @@ -20,7 +20,7 @@ const meta: Meta = { component: `Таблица данных с поддержкой сортировки, пагинации, выбора строк и прокрутки. \`\`\`typescript -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; \`\`\``, }, }, @@ -147,7 +147,7 @@ import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; }; export default meta; -type Story = StoryObj; +type Story = StoryObj; // ── Default ─────────────────────────────────────────────────────────────────── @@ -164,14 +164,14 @@ export const Default: Story = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-data-table-default', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template: \` - + \`, }) export class DataTableDefaultComponent { @@ -209,14 +209,14 @@ export const StripedRows: Story = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-data-table-striped-rows', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template: \` - + \`, }) export class DataTableStripedRowsComponent { @@ -254,20 +254,20 @@ export const Selectable: Story = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-data-table-selectable', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template: \` - + > \`, }) export class DataTableSelectableComponent { @@ -307,14 +307,14 @@ export const GridLines: Story = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-data-table-grid-lines', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template: \` - + \`, }) export class DataTableGridLinesComponent { @@ -352,20 +352,20 @@ export const Pagination: Story = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-data-table-pagination', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template: \` - + > \`, }) export class DataTablePaginationComponent { @@ -399,20 +399,20 @@ export const SelectionRadio: Story = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-data-table-selection-radio', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template: \` - + > \`, }) export class DataTableSelectionRadioComponent { @@ -447,20 +447,20 @@ export const SelectionCheckbox: Story = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-data-table-selection-checkbox', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template: \` - + > \`, }) export class DataTableSelectionCheckboxComponent { @@ -495,19 +495,19 @@ export const ScrollVertical: Story = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-data-table-scroll-vertical', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template: \` - + > \`, }) export class DataTableScrollVerticalComponent { @@ -540,14 +540,14 @@ export const ScrollHorizontal: Story = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-data-table-scroll-horizontal', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template: \` - + \`, }) export class DataTableScrollHorizontalComponent { diff --git a/src/stories/components/data-table/examples/data-table-default.component.ts b/src/stories/components/data-table/examples/data-table-default.component.ts index db4cdd08..434a4c03 100644 --- a/src/stories/components/data-table/examples/data-table-default.component.ts +++ b/src/stories/components/data-table/examples/data-table-default.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; const SHIPMENTS = [ { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, @@ -22,14 +22,14 @@ const COLUMNS: DataTableColumn[] = [ const template = `
- +
`; @Component({ selector: 'app-data-table-default', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template, }) export class DataTableDefaultComponent { diff --git a/src/stories/components/data-table/examples/data-table-grid-lines.component.ts b/src/stories/components/data-table/examples/data-table-grid-lines.component.ts index 2aa5bed3..1f7a94bd 100644 --- a/src/stories/components/data-table/examples/data-table-grid-lines.component.ts +++ b/src/stories/components/data-table/examples/data-table-grid-lines.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; const SHIPMENTS = [ { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, @@ -19,14 +19,14 @@ const COLUMNS: DataTableColumn[] = [ const template = `
- +
`; @Component({ selector: 'app-data-table-grid-lines', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template, }) export class DataTableGridLinesComponent { diff --git a/src/stories/components/data-table/examples/data-table-pagination.component.ts b/src/stories/components/data-table/examples/data-table-pagination.component.ts index 275670db..09180b9b 100644 --- a/src/stories/components/data-table/examples/data-table-pagination.component.ts +++ b/src/stories/components/data-table/examples/data-table-pagination.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; const SHIPMENTS = [ { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, @@ -22,20 +22,20 @@ const COLUMNS: DataTableColumn[] = [ const template = `
- + >
`; @Component({ selector: 'app-data-table-pagination', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template, }) export class DataTablePaginationComponent { diff --git a/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts b/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts index 3c6962b8..0428887d 100644 --- a/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts +++ b/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; const SHIPMENTS = [ { id: 1, trackNumber: 'ЦД-00123456', sender: 'Иванов И.И.', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200, dimensions: '30×20×15 см' }, @@ -21,14 +21,14 @@ const COLUMNS: DataTableColumn[] = [ const template = `
- +
`; @Component({ selector: 'app-data-table-scroll-horizontal', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template, }) export class DataTableScrollHorizontalComponent { diff --git a/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts b/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts index f819b9e3..c7773a5e 100644 --- a/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts +++ b/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; const BASE_SHIPMENTS = [ { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, @@ -21,19 +21,19 @@ const COLUMNS: DataTableColumn[] = [ const template = `
- + >
`; @Component({ selector: 'app-data-table-scroll-vertical', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template, }) export class DataTableScrollVerticalComponent { diff --git a/src/stories/components/data-table/examples/data-table-selectable.component.ts b/src/stories/components/data-table/examples/data-table-selectable.component.ts index 2b9eb35f..4d0e1b8a 100644 --- a/src/stories/components/data-table/examples/data-table-selectable.component.ts +++ b/src/stories/components/data-table/examples/data-table-selectable.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; const SHIPMENTS = [ { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, @@ -19,20 +19,20 @@ const COLUMNS: DataTableColumn[] = [ const template = `
- + >
`; @Component({ selector: 'app-data-table-selectable', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template, }) export class DataTableSelectableComponent { diff --git a/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts b/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts index ea9120e5..20a18330 100644 --- a/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts +++ b/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; const SHIPMENTS = [ { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, @@ -19,20 +19,20 @@ const COLUMNS: DataTableColumn[] = [ const template = `
- + >
`; @Component({ selector: 'app-data-table-selection-checkbox', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template, }) export class DataTableSelectionCheckboxComponent { diff --git a/src/stories/components/data-table/examples/data-table-selection-radio.component.ts b/src/stories/components/data-table/examples/data-table-selection-radio.component.ts index fe6c4114..1aeb0883 100644 --- a/src/stories/components/data-table/examples/data-table-selection-radio.component.ts +++ b/src/stories/components/data-table/examples/data-table-selection-radio.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; const SHIPMENTS = [ { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, @@ -19,20 +19,20 @@ const COLUMNS: DataTableColumn[] = [ const template = `
- + >
`; @Component({ selector: 'app-data-table-selection-radio', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template, }) export class DataTableSelectionRadioComponent { diff --git a/src/stories/components/data-table/examples/data-table-striped-rows.component.ts b/src/stories/components/data-table/examples/data-table-striped-rows.component.ts index e86ca8dd..2f78155d 100644 --- a/src/stories/components/data-table/examples/data-table-striped-rows.component.ts +++ b/src/stories/components/data-table/examples/data-table-striped-rows.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { DataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; const SHIPMENTS = [ { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, @@ -19,14 +19,14 @@ const COLUMNS: DataTableColumn[] = [ const template = `
- +
`; @Component({ selector: 'app-data-table-striped-rows', standalone: true, - imports: [DataTableComponent], + imports: [ExtraDataTableComponent], template, }) export class DataTableStripedRowsComponent { From 70f940dbbebf5a5ccc44eda11c7ac6f22d1780da Mon Sep 17 00:00:00 2001 From: "ak.dmitriev" Date: Fri, 29 May 2026 16:51:20 +0700 Subject: [PATCH 23/28] DS-517 --- .../data-table/data-table.component.ts | 408 ++++++++++++++++-- .../data-table/data-table.stories.ts | 150 +++++++ .../data-table-custom-body.component.ts | 81 ++++ .../examples/data-table-lazy.component.ts | 66 +++ .../p-data-table-custom-body.component.ts | 58 +++ .../p-data-table-default.component.ts | 44 ++ .../p-data-table-grid-lines.component.ts | 41 ++ .../examples/p-data-table-lazy.component.ts | 85 ++++ .../p-data-table-pagination.component.ts | 44 ++ ...-data-table-scroll-horizontal.component.ts | 57 +++ .../p-data-table-scroll-vertical.component.ts | 42 ++ .../p-data-table-selectable.component.ts | 42 ++ ...data-table-selection-checkbox.component.ts | 46 ++ .../p-data-table-selection-radio.component.ts | 44 ++ .../p-data-table-striped-rows.component.ts | 44 ++ .../p-data-table/p-data-table.stories.ts | 198 +++++++++ 16 files changed, 1405 insertions(+), 45 deletions(-) create mode 100644 src/stories/components/data-table/examples/data-table-custom-body.component.ts create mode 100644 src/stories/components/data-table/examples/data-table-lazy.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-custom-body.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-default.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-grid-lines.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-lazy.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-pagination.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-scroll-horizontal.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-scroll-vertical.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-selectable.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-selection-checkbox.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-selection-radio.component.ts create mode 100644 src/stories/components/p-data-table/examples/p-data-table-striped-rows.component.ts create mode 100644 src/stories/components/p-data-table/p-data-table.stories.ts diff --git a/src/lib/components/data-table/data-table.component.ts b/src/lib/components/data-table/data-table.component.ts index 54f5bd8c..0e72e028 100644 --- a/src/lib/components/data-table/data-table.component.ts +++ b/src/lib/components/data-table/data-table.component.ts @@ -1,6 +1,40 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { + AfterContentInit, + Component, + ContentChildren, + EventEmitter, + Input, + Output, + QueryList, + TemplateRef, + ViewChild, +} from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; import { TableModule } from 'primeng/table'; -import { PrimeTemplate } from 'primeng/api'; +import { + PrimeTemplate, + SortMeta, + FilterMetadata, + ScrollerOptions, +} from 'primeng/api'; +import { + TableLazyLoadEvent, + TableRowSelectEvent, + TableRowUnSelectEvent, + TablePageEvent, + TableFilterEvent, + TableRowExpandEvent, + TableRowCollapseEvent, + TableContextMenuSelectEvent, + TableColResizeEvent, + TableColumnReorderEvent, + TableRowReorderEvent, + TableEditInitEvent, + TableEditCompleteEvent, + TableEditCancelEvent, + TableHeaderCheckboxToggleEvent, + TableSelectAllChangeEvent, +} from 'primeng/types/table'; export interface DataTableColumn { field?: string; @@ -15,8 +49,49 @@ export interface DataTableColumn { selector: 'extra-data-table', host: { style: 'display: block' }, standalone: true, - imports: [TableModule, PrimeTemplate], + imports: [TableModule, PrimeTemplate, NgTemplateOutlet], template: ` + + + + @for (col of columns; track $index) { + @if (col.selectionMode === 'single') { + + } @else if (col.selectionMode === 'multiple') { + + + + } @else { + + {{ col.header }} + @if (col.sortable) { + + } + + } + } + + + + + + + @for (col of columns; track $index) { + @if (col.selectionMode === 'single') { + + + + } @else if (col.selectionMode === 'multiple') { + + + + } @else { + {{ rowData[col.field] }} + } + } + + + + - - @for (col of columns; track $index) { - @if (col.selectionMode === 'single') { - - } @else if (col.selectionMode === 'multiple') { - - - - } @else { - - {{ col.header }} - @if (col.sortable) { - - } - - } - } - + - - - @for (col of columns; track $index) { - @if (col.selectionMode === 'single') { - - - - } @else if (col.selectionMode === 'multiple') { - - - - } @else { - {{ rowData[col.field] }} - } - } - + + + + + + @if (captionTemplate) { + + + + } + @if (footerTemplate) { + + + + } + @if (summaryTemplate) { + + + + } + @if (emptyMessageTemplate) { + + + + } + @if (rowExpansionTemplate) { + + + + } + @if (groupHeaderTemplate) { + + + + } + @if (groupFooterTemplate) { + + + + } + @if (paginatorLeftTemplate) { + + + + } + @if (paginatorRightTemplate) { + + + + } + @if (loadingIconTemplate) { + + + + } `, }) -export class ExtraDataTableComponent { +export class ExtraDataTableComponent implements AfterContentInit { + // ── Data ────────────────────────────────────────────────────────────────── @Input() value: any[] = []; @Input() columns: DataTableColumn[] = []; + + // ── Appearance ──────────────────────────────────────────────────────────── @Input() stripedRows = false; @Input() showGridlines = false; - @Input() loading = false; @Input() size: 'small' | 'large' | undefined = undefined; - @Input() scrollable = false; - @Input() scrollHeight = ''; + @Input() styleClass: string | undefined = undefined; + @Input() tableStyle: { [key: string]: any } | undefined = undefined; + @Input() tableStyleClass: string | undefined = undefined; + + // ── Loading ─────────────────────────────────────────────────────────────── + @Input() loading = false; + + // ── Pagination ──────────────────────────────────────────────────────────── @Input() paginator = false; - @Input() rows = 5; - @Input() rowsPerPageOptions: number[] = [5, 10, 25]; + @Input() rows = 10; + @Input() rowsPerPageOptions: number[] = []; + @Input() first: number | null | undefined = 0; + @Input() totalRecords = 0; + @Output() firstChange = new EventEmitter(); + + // ── Scroll ──────────────────────────────────────────────────────────────── + @Input() scrollable = false; + @Input() scrollHeight: string | undefined = undefined; + + // ── Virtual Scroll ──────────────────────────────────────────────────────── + @Input() virtualScroll: boolean | undefined = undefined; + @Input() virtualScrollItemSize: number | undefined = undefined; + @Input() virtualScrollOptions: ScrollerOptions | undefined = undefined; + @Input() virtualScrollDelay = 250; + + // ── Lazy loading ────────────────────────────────────────────────────────── + @Input() lazy = false; + @Input() lazyLoadOnInit = true; + @Output() onLazyLoad = new EventEmitter(); + + // ── Sorting ─────────────────────────────────────────────────────────────── + @Input() sortField: string | undefined | null = undefined; + @Input() sortOrder = 1; + @Input() defaultSortOrder = 1; + @Input() sortMode: 'single' | 'multiple' = 'single'; + @Input() multiSortMeta: SortMeta[] | undefined | null = undefined; + @Input() customSort = false; + @Output() onSort = new EventEmitter(); + @Output() sortFunction = new EventEmitter(); + + // ── Selection ───────────────────────────────────────────────────────────── @Input() selectionMode: 'single' | 'multiple' | undefined = undefined; @Input() selection: any = null; - @Output() selectionChange = new EventEmitter(); @Input() dataKey = 'id'; + @Input() metaKeySelection = false; + @Input() rowSelectable: ((row: { data: any; index: number }) => boolean | undefined) | undefined = undefined; + @Input() selectAll: boolean | null = null; + @Output() selectionChange = new EventEmitter(); + @Output() selectAllChange = new EventEmitter(); + @Output() onRowSelect = new EventEmitter(); + @Output() onRowUnselect = new EventEmitter>(); + @Output() onHeaderCheckboxToggle = new EventEmitter(); + + // ── Context Menu ────────────────────────────────────────────────────────── + @Input() contextMenu: any = undefined; + @Input() contextMenuSelection: any = undefined; + @Output() contextMenuSelectionChange = new EventEmitter(); + @Output() onContextMenuSelect = new EventEmitter(); + + // ── Row Expand ──────────────────────────────────────────────────────────── + @Input() expandedRowKeys: { [s: string]: boolean } = {}; + @Input() rowExpandMode: 'multiple' | 'single' = 'multiple'; + @Output() onRowExpand = new EventEmitter(); + @Output() onRowCollapse = new EventEmitter(); + + // ── Row Group ───────────────────────────────────────────────────────────── + @Input() rowGroupMode: 'subheader' | 'rowspan' | undefined = undefined; + @Input() groupRowsBy: string | undefined = undefined; + @Input() groupRowsByOrder = 1; + + // ── Edit ────────────────────────────────────────────────────────────────── + @Input() editMode: 'cell' | 'row' = 'cell'; + @Output() onEditInit = new EventEmitter(); + @Output() onEditComplete = new EventEmitter(); + @Output() onEditCancel = new EventEmitter(); + + // ── Column Resize ───────────────────────────────────────────────────────── + @Input() resizableColumns = false; + @Input() columnResizeMode: 'fit' | 'expand' = 'fit'; + @Output() onColResize = new EventEmitter(); + + // ── Column Reorder ──────────────────────────────────────────────────────── + @Input() reorderableColumns = false; + @Output() onColReorder = new EventEmitter(); + + // ── Row Reorder ─────────────────────────────────────────────────────────── + @Output() onRowReorder = new EventEmitter(); + + // ── Row Hover ───────────────────────────────────────────────────────────── + @Input() rowHover = false; + + // ── Filter ──────────────────────────────────────────────────────────────── + @Input() filters: { [s: string]: FilterMetadata | FilterMetadata[] } = {}; + @Input() globalFilterFields: string[] | undefined = undefined; + @Input() filterDelay = 300; + @Output() onFilter = new EventEmitter(); + + // ── Pagination events ───────────────────────────────────────────────────── + @Output() onPage = new EventEmitter(); + + // ── State ───────────────────────────────────────────────────────────────── + @Input() stateKey: string | undefined = undefined; + @Input() stateStorage: 'session' | 'local' = 'session'; + + // ── Default templates ───────────────────────────────────────────────────── + @ViewChild('defaultHeaderTpl') defaultHeaderTpl!: TemplateRef; + @ViewChild('defaultBodyTpl') defaultBodyTpl!: TemplateRef; + + // ── User-provided content templates ────────────────────────────────────── + @ContentChildren(PrimeTemplate) templates!: QueryList; + + headerTemplate: TemplateRef | null = null; + bodyTemplate: TemplateRef | null = null; + captionTemplate: TemplateRef | null = null; + footerTemplate: TemplateRef | null = null; + summaryTemplate: TemplateRef | null = null; + emptyMessageTemplate: TemplateRef | null = null; + rowExpansionTemplate: TemplateRef | null = null; + groupHeaderTemplate: TemplateRef | null = null; + groupFooterTemplate: TemplateRef | null = null; + paginatorLeftTemplate: TemplateRef | null = null; + paginatorRightTemplate: TemplateRef | null = null; + loadingIconTemplate: TemplateRef | null = null; + + ngAfterContentInit(): void { + this.templates.forEach((t) => { + switch (t.getType()) { + case 'header': + this.headerTemplate = t.template; + break; + case 'body': + this.bodyTemplate = t.template; + break; + case 'caption': + this.captionTemplate = t.template; + break; + case 'footer': + this.footerTemplate = t.template; + break; + case 'summary': + this.summaryTemplate = t.template; + break; + case 'emptymessage': + this.emptyMessageTemplate = t.template; + break; + case 'rowexpansion': + this.rowExpansionTemplate = t.template; + break; + case 'groupheader': + this.groupHeaderTemplate = t.template; + break; + case 'groupfooter': + this.groupFooterTemplate = t.template; + break; + case 'paginatorleft': + this.paginatorLeftTemplate = t.template; + break; + case 'paginatorright': + this.paginatorRightTemplate = t.template; + break; + case 'loadingicon': + this.loadingIconTemplate = t.template; + break; + } + }); + } } diff --git a/src/stories/components/data-table/data-table.stories.ts b/src/stories/components/data-table/data-table.stories.ts index 03d2fd92..3d614feb 100644 --- a/src/stories/components/data-table/data-table.stories.ts +++ b/src/stories/components/data-table/data-table.stories.ts @@ -9,6 +9,8 @@ import { DataTableSelectionRadioComponent } from './examples/data-table-selectio import { DataTableSelectionCheckboxComponent } from './examples/data-table-selection-checkbox.component'; import { DataTableScrollVerticalComponent } from './examples/data-table-scroll-vertical.component'; import { DataTableScrollHorizontalComponent } from './examples/data-table-scroll-horizontal.component'; +import { DataTableCustomBodyComponent } from './examples/data-table-custom-body.component'; +import { DataTableLazyComponent } from './examples/data-table-lazy.component'; const meta: Meta = { title: 'Components/Data/DataTable', @@ -567,3 +569,151 @@ export class DataTableScrollHorizontalComponent { }, }, }; + +// ── CustomBody ──────────────────────────────────────────────────────────────── + +export const CustomBody: Story = { + name: 'Custom Templates', + decorators: [moduleMetadata({ imports: [DataTableCustomBodyComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: + 'Кастомные шаблоны для заголовка и тела таблицы. Передайте `ng-template` с директивой `pTemplate` внутрь ``. Поддерживаются шаблоны: `header`, `body`, `footer`, `caption`, `summary`, `emptymessage`, `rowexpansion`, `groupheader`, `groupfooter`, `paginatorleft`, `paginatorright`, `loadingicon`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { PrimeTemplate } from 'primeng/api'; +import { TagModule } from 'primeng/tag'; + +@Component({ + selector: 'app-data-table-custom-body', + standalone: true, + imports: [ExtraDataTableComponent, PrimeTemplate, TagModule], + template: \` + + + + + @for (col of columns; track col.field) { + + {{ col.header }} + @if (col.sortable) { } + + } + + + + + + {{ row.trackNumber }} + {{ row.destination }} + + + + {{ row.weight }} кг + {{ row.cost | currency:'RUB':'symbol':'1.0-0':'ru' }} + + + + + Нет данных для отображения + + + + \`, +}) +export class DataTableCustomBodyComponent { + shipments = [...]; + columns: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, + ]; + + getSeverity(status: string) { + switch (status) { + case 'Доставлен': return 'success'; + case 'В пути': return 'info'; + case 'Задержан': return 'danger'; + default: return 'warn'; + } + } +} + `, + }, + }, + }, +}; + +// ── Lazy loading ────────────────────────────────────────────────────────────── + +export const LazyLoading: Story = { + name: 'Lazy Loading', + decorators: [moduleMetadata({ imports: [DataTableLazyComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: + 'Ленивая подгрузка данных с сервера при пагинации/сортировке. Используйте `[lazy]="true"`, `[totalRecords]` и `(onLazyLoad)` для управления загрузкой.', + }, + source: { + language: 'ts', + code: ` +import { Component, OnInit } from '@angular/core'; +import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; +import { TableLazyLoadEvent } from 'primeng/types/table'; + +@Component({ + selector: 'app-data-table-lazy', + standalone: true, + imports: [ExtraDataTableComponent], + template: \` + + \`, +}) +export class DataTableLazyComponent implements OnInit { + shipments: any[] = []; + columns: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, + ]; + totalRecords = 0; + loading = true; + + onLazyLoad(event: TableLazyLoadEvent): void { + this.loading = true; + // Запрос к серверу с event.first, event.rows, event.sortField, event.sortOrder + myApiService.getShipments(event).subscribe(result => { + this.shipments = result.data; + this.totalRecords = result.total; + this.loading = false; + }); + } +} + `, + }, + }, + }, +}; + diff --git a/src/stories/components/data-table/examples/data-table-custom-body.component.ts b/src/stories/components/data-table/examples/data-table-custom-body.component.ts new file mode 100644 index 00000000..f3b95ecb --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-custom-body.component.ts @@ -0,0 +1,81 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { PrimeTemplate } from 'primeng/api'; +import { TagModule } from 'primeng/tag'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +const COLUMNS: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, +]; + +const template = ` +
+ + + + + @for (col of columns; track col.field) { + + {{ col.header }} + @if (col.sortable) { } + + } + + + + + + {{ row.trackNumber }} + {{ row.destination }} + + + + {{ row.weight }} кг + {{ row.cost | currency:'RUB':'symbol':'1.0-0':'ru' }} + + + + + Нет данных для отображения + + + +
+`; + +@Component({ + selector: 'app-data-table-custom-body', + standalone: true, + imports: [ExtraDataTableComponent, PrimeTemplate, TagModule, CommonModule], + template, +}) +export class DataTableCustomBodyComponent { + shipments = SHIPMENTS; + columns = COLUMNS; + + getSeverity(status: string): 'success' | 'warn' | 'danger' | 'info' | 'secondary' | undefined { + switch (status) { + case 'Доставлен': return 'success'; + case 'В пути': return 'info'; + case 'Задержан': return 'danger'; + case 'Ожидание': return 'warn'; + default: return 'secondary'; + } + } +} + diff --git a/src/stories/components/data-table/examples/data-table-lazy.component.ts b/src/stories/components/data-table/examples/data-table-lazy.component.ts new file mode 100644 index 00000000..9ca1f978 --- /dev/null +++ b/src/stories/components/data-table/examples/data-table-lazy.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit } from '@angular/core'; +import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; +import { PrimeTemplate } from 'primeng/api'; +import { TableLazyLoadEvent } from 'primeng/types/table'; + +const ALL_SHIPMENTS = Array.from({ length: 100 }, (_, i) => ({ + id: i + 1, + trackNumber: `ЦД-${String(i + 100000).padStart(8, '0')}`, + destination: ['Москва', 'Новосибирск', 'Екатеринбург', 'Казань', 'Краснодар'][i % 5], + status: ['В пути', 'Доставлен', 'Задержан', 'Ожидание'][i % 4], + weight: Number((Math.random() * 10).toFixed(1)), + cost: Math.floor(Math.random() * 5000) + 200, +})); + +const COLUMNS: DataTableColumn[] = [ + { field: 'trackNumber', header: 'Трек-номер', sortable: true }, + { field: 'destination', header: 'Назначение', sortable: true }, + { field: 'status', header: 'Статус', sortable: true }, + { field: 'weight', header: 'Вес, кг', sortable: true }, + { field: 'cost', header: 'Стоимость, ₽', sortable: true }, +]; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-data-table-lazy', + standalone: true, + imports: [ExtraDataTableComponent, PrimeTemplate], + template, +}) +export class DataTableLazyComponent implements OnInit { + shipments: any[] = []; + columns = COLUMNS; + totalRecords = ALL_SHIPMENTS.length; + loading = true; + + ngOnInit(): void { + // Initial load happens via onLazyLoad + } + + onLazyLoad(event: TableLazyLoadEvent): void { + this.loading = true; + // Simulate async server request + setTimeout(() => { + const start = event.first ?? 0; + const end = start + (event.rows ?? 10); + this.shipments = ALL_SHIPMENTS.slice(start, end); + this.loading = false; + }, 500); + } +} + diff --git a/src/stories/components/p-data-table/examples/p-data-table-custom-body.component.ts b/src/stories/components/p-data-table/examples/p-data-table-custom-body.component.ts new file mode 100644 index 00000000..bd448577 --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-custom-body.component.ts @@ -0,0 +1,58 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TableModule } from 'primeng/table'; +import { TagModule } from 'primeng/tag'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +@Component({ + selector: 'app-p-data-table-custom-body', + standalone: true, + imports: [CommonModule, TableModule, TagModule], + template: ` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + + + + {{ shipment.weight }} кг + {{ shipment.cost | currency:'RUB':'symbol':'1.0-0':'ru' }} + + + + Нет данных для отображения + + + `, +}) +export class PDataTableCustomBodyComponent { + shipments = SHIPMENTS; + + getSeverity(status: string): 'success' | 'warn' | 'danger' | 'info' | 'secondary' | undefined { + switch (status) { + case 'Доставлен': return 'success'; + case 'В пути': return 'info'; + case 'Задержан': return 'danger'; + case 'Ожидание': return 'warn'; + default: return 'secondary'; + } + } +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/examples/p-data-table-default.component.ts b/src/stories/components/p-data-table/examples/p-data-table-default.component.ts new file mode 100644 index 00000000..e16de983 --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-default.component.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +@Component({ + selector: 'app-p-data-table-default', + standalone: true, + imports: [TableModule], + template: ` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + `, +}) +export class PDataTableDefaultComponent { + shipments = SHIPMENTS; +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/examples/p-data-table-grid-lines.component.ts b/src/stories/components/p-data-table/examples/p-data-table-grid-lines.component.ts new file mode 100644 index 00000000..ce4e45e8 --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-grid-lines.component.ts @@ -0,0 +1,41 @@ +import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +@Component({ + selector: 'app-p-data-table-grid-lines', + standalone: true, + imports: [TableModule], + template: ` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + `, +}) +export class PDataTableGridLinesComponent { + shipments = SHIPMENTS; +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/examples/p-data-table-lazy.component.ts b/src/stories/components/p-data-table/examples/p-data-table-lazy.component.ts new file mode 100644 index 00000000..4b2ee67f --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-lazy.component.ts @@ -0,0 +1,85 @@ +import { Component, OnInit } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const ALL_SHIPMENTS = Array.from({ length: 500 }, (_, i) => ({ + id: i + 1, + trackNumber: `ЦД-${String(i + 100000).padStart(8, '0')}`, + destination: ['Москва', 'Новосибирск', 'Екатеринбург', 'Казань', 'Краснодар', 'Санкт-Петербург', 'Воронеж', 'Самара', 'Ростов-на-Дону', 'Уфа'][i % 10], + status: ['В пути', 'Доставлен', 'Задержан', 'Ожидание'][i % 4], + weight: Number(((i * 0.37 + 0.5) % 10).toFixed(1)), + cost: ((i * 137 + 200) % 5000) + 200, +})); + +const PAGE_SIZE = 50; + +@Component({ + selector: 'app-p-data-table-lazy', + standalone: true, + imports: [TableModule], + template: ` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment?.trackNumber }} + {{ shipment?.destination }} + {{ shipment?.status }} + {{ shipment?.weight }} + {{ shipment?.cost }} + + + + + Загрузка… + + + + `, +}) +export class PDataTableLazyComponent implements OnInit { + readonly PAGE_SIZE = PAGE_SIZE; + + shipments: any[] = Array.from({ length: ALL_SHIPMENTS.length }); + totalRecords = ALL_SHIPMENTS.length; + loading = false; + + ngOnInit(): void { + // shipments pre-filled with placeholders so virtual scroll knows total size + } + + onLazyLoad(event: any): void { + this.loading = true; + const start = event.first ?? 0; + const end = start + (event.rows ?? PAGE_SIZE); + + // Simulate async server request + setTimeout(() => { + const page = ALL_SHIPMENTS.slice(start, end); + const updated = [...this.shipments]; + for (let i = 0; i < page.length; i++) { + updated[start + i] = page[i]; + } + this.shipments = updated; + this.loading = false; + }, 300); + } +} diff --git a/src/stories/components/p-data-table/examples/p-data-table-pagination.component.ts b/src/stories/components/p-data-table/examples/p-data-table-pagination.component.ts new file mode 100644 index 00000000..c6af7b1e --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-pagination.component.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +@Component({ + selector: 'app-p-data-table-pagination', + standalone: true, + imports: [TableModule], + template: ` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + `, +}) +export class PDataTablePaginationComponent { + shipments = SHIPMENTS; +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/examples/p-data-table-scroll-horizontal.component.ts b/src/stories/components/p-data-table/examples/p-data-table-scroll-horizontal.component.ts new file mode 100644 index 00000000..46bd0e84 --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-scroll-horizontal.component.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', sender: 'Иванов И.И.', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200, dimensions: '30×20×15 см' }, + { id: 2, trackNumber: 'ЦД-00123457', sender: 'Петров П.П.', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450, dimensions: '10×10×10 см' }, + { id: 3, trackNumber: 'ЦД-00123458', sender: 'Сидоров С.С.', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100, dimensions: '50×40×30 см' }, + { id: 4, trackNumber: 'ЦД-00123459', sender: 'Козлов К.К.', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750, dimensions: '20×15×10 см' }, + { id: 5, trackNumber: 'ЦД-00123460', sender: 'Новиков Н.Н.', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800, dimensions: '40×30×20 см' }, +]; + +@Component({ + selector: 'app-p-data-table-scroll-horizontal', + standalone: true, + imports: [TableModule], + template: ` + + + + + Трек-номер + + + Отправитель + + + Назначение + + + Статус + + + Вес, кг + + + Стоимость, ₽ + + Габариты + + + + + {{ shipment.trackNumber }} + {{ shipment.sender }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + {{ shipment.dimensions }} + + + + `, +}) +export class PDataTableScrollHorizontalComponent { + shipments = SHIPMENTS; +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/examples/p-data-table-scroll-vertical.component.ts b/src/stories/components/p-data-table/examples/p-data-table-scroll-vertical.component.ts new file mode 100644 index 00000000..d3b808a3 --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-scroll-vertical.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const BASE_SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1 }, +]; + +@Component({ + selector: 'app-p-data-table-scroll-vertical', + standalone: true, + imports: [TableModule], + template: ` + + + + Трек-номер + Назначение + Статус + Вес, кг + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + + + + `, +}) +export class PDataTableScrollVerticalComponent { + shipments = [...BASE_SHIPMENTS, ...BASE_SHIPMENTS, ...BASE_SHIPMENTS]; +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/examples/p-data-table-selectable.component.ts b/src/stories/components/p-data-table/examples/p-data-table-selectable.component.ts new file mode 100644 index 00000000..5265ad9a --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-selectable.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +@Component({ + selector: 'app-p-data-table-selectable', + standalone: true, + imports: [TableModule], + template: ` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + `, +}) +export class PDataTableSelectableComponent { + shipments = SHIPMENTS; + selected: any = null; +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/examples/p-data-table-selection-checkbox.component.ts b/src/stories/components/p-data-table/examples/p-data-table-selection-checkbox.component.ts new file mode 100644 index 00000000..63e165ea --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-selection-checkbox.component.ts @@ -0,0 +1,46 @@ +import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, +]; + +@Component({ + selector: 'app-p-data-table-selection-checkbox', + standalone: true, + imports: [TableModule], + template: ` + + + + + + + Трек-номер + Назначение + Статус + Вес, кг + + + + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + + + + `, +}) +export class PDataTableSelectionCheckboxComponent { + shipments = SHIPMENTS; + selected: any[] = []; +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/examples/p-data-table-selection-radio.component.ts b/src/stories/components/p-data-table/examples/p-data-table-selection-radio.component.ts new file mode 100644 index 00000000..0fa3686a --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-selection-radio.component.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, +]; + +@Component({ + selector: 'app-p-data-table-selection-radio', + standalone: true, + imports: [TableModule], + template: ` + + + + + Трек-номер + Назначение + Статус + Вес, кг + + + + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + + + + `, +}) +export class PDataTableSelectionRadioComponent { + shipments = SHIPMENTS; + selected: any = null; +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/examples/p-data-table-striped-rows.component.ts b/src/stories/components/p-data-table/examples/p-data-table-striped-rows.component.ts new file mode 100644 index 00000000..4ffba2ac --- /dev/null +++ b/src/stories/components/p-data-table/examples/p-data-table-striped-rows.component.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +@Component({ + selector: 'app-p-data-table-striped-rows', + standalone: true, + imports: [TableModule], + template: ` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + `, +}) +export class PDataTableStripedRowsComponent { + shipments = SHIPMENTS; +} \ No newline at end of file diff --git a/src/stories/components/p-data-table/p-data-table.stories.ts b/src/stories/components/p-data-table/p-data-table.stories.ts new file mode 100644 index 00000000..7008be57 --- /dev/null +++ b/src/stories/components/p-data-table/p-data-table.stories.ts @@ -0,0 +1,198 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { TableModule } from 'primeng/table'; +import { PDataTableDefaultComponent } from './examples/p-data-table-default.component'; +import { PDataTableStripedRowsComponent } from './examples/p-data-table-striped-rows.component'; +import { PDataTableSelectableComponent } from './examples/p-data-table-selectable.component'; +import { PDataTableGridLinesComponent } from './examples/p-data-table-grid-lines.component'; +import { PDataTablePaginationComponent } from './examples/p-data-table-pagination.component'; +import { PDataTableSelectionRadioComponent } from './examples/p-data-table-selection-radio.component'; +import { PDataTableSelectionCheckboxComponent } from './examples/p-data-table-selection-checkbox.component'; +import { PDataTableScrollVerticalComponent } from './examples/p-data-table-scroll-vertical.component'; +import { PDataTableScrollHorizontalComponent } from './examples/p-data-table-scroll-horizontal.component'; +import { PDataTableCustomBodyComponent } from './examples/p-data-table-custom-body.component'; +import { PDataTableLazyComponent } from './examples/p-data-table-lazy.component'; + +const meta: Meta = { + title: 'Components/Data/DataTable (PrimeNG)', + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: `Таблица данных на базе PrimeNG pTable с поддержкой сортировки, пагинации, выбора строк и прокрутки. + +\`\`\`typescript +import { TableModule } from 'primeng/table'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-datatable' }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'DataTable', + decorators: [moduleMetadata({ imports: [PDataTableDefaultComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Базовая таблица отправлений с сортировкой по всем столбцам на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── StripedRows ─────────────────────────────────────────────────────────────── + +export const StripedRows: Story = { + name: 'StripedRows', + decorators: [moduleMetadata({ imports: [PDataTableStripedRowsComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Чередование цвета строк для улучшения читаемости на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── Selectable ──────────────────────────────────────────────────────────────── + +export const Selectable: Story = { + name: 'Selectable', + decorators: [moduleMetadata({ imports: [PDataTableSelectableComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Выбор строки кликом. Режим single — выбирается одна строка на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── GridLines ───────────────────────────────────────────────────────────────── + +export const GridLines: Story = { + name: 'GridLines', + decorators: [moduleMetadata({ imports: [PDataTableGridLinesComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Сетка между ячейками для наглядного разграничения данных на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── Pagination ──────────────────────────────────────────────────────────────── + +export const Pagination: Story = { + name: 'Pagination', + decorators: [moduleMetadata({ imports: [PDataTablePaginationComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Пагинация для больших наборов данных на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── SelectionRadio ──────────────────────────────────────────────────────────── + +export const SelectionRadio: Story = { + name: 'Row Selection: RadioButton', + decorators: [moduleMetadata({ imports: [PDataTableSelectionRadioComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Выбор одной строки через радио-кнопку на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── SelectionCheckbox ───────────────────────────────────────────────────────── + +export const SelectionCheckbox: Story = { + name: 'Row Selection: Checkbox', + decorators: [moduleMetadata({ imports: [PDataTableSelectionCheckboxComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Множественный выбор строк через чекбоксы на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── ScrollVertical ──────────────────────────────────────────────────────────── + +export const ScrollVertical: Story = { + name: 'Scroll: Vertical', + decorators: [moduleMetadata({ imports: [PDataTableScrollVerticalComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Вертикальная прокрутка с фиксированной высотой контейнера на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── ScrollHorizontal ────────────────────────────────────────────────────────── + +export const ScrollHorizontal: Story = { + name: 'Scroll: Horizontal', + decorators: [moduleMetadata({ imports: [PDataTableScrollHorizontalComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Горизонтальная прокрутка при большом количестве столбцов на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── CustomBody ──────────────────────────────────────────────────────────────── + +export const CustomBody: Story = { + name: 'Custom Templates', + decorators: [moduleMetadata({ imports: [PDataTableCustomBodyComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Кастомные шаблоны для заголовка и тела таблицы на базе PrimeNG pTable.', + }, + }, + }, +}; + +// ── Lazy loading ────────────────────────────────────────────────────────────── + +export const LazyLoading: Story = { + name: 'Lazy Loading', + decorators: [moduleMetadata({ imports: [PDataTableLazyComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Lazy Loading + Virtual Scroll для работы с большими наборами данных. Данные подгружаются порциями при прокрутке таблицы. Используйте `[lazy]="true"`, `[virtualScroll]="true"`, `[virtualScrollItemSize]`, `[totalRecords]` и `(onLazyLoad)` для управления загрузкой данных с сервера.', + }, + }, + }, +}; From d5197b7df59d77e4ec99141732e530ba9cf68a82 Mon Sep 17 00:00:00 2001 From: "ak.dmitriev" Date: Fri, 29 May 2026 18:40:20 +0700 Subject: [PATCH 24/28] DS-517 --- .../data-table/data-table.component.ts | 412 ---------- src/lib/components/data-table/ng-package.json | 7 - src/lib/components/data-table/public_api.ts | 4 - .../data-table/data-table.stories.ts | 719 ---------------- .../data-table-custom-body.component.ts | 81 -- .../examples/data-table-default.component.ts | 38 - .../data-table-grid-lines.component.ts | 35 - .../examples/data-table-lazy.component.ts | 66 -- .../data-table-pagination.component.ts | 44 - .../data-table-scroll-horizontal.component.ts | 37 - .../data-table-scroll-vertical.component.ts | 42 - .../data-table-selectable.component.ts | 42 - ...data-table-selection-checkbox.component.ts | 42 - .../data-table-selection-radio.component.ts | 42 - .../data-table-striped-rows.component.ts | 35 - ...=> primeng-table-custom-body.component.ts} | 2 +- ....ts => primeng-table-default.component.ts} | 2 +- ... => primeng-table-grid-lines.component.ts} | 2 +- ...ent.ts => primeng-table-lazy.component.ts} | 2 +- ... => primeng-table-pagination.component.ts} | 2 +- ...meng-table-scroll-horizontal.component.ts} | 2 +- ...rimeng-table-scroll-vertical.component.ts} | 2 +- ... => primeng-table-selectable.component.ts} | 2 +- ...eng-table-selection-checkbox.component.ts} | 2 +- ...rimeng-table-selection-radio.component.ts} | 2 +- ...> primeng-table-striped-rows.component.ts} | 2 +- .../p-data-table/p-data-table.stories.ts | 198 ----- .../primeng-data-table.stories.ts | 777 ++++++++++++++++++ 28 files changed, 788 insertions(+), 1855 deletions(-) delete mode 100644 src/lib/components/data-table/data-table.component.ts delete mode 100644 src/lib/components/data-table/ng-package.json delete mode 100644 src/lib/components/data-table/public_api.ts delete mode 100644 src/stories/components/data-table/data-table.stories.ts delete mode 100644 src/stories/components/data-table/examples/data-table-custom-body.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-default.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-grid-lines.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-lazy.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-pagination.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-selectable.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-selection-radio.component.ts delete mode 100644 src/stories/components/data-table/examples/data-table-striped-rows.component.ts rename src/stories/components/p-data-table/examples/{p-data-table-custom-body.component.ts => primeng-table-custom-body.component.ts} (98%) rename src/stories/components/p-data-table/examples/{p-data-table-default.component.ts => primeng-table-default.component.ts} (98%) rename src/stories/components/p-data-table/examples/{p-data-table-grid-lines.component.ts => primeng-table-grid-lines.component.ts} (97%) rename src/stories/components/p-data-table/examples/{p-data-table-lazy.component.ts => primeng-table-lazy.component.ts} (98%) rename src/stories/components/p-data-table/examples/{p-data-table-pagination.component.ts => primeng-table-pagination.component.ts} (98%) rename src/stories/components/p-data-table/examples/{p-data-table-scroll-horizontal.component.ts => primeng-table-scroll-horizontal.component.ts} (98%) rename src/stories/components/p-data-table/examples/{p-data-table-scroll-vertical.component.ts => primeng-table-scroll-vertical.component.ts} (97%) rename src/stories/components/p-data-table/examples/{p-data-table-selectable.component.ts => primeng-table-selectable.component.ts} (97%) rename src/stories/components/p-data-table/examples/{p-data-table-selection-checkbox.component.ts => primeng-table-selection-checkbox.component.ts} (97%) rename src/stories/components/p-data-table/examples/{p-data-table-selection-radio.component.ts => primeng-table-selection-radio.component.ts} (97%) rename src/stories/components/p-data-table/examples/{p-data-table-striped-rows.component.ts => primeng-table-striped-rows.component.ts} (98%) delete mode 100644 src/stories/components/p-data-table/p-data-table.stories.ts create mode 100644 src/stories/components/p-data-table/primeng-data-table.stories.ts diff --git a/src/lib/components/data-table/data-table.component.ts b/src/lib/components/data-table/data-table.component.ts deleted file mode 100644 index 0e72e028..00000000 --- a/src/lib/components/data-table/data-table.component.ts +++ /dev/null @@ -1,412 +0,0 @@ -import { - AfterContentInit, - Component, - ContentChildren, - EventEmitter, - Input, - Output, - QueryList, - TemplateRef, - ViewChild, -} from '@angular/core'; -import { NgTemplateOutlet } from '@angular/common'; -import { TableModule } from 'primeng/table'; -import { - PrimeTemplate, - SortMeta, - FilterMetadata, - ScrollerOptions, -} from 'primeng/api'; -import { - TableLazyLoadEvent, - TableRowSelectEvent, - TableRowUnSelectEvent, - TablePageEvent, - TableFilterEvent, - TableRowExpandEvent, - TableRowCollapseEvent, - TableContextMenuSelectEvent, - TableColResizeEvent, - TableColumnReorderEvent, - TableRowReorderEvent, - TableEditInitEvent, - TableEditCompleteEvent, - TableEditCancelEvent, - TableHeaderCheckboxToggleEvent, - TableSelectAllChangeEvent, -} from 'primeng/types/table'; - -export interface DataTableColumn { - field?: string; - header?: string; - sortable?: boolean; - style?: string; - headerStyle?: string; - selectionMode?: 'single' | 'multiple'; -} - -@Component({ - selector: 'extra-data-table', - host: { style: 'display: block' }, - standalone: true, - imports: [TableModule, PrimeTemplate, NgTemplateOutlet], - template: ` - - - - @for (col of columns; track $index) { - @if (col.selectionMode === 'single') { - - } @else if (col.selectionMode === 'multiple') { - - - - } @else { - - {{ col.header }} - @if (col.sortable) { - - } - - } - } - - - - - - - @for (col of columns; track $index) { - @if (col.selectionMode === 'single') { - - - - } @else if (col.selectionMode === 'multiple') { - - - - } @else { - {{ rowData[col.field] }} - } - } - - - - - - - - - - - - - - - - @if (captionTemplate) { - - - - } - @if (footerTemplate) { - - - - } - @if (summaryTemplate) { - - - - } - @if (emptyMessageTemplate) { - - - - } - @if (rowExpansionTemplate) { - - - - } - @if (groupHeaderTemplate) { - - - - } - @if (groupFooterTemplate) { - - - - } - @if (paginatorLeftTemplate) { - - - - } - @if (paginatorRightTemplate) { - - - - } - @if (loadingIconTemplate) { - - - - } - - `, -}) -export class ExtraDataTableComponent implements AfterContentInit { - // ── Data ────────────────────────────────────────────────────────────────── - @Input() value: any[] = []; - @Input() columns: DataTableColumn[] = []; - - // ── Appearance ──────────────────────────────────────────────────────────── - @Input() stripedRows = false; - @Input() showGridlines = false; - @Input() size: 'small' | 'large' | undefined = undefined; - @Input() styleClass: string | undefined = undefined; - @Input() tableStyle: { [key: string]: any } | undefined = undefined; - @Input() tableStyleClass: string | undefined = undefined; - - // ── Loading ─────────────────────────────────────────────────────────────── - @Input() loading = false; - - // ── Pagination ──────────────────────────────────────────────────────────── - @Input() paginator = false; - @Input() rows = 10; - @Input() rowsPerPageOptions: number[] = []; - @Input() first: number | null | undefined = 0; - @Input() totalRecords = 0; - @Output() firstChange = new EventEmitter(); - - // ── Scroll ──────────────────────────────────────────────────────────────── - @Input() scrollable = false; - @Input() scrollHeight: string | undefined = undefined; - - // ── Virtual Scroll ──────────────────────────────────────────────────────── - @Input() virtualScroll: boolean | undefined = undefined; - @Input() virtualScrollItemSize: number | undefined = undefined; - @Input() virtualScrollOptions: ScrollerOptions | undefined = undefined; - @Input() virtualScrollDelay = 250; - - // ── Lazy loading ────────────────────────────────────────────────────────── - @Input() lazy = false; - @Input() lazyLoadOnInit = true; - @Output() onLazyLoad = new EventEmitter(); - - // ── Sorting ─────────────────────────────────────────────────────────────── - @Input() sortField: string | undefined | null = undefined; - @Input() sortOrder = 1; - @Input() defaultSortOrder = 1; - @Input() sortMode: 'single' | 'multiple' = 'single'; - @Input() multiSortMeta: SortMeta[] | undefined | null = undefined; - @Input() customSort = false; - @Output() onSort = new EventEmitter(); - @Output() sortFunction = new EventEmitter(); - - // ── Selection ───────────────────────────────────────────────────────────── - @Input() selectionMode: 'single' | 'multiple' | undefined = undefined; - @Input() selection: any = null; - @Input() dataKey = 'id'; - @Input() metaKeySelection = false; - @Input() rowSelectable: ((row: { data: any; index: number }) => boolean | undefined) | undefined = undefined; - @Input() selectAll: boolean | null = null; - @Output() selectionChange = new EventEmitter(); - @Output() selectAllChange = new EventEmitter(); - @Output() onRowSelect = new EventEmitter(); - @Output() onRowUnselect = new EventEmitter>(); - @Output() onHeaderCheckboxToggle = new EventEmitter(); - - // ── Context Menu ────────────────────────────────────────────────────────── - @Input() contextMenu: any = undefined; - @Input() contextMenuSelection: any = undefined; - @Output() contextMenuSelectionChange = new EventEmitter(); - @Output() onContextMenuSelect = new EventEmitter(); - - // ── Row Expand ──────────────────────────────────────────────────────────── - @Input() expandedRowKeys: { [s: string]: boolean } = {}; - @Input() rowExpandMode: 'multiple' | 'single' = 'multiple'; - @Output() onRowExpand = new EventEmitter(); - @Output() onRowCollapse = new EventEmitter(); - - // ── Row Group ───────────────────────────────────────────────────────────── - @Input() rowGroupMode: 'subheader' | 'rowspan' | undefined = undefined; - @Input() groupRowsBy: string | undefined = undefined; - @Input() groupRowsByOrder = 1; - - // ── Edit ────────────────────────────────────────────────────────────────── - @Input() editMode: 'cell' | 'row' = 'cell'; - @Output() onEditInit = new EventEmitter(); - @Output() onEditComplete = new EventEmitter(); - @Output() onEditCancel = new EventEmitter(); - - // ── Column Resize ───────────────────────────────────────────────────────── - @Input() resizableColumns = false; - @Input() columnResizeMode: 'fit' | 'expand' = 'fit'; - @Output() onColResize = new EventEmitter(); - - // ── Column Reorder ──────────────────────────────────────────────────────── - @Input() reorderableColumns = false; - @Output() onColReorder = new EventEmitter(); - - // ── Row Reorder ─────────────────────────────────────────────────────────── - @Output() onRowReorder = new EventEmitter(); - - // ── Row Hover ───────────────────────────────────────────────────────────── - @Input() rowHover = false; - - // ── Filter ──────────────────────────────────────────────────────────────── - @Input() filters: { [s: string]: FilterMetadata | FilterMetadata[] } = {}; - @Input() globalFilterFields: string[] | undefined = undefined; - @Input() filterDelay = 300; - @Output() onFilter = new EventEmitter(); - - // ── Pagination events ───────────────────────────────────────────────────── - @Output() onPage = new EventEmitter(); - - // ── State ───────────────────────────────────────────────────────────────── - @Input() stateKey: string | undefined = undefined; - @Input() stateStorage: 'session' | 'local' = 'session'; - - // ── Default templates ───────────────────────────────────────────────────── - @ViewChild('defaultHeaderTpl') defaultHeaderTpl!: TemplateRef; - @ViewChild('defaultBodyTpl') defaultBodyTpl!: TemplateRef; - - // ── User-provided content templates ────────────────────────────────────── - @ContentChildren(PrimeTemplate) templates!: QueryList; - - headerTemplate: TemplateRef | null = null; - bodyTemplate: TemplateRef | null = null; - captionTemplate: TemplateRef | null = null; - footerTemplate: TemplateRef | null = null; - summaryTemplate: TemplateRef | null = null; - emptyMessageTemplate: TemplateRef | null = null; - rowExpansionTemplate: TemplateRef | null = null; - groupHeaderTemplate: TemplateRef | null = null; - groupFooterTemplate: TemplateRef | null = null; - paginatorLeftTemplate: TemplateRef | null = null; - paginatorRightTemplate: TemplateRef | null = null; - loadingIconTemplate: TemplateRef | null = null; - - ngAfterContentInit(): void { - this.templates.forEach((t) => { - switch (t.getType()) { - case 'header': - this.headerTemplate = t.template; - break; - case 'body': - this.bodyTemplate = t.template; - break; - case 'caption': - this.captionTemplate = t.template; - break; - case 'footer': - this.footerTemplate = t.template; - break; - case 'summary': - this.summaryTemplate = t.template; - break; - case 'emptymessage': - this.emptyMessageTemplate = t.template; - break; - case 'rowexpansion': - this.rowExpansionTemplate = t.template; - break; - case 'groupheader': - this.groupHeaderTemplate = t.template; - break; - case 'groupfooter': - this.groupFooterTemplate = t.template; - break; - case 'paginatorleft': - this.paginatorLeftTemplate = t.template; - break; - case 'paginatorright': - this.paginatorRightTemplate = t.template; - break; - case 'loadingicon': - this.loadingIconTemplate = t.template; - break; - } - }); - } -} diff --git a/src/lib/components/data-table/ng-package.json b/src/lib/components/data-table/ng-package.json deleted file mode 100644 index ecdf8fea..00000000 --- a/src/lib/components/data-table/ng-package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "ng-packagr/ng-package.schema.json", - "lib": { - "entryFile": "public_api.ts" - } -} - diff --git a/src/lib/components/data-table/public_api.ts b/src/lib/components/data-table/public_api.ts deleted file mode 100644 index 8b3cbf32..00000000 --- a/src/lib/components/data-table/public_api.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './data-table.component'; - - - diff --git a/src/stories/components/data-table/data-table.stories.ts b/src/stories/components/data-table/data-table.stories.ts deleted file mode 100644 index 3d614feb..00000000 --- a/src/stories/components/data-table/data-table.stories.ts +++ /dev/null @@ -1,719 +0,0 @@ -import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; -import { ExtraDataTableComponent } from '../../../lib/components/data-table/data-table.component'; -import { DataTableDefaultComponent } from './examples/data-table-default.component'; -import { DataTableStripedRowsComponent } from './examples/data-table-striped-rows.component'; -import { DataTableSelectableComponent } from './examples/data-table-selectable.component'; -import { DataTableGridLinesComponent } from './examples/data-table-grid-lines.component'; -import { DataTablePaginationComponent } from './examples/data-table-pagination.component'; -import { DataTableSelectionRadioComponent } from './examples/data-table-selection-radio.component'; -import { DataTableSelectionCheckboxComponent } from './examples/data-table-selection-checkbox.component'; -import { DataTableScrollVerticalComponent } from './examples/data-table-scroll-vertical.component'; -import { DataTableScrollHorizontalComponent } from './examples/data-table-scroll-horizontal.component'; -import { DataTableCustomBodyComponent } from './examples/data-table-custom-body.component'; -import { DataTableLazyComponent } from './examples/data-table-lazy.component'; - -const meta: Meta = { - title: 'Components/Data/DataTable', - component: ExtraDataTableComponent, - tags: ['autodocs'], - parameters: { - docs: { - description: { - component: `Таблица данных с поддержкой сортировки, пагинации, выбора строк и прокрутки. - -\`\`\`typescript -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; -\`\`\``, - }, - }, - designTokens: { prefix: '--p-datatable' }, - }, - argTypes: { - value: { - control: false, - description: 'Массив данных для отображения в таблице.', - table: { - category: 'Props', - type: { summary: 'any[]' }, - }, - }, - columns: { - control: false, - description: 'Определения столбцов таблицы.', - table: { - category: 'Props', - type: { summary: 'DataTableColumn[]' }, - }, - }, - stripedRows: { - control: 'boolean', - description: 'Чередование цвета строк.', - table: { - category: 'Props', - defaultValue: { summary: 'false' }, - type: { summary: 'boolean' }, - }, - }, - showGridlines: { - control: 'boolean', - description: 'Отображение сетки между ячейками.', - table: { - category: 'Props', - defaultValue: { summary: 'false' }, - type: { summary: 'boolean' }, - }, - }, - loading: { - control: 'boolean', - description: 'Отображает индикатор загрузки.', - table: { - category: 'Props', - defaultValue: { summary: 'false' }, - type: { summary: 'boolean' }, - }, - }, - size: { - control: 'select', - options: ['small', 'large', undefined], - description: 'Размер таблицы.', - table: { - category: 'Props', - defaultValue: { summary: 'undefined (normal)' }, - type: { summary: "'small' | 'large' | undefined" }, - }, - }, - scrollable: { - control: 'boolean', - description: 'Включает прокрутку таблицы.', - table: { - category: 'Props', - defaultValue: { summary: 'false' }, - type: { summary: 'boolean' }, - }, - }, - scrollHeight: { - control: 'text', - description: 'Высота области прокрутки (например "400px"). Работает только при scrollable=true.', - table: { - category: 'Props', - defaultValue: { summary: "''" }, - type: { summary: 'string' }, - }, - }, - paginator: { - control: 'boolean', - description: 'Включает пагинацию.', - table: { - category: 'Props', - defaultValue: { summary: 'false' }, - type: { summary: 'boolean' }, - }, - }, - rows: { - control: 'number', - description: 'Количество строк на странице (при paginator=true).', - table: { - category: 'Props', - defaultValue: { summary: '5' }, - type: { summary: 'number' }, - }, - }, - selectionMode: { - control: 'select', - options: ['single', 'multiple', undefined], - description: 'Режим выбора строк.', - table: { - category: 'Props', - defaultValue: { summary: 'undefined' }, - type: { summary: "'single' | 'multiple' | undefined" }, - }, - }, - dataKey: { - control: 'text', - description: 'Поле объекта, используемое как уникальный идентификатор строки.', - table: { - category: 'Props', - defaultValue: { summary: "'id'" }, - type: { summary: 'string' }, - }, - }, - selectionChange: { - control: false, - description: 'Событие изменения выбранных строк.', - table: { - category: 'Events', - type: { summary: 'EventEmitter' }, - }, - }, - }, -}; - -export default meta; -type Story = StoryObj; - -// ── Default ─────────────────────────────────────────────────────────────────── - -export const Default: Story = { - name: 'DataTable', - decorators: [moduleMetadata({ imports: [DataTableDefaultComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Базовая таблица отправлений с сортировкой по всем столбцам.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-data-table-default', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTableDefaultComponent { - shipments = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - ]; - - columns: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, - ]; -} - `, - }, - }, - }, -}; - -// ── StripedRows ─────────────────────────────────────────────────────────────── - -export const StripedRows: Story = { - name: 'StripedRows', - decorators: [moduleMetadata({ imports: [DataTableStripedRowsComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Чередование цвета строк для улучшения читаемости.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-data-table-striped-rows', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTableStripedRowsComponent { - shipments = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - ]; - - columns: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, - ]; -} - `, - }, - }, - }, -}; - -// ── Selectable ──────────────────────────────────────────────────────────────── - -export const Selectable: Story = { - name: 'Selectable', - decorators: [moduleMetadata({ imports: [DataTableSelectableComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Выбор строки кликом. Режим single — выбирается одна строка.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-data-table-selectable', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTableSelectableComponent { - shipments = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - ]; - - columns: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, - ]; - - selected: any = null; -} - `, - }, - }, - }, -}; - -// ── GridLines ───────────────────────────────────────────────────────────────── - -export const GridLines: Story = { - name: 'GridLines', - decorators: [moduleMetadata({ imports: [DataTableGridLinesComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Сетка между ячейками для наглядного разграничения данных.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-data-table-grid-lines', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTableGridLinesComponent { - shipments = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - ]; - - columns: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, - ]; -} - `, - }, - }, - }, -}; - -// ── Pagination ──────────────────────────────────────────────────────────────── - -export const Pagination: Story = { - name: 'Pagination', - decorators: [moduleMetadata({ imports: [DataTablePaginationComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Пагинация для больших наборов данных. Управление количеством строк на странице.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-data-table-pagination', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTablePaginationComponent { - shipments = [...]; // массив отправлений - columns: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, - ]; -} - `, - }, - }, - }, -}; - -// ── SelectionRadio ──────────────────────────────────────────────────────────── - -export const SelectionRadio: Story = { - name: 'Row Selection: RadioButton', - decorators: [moduleMetadata({ imports: [DataTableSelectionRadioComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Выбор одной строки через радио-кнопку. Укажите selectionMode: "single" в первом столбце.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-data-table-selection-radio', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTableSelectionRadioComponent { - shipments = [...]; // массив отправлений - columns: DataTableColumn[] = [ - { selectionMode: 'single' }, - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - ]; - selected: any = null; -} - `, - }, - }, - }, -}; - -// ── SelectionCheckbox ───────────────────────────────────────────────────────── - -export const SelectionCheckbox: Story = { - name: 'Row Selection: Checkbox', - decorators: [moduleMetadata({ imports: [DataTableSelectionCheckboxComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Множественный выбор строк через чекбоксы. Первый столбец с selectionMode: "multiple" добавляет чекбокс в заголовок.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-data-table-selection-checkbox', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTableSelectionCheckboxComponent { - shipments = [...]; // массив отправлений - columns: DataTableColumn[] = [ - { selectionMode: 'multiple' }, - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - ]; - selected: any[] = []; -} - `, - }, - }, - }, -}; - -// ── ScrollVertical ──────────────────────────────────────────────────────────── - -export const ScrollVertical: Story = { - name: 'Scroll: Vertical', - decorators: [moduleMetadata({ imports: [DataTableScrollVerticalComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Вертикальная прокрутка с фиксированной высотой контейнера. Заголовок остаётся видимым.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-data-table-scroll-vertical', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTableScrollVerticalComponent { - shipments = [...]; // большой массив отправлений - columns: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - ]; -} - `, - }, - }, - }, -}; - -// ── ScrollHorizontal ────────────────────────────────────────────────────────── - -export const ScrollHorizontal: Story = { - name: 'Scroll: Horizontal', - decorators: [moduleMetadata({ imports: [DataTableScrollHorizontalComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Горизонтальная прокрутка при большом количестве столбцов. Используйте style с min-width на столбцах.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-data-table-scroll-horizontal', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTableScrollHorizontalComponent { - shipments = [...]; // массив отправлений - columns: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true, style: 'min-width: 160px' }, - { field: 'sender', header: 'Отправитель', sortable: true, style: 'min-width: 160px' }, - { field: 'destination', header: 'Назначение', sortable: true, style: 'min-width: 160px' }, - { field: 'status', header: 'Статус', sortable: true, style: 'min-width: 140px' }, - { field: 'weight', header: 'Вес, кг', sortable: true, style: 'min-width: 120px' }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true, style: 'min-width: 140px' }, - { field: 'dimensions', header: 'Габариты', sortable: false, style: 'min-width: 160px' }, - ]; -} - `, - }, - }, - }, -}; - -// ── CustomBody ──────────────────────────────────────────────────────────────── - -export const CustomBody: Story = { - name: 'Custom Templates', - decorators: [moduleMetadata({ imports: [DataTableCustomBodyComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: - 'Кастомные шаблоны для заголовка и тела таблицы. Передайте `ng-template` с директивой `pTemplate` внутрь ``. Поддерживаются шаблоны: `header`, `body`, `footer`, `caption`, `summary`, `emptymessage`, `rowexpansion`, `groupheader`, `groupfooter`, `paginatorleft`, `paginatorright`, `loadingicon`.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; -import { PrimeTemplate } from 'primeng/api'; -import { TagModule } from 'primeng/tag'; - -@Component({ - selector: 'app-data-table-custom-body', - standalone: true, - imports: [ExtraDataTableComponent, PrimeTemplate, TagModule], - template: \` - - - - - @for (col of columns; track col.field) { - - {{ col.header }} - @if (col.sortable) { } - - } - - - - - - {{ row.trackNumber }} - {{ row.destination }} - - - - {{ row.weight }} кг - {{ row.cost | currency:'RUB':'symbol':'1.0-0':'ru' }} - - - - - Нет данных для отображения - - - - \`, -}) -export class DataTableCustomBodyComponent { - shipments = [...]; - columns: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, - ]; - - getSeverity(status: string) { - switch (status) { - case 'Доставлен': return 'success'; - case 'В пути': return 'info'; - case 'Задержан': return 'danger'; - default: return 'warn'; - } - } -} - `, - }, - }, - }, -}; - -// ── Lazy loading ────────────────────────────────────────────────────────────── - -export const LazyLoading: Story = { - name: 'Lazy Loading', - decorators: [moduleMetadata({ imports: [DataTableLazyComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: - 'Ленивая подгрузка данных с сервера при пагинации/сортировке. Используйте `[lazy]="true"`, `[totalRecords]` и `(onLazyLoad)` для управления загрузкой.', - }, - source: { - language: 'ts', - code: ` -import { Component, OnInit } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '@cdek-it/angular-ui-kit'; -import { TableLazyLoadEvent } from 'primeng/types/table'; - -@Component({ - selector: 'app-data-table-lazy', - standalone: true, - imports: [ExtraDataTableComponent], - template: \` - - \`, -}) -export class DataTableLazyComponent implements OnInit { - shipments: any[] = []; - columns: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, - ]; - totalRecords = 0; - loading = true; - - onLazyLoad(event: TableLazyLoadEvent): void { - this.loading = true; - // Запрос к серверу с event.first, event.rows, event.sortField, event.sortOrder - myApiService.getShipments(event).subscribe(result => { - this.shipments = result.data; - this.totalRecords = result.total; - this.loading = false; - }); - } -} - `, - }, - }, - }, -}; - diff --git a/src/stories/components/data-table/examples/data-table-custom-body.component.ts b/src/stories/components/data-table/examples/data-table-custom-body.component.ts deleted file mode 100644 index f3b95ecb..00000000 --- a/src/stories/components/data-table/examples/data-table-custom-body.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; -import { PrimeTemplate } from 'primeng/api'; -import { TagModule } from 'primeng/tag'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, -]; - -const COLUMNS: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, -]; - -const template = ` -
- - - - - @for (col of columns; track col.field) { - - {{ col.header }} - @if (col.sortable) { } - - } - - - - - - {{ row.trackNumber }} - {{ row.destination }} - - - - {{ row.weight }} кг - {{ row.cost | currency:'RUB':'symbol':'1.0-0':'ru' }} - - - - - Нет данных для отображения - - - -
-`; - -@Component({ - selector: 'app-data-table-custom-body', - standalone: true, - imports: [ExtraDataTableComponent, PrimeTemplate, TagModule, CommonModule], - template, -}) -export class DataTableCustomBodyComponent { - shipments = SHIPMENTS; - columns = COLUMNS; - - getSeverity(status: string): 'success' | 'warn' | 'danger' | 'info' | 'secondary' | undefined { - switch (status) { - case 'Доставлен': return 'success'; - case 'В пути': return 'info'; - case 'Задержан': return 'danger'; - case 'Ожидание': return 'warn'; - default: return 'secondary'; - } - } -} - diff --git a/src/stories/components/data-table/examples/data-table-default.component.ts b/src/stories/components/data-table/examples/data-table-default.component.ts deleted file mode 100644 index 434a4c03..00000000 --- a/src/stories/components/data-table/examples/data-table-default.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, - { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, - { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, - { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, -]; - -const COLUMNS: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-default', - standalone: true, - imports: [ExtraDataTableComponent], - template, -}) -export class DataTableDefaultComponent { - shipments = SHIPMENTS; - columns = COLUMNS; -} diff --git a/src/stories/components/data-table/examples/data-table-grid-lines.component.ts b/src/stories/components/data-table/examples/data-table-grid-lines.component.ts deleted file mode 100644 index 1f7a94bd..00000000 --- a/src/stories/components/data-table/examples/data-table-grid-lines.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, -]; - -const COLUMNS: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-grid-lines', - standalone: true, - imports: [ExtraDataTableComponent], - template, -}) -export class DataTableGridLinesComponent { - shipments = SHIPMENTS; - columns = COLUMNS; -} diff --git a/src/stories/components/data-table/examples/data-table-lazy.component.ts b/src/stories/components/data-table/examples/data-table-lazy.component.ts deleted file mode 100644 index 9ca1f978..00000000 --- a/src/stories/components/data-table/examples/data-table-lazy.component.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; -import { PrimeTemplate } from 'primeng/api'; -import { TableLazyLoadEvent } from 'primeng/types/table'; - -const ALL_SHIPMENTS = Array.from({ length: 100 }, (_, i) => ({ - id: i + 1, - trackNumber: `ЦД-${String(i + 100000).padStart(8, '0')}`, - destination: ['Москва', 'Новосибирск', 'Екатеринбург', 'Казань', 'Краснодар'][i % 5], - status: ['В пути', 'Доставлен', 'Задержан', 'Ожидание'][i % 4], - weight: Number((Math.random() * 10).toFixed(1)), - cost: Math.floor(Math.random() * 5000) + 200, -})); - -const COLUMNS: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-lazy', - standalone: true, - imports: [ExtraDataTableComponent, PrimeTemplate], - template, -}) -export class DataTableLazyComponent implements OnInit { - shipments: any[] = []; - columns = COLUMNS; - totalRecords = ALL_SHIPMENTS.length; - loading = true; - - ngOnInit(): void { - // Initial load happens via onLazyLoad - } - - onLazyLoad(event: TableLazyLoadEvent): void { - this.loading = true; - // Simulate async server request - setTimeout(() => { - const start = event.first ?? 0; - const end = start + (event.rows ?? 10); - this.shipments = ALL_SHIPMENTS.slice(start, end); - this.loading = false; - }, 500); - } -} - diff --git a/src/stories/components/data-table/examples/data-table-pagination.component.ts b/src/stories/components/data-table/examples/data-table-pagination.component.ts deleted file mode 100644 index 09180b9b..00000000 --- a/src/stories/components/data-table/examples/data-table-pagination.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, - { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, - { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, - { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, -]; - -const COLUMNS: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-pagination', - standalone: true, - imports: [ExtraDataTableComponent], - template, -}) -export class DataTablePaginationComponent { - shipments = SHIPMENTS; - columns = COLUMNS; -} diff --git a/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts b/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts deleted file mode 100644 index 0428887d..00000000 --- a/src/stories/components/data-table/examples/data-table-scroll-horizontal.component.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', sender: 'Иванов И.И.', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200, dimensions: '30×20×15 см' }, - { id: 2, trackNumber: 'ЦД-00123457', sender: 'Петров П.П.', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450, dimensions: '10×10×10 см' }, - { id: 3, trackNumber: 'ЦД-00123458', sender: 'Сидоров С.С.', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100, dimensions: '50×40×30 см' }, - { id: 4, trackNumber: 'ЦД-00123459', sender: 'Козлов К.К.', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750, dimensions: '20×15×10 см' }, - { id: 5, trackNumber: 'ЦД-00123460', sender: 'Новиков Н.Н.', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800, dimensions: '40×30×20 см' }, -]; - -const COLUMNS: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true, style: 'min-width: 160px' }, - { field: 'sender', header: 'Отправитель', sortable: true, style: 'min-width: 160px' }, - { field: 'destination', header: 'Назначение', sortable: true, style: 'min-width: 160px' }, - { field: 'status', header: 'Статус', sortable: true, style: 'min-width: 140px' }, - { field: 'weight', header: 'Вес, кг', sortable: true, style: 'min-width: 120px' }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true, style: 'min-width: 140px' }, - { field: 'dimensions', header: 'Габариты', sortable: false, style: 'min-width: 160px' }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-scroll-horizontal', - standalone: true, - imports: [ExtraDataTableComponent], - template, -}) -export class DataTableScrollHorizontalComponent { - shipments = SHIPMENTS; - columns = COLUMNS; -} diff --git a/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts b/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts deleted file mode 100644 index c7773a5e..00000000 --- a/src/stories/components/data-table/examples/data-table-scroll-vertical.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; - -const BASE_SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, - { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5 }, - { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0 }, - { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1 }, -]; - -const COLUMNS: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-scroll-vertical', - standalone: true, - imports: [ExtraDataTableComponent], - template, -}) -export class DataTableScrollVerticalComponent { - shipments = [...BASE_SHIPMENTS, ...BASE_SHIPMENTS, ...BASE_SHIPMENTS]; - columns = COLUMNS; -} diff --git a/src/stories/components/data-table/examples/data-table-selectable.component.ts b/src/stories/components/data-table/examples/data-table-selectable.component.ts deleted file mode 100644 index 4d0e1b8a..00000000 --- a/src/stories/components/data-table/examples/data-table-selectable.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, -]; - -const COLUMNS: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-selectable', - standalone: true, - imports: [ExtraDataTableComponent], - template, -}) -export class DataTableSelectableComponent { - shipments = SHIPMENTS; - columns = COLUMNS; - selected: any = null; -} diff --git a/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts b/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts deleted file mode 100644 index 20a18330..00000000 --- a/src/stories/components/data-table/examples/data-table-selection-checkbox.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, -]; - -const COLUMNS: DataTableColumn[] = [ - { selectionMode: 'multiple' }, - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-selection-checkbox', - standalone: true, - imports: [ExtraDataTableComponent], - template, -}) -export class DataTableSelectionCheckboxComponent { - shipments = SHIPMENTS; - columns = COLUMNS; - selected: any[] = []; -} diff --git a/src/stories/components/data-table/examples/data-table-selection-radio.component.ts b/src/stories/components/data-table/examples/data-table-selection-radio.component.ts deleted file mode 100644 index 1aeb0883..00000000 --- a/src/stories/components/data-table/examples/data-table-selection-radio.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, -]; - -const COLUMNS: DataTableColumn[] = [ - { selectionMode: 'single' }, - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-selection-radio', - standalone: true, - imports: [ExtraDataTableComponent], - template, -}) -export class DataTableSelectionRadioComponent { - shipments = SHIPMENTS; - columns = COLUMNS; - selected: any = null; -} diff --git a/src/stories/components/data-table/examples/data-table-striped-rows.component.ts b/src/stories/components/data-table/examples/data-table-striped-rows.component.ts deleted file mode 100644 index 2f78155d..00000000 --- a/src/stories/components/data-table/examples/data-table-striped-rows.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Component } from '@angular/core'; -import { ExtraDataTableComponent, DataTableColumn } from '../../../../lib/components/data-table/data-table.component'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, -]; - -const COLUMNS: DataTableColumn[] = [ - { field: 'trackNumber', header: 'Трек-номер', sortable: true }, - { field: 'destination', header: 'Назначение', sortable: true }, - { field: 'status', header: 'Статус', sortable: true }, - { field: 'weight', header: 'Вес, кг', sortable: true }, - { field: 'cost', header: 'Стоимость, ₽', sortable: true }, -]; - -const template = ` -
- -
-`; - -@Component({ - selector: 'app-data-table-striped-rows', - standalone: true, - imports: [ExtraDataTableComponent], - template, -}) -export class DataTableStripedRowsComponent { - shipments = SHIPMENTS; - columns = COLUMNS; -} diff --git a/src/stories/components/p-data-table/examples/p-data-table-custom-body.component.ts b/src/stories/components/p-data-table/examples/primeng-table-custom-body.component.ts similarity index 98% rename from src/stories/components/p-data-table/examples/p-data-table-custom-body.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-custom-body.component.ts index bd448577..edd8a3eb 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-custom-body.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-custom-body.component.ts @@ -12,7 +12,7 @@ const SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-custom-body', + selector: 'app-primeng-table-custom-body', standalone: true, imports: [CommonModule, TableModule, TagModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-default.component.ts b/src/stories/components/p-data-table/examples/primeng-table-default.component.ts similarity index 98% rename from src/stories/components/p-data-table/examples/p-data-table-default.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-default.component.ts index e16de983..0df4bcc9 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-default.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-default.component.ts @@ -13,7 +13,7 @@ const SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-default', + selector: 'app-primeng-table-default', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-grid-lines.component.ts b/src/stories/components/p-data-table/examples/primeng-table-grid-lines.component.ts similarity index 97% rename from src/stories/components/p-data-table/examples/p-data-table-grid-lines.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-grid-lines.component.ts index ce4e45e8..5478270e 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-grid-lines.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-grid-lines.component.ts @@ -10,7 +10,7 @@ const SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-grid-lines', + selector: 'app-primeng-table-grid-lines', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-lazy.component.ts b/src/stories/components/p-data-table/examples/primeng-table-lazy.component.ts similarity index 98% rename from src/stories/components/p-data-table/examples/p-data-table-lazy.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-lazy.component.ts index 4b2ee67f..44e4b920 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-lazy.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-lazy.component.ts @@ -13,7 +13,7 @@ const ALL_SHIPMENTS = Array.from({ length: 500 }, (_, i) => ({ const PAGE_SIZE = 50; @Component({ - selector: 'app-p-data-table-lazy', + selector: 'app-primeng-table-lazy', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-pagination.component.ts b/src/stories/components/p-data-table/examples/primeng-table-pagination.component.ts similarity index 98% rename from src/stories/components/p-data-table/examples/p-data-table-pagination.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-pagination.component.ts index c6af7b1e..3bb24c9c 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-pagination.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-pagination.component.ts @@ -13,7 +13,7 @@ const SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-pagination', + selector: 'app-primeng-table-pagination', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-scroll-horizontal.component.ts b/src/stories/components/p-data-table/examples/primeng-table-scroll-horizontal.component.ts similarity index 98% rename from src/stories/components/p-data-table/examples/p-data-table-scroll-horizontal.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-scroll-horizontal.component.ts index 46bd0e84..8128b89f 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-scroll-horizontal.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-scroll-horizontal.component.ts @@ -10,7 +10,7 @@ const SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-scroll-horizontal', + selector: 'app-primeng-table-scroll-horizontal', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-scroll-vertical.component.ts b/src/stories/components/p-data-table/examples/primeng-table-scroll-vertical.component.ts similarity index 97% rename from src/stories/components/p-data-table/examples/p-data-table-scroll-vertical.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-scroll-vertical.component.ts index d3b808a3..800b185d 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-scroll-vertical.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-scroll-vertical.component.ts @@ -13,7 +13,7 @@ const BASE_SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-scroll-vertical', + selector: 'app-primeng-table-scroll-vertical', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-selectable.component.ts b/src/stories/components/p-data-table/examples/primeng-table-selectable.component.ts similarity index 97% rename from src/stories/components/p-data-table/examples/p-data-table-selectable.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-selectable.component.ts index 5265ad9a..58607acb 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-selectable.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-selectable.component.ts @@ -10,7 +10,7 @@ const SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-selectable', + selector: 'app-primeng-table-selectable', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-selection-checkbox.component.ts b/src/stories/components/p-data-table/examples/primeng-table-selection-checkbox.component.ts similarity index 97% rename from src/stories/components/p-data-table/examples/p-data-table-selection-checkbox.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-selection-checkbox.component.ts index 63e165ea..c03764b6 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-selection-checkbox.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-selection-checkbox.component.ts @@ -10,7 +10,7 @@ const SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-selection-checkbox', + selector: 'app-primeng-table-selection-checkbox', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-selection-radio.component.ts b/src/stories/components/p-data-table/examples/primeng-table-selection-radio.component.ts similarity index 97% rename from src/stories/components/p-data-table/examples/p-data-table-selection-radio.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-selection-radio.component.ts index 0fa3686a..e31ecaa5 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-selection-radio.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-selection-radio.component.ts @@ -10,7 +10,7 @@ const SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-selection-radio', + selector: 'app-primeng-table-selection-radio', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/examples/p-data-table-striped-rows.component.ts b/src/stories/components/p-data-table/examples/primeng-table-striped-rows.component.ts similarity index 98% rename from src/stories/components/p-data-table/examples/p-data-table-striped-rows.component.ts rename to src/stories/components/p-data-table/examples/primeng-table-striped-rows.component.ts index 4ffba2ac..6ca59979 100644 --- a/src/stories/components/p-data-table/examples/p-data-table-striped-rows.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-striped-rows.component.ts @@ -13,7 +13,7 @@ const SHIPMENTS = [ ]; @Component({ - selector: 'app-p-data-table-striped-rows', + selector: 'app-primeng-table-striped-rows', standalone: true, imports: [TableModule], template: ` diff --git a/src/stories/components/p-data-table/p-data-table.stories.ts b/src/stories/components/p-data-table/p-data-table.stories.ts deleted file mode 100644 index 7008be57..00000000 --- a/src/stories/components/p-data-table/p-data-table.stories.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; -import { TableModule } from 'primeng/table'; -import { PDataTableDefaultComponent } from './examples/p-data-table-default.component'; -import { PDataTableStripedRowsComponent } from './examples/p-data-table-striped-rows.component'; -import { PDataTableSelectableComponent } from './examples/p-data-table-selectable.component'; -import { PDataTableGridLinesComponent } from './examples/p-data-table-grid-lines.component'; -import { PDataTablePaginationComponent } from './examples/p-data-table-pagination.component'; -import { PDataTableSelectionRadioComponent } from './examples/p-data-table-selection-radio.component'; -import { PDataTableSelectionCheckboxComponent } from './examples/p-data-table-selection-checkbox.component'; -import { PDataTableScrollVerticalComponent } from './examples/p-data-table-scroll-vertical.component'; -import { PDataTableScrollHorizontalComponent } from './examples/p-data-table-scroll-horizontal.component'; -import { PDataTableCustomBodyComponent } from './examples/p-data-table-custom-body.component'; -import { PDataTableLazyComponent } from './examples/p-data-table-lazy.component'; - -const meta: Meta = { - title: 'Components/Data/DataTable (PrimeNG)', - tags: ['autodocs'], - parameters: { - docs: { - description: { - component: `Таблица данных на базе PrimeNG pTable с поддержкой сортировки, пагинации, выбора строк и прокрутки. - -\`\`\`typescript -import { TableModule } from 'primeng/table'; -\`\`\``, - }, - }, - designTokens: { prefix: '--p-datatable' }, - }, -}; - -export default meta; -type Story = StoryObj; - -// ── Default ─────────────────────────────────────────────────────────────────── - -export const Default: Story = { - name: 'DataTable', - decorators: [moduleMetadata({ imports: [PDataTableDefaultComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Базовая таблица отправлений с сортировкой по всем столбцам на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── StripedRows ─────────────────────────────────────────────────────────────── - -export const StripedRows: Story = { - name: 'StripedRows', - decorators: [moduleMetadata({ imports: [PDataTableStripedRowsComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Чередование цвета строк для улучшения читаемости на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── Selectable ──────────────────────────────────────────────────────────────── - -export const Selectable: Story = { - name: 'Selectable', - decorators: [moduleMetadata({ imports: [PDataTableSelectableComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Выбор строки кликом. Режим single — выбирается одна строка на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── GridLines ───────────────────────────────────────────────────────────────── - -export const GridLines: Story = { - name: 'GridLines', - decorators: [moduleMetadata({ imports: [PDataTableGridLinesComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Сетка между ячейками для наглядного разграничения данных на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── Pagination ──────────────────────────────────────────────────────────────── - -export const Pagination: Story = { - name: 'Pagination', - decorators: [moduleMetadata({ imports: [PDataTablePaginationComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Пагинация для больших наборов данных на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── SelectionRadio ──────────────────────────────────────────────────────────── - -export const SelectionRadio: Story = { - name: 'Row Selection: RadioButton', - decorators: [moduleMetadata({ imports: [PDataTableSelectionRadioComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Выбор одной строки через радио-кнопку на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── SelectionCheckbox ───────────────────────────────────────────────────────── - -export const SelectionCheckbox: Story = { - name: 'Row Selection: Checkbox', - decorators: [moduleMetadata({ imports: [PDataTableSelectionCheckboxComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Множественный выбор строк через чекбоксы на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── ScrollVertical ──────────────────────────────────────────────────────────── - -export const ScrollVertical: Story = { - name: 'Scroll: Vertical', - decorators: [moduleMetadata({ imports: [PDataTableScrollVerticalComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Вертикальная прокрутка с фиксированной высотой контейнера на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── ScrollHorizontal ────────────────────────────────────────────────────────── - -export const ScrollHorizontal: Story = { - name: 'Scroll: Horizontal', - decorators: [moduleMetadata({ imports: [PDataTableScrollHorizontalComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Горизонтальная прокрутка при большом количестве столбцов на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── CustomBody ──────────────────────────────────────────────────────────────── - -export const CustomBody: Story = { - name: 'Custom Templates', - decorators: [moduleMetadata({ imports: [PDataTableCustomBodyComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Кастомные шаблоны для заголовка и тела таблицы на базе PrimeNG pTable.', - }, - }, - }, -}; - -// ── Lazy loading ────────────────────────────────────────────────────────────── - -export const LazyLoading: Story = { - name: 'Lazy Loading', - decorators: [moduleMetadata({ imports: [PDataTableLazyComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Lazy Loading + Virtual Scroll для работы с большими наборами данных. Данные подгружаются порциями при прокрутке таблицы. Используйте `[lazy]="true"`, `[virtualScroll]="true"`, `[virtualScrollItemSize]`, `[totalRecords]` и `(onLazyLoad)` для управления загрузкой данных с сервера.', - }, - }, - }, -}; diff --git a/src/stories/components/p-data-table/primeng-data-table.stories.ts b/src/stories/components/p-data-table/primeng-data-table.stories.ts new file mode 100644 index 00000000..ed973d8e --- /dev/null +++ b/src/stories/components/p-data-table/primeng-data-table.stories.ts @@ -0,0 +1,777 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { PDataTableDefaultComponent } from './examples/primeng-table-default.component'; +import { PDataTableStripedRowsComponent } from './examples/primeng-table-striped-rows.component'; +import { PDataTableSelectableComponent } from './examples/primeng-table-selectable.component'; +import { PDataTableGridLinesComponent } from './examples/primeng-table-grid-lines.component'; +import { PDataTablePaginationComponent } from './examples/primeng-table-pagination.component'; +import { PDataTableSelectionRadioComponent } from './examples/primeng-table-selection-radio.component'; +import { PDataTableSelectionCheckboxComponent } from './examples/primeng-table-selection-checkbox.component'; +import { PDataTableScrollVerticalComponent } from './examples/primeng-table-scroll-vertical.component'; +import { PDataTableScrollHorizontalComponent } from './examples/primeng-table-scroll-horizontal.component'; +import { PDataTableCustomBodyComponent } from './examples/primeng-table-custom-body.component'; +import { PDataTableLazyComponent } from './examples/primeng-table-lazy.component'; + +const meta: Meta = { + title: 'Components/Data/DataTable (PrimeNG)', + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: `Таблица данных на базе PrimeNG pTable с поддержкой сортировки, пагинации, выбора строк и прокрутки. + +\`\`\`typescript +import { TableModule } from 'primeng/table'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-datatable' }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'DataTable', + decorators: [moduleMetadata({ imports: [PDataTableDefaultComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Базовая таблица отправлений с сортировкой по всем столбцам на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +@Component({ + selector: 'app-primeng-table-default', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class PDataTableDefaultComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; + +// ── StripedRows ─────────────────────────────────────────────────────────────── + +export const StripedRows: Story = { + name: 'StripedRows', + decorators: [moduleMetadata({ imports: [PDataTableStripedRowsComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Чередование цвета строк для улучшения читаемости на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +@Component({ + selector: 'app-primeng-table-striped-rows', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class PDataTableStripedRowsComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; + +// ── Selectable ──────────────────────────────────────────────────────────────── + +export const Selectable: Story = { + name: 'Selectable', + decorators: [moduleMetadata({ imports: [PDataTableSelectableComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Выбор строки кликом. Режим single — выбирается одна строка на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +@Component({ + selector: 'app-primeng-table-selectable', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class PDataTableSelectableComponent { + shipments = SHIPMENTS; + selected: any = null; +}`, + }, + }, + }, +}; + +// ── GridLines ───────────────────────────────────────────────────────────────── + +export const GridLines: Story = { + name: 'GridLines', + decorators: [moduleMetadata({ imports: [PDataTableGridLinesComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Сетка между ячейками для наглядного разграничения данных на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +@Component({ + selector: 'app-primeng-table-grid-lines', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class PDataTableGridLinesComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; + +// ── Pagination ──────────────────────────────────────────────────────────────── + +export const Pagination: Story = { + name: 'Pagination', + decorators: [moduleMetadata({ imports: [PDataTablePaginationComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Пагинация для больших наборов данных на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +@Component({ + selector: 'app-primeng-table-pagination', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class PDataTablePaginationComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; + +// ── SelectionRadio ──────────────────────────────────────────────────────────── + +export const SelectionRadio: Story = { + name: 'Row Selection: RadioButton', + decorators: [moduleMetadata({ imports: [PDataTableSelectionRadioComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Выбор одной строки через радио-кнопку на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, +]; + +@Component({ + selector: 'app-primeng-table-selection-radio', + standalone: true, + imports: [TableModule], + template: \` + + + + + Трек-номер + Назначение + Статус + Вес, кг + + + + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + + + + \`, +}) +export class PDataTableSelectionRadioComponent { + shipments = SHIPMENTS; + selected: any = null; +}`, + }, + }, + }, +}; + +// ── SelectionCheckbox ───────────────────────────────────────────────────────── + +export const SelectionCheckbox: Story = { + name: 'Row Selection: Checkbox', + decorators: [moduleMetadata({ imports: [PDataTableSelectionCheckboxComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Множественный выбор строк через чекбоксы на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, +]; + +@Component({ + selector: 'app-primeng-table-selection-checkbox', + standalone: true, + imports: [TableModule], + template: \` + + + + + + + Трек-номер + Назначение + Статус + Вес, кг + + + + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + + + + \`, +}) +export class PDataTableSelectionCheckboxComponent { + shipments = SHIPMENTS; + selected: any[] = []; +}`, + }, + }, + }, +}; + +// ── ScrollVertical ──────────────────────────────────────────────────────────── + +export const ScrollVertical: Story = { + name: 'Scroll: Vertical', + decorators: [moduleMetadata({ imports: [PDataTableScrollVerticalComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Вертикальная прокрутка с фиксированной высотой контейнера на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const BASE_SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1 }, +]; + +@Component({ + selector: 'app-primeng-table-scroll-vertical', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + + + + \`, +}) +export class PDataTableScrollVerticalComponent { + shipments = [...BASE_SHIPMENTS, ...BASE_SHIPMENTS, ...BASE_SHIPMENTS]; +}`, + }, + }, + }, +}; + +// ── ScrollHorizontal ────────────────────────────────────────────────────────── + +export const ScrollHorizontal: Story = { + name: 'Scroll: Horizontal', + decorators: [moduleMetadata({ imports: [PDataTableScrollHorizontalComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Горизонтальная прокрутка при большом количестве столбцов на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', sender: 'Иванов И.И.', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200, dimensions: '30×20×15 см' }, + { id: 2, trackNumber: 'ЦД-00123457', sender: 'Петров П.П.', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450, dimensions: '10×10×10 см' }, + { id: 3, trackNumber: 'ЦД-00123458', sender: 'Сидоров С.С.', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100, dimensions: '50×40×30 см' }, + { id: 4, trackNumber: 'ЦД-00123459', sender: 'Козлов К.К.', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750, dimensions: '20×15×10 см' }, + { id: 5, trackNumber: 'ЦД-00123460', sender: 'Новиков Н.Н.', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800, dimensions: '40×30×20 см' }, +]; + +@Component({ + selector: 'app-primeng-table-scroll-horizontal', + standalone: true, + imports: [TableModule], + template: \` + + + + + Трек-номер + + + Отправитель + + + Назначение + + + Статус + + + Вес, кг + + + Стоимость, ₽ + + Габариты + + + + + {{ shipment.trackNumber }} + {{ shipment.sender }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + {{ shipment.dimensions }} + + + + \`, +}) +export class PDataTableScrollHorizontalComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; + +// ── CustomBody ──────────────────────────────────────────────────────────────── + +export const CustomBody: Story = { + name: 'Custom Templates', + decorators: [moduleMetadata({ imports: [PDataTableCustomBodyComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Кастомные шаблоны для заголовка и тела таблицы на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TableModule } from 'primeng/table'; +import { TagModule } from 'primeng/tag'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +@Component({ + selector: 'app-primeng-table-custom-body', + standalone: true, + imports: [CommonModule, TableModule, TagModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + + + + {{ shipment.weight }} кг + {{ shipment.cost | currency:'RUB':'symbol':'1.0-0':'ru' }} + + + + Нет данных для отображения + + + \`, +}) +export class PDataTableCustomBodyComponent { + shipments = SHIPMENTS; + + getSeverity(status: string): 'success' | 'warn' | 'danger' | 'info' | 'secondary' | undefined { + switch (status) { + case 'Доставлен': return 'success'; + case 'В пути': return 'info'; + case 'Задержан': return 'danger'; + case 'Ожидание': return 'warn'; + default: return 'secondary'; + } + } +}`, + }, + }, + }, +}; + +// ── Lazy loading ────────────────────────────────────────────────────────────── + +export const LazyLoading: Story = { + name: 'Lazy Loading', + decorators: [moduleMetadata({ imports: [PDataTableLazyComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Lazy Loading + Virtual Scroll для работы с большими наборами данных. Данные подгружаются порциями при прокрутке таблицы. Используйте `[lazy]="true"`, `[virtualScroll]="true"`, `[virtualScrollItemSize]`, `[totalRecords]` и `(onLazyLoad)` для управления загрузкой данных с сервера.', + }, + source: { + language: 'ts', + code: `import { Component, OnInit } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const ALL_SHIPMENTS = Array.from({ length: 500 }, (_, i) => ({ + id: i + 1, + trackNumber: \`ЦД-\${String(i + 100000).padStart(8, '0')}\`, + destination: ['Москва', 'Новосибирск', 'Екатеринбург', 'Казань', 'Краснодар', 'Санкт-Петербург', 'Воронеж', 'Самара', 'Ростов-на-Дону', 'Уфа'][i % 10], + status: ['В пути', 'Доставлен', 'Задержан', 'Ожидание'][i % 4], + weight: Number(((i * 0.37 + 0.5) % 10).toFixed(1)), + cost: ((i * 137 + 200) % 5000) + 200, +})); + +const PAGE_SIZE = 50; + +@Component({ + selector: 'app-primeng-table-lazy', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment?.trackNumber }} + {{ shipment?.destination }} + {{ shipment?.status }} + {{ shipment?.weight }} + {{ shipment?.cost }} + + + + + Загрузка… + + + + \`, +}) +export class PDataTableLazyComponent implements OnInit { + readonly PAGE_SIZE = PAGE_SIZE; + + shipments: any[] = Array.from({ length: ALL_SHIPMENTS.length }); + totalRecords = ALL_SHIPMENTS.length; + loading = false; + + ngOnInit(): void { + // shipments pre-filled with placeholders so virtual scroll knows total size + } + + onLazyLoad(event: any): void { + this.loading = true; + const start = event.first ?? 0; + const end = start + (event.rows ?? PAGE_SIZE); + + // Simulate async server request + setTimeout(() => { + const page = ALL_SHIPMENTS.slice(start, end); + const updated = [...this.shipments]; + for (let i = 0; i < page.length; i++) { + updated[start + i] = page[i]; + } + this.shipments = updated; + this.loading = false; + }, 300); + } +}`, + }, + }, + }, +}; From 5b3fd845c05fad159b7b297321a7ef8531c7b4fb Mon Sep 17 00:00:00 2001 From: "ak.dmitriev" Date: Fri, 29 May 2026 19:02:10 +0700 Subject: [PATCH 25/28] DS-517 --- .../primeng-table-custom-body.component.ts | 78 +- .../primeng-table-default.component.ts | 64 +- .../primeng-table-grid-lines.component.ts | 61 +- .../examples/primeng-table-lazy.component.ts | 105 ++- .../primeng-table-pagination.component.ts | 64 +- ...imeng-table-scroll-horizontal.component.ts | 77 +- ...primeng-table-scroll-vertical.component.ts | 62 +- .../primeng-table-selectable.component.ts | 62 +- ...meng-table-selection-checkbox.component.ts | 66 +- ...primeng-table-selection-radio.component.ts | 64 +- .../primeng-table-striped-rows.component.ts | 64 +- .../primeng-data-table.stories.ts | 794 +----------------- 12 files changed, 793 insertions(+), 768 deletions(-) diff --git a/src/stories/components/p-data-table/examples/primeng-table-custom-body.component.ts b/src/stories/components/p-data-table/examples/primeng-table-custom-body.component.ts index edd8a3eb..f3920a93 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-custom-body.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-custom-body.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { CommonModule } from '@angular/common'; import { TableModule } from 'primeng/table'; import { TagModule } from 'primeng/tag'; @@ -55,4 +56,79 @@ export class PDataTableCustomBodyComponent { default: return 'secondary'; } } -} \ No newline at end of file +} + +export const CustomBody: StoryObj = { + name: 'Custom Templates', + decorators: [moduleMetadata({ imports: [PDataTableCustomBodyComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Кастомные шаблоны для заголовка и тела таблицы на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TableModule } from 'primeng/table'; +import { TagModule } from 'primeng/tag'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [CommonModule, TableModule, TagModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + + + + {{ shipment.weight }} кг + {{ shipment.cost | currency:'RUB':'symbol':'1.0-0':'ru' }} + + + + Нет данных для отображения + + + \`, +}) +export class ExampleComponent { + shipments = SHIPMENTS; + + getSeverity(status: string): 'success' | 'warn' | 'danger' | 'info' | 'secondary' | undefined { + switch (status) { + case 'Доставлен': return 'success'; + case 'В пути': return 'info'; + case 'Задержан': return 'danger'; + case 'Ожидание': return 'warn'; + default: return 'secondary'; + } + } +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-default.component.ts b/src/stories/components/p-data-table/examples/primeng-table-default.component.ts index 0df4bcc9..f50fd9a5 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-default.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-default.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const SHIPMENTS = [ @@ -41,4 +42,65 @@ const SHIPMENTS = [ }) export class PDataTableDefaultComponent { shipments = SHIPMENTS; -} \ No newline at end of file +} + +export const Default: StoryObj = { + name: 'DataTable', + decorators: [moduleMetadata({ imports: [PDataTableDefaultComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Базовая таблица отправлений с сортировкой по всем столбцам на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class ExampleComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-grid-lines.component.ts b/src/stories/components/p-data-table/examples/primeng-table-grid-lines.component.ts index 5478270e..4eb8316b 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-grid-lines.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-grid-lines.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const SHIPMENTS = [ @@ -38,4 +39,62 @@ const SHIPMENTS = [ }) export class PDataTableGridLinesComponent { shipments = SHIPMENTS; -} \ No newline at end of file +} + +export const GridLines: StoryObj = { + name: 'GridLines', + decorators: [moduleMetadata({ imports: [PDataTableGridLinesComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Сетка между ячейками для наглядного разграничения данных на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class ExampleComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-lazy.component.ts b/src/stories/components/p-data-table/examples/primeng-table-lazy.component.ts index 44e4b920..62c6905b 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-lazy.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-lazy.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const ALL_SHIPMENTS = Array.from({ length: 500 }, (_, i) => ({ @@ -62,16 +63,13 @@ export class PDataTableLazyComponent implements OnInit { totalRecords = ALL_SHIPMENTS.length; loading = false; - ngOnInit(): void { - // shipments pre-filled with placeholders so virtual scroll knows total size - } + ngOnInit(): void {} onLazyLoad(event: any): void { this.loading = true; const start = event.first ?? 0; const end = start + (event.rows ?? PAGE_SIZE); - // Simulate async server request setTimeout(() => { const page = ALL_SHIPMENTS.slice(start, end); const updated = [...this.shipments]; @@ -83,3 +81,102 @@ export class PDataTableLazyComponent implements OnInit { }, 300); } } + +export const LazyLoading: StoryObj = { + name: 'Lazy Loading', + decorators: [moduleMetadata({ imports: [PDataTableLazyComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Lazy Loading + Virtual Scroll для работы с большими наборами данных. Данные подгружаются порциями при прокрутке таблицы. Используйте `[lazy]="true"`, `[virtualScroll]="true"`, `[virtualScrollItemSize]`, `[totalRecords]` и `(onLazyLoad)` для управления загрузкой данных с сервера.', + }, + source: { + language: 'ts', + code: `import { Component, OnInit } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const ALL_SHIPMENTS = Array.from({ length: 500 }, (_, i) => ({ + id: i + 1, + trackNumber: \`ЦД-\${String(i + 100000).padStart(8, '0')}\`, + destination: ['Москва', 'Новосибирск', 'Екатеринбург', 'Казань', 'Краснодар', 'Санкт-Петербург', 'Воронеж', 'Самара', 'Ростов-на-Дону', 'Уфа'][i % 10], + status: ['В пути', 'Доставлен', 'Задержан', 'Ожидание'][i % 4], + weight: Number(((i * 0.37 + 0.5) % 10).toFixed(1)), + cost: ((i * 137 + 200) % 5000) + 200, +})); + +const PAGE_SIZE = 50; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment?.trackNumber }} + {{ shipment?.destination }} + {{ shipment?.status }} + {{ shipment?.weight }} + {{ shipment?.cost }} + + + + + Загрузка… + + + + \`, +}) +export class ExampleComponent implements OnInit { + readonly PAGE_SIZE = PAGE_SIZE; + + shipments: any[] = Array.from({ length: ALL_SHIPMENTS.length }); + totalRecords = ALL_SHIPMENTS.length; + loading = false; + + ngOnInit(): void {} + + onLazyLoad(event: any): void { + this.loading = true; + const start = event.first ?? 0; + const end = start + (event.rows ?? PAGE_SIZE); + + setTimeout(() => { + const page = ALL_SHIPMENTS.slice(start, end); + const updated = [...this.shipments]; + for (let i = 0; i < page.length; i++) { + updated[start + i] = page[i]; + } + this.shipments = updated; + this.loading = false; + }, 300); + } +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-pagination.component.ts b/src/stories/components/p-data-table/examples/primeng-table-pagination.component.ts index 3bb24c9c..3c4446c7 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-pagination.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-pagination.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const SHIPMENTS = [ @@ -41,4 +42,65 @@ const SHIPMENTS = [ }) export class PDataTablePaginationComponent { shipments = SHIPMENTS; -} \ No newline at end of file +} + +export const Pagination: StoryObj = { + name: 'Pagination', + decorators: [moduleMetadata({ imports: [PDataTablePaginationComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Пагинация для больших наборов данных на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class ExampleComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-scroll-horizontal.component.ts b/src/stories/components/p-data-table/examples/primeng-table-scroll-horizontal.component.ts index 8128b89f..9e3cb8ca 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-scroll-horizontal.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-scroll-horizontal.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const SHIPMENTS = [ @@ -54,4 +55,78 @@ const SHIPMENTS = [ }) export class PDataTableScrollHorizontalComponent { shipments = SHIPMENTS; -} \ No newline at end of file +} + +export const ScrollHorizontal: StoryObj = { + name: 'Scroll: Horizontal', + decorators: [moduleMetadata({ imports: [PDataTableScrollHorizontalComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Горизонтальная прокрутка при большом количестве столбцов на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', sender: 'Иванов И.И.', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200, dimensions: '30×20×15 см' }, + { id: 2, trackNumber: 'ЦД-00123457', sender: 'Петров П.П.', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450, dimensions: '10×10×10 см' }, + { id: 3, trackNumber: 'ЦД-00123458', sender: 'Сидоров С.С.', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100, dimensions: '50×40×30 см' }, + { id: 4, trackNumber: 'ЦД-00123459', sender: 'Козлов К.К.', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750, dimensions: '20×15×10 см' }, + { id: 5, trackNumber: 'ЦД-00123460', sender: 'Новиков Н.Н.', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800, dimensions: '40×30×20 см' }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + + Трек-номер + + + Отправитель + + + Назначение + + + Статус + + + Вес, кг + + + Стоимость, ₽ + + Габариты + + + + + {{ shipment.trackNumber }} + {{ shipment.sender }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + {{ shipment.dimensions }} + + + + \`, +}) +export class ExampleComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-scroll-vertical.component.ts b/src/stories/components/p-data-table/examples/primeng-table-scroll-vertical.component.ts index 800b185d..ea9a6cc9 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-scroll-vertical.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-scroll-vertical.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const BASE_SHIPMENTS = [ @@ -39,4 +40,63 @@ const BASE_SHIPMENTS = [ }) export class PDataTableScrollVerticalComponent { shipments = [...BASE_SHIPMENTS, ...BASE_SHIPMENTS, ...BASE_SHIPMENTS]; -} \ No newline at end of file +} + +export const ScrollVertical: StoryObj = { + name: 'Scroll: Vertical', + decorators: [moduleMetadata({ imports: [PDataTableScrollVerticalComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Вертикальная прокрутка с фиксированной высотой контейнера на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const BASE_SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1 }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + + + + \`, +}) +export class ExampleComponent { + shipments = [...BASE_SHIPMENTS, ...BASE_SHIPMENTS, ...BASE_SHIPMENTS]; +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-selectable.component.ts b/src/stories/components/p-data-table/examples/primeng-table-selectable.component.ts index 58607acb..58b0f541 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-selectable.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-selectable.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const SHIPMENTS = [ @@ -39,4 +40,63 @@ const SHIPMENTS = [ export class PDataTableSelectableComponent { shipments = SHIPMENTS; selected: any = null; -} \ No newline at end of file +} + +export const Selectable: StoryObj = { + name: 'Selectable', + decorators: [moduleMetadata({ imports: [PDataTableSelectableComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Выбор строки кликом. Режим single — выбирается одна строка на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class ExampleComponent { + shipments = SHIPMENTS; + selected: any = null; +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-selection-checkbox.component.ts b/src/stories/components/p-data-table/examples/primeng-table-selection-checkbox.component.ts index c03764b6..76ad3882 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-selection-checkbox.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-selection-checkbox.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const SHIPMENTS = [ @@ -43,4 +44,67 @@ const SHIPMENTS = [ export class PDataTableSelectionCheckboxComponent { shipments = SHIPMENTS; selected: any[] = []; -} \ No newline at end of file +} + +export const SelectionCheckbox: StoryObj = { + name: 'Row Selection: Checkbox', + decorators: [moduleMetadata({ imports: [PDataTableSelectionCheckboxComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Множественный выбор строк через чекбоксы на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + + + + Трек-номер + Назначение + Статус + Вес, кг + + + + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + + + + \`, +}) +export class ExampleComponent { + shipments = SHIPMENTS; + selected: any[] = []; +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-selection-radio.component.ts b/src/stories/components/p-data-table/examples/primeng-table-selection-radio.component.ts index e31ecaa5..f955a99e 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-selection-radio.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-selection-radio.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const SHIPMENTS = [ @@ -41,4 +42,65 @@ const SHIPMENTS = [ export class PDataTableSelectionRadioComponent { shipments = SHIPMENTS; selected: any = null; -} \ No newline at end of file +} + +export const SelectionRadio: StoryObj = { + name: 'Row Selection: RadioButton', + decorators: [moduleMetadata({ imports: [PDataTableSelectionRadioComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Выбор одной строки через радио-кнопку на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + + Трек-номер + Назначение + Статус + Вес, кг + + + + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + + + + \`, +}) +export class ExampleComponent { + shipments = SHIPMENTS; + selected: any = null; +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/examples/primeng-table-striped-rows.component.ts b/src/stories/components/p-data-table/examples/primeng-table-striped-rows.component.ts index 6ca59979..52ef46c9 100644 --- a/src/stories/components/p-data-table/examples/primeng-table-striped-rows.component.ts +++ b/src/stories/components/p-data-table/examples/primeng-table-striped-rows.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; import { TableModule } from 'primeng/table'; const SHIPMENTS = [ @@ -41,4 +42,65 @@ const SHIPMENTS = [ }) export class PDataTableStripedRowsComponent { shipments = SHIPMENTS; -} \ No newline at end of file +} + +export const StripedRows: StoryObj = { + name: 'StripedRows', + decorators: [moduleMetadata({ imports: [PDataTableStripedRowsComponent] })], + render: () => ({ template: `` }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Чередование цвета строк для улучшения читаемости на базе PrimeNG pTable.', + }, + source: { + language: 'ts', + code: `import { Component } from '@angular/core'; +import { TableModule } from 'primeng/table'; + +const SHIPMENTS = [ + { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, + { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, + { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, + { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, + { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, + { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, + { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, + { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, +]; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [TableModule], + template: \` + + + + Трек-номер + Назначение + Статус + Вес, кг + Стоимость, ₽ + + + + + {{ shipment.trackNumber }} + {{ shipment.destination }} + {{ shipment.status }} + {{ shipment.weight }} + {{ shipment.cost }} + + + + \`, +}) +export class ExampleComponent { + shipments = SHIPMENTS; +}`, + }, + }, + }, +}; diff --git a/src/stories/components/p-data-table/primeng-data-table.stories.ts b/src/stories/components/p-data-table/primeng-data-table.stories.ts index ed973d8e..a711e0d2 100644 --- a/src/stories/components/p-data-table/primeng-data-table.stories.ts +++ b/src/stories/components/p-data-table/primeng-data-table.stories.ts @@ -1,19 +1,36 @@ import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; -import { PDataTableDefaultComponent } from './examples/primeng-table-default.component'; -import { PDataTableStripedRowsComponent } from './examples/primeng-table-striped-rows.component'; -import { PDataTableSelectableComponent } from './examples/primeng-table-selectable.component'; -import { PDataTableGridLinesComponent } from './examples/primeng-table-grid-lines.component'; -import { PDataTablePaginationComponent } from './examples/primeng-table-pagination.component'; -import { PDataTableSelectionRadioComponent } from './examples/primeng-table-selection-radio.component'; -import { PDataTableSelectionCheckboxComponent } from './examples/primeng-table-selection-checkbox.component'; -import { PDataTableScrollVerticalComponent } from './examples/primeng-table-scroll-vertical.component'; -import { PDataTableScrollHorizontalComponent } from './examples/primeng-table-scroll-horizontal.component'; -import { PDataTableCustomBodyComponent } from './examples/primeng-table-custom-body.component'; -import { PDataTableLazyComponent } from './examples/primeng-table-lazy.component'; +import { PDataTableDefaultComponent, Default } from './examples/primeng-table-default.component'; +import { PDataTableStripedRowsComponent, StripedRows } from './examples/primeng-table-striped-rows.component'; +import { PDataTableSelectableComponent, Selectable } from './examples/primeng-table-selectable.component'; +import { PDataTableGridLinesComponent, GridLines } from './examples/primeng-table-grid-lines.component'; +import { PDataTablePaginationComponent, Pagination } from './examples/primeng-table-pagination.component'; +import { PDataTableSelectionRadioComponent, SelectionRadio } from './examples/primeng-table-selection-radio.component'; +import { PDataTableSelectionCheckboxComponent, SelectionCheckbox } from './examples/primeng-table-selection-checkbox.component'; +import { PDataTableScrollVerticalComponent, ScrollVertical } from './examples/primeng-table-scroll-vertical.component'; +import { PDataTableScrollHorizontalComponent, ScrollHorizontal } from './examples/primeng-table-scroll-horizontal.component'; +import { PDataTableCustomBodyComponent, CustomBody } from './examples/primeng-table-custom-body.component'; +import { PDataTableLazyComponent, LazyLoading } from './examples/primeng-table-lazy.component'; const meta: Meta = { title: 'Components/Data/DataTable (PrimeNG)', tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + PDataTableDefaultComponent, + PDataTableStripedRowsComponent, + PDataTableSelectableComponent, + PDataTableGridLinesComponent, + PDataTablePaginationComponent, + PDataTableSelectionRadioComponent, + PDataTableSelectionCheckboxComponent, + PDataTableScrollVerticalComponent, + PDataTableScrollHorizontalComponent, + PDataTableCustomBodyComponent, + PDataTableLazyComponent, + ], + }), + ], parameters: { docs: { description: { @@ -31,747 +48,16 @@ import { TableModule } from 'primeng/table'; export default meta; type Story = StoryObj; -// ── Default ─────────────────────────────────────────────────────────────────── - -export const Default: Story = { - name: 'DataTable', - decorators: [moduleMetadata({ imports: [PDataTableDefaultComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Базовая таблица отправлений с сортировкой по всем столбцам на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, - { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, - { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, - { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, -]; - -@Component({ - selector: 'app-primeng-table-default', - standalone: true, - imports: [TableModule], - template: \` - - - - Трек-номер - Назначение - Статус - Вес, кг - Стоимость, ₽ - - - - - {{ shipment.trackNumber }} - {{ shipment.destination }} - {{ shipment.status }} - {{ shipment.weight }} - {{ shipment.cost }} - - - - \`, -}) -export class PDataTableDefaultComponent { - shipments = SHIPMENTS; -}`, - }, - }, - }, -}; - -// ── StripedRows ─────────────────────────────────────────────────────────────── - -export const StripedRows: Story = { - name: 'StripedRows', - decorators: [moduleMetadata({ imports: [PDataTableStripedRowsComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Чередование цвета строк для улучшения читаемости на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, - { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, - { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, - { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, -]; - -@Component({ - selector: 'app-primeng-table-striped-rows', - standalone: true, - imports: [TableModule], - template: \` - - - - Трек-номер - Назначение - Статус - Вес, кг - Стоимость, ₽ - - - - - {{ shipment.trackNumber }} - {{ shipment.destination }} - {{ shipment.status }} - {{ shipment.weight }} - {{ shipment.cost }} - - - - \`, -}) -export class PDataTableStripedRowsComponent { - shipments = SHIPMENTS; -}`, - }, - }, - }, -}; - -// ── Selectable ──────────────────────────────────────────────────────────────── - -export const Selectable: Story = { - name: 'Selectable', - decorators: [moduleMetadata({ imports: [PDataTableSelectableComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Выбор строки кликом. Режим single — выбирается одна строка на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, -]; - -@Component({ - selector: 'app-primeng-table-selectable', - standalone: true, - imports: [TableModule], - template: \` - - - - Трек-номер - Назначение - Статус - Вес, кг - Стоимость, ₽ - - - - - {{ shipment.trackNumber }} - {{ shipment.destination }} - {{ shipment.status }} - {{ shipment.weight }} - {{ shipment.cost }} - - - - \`, -}) -export class PDataTableSelectableComponent { - shipments = SHIPMENTS; - selected: any = null; -}`, - }, - }, - }, -}; - -// ── GridLines ───────────────────────────────────────────────────────────────── - -export const GridLines: Story = { - name: 'GridLines', - decorators: [moduleMetadata({ imports: [PDataTableGridLinesComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Сетка между ячейками для наглядного разграничения данных на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, -]; - -@Component({ - selector: 'app-primeng-table-grid-lines', - standalone: true, - imports: [TableModule], - template: \` - - - - Трек-номер - Назначение - Статус - Вес, кг - Стоимость, ₽ - - - - - {{ shipment.trackNumber }} - {{ shipment.destination }} - {{ shipment.status }} - {{ shipment.weight }} - {{ shipment.cost }} - - - - \`, -}) -export class PDataTableGridLinesComponent { - shipments = SHIPMENTS; -}`, - }, - }, - }, -}; - -// ── Pagination ──────────────────────────────────────────────────────────────── - -export const Pagination: Story = { - name: 'Pagination', - decorators: [moduleMetadata({ imports: [PDataTablePaginationComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Пагинация для больших наборов данных на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, - { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5, cost: 350 }, - { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0, cost: 3200 }, - { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1, cost: 980 }, -]; - -@Component({ - selector: 'app-primeng-table-pagination', - standalone: true, - imports: [TableModule], - template: \` - - - - Трек-номер - Назначение - Статус - Вес, кг - Стоимость, ₽ - - - - - {{ shipment.trackNumber }} - {{ shipment.destination }} - {{ shipment.status }} - {{ shipment.weight }} - {{ shipment.cost }} - - - - \`, -}) -export class PDataTablePaginationComponent { - shipments = SHIPMENTS; -}`, - }, - }, - }, -}; - -// ── SelectionRadio ──────────────────────────────────────────────────────────── - -export const SelectionRadio: Story = { - name: 'Row Selection: RadioButton', - decorators: [moduleMetadata({ imports: [PDataTableSelectionRadioComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Выбор одной строки через радио-кнопку на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, -]; - -@Component({ - selector: 'app-primeng-table-selection-radio', - standalone: true, - imports: [TableModule], - template: \` - - - - - Трек-номер - Назначение - Статус - Вес, кг - - - - - - - - {{ shipment.trackNumber }} - {{ shipment.destination }} - {{ shipment.status }} - {{ shipment.weight }} - - - - \`, -}) -export class PDataTableSelectionRadioComponent { - shipments = SHIPMENTS; - selected: any = null; -}`, - }, - }, - }, -}; - -// ── SelectionCheckbox ───────────────────────────────────────────────────────── - -export const SelectionCheckbox: Story = { - name: 'Row Selection: Checkbox', - decorators: [moduleMetadata({ imports: [PDataTableSelectionCheckboxComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Множественный выбор строк через чекбоксы на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, -]; - -@Component({ - selector: 'app-primeng-table-selection-checkbox', - standalone: true, - imports: [TableModule], - template: \` - - - - - - - Трек-номер - Назначение - Статус - Вес, кг - - - - - - - - {{ shipment.trackNumber }} - {{ shipment.destination }} - {{ shipment.status }} - {{ shipment.weight }} - - - - \`, -}) -export class PDataTableSelectionCheckboxComponent { - shipments = SHIPMENTS; - selected: any[] = []; -}`, - }, - }, - }, -}; - -// ── ScrollVertical ──────────────────────────────────────────────────────────── - -export const ScrollVertical: Story = { - name: 'Scroll: Vertical', - decorators: [moduleMetadata({ imports: [PDataTableScrollVerticalComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Вертикальная прокрутка с фиксированной высотой контейнера на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const BASE_SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7 }, - { id: 6, trackNumber: 'ЦД-00123461', destination: 'Воронеж', status: 'Ожидание', weight: 0.5 }, - { id: 7, trackNumber: 'ЦД-00123462', destination: 'Самара', status: 'В пути', weight: 8.0 }, - { id: 8, trackNumber: 'ЦД-00123463', destination: 'Ростов-на-Дону', status: 'Доставлен', weight: 2.1 }, -]; - -@Component({ - selector: 'app-primeng-table-scroll-vertical', - standalone: true, - imports: [TableModule], - template: \` - - - - Трек-номер - Назначение - Статус - Вес, кг - - - - - {{ shipment.trackNumber }} - {{ shipment.destination }} - {{ shipment.status }} - {{ shipment.weight }} - - - - \`, -}) -export class PDataTableScrollVerticalComponent { - shipments = [...BASE_SHIPMENTS, ...BASE_SHIPMENTS, ...BASE_SHIPMENTS]; -}`, - }, - }, - }, -}; - -// ── ScrollHorizontal ────────────────────────────────────────────────────────── - -export const ScrollHorizontal: Story = { - name: 'Scroll: Horizontal', - decorators: [moduleMetadata({ imports: [PDataTableScrollHorizontalComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Горизонтальная прокрутка при большом количестве столбцов на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', sender: 'Иванов И.И.', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200, dimensions: '30×20×15 см' }, - { id: 2, trackNumber: 'ЦД-00123457', sender: 'Петров П.П.', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450, dimensions: '10×10×10 см' }, - { id: 3, trackNumber: 'ЦД-00123458', sender: 'Сидоров С.С.', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100, dimensions: '50×40×30 см' }, - { id: 4, trackNumber: 'ЦД-00123459', sender: 'Козлов К.К.', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750, dimensions: '20×15×10 см' }, - { id: 5, trackNumber: 'ЦД-00123460', sender: 'Новиков Н.Н.', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800, dimensions: '40×30×20 см' }, -]; - -@Component({ - selector: 'app-primeng-table-scroll-horizontal', - standalone: true, - imports: [TableModule], - template: \` - - - - - Трек-номер - - - Отправитель - - - Назначение - - - Статус - - - Вес, кг - - - Стоимость, ₽ - - Габариты - - - - - {{ shipment.trackNumber }} - {{ shipment.sender }} - {{ shipment.destination }} - {{ shipment.status }} - {{ shipment.weight }} - {{ shipment.cost }} - {{ shipment.dimensions }} - - - - \`, -}) -export class PDataTableScrollHorizontalComponent { - shipments = SHIPMENTS; -}`, - }, - }, - }, -}; - -// ── CustomBody ──────────────────────────────────────────────────────────────── - -export const CustomBody: Story = { - name: 'Custom Templates', - decorators: [moduleMetadata({ imports: [PDataTableCustomBodyComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Кастомные шаблоны для заголовка и тела таблицы на базе PrimeNG pTable.', - }, - source: { - language: 'ts', - code: `import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { TableModule } from 'primeng/table'; -import { TagModule } from 'primeng/tag'; - -const SHIPMENTS = [ - { id: 1, trackNumber: 'ЦД-00123456', destination: 'Москва', status: 'В пути', weight: 2.5, cost: 1200 }, - { id: 2, trackNumber: 'ЦД-00123457', destination: 'Новосибирск', status: 'Доставлен', weight: 0.8, cost: 450 }, - { id: 3, trackNumber: 'ЦД-00123458', destination: 'Екатеринбург', status: 'Задержан', weight: 5.2, cost: 2100 }, - { id: 4, trackNumber: 'ЦД-00123459', destination: 'Казань', status: 'В пути', weight: 1.3, cost: 750 }, - { id: 5, trackNumber: 'ЦД-00123460', destination: 'Краснодар', status: 'Доставлен', weight: 3.7, cost: 1800 }, -]; - -@Component({ - selector: 'app-primeng-table-custom-body', - standalone: true, - imports: [CommonModule, TableModule, TagModule], - template: \` - - - - Трек-номер - Назначение - Статус - Вес, кг - Стоимость, ₽ - - - - - {{ shipment.trackNumber }} - {{ shipment.destination }} - - - - {{ shipment.weight }} кг - {{ shipment.cost | currency:'RUB':'symbol':'1.0-0':'ru' }} - - - - Нет данных для отображения - - - \`, -}) -export class PDataTableCustomBodyComponent { - shipments = SHIPMENTS; - - getSeverity(status: string): 'success' | 'warn' | 'danger' | 'info' | 'secondary' | undefined { - switch (status) { - case 'Доставлен': return 'success'; - case 'В пути': return 'info'; - case 'Задержан': return 'danger'; - case 'Ожидание': return 'warn'; - default: return 'secondary'; - } - } -}`, - }, - }, - }, -}; - -// ── Lazy loading ────────────────────────────────────────────────────────────── - -export const LazyLoading: Story = { - name: 'Lazy Loading', - decorators: [moduleMetadata({ imports: [PDataTableLazyComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Lazy Loading + Virtual Scroll для работы с большими наборами данных. Данные подгружаются порциями при прокрутке таблицы. Используйте `[lazy]="true"`, `[virtualScroll]="true"`, `[virtualScrollItemSize]`, `[totalRecords]` и `(onLazyLoad)` для управления загрузкой данных с сервера.', - }, - source: { - language: 'ts', - code: `import { Component, OnInit } from '@angular/core'; -import { TableModule } from 'primeng/table'; - -const ALL_SHIPMENTS = Array.from({ length: 500 }, (_, i) => ({ - id: i + 1, - trackNumber: \`ЦД-\${String(i + 100000).padStart(8, '0')}\`, - destination: ['Москва', 'Новосибирск', 'Екатеринбург', 'Казань', 'Краснодар', 'Санкт-Петербург', 'Воронеж', 'Самара', 'Ростов-на-Дону', 'Уфа'][i % 10], - status: ['В пути', 'Доставлен', 'Задержан', 'Ожидание'][i % 4], - weight: Number(((i * 0.37 + 0.5) % 10).toFixed(1)), - cost: ((i * 137 + 200) % 5000) + 200, -})); - -const PAGE_SIZE = 50; - -@Component({ - selector: 'app-primeng-table-lazy', - standalone: true, - imports: [TableModule], - template: \` - - - - Трек-номер - Назначение - Статус - Вес, кг - Стоимость, ₽ - - - - - {{ shipment?.trackNumber }} - {{ shipment?.destination }} - {{ shipment?.status }} - {{ shipment?.weight }} - {{ shipment?.cost }} - - - - - Загрузка… - - - - \`, -}) -export class PDataTableLazyComponent implements OnInit { - readonly PAGE_SIZE = PAGE_SIZE; - - shipments: any[] = Array.from({ length: ALL_SHIPMENTS.length }); - totalRecords = ALL_SHIPMENTS.length; - loading = false; - - ngOnInit(): void { - // shipments pre-filled with placeholders so virtual scroll knows total size - } - - onLazyLoad(event: any): void { - this.loading = true; - const start = event.first ?? 0; - const end = start + (event.rows ?? PAGE_SIZE); - - // Simulate async server request - setTimeout(() => { - const page = ALL_SHIPMENTS.slice(start, end); - const updated = [...this.shipments]; - for (let i = 0; i < page.length; i++) { - updated[start + i] = page[i]; - } - this.shipments = updated; - this.loading = false; - }, 300); - } -}`, - }, - }, - }, +export { + Default, + StripedRows, + Selectable, + GridLines, + Pagination, + SelectionRadio, + SelectionCheckbox, + ScrollVertical, + ScrollHorizontal, + CustomBody, + LazyLoading, }; From 9ee5e314b0bd488d76ae2e3612992d216c9aee56 Mon Sep 17 00:00:00 2001 From: "ak.dmitriev" Date: Fri, 29 May 2026 19:37:52 +0700 Subject: [PATCH 26/28] DS-517 --- src/lib/components/stepper/ng-package.json | 7 +++++ src/lib/components/stepper/public_api.ts | 4 +++ .../components/stepper/stepper.component.ts | 28 +++++++++---------- .../examples/stepper-error.component.ts | 26 ++++++++--------- .../examples/stepper-linear.component.ts | 26 ++++++++--------- .../examples/stepper-steps-only.component.ts | 26 ++++++++--------- .../examples/stepper-vertical.component.ts | 26 ++++++++--------- .../components/stepper/stepper.stories.ts | 24 ++++++++-------- 8 files changed, 89 insertions(+), 78 deletions(-) create mode 100644 src/lib/components/stepper/ng-package.json create mode 100644 src/lib/components/stepper/public_api.ts diff --git a/src/lib/components/stepper/ng-package.json b/src/lib/components/stepper/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/stepper/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/stepper/public_api.ts b/src/lib/components/stepper/public_api.ts new file mode 100644 index 00000000..1054ead1 --- /dev/null +++ b/src/lib/components/stepper/public_api.ts @@ -0,0 +1,4 @@ +export * from './stepper.component'; + + + diff --git a/src/lib/components/stepper/stepper.component.ts b/src/lib/components/stepper/stepper.component.ts index 2128eacf..ed4ae504 100644 --- a/src/lib/components/stepper/stepper.component.ts +++ b/src/lib/components/stepper/stepper.component.ts @@ -6,10 +6,10 @@ import { Step } from 'primeng/stepper'; import { StepPanels } from 'primeng/stepper'; import { StepPanel } from 'primeng/stepper'; import { StepItem } from 'primeng/stepper'; -import { ButtonComponent } from '../button/button.component'; +import { ExtraButtonComponent } from '../button/button.component'; -export interface StepperItem { - value: string; +export interface ExtraStepperItem { + value: number | undefined; label: string; caption?: string; content?: string; @@ -18,10 +18,10 @@ export interface StepperItem { } @Component({ - selector: 'stepper', + selector: 'extra-stepper', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [Stepper, StepList, Step, StepPanels, StepPanel, StepItem, ButtonComponent, NgClass], + imports: [Stepper, StepList, Step, StepPanels, StepPanel, StepItem, ExtraButtonComponent, NgClass], template: ` {{ step.content }}

@if (!first) { - + } @if (!last) { - + }
@@ -83,10 +83,10 @@ export interface StepperItem {

{{ step.content }}

@if (!first) { - + } @if (!last) { - + }
@@ -98,16 +98,16 @@ export interface StepperItem {
`, }) -export class StepperComponent { - @Input() value: string = '1'; - @Input() steps: StepperItem[] = []; +export class ExtraStepperComponent { + @Input() value: number | undefined = 1; + @Input() steps: ExtraStepperItem[] = []; @Input() linear = false; @Input() orientation: 'horizontal' | 'vertical' = 'horizontal'; @Input() showPanels = true; - @Output() valueChange = new EventEmitter(); + @Output() valueChange = new EventEmitter(); - onValueChange(newValue: string): void { + onValueChange(newValue: number | undefined): void { this.value = newValue; this.valueChange.emit(newValue); } diff --git a/src/stories/components/stepper/examples/stepper-error.component.ts b/src/stories/components/stepper/examples/stepper-error.component.ts index 00351522..d2080d48 100644 --- a/src/stories/components/stepper/examples/stepper-error.component.ts +++ b/src/stories/components/stepper/examples/stepper-error.component.ts @@ -1,10 +1,10 @@ import { Component } from '@angular/core'; import { StoryObj } from '@storybook/angular'; -import { StepperComponent, StepperItem } from '../../../../lib/components/stepper/stepper.component'; +import { ExtraStepperComponent, ExtraStepperItem } from '../../../../lib/components/stepper/stepper.component'; const template = `
- +
`; const styles = ''; @@ -12,15 +12,15 @@ const styles = ''; @Component({ selector: 'app-stepper-error', standalone: true, - imports: [StepperComponent], + imports: [ExtraStepperComponent], template, styles, }) export class StepperErrorComponent { - steps: StepperItem[] = [ - { value: '1', label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, - { value: '2', label: 'Stepper', caption: 'caption', content: 'Step 2 Content (Invalid)', invalid: true }, - { value: '3', label: 'Stepper', caption: 'caption', content: 'Step 3 Content', disabled: true }, + steps: ExtraStepperItem[] = [ + { value: 1, label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, + { value: 2, label: 'Stepper', caption: 'caption', content: 'Step 2 Content (Invalid)', invalid: true }, + { value: 3, label: 'Stepper', caption: 'caption', content: 'Step 3 Content', disabled: true }, ]; } @@ -37,21 +37,21 @@ export const Error: StoryObj = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { StepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-stepper-error', standalone: true, - imports: [StepperComponent], + imports: [ExtraStepperComponent], template: \` - + \`, }) export class StepperErrorComponent { steps: StepperItem[] = [ - { value: '1', label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, - { value: '2', label: 'Stepper', caption: 'caption', content: 'Step 2 Content (Invalid)', invalid: true }, - { value: '3', label: 'Stepper', caption: 'caption', content: 'Step 3 Content', disabled: true }, + { value: 1, label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, + { value: 2, label: 'Stepper', caption: 'caption', content: 'Step 2 Content (Invalid)', invalid: true }, + { value: 3, label: 'Stepper', caption: 'caption', content: 'Step 3 Content', disabled: true }, ]; } `, diff --git a/src/stories/components/stepper/examples/stepper-linear.component.ts b/src/stories/components/stepper/examples/stepper-linear.component.ts index be97b1a8..245d6d9c 100644 --- a/src/stories/components/stepper/examples/stepper-linear.component.ts +++ b/src/stories/components/stepper/examples/stepper-linear.component.ts @@ -1,10 +1,10 @@ import { Component } from '@angular/core'; import { StoryObj } from '@storybook/angular'; -import { StepperComponent, StepperItem } from '../../../../lib/components/stepper/stepper.component'; +import { ExtraStepperComponent, ExtraStepperItem } from '../../../../lib/components/stepper/stepper.component'; const template = `
- +
`; const styles = ''; @@ -12,15 +12,15 @@ const styles = ''; @Component({ selector: 'app-stepper-linear', standalone: true, - imports: [StepperComponent], + imports: [ExtraStepperComponent], template, styles, }) export class StepperLinearComponent { - steps: StepperItem[] = [ - { value: '1', label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, - { value: '2', label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, - { value: '3', label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, + steps: ExtraStepperItem[] = [ + { value: 1, label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, + { value: 2, label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, + { value: 3, label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, ]; } @@ -36,21 +36,21 @@ export const Linear: StoryObj = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { StepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-stepper-linear', standalone: true, - imports: [StepperComponent], + imports: [ExtraStepperComponent], template: \` - + \`, }) export class StepperLinearComponent { steps: StepperItem[] = [ - { value: '1', label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, - { value: '2', label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, - { value: '3', label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, + { value: 1, label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, + { value: 2, label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, + { value: 3, label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, ]; } `, diff --git a/src/stories/components/stepper/examples/stepper-steps-only.component.ts b/src/stories/components/stepper/examples/stepper-steps-only.component.ts index f618787e..2a7288d5 100644 --- a/src/stories/components/stepper/examples/stepper-steps-only.component.ts +++ b/src/stories/components/stepper/examples/stepper-steps-only.component.ts @@ -1,10 +1,10 @@ import { Component } from '@angular/core'; import { StoryObj } from '@storybook/angular'; -import { StepperComponent, StepperItem } from '../../../../lib/components/stepper/stepper.component'; +import { ExtraStepperComponent, ExtraStepperItem } from '../../../../lib/components/stepper/stepper.component'; const template = `
- +
`; const styles = ''; @@ -12,15 +12,15 @@ const styles = ''; @Component({ selector: 'app-stepper-steps-only', standalone: true, - imports: [StepperComponent], + imports: [ExtraStepperComponent], template, styles, }) export class StepperStepsOnlyComponent { - steps: StepperItem[] = [ - { value: '1', label: 'Stepper', caption: 'caption' }, - { value: '2', label: 'Stepper', caption: 'caption' }, - { value: '3', label: 'Stepper', caption: 'caption' }, + steps: ExtraStepperItem[] = [ + { value: 1, label: 'Stepper', caption: 'caption' }, + { value: 2, label: 'Stepper', caption: 'caption' }, + { value: 3, label: 'Stepper', caption: 'caption' }, ]; } @@ -36,21 +36,21 @@ export const StepsOnly: StoryObj = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { StepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-stepper-steps-only', standalone: true, - imports: [StepperComponent], + imports: [ExtraStepperComponent], template: \` - + \`, }) export class StepperStepsOnlyComponent { steps: StepperItem[] = [ - { value: '1', label: 'Stepper', caption: 'caption' }, - { value: '2', label: 'Stepper', caption: 'caption' }, - { value: '3', label: 'Stepper', caption: 'caption' }, + { value: 1, label: 'Stepper', caption: 'caption' }, + { value: 2, label: 'Stepper', caption: 'caption' }, + { value: 3, label: 'Stepper', caption: 'caption' }, ]; } `, diff --git a/src/stories/components/stepper/examples/stepper-vertical.component.ts b/src/stories/components/stepper/examples/stepper-vertical.component.ts index a6172221..695bd9f3 100644 --- a/src/stories/components/stepper/examples/stepper-vertical.component.ts +++ b/src/stories/components/stepper/examples/stepper-vertical.component.ts @@ -1,10 +1,10 @@ import { Component } from '@angular/core'; import { StoryObj } from '@storybook/angular'; -import { StepperComponent, StepperItem } from '../../../../lib/components/stepper/stepper.component'; +import { ExtraStepperComponent, ExtraStepperItem } from '../../../../lib/components/stepper/stepper.component'; const template = `
- +
`; const styles = ''; @@ -12,15 +12,15 @@ const styles = ''; @Component({ selector: 'app-stepper-vertical', standalone: true, - imports: [StepperComponent], + imports: [ExtraStepperComponent], template, styles, }) export class StepperVerticalComponent { - steps: StepperItem[] = [ - { value: '1', label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, - { value: '2', label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, - { value: '3', label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, + steps: ExtraStepperItem[] = [ + { value: 1, label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, + { value: 2, label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, + { value: 3, label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, ]; } @@ -36,21 +36,21 @@ export const Vertical: StoryObj = { language: 'ts', code: ` import { Component } from '@angular/core'; -import { StepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-stepper-vertical', standalone: true, - imports: [StepperComponent], + imports: [ExtraStepperComponent], template: \` - + \`, }) export class StepperVerticalComponent { steps: StepperItem[] = [ - { value: '1', label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, - { value: '2', label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, - { value: '3', label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, + { value: 1, label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, + { value: 2, label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, + { value: 3, label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, ]; } `, diff --git a/src/stories/components/stepper/stepper.stories.ts b/src/stories/components/stepper/stepper.stories.ts index b9cd7548..41e8d72f 100644 --- a/src/stories/components/stepper/stepper.stories.ts +++ b/src/stories/components/stepper/stepper.stories.ts @@ -1,20 +1,20 @@ import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; -import { StepperComponent } from '../../../lib/components/stepper/stepper.component'; +import { ExtraStepperComponent } from '../../../lib/components/stepper/stepper.component'; import { StepperVerticalComponent, Vertical as VerticalStory } from './examples/stepper-vertical.component'; import { StepperLinearComponent, Linear as LinearStory } from './examples/stepper-linear.component'; import { StepperStepsOnlyComponent, StepsOnly as StepsOnlyStory } from './examples/stepper-steps-only.component'; import { StepperErrorComponent, Error as ErrorStory } from './examples/stepper-error.component'; -type StepperArgs = StepperComponent; +type StepperArgs = ExtraStepperComponent; const meta: Meta = { title: 'Components/Stepper', - component: StepperComponent, + component: ExtraStepperComponent, tags: ['autodocs'], decorators: [ moduleMetadata({ imports: [ - StepperComponent, + ExtraStepperComponent, StepperVerticalComponent, StepperLinearComponent, StepperStepsOnlyComponent, @@ -28,7 +28,7 @@ const meta: Meta = { component: `Пошаговый навигационный компонент для отображения прогресса через последовательность шагов. \`\`\`typescript -import { StepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; \`\`\``, }, }, @@ -36,12 +36,12 @@ import { StepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; }, argTypes: { value: { - control: 'text', + control: 'number', description: 'Значение активного шага', table: { category: 'Props', defaultValue: { summary: '1' }, - type: { summary: 'string' }, + type: { summary: 'number | undefined' }, }, }, linear: { @@ -99,16 +99,16 @@ export const Default: Story = { if (args.orientation === 'vertical') parts.push(`orientation="vertical"`); if (!args.showPanels) parts.push(`[showPanels]="false"`); - const template = ``; + const template = ``; return { props: args, template }; }, args: { - value: '1', + value: 1, steps: [ - { value: '1', label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, - { value: '2', label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, - { value: '3', label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, + { value: 1, label: 'Stepper', caption: 'caption', content: 'Step 1 Content' }, + { value: 2, label: 'Stepper', caption: 'caption', content: 'Step 2 Content' }, + { value: 3, label: 'Stepper', caption: 'caption', content: 'Step 3 Content' }, ], linear: false, orientation: 'horizontal', From 5686e25263c7eb7076c04ecb6992ebf3344a48ec Mon Sep 17 00:00:00 2001 From: "ak.dmitriev" Date: Fri, 29 May 2026 20:04:59 +0700 Subject: [PATCH 27/28] DS-493 --- .../accordion/accordion.component.ts | 18 +-- src/lib/components/accordion/ng-package.json | 6 + src/lib/components/accordion/public_api.ts | 1 + .../components/accordion/accordion.stories.ts | 151 ++---------------- .../examples/accordion-disabled.component.ts | 103 ++++++++---- .../examples/accordion-multiple.component.ts | 100 ++++++++---- 6 files changed, 173 insertions(+), 206 deletions(-) create mode 100644 src/lib/components/accordion/ng-package.json create mode 100644 src/lib/components/accordion/public_api.ts diff --git a/src/lib/components/accordion/accordion.component.ts b/src/lib/components/accordion/accordion.component.ts index 98ab31ea..7b34d2c8 100644 --- a/src/lib/components/accordion/accordion.component.ts +++ b/src/lib/components/accordion/accordion.component.ts @@ -1,7 +1,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Accordion, AccordionPanel, AccordionHeader, AccordionContent } from 'primeng/accordion'; -export interface AccordionItem { +export interface ExtraAccordionItem { value: string; header: string; content: string; @@ -10,16 +10,12 @@ export interface AccordionItem { } @Component({ - selector: 'accordion', + selector: 'extra-accordion', host: { style: 'display: block' }, standalone: true, imports: [Accordion, AccordionPanel, AccordionHeader, AccordionContent], template: ` - + @for (item of items; track item.value) { @@ -34,11 +30,11 @@ export interface AccordionItem { } - `, + ` }) -export class AccordionComponent { - @Input() items: AccordionItem[] = []; +export class ExtraAccordionComponent { + @Input() items: ExtraAccordionItem[] = []; @Input() multiple = false; @Input() activeValue: string | null = '0'; - @Output() activeValueChange = new EventEmitter(); + @Output() activeValueChange = new EventEmitter(); } diff --git a/src/lib/components/accordion/ng-package.json b/src/lib/components/accordion/ng-package.json new file mode 100644 index 00000000..3a74fd76 --- /dev/null +++ b/src/lib/components/accordion/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/lib/components/accordion/public_api.ts b/src/lib/components/accordion/public_api.ts new file mode 100644 index 00000000..b1a99b0d --- /dev/null +++ b/src/lib/components/accordion/public_api.ts @@ -0,0 +1 @@ +export * from './accordion.component'; diff --git a/src/stories/components/accordion/accordion.stories.ts b/src/stories/components/accordion/accordion.stories.ts index b99f1a43..073685c6 100644 --- a/src/stories/components/accordion/accordion.stories.ts +++ b/src/stories/components/accordion/accordion.stories.ts @@ -1,9 +1,9 @@ import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; -import { AccordionComponent, AccordionItem } from '../../../lib/components/accordion/accordion.component'; -import { AccordionMultipleComponent } from './examples/accordion-multiple.component'; -import { AccordionDisabledComponent } from './examples/accordion-disabled.component'; +import { ExtraAccordionComponent, ExtraAccordionItem } from '../../../lib/components/accordion/accordion.component'; +import { AccordionMultipleComponent, Multiple } from './examples/accordion-multiple.component'; +import { AccordionDisabledComponent, Disabled } from './examples/accordion-disabled.component'; -const defaultItems: AccordionItem[] = [ +const defaultItems: ExtraAccordionItem[] = [ { value: '0', header: 'Данные отправления', @@ -24,22 +24,14 @@ const defaultItems: AccordionItem[] = [ }, ]; -const commonTemplate = ` - -`; - -const meta: Meta = { +const meta: Meta = { title: 'Components/Panel/Accordion', - component: AccordionComponent, + component: ExtraAccordionComponent, tags: ['autodocs'], decorators: [ moduleMetadata({ imports: [ - AccordionComponent, + ExtraAccordionComponent, AccordionMultipleComponent, AccordionDisabledComponent, ], @@ -69,7 +61,7 @@ import { AccordionModule } from 'primeng/accordion'; }, activeValue: { control: 'text', - description: 'Значение активной панели (value из AccordionItem)', + description: 'Значение активной панели (value из ExtraAccordionItem)', table: { category: 'Props', defaultValue: { summary: "'0'" }, @@ -96,7 +88,7 @@ import { AccordionModule } from 'primeng/accordion'; }; export default meta; -type Story = StoryObj; +type Story = StoryObj; // ── Default ────────────────────────────────────────────────────────────────── @@ -108,7 +100,7 @@ export const Default: Story = { if (args.multiple) parts.push(`[multiple]="true"`); if (args.activeValue && args.activeValue !== '0') parts.push(`activeValue="${args.activeValue}"`); - const template = ``; + const template = ``; return { props: args, template }; }, @@ -121,125 +113,4 @@ export const Default: Story = { }, }; -// ── Stories ────────────────────────────────────────────────────────────────── - -export const Multiple: Story = { - render: (args) => ({ props: args, template: commonTemplate }), - args: { multiple: true, activeValue: '0' }, - parameters: { - docs: { - description: { story: 'Режим множественного раскрытия — несколько панелей могут быть открыты одновременно.' }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { AccordionComponent, AccordionItem } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-accordion-multiple', - standalone: true, - imports: [AccordionComponent], - template: \` - - \`, -}) -export class AccordionMultipleComponent { - items: AccordionItem[] = [ - { - value: '0', - header: 'Данные отправления', - icon: 'ti ti-package', - content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места', - }, - { - value: '1', - header: 'Маршрут доставки', - icon: 'ti ti-map-pin', - content: 'Принят в Москве 14 апр 09:15 → Доставлен 15 апр 14:20', - }, - { - value: '2', - header: 'Стоимость отправления', - icon: 'ti ti-receipt', - content: 'Итого: 525 ₽ · Оплачено: карта *4321', - }, - ]; -} - `, - }, - }, - }, -}; - -export const Disabled: Story = { - render: (args) => ({ props: args, template: commonTemplate }), - args: { - items: [ - { - value: '0', - header: 'Данные отправления', - icon: 'ti ti-package', - content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Отправитель: ООО «Логистика+»', - }, - { - value: '1', - header: 'Документы (недоступно)', - icon: 'ti ti-file-description', - content: 'Документация по отправлению временно недоступна.', - disabled: true, - }, - { - value: '2', - header: 'Стоимость отправления', - icon: 'ti ti-receipt', - content: 'Стоимость доставки: 450 ₽ · НДС: 75 ₽ · Итого: 525 ₽ · Оплачено: карта *4321', - }, - ], - multiple: false, - activeValue: '0', - }, - parameters: { - docs: { - description: { story: 'Заблокированная панель — элемент недоступен для взаимодействия.' }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { AccordionComponent, AccordionItem } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-accordion-disabled', - standalone: true, - imports: [AccordionComponent], - template: \` - - \`, -}) -export class AccordionDisabledComponent { - items: AccordionItem[] = [ - { - value: '0', - header: 'Данные отправления', - icon: 'ti ti-package', - content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места', - }, - { - value: '1', - header: 'Документы (недоступно)', - icon: 'ti ti-file-description', - content: 'Документация по отправлению временно недоступна.', - disabled: true, - }, - { - value: '2', - header: 'Стоимость отправления', - icon: 'ti ti-receipt', - content: 'Итого: 525 ₽ · Оплачено: карта *4321', - }, - ]; -} - `, - }, - }, - }, -}; +export { Multiple, Disabled }; diff --git a/src/stories/components/accordion/examples/accordion-disabled.component.ts b/src/stories/components/accordion/examples/accordion-disabled.component.ts index b6a16441..4e95d5c3 100644 --- a/src/stories/components/accordion/examples/accordion-disabled.component.ts +++ b/src/stories/components/accordion/examples/accordion-disabled.component.ts @@ -1,42 +1,89 @@ import { Component } from '@angular/core'; -import { AccordionComponent, AccordionItem } from '../../../../lib/components/accordion/accordion.component'; - -const items: AccordionItem[] = [ - { - value: '0', - header: 'Данные отправления', - icon: 'ti ti-package', - content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Отправитель: ООО «Логистика+»', - }, - { - value: '1', - header: 'Документы (недоступно)', - icon: 'ti ti-file-description', - content: 'Документация по отправлению временно недоступна.', - disabled: true, - }, - { - value: '2', - header: 'Стоимость отправления', - icon: 'ti ti-receipt', - content: 'Стоимость доставки: 450 ₽ · НДС: 75 ₽ · Итого: 525 ₽ · Оплачено: карта *4321', - }, -]; +import { StoryObj } from '@storybook/angular'; +import { ExtraAccordionComponent, ExtraAccordionItem } from '../../../../lib/components/accordion/accordion.component'; const template = `
- +
`; -const styles = ''; @Component({ selector: 'app-accordion-disabled', standalone: true, - imports: [AccordionComponent], + imports: [ExtraAccordionComponent], template, - styles, }) export class AccordionDisabledComponent { - items = items; + items: ExtraAccordionItem[] = [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Отправитель: ООО «Логистика+»', + }, + { + value: '1', + header: 'Документы (недоступно)', + icon: 'ti ti-file-description', + content: 'Документация по отправлению временно недоступна.', + disabled: true, + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Стоимость доставки: 450 ₽ · НДС: 75 ₽ · Итого: 525 ₽ · Оплачено: карта *4321', + }, + ]; +} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Заблокированная панель — элемент недоступен для взаимодействия.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraAccordionComponent, ExtraAccordionItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-accordion-disabled', + standalone: true, + imports: [ExtraAccordionComponent], + template: \` + + \`, +}) +export class AccordionDisabledComponent { + items: ExtraAccordionItem[] = [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места', + }, + { + value: '1', + header: 'Документы (недоступно)', + icon: 'ti ti-file-description', + content: 'Документация по отправлению временно недоступна.', + disabled: true, + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Итого: 525 ₽ · Оплачено: карта *4321', + }, + ]; } + `, + }, + }, + }, +}; diff --git a/src/stories/components/accordion/examples/accordion-multiple.component.ts b/src/stories/components/accordion/examples/accordion-multiple.component.ts index a6399aaa..e2b02592 100644 --- a/src/stories/components/accordion/examples/accordion-multiple.component.ts +++ b/src/stories/components/accordion/examples/accordion-multiple.component.ts @@ -1,41 +1,87 @@ import { Component } from '@angular/core'; -import { AccordionComponent, AccordionItem } from '../../../../lib/components/accordion/accordion.component'; - -const items: AccordionItem[] = [ - { - value: '0', - header: 'Данные отправления', - icon: 'ti ti-package', - content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Отправитель: ООО «Логистика+»', - }, - { - value: '1', - header: 'Маршрут доставки', - icon: 'ti ti-map-pin', - content: 'Принят в Москве 14 апр 09:15 → Сортировочный центр 14 апр 14:30 → Передан перевозчику → Прибыл в Новосибирск 15 апр 08:00 → Доставлен 15 апр 14:20', - }, - { - value: '2', - header: 'Стоимость отправления', - icon: 'ti ti-receipt', - content: 'Стоимость доставки: 450 ₽ · НДС: 75 ₽ · Итого: 525 ₽ · Оплачено: карта *4321', - }, -]; +import { StoryObj } from '@storybook/angular'; +import { ExtraAccordionComponent, ExtraAccordionItem } from '../../../../lib/components/accordion/accordion.component'; const template = `
- +
`; -const styles = ''; @Component({ selector: 'app-accordion-multiple', standalone: true, - imports: [AccordionComponent], + imports: [ExtraAccordionComponent], template, - styles, }) export class AccordionMultipleComponent { - items = items; + items: ExtraAccordionItem[] = [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Отправитель: ООО «Логистика+»', + }, + { + value: '1', + header: 'Маршрут доставки', + icon: 'ti ti-map-pin', + content: 'Принят в Москве 14 апр 09:15 → Сортировочный центр 14 апр 14:30 → Передан перевозчику → Прибыл в Новосибирск 15 апр 08:00 → Доставлен 15 апр 14:20', + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Стоимость доставки: 450 ₽ · НДС: 75 ₽ · Итого: 525 ₽ · Оплачено: карта *4321', + }, + ]; +} + +export const Multiple: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Режим множественного раскрытия — несколько панелей могут быть открыты одновременно.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraAccordionComponent, ExtraAccordionItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-accordion-multiple', + standalone: true, + imports: [ExtraAccordionComponent], + template: \` + + \`, +}) +export class AccordionMultipleComponent { + items: ExtraAccordionItem[] = [ + { + value: '0', + header: 'Данные отправления', + icon: 'ti ti-package', + content: 'Заказ №ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места', + }, + { + value: '1', + header: 'Маршрут доставки', + icon: 'ti ti-map-pin', + content: 'Принят в Москве 14 апр 09:15 → Доставлен 15 апр 14:20', + }, + { + value: '2', + header: 'Стоимость отправления', + icon: 'ti ti-receipt', + content: 'Итого: 525 ₽ · Оплачено: карта *4321', + }, + ]; } + `, + }, + }, + }, +}; From 5bf25e52776b1c1f9c73620c75fc5819e34e4a96 Mon Sep 17 00:00:00 2001 From: "ak.dmitriev" Date: Sat, 30 May 2026 19:00:27 +0700 Subject: [PATCH 28/28] DS-497 --- .../confirm-dialog.component.ts | 32 +-- .../confirm-dialog/confirm-dialog.service.ts | 10 +- .../components/confirm-dialog/ng-package.json | 7 + .../components/confirm-dialog/public_api.ts | 2 + src/lib/providers/prime-preset/map-tokens.ts | 41 ++-- src/prime-preset/map-tokens.ts | 41 ---- .../confirm-dialog/confirm-dialog.stories.ts | 220 +++--------------- .../confirm-dialog-default.component.ts | 57 ++++- .../confirm-dialog-severities.component.ts | 101 +++++++- .../confirm-dialog-sizes.component.ts | 92 ++++++-- 10 files changed, 301 insertions(+), 302 deletions(-) create mode 100644 src/lib/components/confirm-dialog/ng-package.json create mode 100644 src/lib/components/confirm-dialog/public_api.ts delete mode 100644 src/prime-preset/map-tokens.ts diff --git a/src/lib/components/confirm-dialog/confirm-dialog.component.ts b/src/lib/components/confirm-dialog/confirm-dialog.component.ts index 033da08a..5f114bf0 100644 --- a/src/lib/components/confirm-dialog/confirm-dialog.component.ts +++ b/src/lib/components/confirm-dialog/confirm-dialog.component.ts @@ -2,16 +2,16 @@ import { Component, Input, TemplateRef } from '@angular/core'; import { NgTemplateOutlet } from '@angular/common'; import { ConfirmDialog } from 'primeng/confirmdialog'; import { PrimeTemplate } from 'primeng/api'; -import { ButtonComponent } from '../button/button.component'; +import { ExtraButtonComponent } from '../button/button.component'; -export type ConfirmDialogSize = 'sm' | 'default' | 'lg' | 'xlg'; -export type ConfirmDialogSeverity = 'success' | 'info' | 'warn' | 'help' | 'danger' | 'default'; +export type ExtraConfirmDialogSize = 'sm' | 'default' | 'lg' | 'xlg'; +export type ExtraConfirmDialogSeverity = 'success' | 'info' | 'warn' | 'help' | 'danger' | 'default'; @Component({ - selector: 'confirm-dialog', + selector: 'extra-confirm-dialog', host: { style: 'display: contents' }, standalone: true, - imports: [ConfirmDialog, ButtonComponent, PrimeTemplate, NgTemplateOutlet], + imports: [ConfirmDialog, ExtraButtonComponent, PrimeTemplate, NgTemplateOutlet], template: ` @@ -25,14 +25,14 @@ export type ConfirmDialogSeverity = 'success' | 'info' | 'warn' | 'help' | 'dang {{ message.header }}
- + > }
@@ -44,26 +44,26 @@ export type ConfirmDialogSeverity = 'success' | 'info' | 'warn' | 'help' | 'dang } @else { } `, }) -export class ConfirmDialogComponent { +export class ExtraConfirmDialogComponent { @Input() key = ''; - @Input() size: ConfirmDialogSize = 'default'; - @Input() severity: ConfirmDialogSeverity = 'default'; + @Input() size: ExtraConfirmDialogSize = 'default'; + @Input() severity: ExtraConfirmDialogSeverity = 'default'; @Input() headerTemplate: TemplateRef | null = null; @Input() footerTemplate: TemplateRef | null = null; @@ -73,7 +73,7 @@ export class ConfirmDialogComponent { else if (this.size === 'lg') classes.push('p-confirmdialog-lg'); else if (this.size === 'xlg') classes.push('p-confirmdialog-xlg'); - const severityMap: Record = { + const severityMap: Record = { success: 'p-confirm-dialog-accept', info: 'p-confirm-dialog-info', warn: 'p-confirm-dialog-warn', diff --git a/src/lib/components/confirm-dialog/confirm-dialog.service.ts b/src/lib/components/confirm-dialog/confirm-dialog.service.ts index b4245602..e5a99f56 100644 --- a/src/lib/components/confirm-dialog/confirm-dialog.service.ts +++ b/src/lib/components/confirm-dialog/confirm-dialog.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, Provider } from '@angular/core'; import { Confirmation, ConfirmationService } from 'primeng/api'; export type ConfirmDialogOptions = Pick< @@ -6,12 +6,12 @@ export type ConfirmDialogOptions = Pick< 'key' | 'message' | 'header' | 'icon' | 'acceptLabel' | 'rejectLabel' | 'accept' | 'reject' | 'acceptButtonProps' >; +export function provideConfirmDialog(): Provider[] { + return [ConfirmationService, ConfirmDialogService]; +} + @Injectable() export class ConfirmDialogService { - static providers() { - return [ConfirmationService, ConfirmDialogService]; - } - constructor(private readonly confirmationService: ConfirmationService) {} confirm(options: ConfirmDialogOptions): void { diff --git a/src/lib/components/confirm-dialog/ng-package.json b/src/lib/components/confirm-dialog/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/confirm-dialog/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/confirm-dialog/public_api.ts b/src/lib/components/confirm-dialog/public_api.ts new file mode 100644 index 00000000..cdf98805 --- /dev/null +++ b/src/lib/components/confirm-dialog/public_api.ts @@ -0,0 +1,2 @@ +export * from './confirm-dialog.component'; +export * from './confirm-dialog.service'; diff --git a/src/lib/providers/prime-preset/map-tokens.ts b/src/lib/providers/prime-preset/map-tokens.ts index ed585ea3..d5947c45 100644 --- a/src/lib/providers/prime-preset/map-tokens.ts +++ b/src/lib/providers/prime-preset/map-tokens.ts @@ -23,6 +23,7 @@ import { messageCss } from './tokens/components/message'; import { inputotpCss } from './tokens/components/inputotp'; import { carouselCss } from './tokens/components/carousel'; import { galleriaCss } from './tokens/components/galleria'; +import { confirmDialogCss } from './tokens/components/confirm-dialog'; const presetTokens: Preset = { primitive: tokens.primitive as unknown as AuraBaseDesignTokens['primitive'], @@ -31,62 +32,62 @@ const presetTokens: Preset = { ...(tokens.components as unknown as ComponentsDesignTokens), avatar: { ...(tokens.components.avatar as unknown as ComponentsDesignTokens['avatar']), - css: avatarCss, + css: avatarCss }, card: { ...(tokens.components.card as unknown as ComponentsDesignTokens['card']), - css: cardCss, + css: cardCss }, checkbox: { ...(tokens.components.checkbox as unknown as ComponentsDesignTokens['checkbox']), - css: checkboxCss, + css: checkboxCss }, button: { ...(tokens.components.button as unknown as ComponentsDesignTokens['button']), - css: buttonCss, + css: buttonCss }, message: { ...(tokens.components.message as unknown as ComponentsDesignTokens['message']), - css: messageCss, + css: messageCss }, progressspinner: { ...(tokens.components.progressspinner as unknown as ComponentsDesignTokens['progressspinner']), - css: progressspinnerCss, + css: progressspinnerCss }, inputotp: { ...(tokens.components.inputotp as unknown as ComponentsDesignTokens['inputotp']), - css: inputotpCss, + css: inputotpCss }, inputtext: { ...(tokens.components.inputtext as unknown as ComponentsDesignTokens['inputtext']), - css: inputtextCss, + css: inputtextCss }, inputmask: { - css: inputmaskCss, + css: inputmaskCss }, inputgroup: { ...(tokens.components.inputgroup as unknown as ComponentsDesignTokens['inputgroup']), - css: inputgroupCss, + css: inputgroupCss }, tag: { ...(tokens.components.tag as unknown as ComponentsDesignTokens['tag']), - css: tagCss, + css: tagCss }, textarea: { ...(tokens.components.textarea as unknown as ComponentsDesignTokens['textarea']), - css: textareaCss, + css: textareaCss }, tooltip: { ...(tokens.components.tooltip as unknown as ComponentsDesignTokens['tooltip']), - css: tooltipCss, + css: tooltipCss }, megamenu: { ...(tokens.components.megamenu as unknown as ComponentsDesignTokens['megamenu']), - css: megamenuCss, + css: megamenuCss }, select: { ...(tokens.components.select as unknown as ComponentsDesignTokens['select']), - css: selectCss, + css: selectCss }, passwordCss: { ...(tokens.components.password as unknown as ComponentsDesignTokens['password']), @@ -94,13 +95,17 @@ const presetTokens: Preset = { }, carousel: { ...(tokens.components.carousel as unknown as ComponentsDesignTokens['carousel']), - css: carouselCss, + css: carouselCss }, galleria: { ...(tokens.components.galleria as unknown as ComponentsDesignTokens['galleria']), - css: galleriaCss, + css: galleriaCss }, - } as ComponentsDesignTokens, + confirmdialog: { + ...(tokens.components.confirmdialog as unknown as ComponentsDesignTokens['confirmdialog']), + css: confirmDialogCss + } + } as ComponentsDesignTokens }; export default presetTokens; diff --git a/src/prime-preset/map-tokens.ts b/src/prime-preset/map-tokens.ts deleted file mode 100644 index afbad16c..00000000 --- a/src/prime-preset/map-tokens.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Preset } from '@primeuix/themes/types'; -import type { ComponentsDesignTokens } from '@primeuix/themes/types'; -import type { AuraBaseDesignTokens } from '@primeuix/themes/aura/base'; - -import tokens from './tokens/tokens.json'; -import { buttonCss } from './tokens/components/button'; -import { dataTableCss } from './tokens/components/data-table'; -import { checkboxCss } from './tokens/components/checkbox'; -import { confirmDialogCss } from './tokens/components/confirm-dialog'; -import { dialogCss } from './tokens/components/dialog'; -import { tagCss } from './tokens/components/tag'; -import { tooltipCss } from './tokens/components/tooltip'; - -const presetTokens: Preset = { - primitive: tokens.primitive as unknown as AuraBaseDesignTokens['primitive'], - semantic: tokens.semantic as unknown as AuraBaseDesignTokens['semantic'], - components: { - ...(tokens.components as unknown as ComponentsDesignTokens), - button: { - ...(tokens.components.button as unknown as ComponentsDesignTokens['button']), - css: buttonCss, - }, - confirmdialog: { - ...(tokens.components.confirmdialog as unknown as ComponentsDesignTokens['confirmdialog']), - css: confirmDialogCss, - }, - dialog: { - ...(tokens.components.dialog as unknown as ComponentsDesignTokens['dialog']), - css: dialogCss, - }, - tag: { - ...(tokens.components.tag as unknown as ComponentsDesignTokens['tag']), - css: tagCss, - datatable: { - ...(tokens.components.datatable as unknown as ComponentsDesignTokens['datatable']), - css: dataTableCss, - }, - } as ComponentsDesignTokens, -}; - -export default presetTokens; diff --git a/src/stories/components/confirm-dialog/confirm-dialog.stories.ts b/src/stories/components/confirm-dialog/confirm-dialog.stories.ts index b35988f1..f967afba 100644 --- a/src/stories/components/confirm-dialog/confirm-dialog.stories.ts +++ b/src/stories/components/confirm-dialog/confirm-dialog.stories.ts @@ -1,24 +1,35 @@ import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; -import { ConfirmDialogComponent } from '../../../lib/components/confirm-dialog/confirm-dialog.component'; -import { ConfirmDialogDefaultComponent } from './examples/confirm-dialog-default.component'; -import { ConfirmDialogSeveritiesComponent } from './examples/confirm-dialog-severities.component'; -import { ConfirmDialogSizesComponent } from './examples/confirm-dialog-sizes.component'; +import { ExtraConfirmDialogComponent as ConfirmDialogComponent } from '../../../lib/components/confirm-dialog/confirm-dialog.component'; +import { ConfirmDialogDefaultComponent, Default as DefaultStory } from './examples/confirm-dialog-default.component'; +import { ConfirmDialogSeveritiesComponent, Severities } from './examples/confirm-dialog-severities.component'; +import { ConfirmDialogSizesComponent, Sizes as SizesStory } from './examples/confirm-dialog-sizes.component'; +import { provideConfirmDialog } from '../../../lib/components/confirm-dialog/confirm-dialog.service'; const meta: Meta = { title: 'Components/Overlay/ConfirmDialog', component: ConfirmDialogComponent, tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ConfirmDialogDefaultComponent, + ConfirmDialogSeveritiesComponent, + ConfirmDialogSizesComponent, + ], + providers: [provideConfirmDialog()], + }), + ], parameters: { docs: { description: { component: `Компонент для подтверждения действий пользователя. \`\`\`typescript -import { ConfirmDialogComponent, ConfirmDialogService } from '@cdek-it/angular-ui-kit'; +import { ExtraConfirmDialogComponent, ConfirmDialogService, provideConfirmDialog } from '@cdek-it/angular-ui-kit'; \`\`\``, }, }, - designTokens: { prefix: '--p-confirmdialog' }, + designTokens: { prefix: '--p-confirmdialog' } }, argTypes: { key: { @@ -26,8 +37,8 @@ import { ConfirmDialogComponent, ConfirmDialogService } from '@cdek-it/angular-u description: 'Идентификатор группы для адресной отправки сообщений.', table: { category: 'Props', - type: { summary: 'string' }, - }, + type: { summary: 'string' } + } }, size: { control: 'select', @@ -36,8 +47,8 @@ import { ConfirmDialogComponent, ConfirmDialogService } from '@cdek-it/angular-u table: { category: 'Props', defaultValue: { summary: 'default' }, - type: { summary: "'sm' | 'default' | 'lg' | 'xlg'" }, - }, + type: { summary: "'sm' | 'default' | 'lg' | 'xlg'" } + } }, severity: { control: 'select', @@ -46,10 +57,10 @@ import { ConfirmDialogComponent, ConfirmDialogService } from '@cdek-it/angular-u table: { category: 'Props', defaultValue: { summary: 'default' }, - type: { summary: "'default' | 'success' | 'info' | 'warn' | 'help' | 'danger'" }, - }, - }, - }, + type: { summary: "'default' | 'success' | 'info' | 'warn' | 'help' | 'danger'" } + } + } + } }; export default meta; @@ -57,187 +68,12 @@ type Story = StoryObj; // ── Default ─────────────────────────────────────────────────────────────────── -export const Default: Story = { - name: 'ConfirmDialog', - decorators: [moduleMetadata({ imports: [ConfirmDialogDefaultComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Базовый пример диалога подтверждения.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ConfirmDialogComponent, ConfirmDialogService, ButtonComponent } from '@cdek-it/angular-ui-kit'; - -@Component({ - selector: 'app-confirm-dialog-default', - standalone: true, - imports: [ConfirmDialogComponent, ButtonComponent], - providers: [ConfirmDialogService.providers()], - template: \` - - - \`, -}) -export class ConfirmDialogDefaultComponent { - constructor(private confirmDialogService: ConfirmDialogService) {} - - showConfirm(): void { - this.confirmDialogService.confirm({ - key: 'cd-default', - message: 'Вы уверены, что хотите продолжить?', - header: 'Подтверждение', - icon: 'ti ti-alert-triangle', - acceptLabel: 'Да', - rejectLabel: 'Нет', - accept: () => {}, - reject: () => {}, - }); - } -} - `, - }, - }, - }, -}; +export const ConfirmDialog: Story = DefaultStory; // ── Severities ──────────────────────────────────────────────────────────────── -export const Severities: Story = { - name: 'Severities', - decorators: [moduleMetadata({ imports: [ConfirmDialogSeveritiesComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Варианты диалога с различными уровнями важности: success, info, warn, help, danger.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ConfirmDialogComponent, ConfirmDialogService, ButtonComponent } from '@cdek-it/angular-ui-kit'; - -const SEVERITIES = [ - { type: 'success', buttonSeverity: 'success', icon: 'ti ti-circle-check', label: 'Успех', header: 'Успех', message: 'Операция выполнена успешно.', acceptLabel: 'OK' }, - { type: 'info', buttonSeverity: 'info', icon: 'ti ti-info-circle', label: 'Информация', header: 'Информация', message: 'Это информационное сообщение.', acceptLabel: 'Понятно' }, - { type: 'warn', buttonSeverity: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение', header: 'Предупреждение', message: 'Внимание! Это действие может иметь последствия.', acceptLabel: 'Продолжить' }, - { type: 'help', buttonSeverity: 'help', icon: 'ti ti-help-circle', label: 'Справка', header: 'Справка', message: 'Нужна помощь с этим действием?', acceptLabel: 'Да' }, - { type: 'danger', buttonSeverity: 'danger', icon: 'ti ti-circle-x', label: 'Удаление', header: 'Подтверждение', message: 'Это действие нельзя отменить. Продолжить?', acceptLabel: 'Удалить' }, -]; - -@Component({ - selector: 'app-confirm-dialog-severities', - standalone: true, - imports: [ConfirmDialogComponent, ButtonComponent], - providers: [ConfirmDialogService.providers()], - template: \` - - - - - - -
- @for (severity of severities; track severity.type) { - - } -
- \`, -}) -export class ConfirmDialogSeveritiesComponent { - severities = SEVERITIES; - constructor(private confirmDialogService: ConfirmDialogService) {} - - showConfirm(severity: any): void { - this.confirmDialogService.confirm({ - key: 'cd-severity-' + severity.type, - message: severity.message, - header: severity.header, - icon: severity.icon, - acceptLabel: severity.acceptLabel, - rejectLabel: 'Нет', - acceptButtonProps: { severity: severity.buttonSeverity }, - accept: () => {}, - reject: () => {}, - }); - } -} - `, - }, - }, - }, -}; +export const SeveritiesStory: Story = Severities; // ── Sizes ───────────────────────────────────────────────────────────────────── -export const Sizes: Story = { - name: 'Sizes', - decorators: [moduleMetadata({ imports: [ConfirmDialogSizesComponent] })], - render: () => ({ template: `` }), - parameters: { - docs: { - description: { - story: 'Доступные размеры диалога: sm, base, lg, xlg.', - }, - source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { ConfirmDialogComponent, ConfirmDialogService, ButtonComponent } from '@cdek-it/angular-ui-kit'; - -const SIZES = [ - { key: 'sm', label: 'Small' }, - { key: 'default', label: 'Base' }, - { key: 'lg', label: 'Large' }, - { key: 'xlg', label: 'Extra Large' }, -]; - -@Component({ - selector: 'app-confirm-dialog-sizes', - standalone: true, - imports: [ConfirmDialogComponent, ButtonComponent], - providers: [ConfirmDialogService.providers()], - template: \` - - - - - -
- @for (size of sizes; track size.key) { - - } -
- \`, -}) -export class ConfirmDialogSizesComponent { - sizes = SIZES; - constructor(private confirmDialogService: ConfirmDialogService) {} - - showConfirm(size: any): void { - this.confirmDialogService.confirm({ - key: 'cd-size-' + size.key, - message: 'Это диалог размера ' + size.label, - header: 'Подтверждение', - icon: 'ti ti-alert-triangle', - acceptLabel: 'Да', - rejectLabel: 'Нет', - accept: () => {}, - reject: () => {}, - }); - } -} - `, - }, - }, - }, -}; +export const Sizes: Story = SizesStory; diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts index 8bda09d6..44db24ee 100644 --- a/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts @@ -1,21 +1,20 @@ import { Component } from '@angular/core'; -import { ButtonComponent } from '../../../../lib/components/button/button.component'; -import { ConfirmDialogComponent } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraConfirmDialogComponent } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; import { ConfirmDialogService } from '../../../../lib/components/confirm-dialog/confirm-dialog.service'; const template = `
- + - +
`; @Component({ selector: 'app-confirm-dialog-default', standalone: true, - imports: [ConfirmDialogComponent, ButtonComponent], - providers: [ConfirmDialogService.providers()], + imports: [ExtraConfirmDialogComponent, ExtraButtonComponent], template, }) export class ConfirmDialogDefaultComponent { @@ -34,3 +33,49 @@ export class ConfirmDialogDefaultComponent { }); } } + +export const Default = { + name: 'ConfirmDialog', + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Базовый пример диалога подтверждения.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraConfirmDialogComponent, ConfirmDialogService, ExtraButtonComponent, provideConfirmDialog } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-confirm-dialog-default', + standalone: true, + imports: [ExtraConfirmDialogComponent, ExtraButtonComponent], + providers: [provideConfirmDialog()], + template: \` + + + \`, +}) +export class ConfirmDialogDefaultComponent { + constructor(private confirmDialogService: ConfirmDialogService) {} + + showConfirm(): void { + this.confirmDialogService.confirm({ + key: 'cd-default', + message: 'Вы уверены, что хотите продолжить?', + header: 'Подтверждение', + icon: 'ti ti-alert-triangle', + acceptLabel: 'Да', + rejectLabel: 'Нет', + accept: () => {}, + reject: () => {}, + }); + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts index 95144b13..00a0920a 100644 --- a/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { ButtonComponent } from '../../../../lib/components/button/button.component'; -import { ConfirmDialogComponent } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraConfirmDialogComponent } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; import { ConfirmDialogService } from '../../../../lib/components/confirm-dialog/confirm-dialog.service'; interface SeverityItem { @@ -63,20 +63,20 @@ const SEVERITIES: SeverityItem[] = [ const template = `
- - - - - + + + + +
@for (severity of severities; track severity.type) { - + > }
@@ -85,8 +85,7 @@ const template = ` @Component({ selector: 'app-confirm-dialog-severities', standalone: true, - imports: [ConfirmDialogComponent, ButtonComponent], - providers: [ConfirmDialogService.providers()], + imports: [ExtraConfirmDialogComponent, ExtraButtonComponent], template, }) export class ConfirmDialogSeveritiesComponent { @@ -108,3 +107,83 @@ export class ConfirmDialogSeveritiesComponent { }); } } + +export const Severities = { + name: 'Severities', + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Варианты диалога с различными уровнями важности: success, info, warn, help, danger.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraConfirmDialogComponent, ConfirmDialogService, ExtraButtonComponent, provideConfirmDialog } from '@cdek-it/angular-ui-kit'; + +interface SeverityItem { + type: 'success' | 'info' | 'warn' | 'help' | 'danger'; + buttonSeverity: 'success' | 'info' | 'warn' | 'help' | 'danger'; + icon: string; + label: string; + header: string; + message: string; + acceptLabel: string; +} + +const SEVERITIES: SeverityItem[] = [ + { type: 'success', buttonSeverity: 'success', icon: 'ti ti-circle-check', label: 'Успех', header: 'Успех', message: 'Операция выполнена успешно.', acceptLabel: 'OK' }, + { type: 'info', buttonSeverity: 'info', icon: 'ti ti-info-circle', label: 'Информация', header: 'Информация', message: 'Это информационное сообщение.', acceptLabel: 'Понятно' }, + { type: 'warn', buttonSeverity: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение', header: 'Предупреждение', message: 'Внимание! Это действие может иметь последствия.', acceptLabel: 'Продолжить' }, + { type: 'help', buttonSeverity: 'help', icon: 'ti ti-help-circle', label: 'Справка', header: 'Справка', message: 'Нужна помощь с этим действием?', acceptLabel: 'Да' }, + { type: 'danger', buttonSeverity: 'danger', icon: 'ti ti-circle-x', label: 'Удаление', header: 'Подтверждение', message: 'Это действие нельзя отменить. Продолжить?', acceptLabel: 'Удалить' }, +]; + +@Component({ + selector: 'app-confirm-dialog-severities', + standalone: true, + imports: [ExtraConfirmDialogComponent, ExtraButtonComponent], + providers: [provideConfirmDialog()], + template: \` + + + + + + +
+ @for (severity of severities; track severity.type) { + + } +
+ \`, +}) +export class ConfirmDialogSeveritiesComponent { + severities = SEVERITIES; + constructor(private confirmDialogService: ConfirmDialogService) {} + + showConfirm(severity: SeverityItem): void { + this.confirmDialogService.confirm({ + key: 'cd-severity-' + severity.type, + message: severity.message, + header: severity.header, + icon: severity.icon, + acceptLabel: severity.acceptLabel, + rejectLabel: 'Нет', + acceptButtonProps: { severity: severity.buttonSeverity }, + accept: () => {}, + reject: () => {}, + }); + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts b/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts index 717cba6d..5fc3945d 100644 --- a/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts @@ -1,13 +1,13 @@ import { Component } from '@angular/core'; -import { ButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; import { - ConfirmDialogComponent, - ConfirmDialogSize, + ExtraConfirmDialogComponent, + ExtraConfirmDialogSize, } from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; import { ConfirmDialogService } from '../../../../lib/components/confirm-dialog/confirm-dialog.service'; interface SizeItem { - key: ConfirmDialogSize; + key: ExtraConfirmDialogSize; label: string; } @@ -20,18 +20,18 @@ const SIZES: SizeItem[] = [ const template = `
- - - - + + + +
@for (size of sizes; track size.key) { - + > }
@@ -40,8 +40,7 @@ const template = ` @Component({ selector: 'app-confirm-dialog-sizes', standalone: true, - imports: [ConfirmDialogComponent, ButtonComponent], - providers: [ConfirmDialogService.providers()], + imports: [ExtraConfirmDialogComponent, ExtraButtonComponent], template, }) export class ConfirmDialogSizesComponent { @@ -62,3 +61,70 @@ export class ConfirmDialogSizesComponent { }); } } + +export const Sizes = { + name: 'Sizes', + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Доступные размеры диалога: sm, base, lg, xlg.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraConfirmDialogComponent, ExtraConfirmDialogSize, ConfirmDialogService, ExtraButtonComponent, provideConfirmDialog } from '@cdek-it/angular-ui-kit'; + +interface SizeItem { + key: ExtraConfirmDialogSize; + label: string; +} + +const SIZES: SizeItem[] = [ + { key: 'sm', label: 'Small' }, + { key: 'default', label: 'Base' }, + { key: 'lg', label: 'Large' }, + { key: 'xlg', label: 'Extra Large' }, +]; + +@Component({ + selector: 'app-confirm-dialog-sizes', + standalone: true, + imports: [ExtraConfirmDialogComponent, ExtraButtonComponent], + providers: [provideConfirmDialog()], + template: \` + + + + + +
+ @for (size of sizes; track size.key) { + + } +
+ \`, +}) +export class ConfirmDialogSizesComponent { + sizes = SIZES; + constructor(private confirmDialogService: ConfirmDialogService) {} + + showConfirm(size: SizeItem): void { + this.confirmDialogService.confirm({ + key: 'cd-size-' + size.key, + message: 'Это диалог размера ' + size.label, + header: 'Подтверждение', + icon: 'ti ti-alert-triangle', + acceptLabel: 'Да', + rejectLabel: 'Нет', + accept: () => {}, + reject: () => {}, + }); + } +} + `, + }, + }, + }, +};