diff --git a/.gitignore b/.gitignore index 863dd85..e535eb8 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,6 @@ src/assets/components/themes /debug-storybook.log /documentation.json -.claude/* - .playwright-mcp/* + +.claude/* diff --git a/src/lib/components/accordion/accordion.component.ts b/src/lib/components/accordion/accordion.component.ts new file mode 100644 index 0000000..7b34d2c --- /dev/null +++ b/src/lib/components/accordion/accordion.component.ts @@ -0,0 +1,40 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Accordion, AccordionPanel, AccordionHeader, AccordionContent } from 'primeng/accordion'; + +export interface ExtraAccordionItem { + value: string; + header: string; + content: string; + icon?: string; + disabled?: boolean; +} + +@Component({ + selector: 'extra-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 ExtraAccordionComponent { + @Input() items: ExtraAccordionItem[] = []; + @Input() multiple = false; + @Input() activeValue: string | null = '0'; + @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 0000000..3a74fd7 --- /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 0000000..b1a99b0 --- /dev/null +++ b/src/lib/components/accordion/public_api.ts @@ -0,0 +1 @@ +export * from './accordion.component'; 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 0000000..5f114bf --- /dev/null +++ b/src/lib/components/confirm-dialog/confirm-dialog.component.ts @@ -0,0 +1,88 @@ +import { Component, Input, TemplateRef } from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; +import { ConfirmDialog } from 'primeng/confirmdialog'; +import { PrimeTemplate } from 'primeng/api'; +import { ExtraButtonComponent } from '../button/button.component'; + +export type ExtraConfirmDialogSize = 'sm' | 'default' | 'lg' | 'xlg'; +export type ExtraConfirmDialogSeverity = 'success' | 'info' | 'warn' | 'help' | 'danger' | 'default'; + +@Component({ + selector: 'extra-confirm-dialog', + host: { style: 'display: contents' }, + standalone: true, + imports: [ConfirmDialog, ExtraButtonComponent, PrimeTemplate, NgTemplateOutlet], + template: ` + + + @if (headerTemplate) { + + + } @else { +
+
+ + {{ message.header }} +
+ +
+ } +
+

{{ message.message }}

+
+ @if (footerTemplate) { + + + } @else { + + } +
+
+ `, +}) +export class ExtraConfirmDialogComponent { + @Input() key = ''; + @Input() size: ExtraConfirmDialogSize = 'default'; + @Input() severity: ExtraConfirmDialogSeverity = 'default'; + @Input() headerTemplate: TemplateRef | null = null; + @Input() footerTemplate: TemplateRef | null = null; + + 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/lib/components/confirm-dialog/confirm-dialog.service.ts b/src/lib/components/confirm-dialog/confirm-dialog.service.ts new file mode 100644 index 0000000..e5a99f5 --- /dev/null +++ b/src/lib/components/confirm-dialog/confirm-dialog.service.ts @@ -0,0 +1,20 @@ +import { Injectable, Provider } from '@angular/core'; +import { Confirmation, ConfirmationService } from 'primeng/api'; + +export type ConfirmDialogOptions = Pick< + Confirmation, + 'key' | 'message' | 'header' | 'icon' | 'acceptLabel' | 'rejectLabel' | 'accept' | 'reject' | 'acceptButtonProps' +>; + +export function provideConfirmDialog(): Provider[] { + return [ConfirmationService, ConfirmDialogService]; +} + +@Injectable() +export class ConfirmDialogService { + constructor(private readonly confirmationService: ConfirmationService) {} + + confirm(options: ConfirmDialogOptions): void { + this.confirmationService.confirm(options); + } +} 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 0000000..ecdf8fe --- /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 0000000..cdf9880 --- /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/components/stepper/ng-package.json b/src/lib/components/stepper/ng-package.json new file mode 100644 index 0000000..ecdf8fe --- /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 0000000..1054ead --- /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 new file mode 100644 index 0000000..ed4ae50 --- /dev/null +++ b/src/lib/components/stepper/stepper.component.ts @@ -0,0 +1,114 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { NgClass } from '@angular/common'; +import { Stepper } from 'primeng/stepper'; +import { StepList } from 'primeng/stepper'; +import { Step } from 'primeng/stepper'; +import { StepPanels } from 'primeng/stepper'; +import { StepPanel } from 'primeng/stepper'; +import { StepItem } from 'primeng/stepper'; +import { ExtraButtonComponent } from '../button/button.component'; + +export interface ExtraStepperItem { + value: number | undefined; + label: string; + caption?: string; + content?: string; + disabled?: boolean; + invalid?: boolean; +} + +@Component({ + selector: 'extra-stepper', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [Stepper, StepList, Step, StepPanels, StepPanel, StepItem, ExtraButtonComponent, NgClass], + template: ` + + @if (orientation === 'horizontal') { + + @for (step of steps; track step.value) { + + {{ step.label }} + @if (step.caption) { +
{{ step.caption }}
+ } +
+ } +
+ } + @if (orientation === 'horizontal' && showPanels) { + + @for (step of steps; track step.value; let i = $index; let first = $first; let last = $last) { + + +

{{ step.content }}

+
+ @if (!first) { + + } + @if (!last) { + + } +
+
+
+ } +
+ } + + @if (orientation === 'vertical') { + @for (step of steps; track step.value; let i = $index; let first = $first; let last = $last) { + + + {{ step.label }} + @if (step.caption) { +
{{ step.caption }}
+ } +
+ @if (showPanels) { + + +

{{ step.content }}

+
+ @if (!first) { + + } + @if (!last) { + + } +
+
+
+ } +
+ } + } +
+ `, +}) +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(); + + onValueChange(newValue: number | undefined): void { + this.value = newValue; + this.valueChange.emit(newValue); + } +} diff --git a/src/lib/providers/prime-preset/map-tokens.ts b/src/lib/providers/prime-preset/map-tokens.ts index ed585ea..d5947c4 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/lib/providers/prime-preset/tokens/components/accordion.ts b/src/lib/providers/prime-preset/tokens/components/accordion.ts new file mode 100644 index 0000000..b9b38aa --- /dev/null +++ b/src/lib/providers/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/lib/providers/prime-preset/tokens/components/confirm-dialog.ts b/src/lib/providers/prime-preset/tokens/components/confirm-dialog.ts new file mode 100644 index 0000000..bc9e3ff --- /dev/null +++ b/src/lib/providers/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 i { + 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 i, + .p-confirmdialog.p-confirm-dialog-accept .p-dialog-title i { + color: ${dt('confirmdialog.extend.extIcon.success')}; + } + + .p-confirmdialog[data-pc-severity="info"] .p-dialog-title i, + .p-confirmdialog.p-confirm-dialog-info .p-dialog-title i { + color: ${dt('confirmdialog.extend.extIcon.info')}; + } + + .p-confirmdialog[data-pc-severity="warn"] .p-dialog-title i, + .p-confirmdialog.p-confirm-dialog-warn .p-dialog-title i { + color: ${dt('confirmdialog.extend.extIcon.warn')}; + } + + .p-confirmdialog[data-pc-severity="help"] .p-dialog-title i, + .p-confirmdialog.p-confirm-dialog-help .p-dialog-title i { + color: ${dt('confirmdialog.extend.extIcon.help')}; + } + + .p-confirmdialog[data-pc-severity="danger"] .p-dialog-title i, + .p-confirmdialog[data-pc-severity="error"] .p-dialog-title i, + .p-confirmdialog.p-confirm-dialog-error .p-dialog-title i { + color: ${dt('confirmdialog.extend.extIcon.danger')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/data-table.ts b/src/lib/providers/prime-preset/tokens/components/data-table.ts new file mode 100644 index 0000000..d5d0960 --- /dev/null +++ b/src/lib/providers/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('text.hoverColor')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/dialog.ts b/src/lib/providers/prime-preset/tokens/components/dialog.ts index c157e61..7426012 100644 --- a/src/lib/providers/prime-preset/tokens/components/dialog.ts +++ b/src/lib/providers/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/lib/providers/prime-preset/tokens/components/stepper.ts b/src/lib/providers/prime-preset/tokens/components/stepper.ts new file mode 100644 index 0000000..694d477 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/stepper.ts @@ -0,0 +1,66 @@ +const stepperCss = ({ dt }: { dt: (token: string) => string }): string => ` + +/* Caption - gap */ +.p-stepper .p-step-title { + display: flex; + flex-direction: column; + gap: ${dt('stepper.extend.extCaption.gap')}; + line-height: ${dt('fonts.lineHeight.200')}; + align-items: flex-start; + text-align: left; +} + +/* Caption - secondary text */ +.p-stepper .p-step-title .caption-secondary { + display: flex; + justify-content: flex-start; + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: normal; + color: ${dt('text.mutedColor')}; +} + +/* Номер шага */ +.p-stepper .p-step-number { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.medium')}; + line-height: ${dt('fonts.lineHeight.150')}; +} + +/* Border для номера шага */ +.p-stepper .p-step-number.p-step-number { + border-width: ${dt('stepper.extend.extStepNumber.borderWidth')}; +} + +/* Invalid состояние для step-number - только когда активен */ +.p-stepper .step-invalid.p-step-active .p-step-number { + background: ${dt('stepper.extend.extStepNumber.invalidBackground')}; + color: ${dt('stepper.extend.extStepNumber.invalidColor')}; + border-color: ${dt('stepper.extend.extStepNumber.invalidBorderColor')}; +} + +/* Step-number с иконкой */ +.p-stepper .p-step-number:has(i) { + background: ${dt('colorScheme.light.transparent')}; + border-color: ${dt('colorScheme.light.transparent')}; +} + +/* Размер иконок в step-number */ +.p-stepper .p-step-number i { + font-size: ${dt('stepper.extend.extStepNumber.iconSize')}; +} + +/* Активное состояние - иконка в цвет фона */ +.p-stepper .p-step-active .p-step-number i { + color: ${dt('stepper.stepNumber.activeBackground')}; +} + +/* Прозрачный фон для панелей */ +.p-stepper .p-steppanel { + background: ${dt('colorScheme.light.transparent')}; +} +`; + +export { stepperCss }; diff --git a/src/lib/providers/prime-preset/tokens/tokens.json b/src/lib/providers/prime-preset/tokens/tokens.json index 5824c87..4667831 100644 --- a/src/lib/providers/prime-preset/tokens/tokens.json +++ b/src/lib/providers/prime-preset/tokens/tokens.json @@ -1379,7 +1379,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 0000000..073685c --- /dev/null +++ b/src/stories/components/accordion/accordion.stories.ts @@ -0,0 +1,116 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +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: 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', + }, +]; + +const meta: Meta = { + title: 'Components/Panel/Accordion', + component: ExtraAccordionComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraAccordionComponent, + 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 из ExtraAccordionItem)', + 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 для интерактивного изменения пропсов.', + }, + }, + }, +}; + +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 new file mode 100644 index 0000000..4e95d5c --- /dev/null +++ b/src/stories/components/accordion/examples/accordion-disabled.component.ts @@ -0,0 +1,89 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraAccordionComponent, ExtraAccordionItem } from '../../../../lib/components/accordion/accordion.component'; + +const template = ` +
+ +
+`; + +@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: 'Стоимость доставки: 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 new file mode 100644 index 0000000..e2b0259 --- /dev/null +++ b/src/stories/components/accordion/examples/accordion-multiple.component.ts @@ -0,0 +1,87 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraAccordionComponent, ExtraAccordionItem } from '../../../../lib/components/accordion/accordion.component'; + +const template = ` +
+ +
+`; + +@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 → Сортировочный центр 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', + }, + ]; +} + `, + }, + }, + }, +}; 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 0000000..f967afb --- /dev/null +++ b/src/stories/components/confirm-dialog/confirm-dialog.stories.ts @@ -0,0 +1,79 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +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 { ExtraConfirmDialogComponent, ConfirmDialogService, provideConfirmDialog } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + 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 ConfirmDialog: Story = DefaultStory; + +// ── Severities ──────────────────────────────────────────────────────────────── + +export const SeveritiesStory: Story = Severities; + +// ── Sizes ───────────────────────────────────────────────────────────────────── + +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 new file mode 100644 index 0000000..44db24e --- /dev/null +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-default.component.ts @@ -0,0 +1,81 @@ +import { Component } from '@angular/core'; +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: [ExtraConfirmDialogComponent, ExtraButtonComponent], + 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 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 new file mode 100644 index 0000000..00a0920 --- /dev/null +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-severities.component.ts @@ -0,0 +1,189 @@ +import { Component } from '@angular/core'; +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 { + 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: [ExtraConfirmDialogComponent, ExtraButtonComponent], + template, +}) +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: () => {}, + }); + } +} + +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 new file mode 100644 index 0000000..5fc3945 --- /dev/null +++ b/src/stories/components/confirm-dialog/examples/confirm-dialog-sizes.component.ts @@ -0,0 +1,130 @@ +import { Component } from '@angular/core'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { + ExtraConfirmDialogComponent, + ExtraConfirmDialogSize, +} from '../../../../lib/components/confirm-dialog/confirm-dialog.component'; +import { ConfirmDialogService } from '../../../../lib/components/confirm-dialog/confirm-dialog.service'; + +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' }, +]; + +const template = ` +
+ + + + + +
+ @for (size of sizes; track size.key) { + + } +
+
+`; + +@Component({ + selector: 'app-confirm-dialog-sizes', + standalone: true, + imports: [ExtraConfirmDialogComponent, ExtraButtonComponent], + template, +}) +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: () => {}, + }); + } +} + +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: () => {}, + }); + } +} + `, + }, + }, + }, +}; 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 new file mode 100644 index 0000000..f3920a9 --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-custom-body.component.ts @@ -0,0 +1,134 @@ +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'; + +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'; + } + } +} + +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 new file mode 100644 index 0000000..f50fd9a --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-default.component.ts @@ -0,0 +1,106 @@ +import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; +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; +} + +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 new file mode 100644 index 0000000..4eb8316 --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-grid-lines.component.ts @@ -0,0 +1,100 @@ +import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; +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; +} + +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 new file mode 100644 index 0000000..62c6905 --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-lazy.component.ts @@ -0,0 +1,182 @@ +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) => ({ + 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 {} + + 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); + } +} + +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 new file mode 100644 index 0000000..3c4446c --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-pagination.component.ts @@ -0,0 +1,106 @@ +import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; +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; +} + +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 new file mode 100644 index 0000000..9e3cb8c --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-scroll-horizontal.component.ts @@ -0,0 +1,132 @@ +import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; +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; +} + +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 new file mode 100644 index 0000000..ea9a6cc --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-scroll-vertical.component.ts @@ -0,0 +1,102 @@ +import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; +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]; +} + +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 new file mode 100644 index 0000000..58b0f54 --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-selectable.component.ts @@ -0,0 +1,102 @@ +import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; +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; +} + +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 new file mode 100644 index 0000000..76ad388 --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-selection-checkbox.component.ts @@ -0,0 +1,110 @@ +import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; +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[] = []; +} + +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 new file mode 100644 index 0000000..f955a99 --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-selection-radio.component.ts @@ -0,0 +1,106 @@ +import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; +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; +} + +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 new file mode 100644 index 0000000..52ef46c --- /dev/null +++ b/src/stories/components/p-data-table/examples/primeng-table-striped-rows.component.ts @@ -0,0 +1,106 @@ +import { Component } from '@angular/core'; +import { StoryObj, moduleMetadata } from '@storybook/angular'; +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; +} + +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 new file mode 100644 index 0000000..a711e0d --- /dev/null +++ b/src/stories/components/p-data-table/primeng-data-table.stories.ts @@ -0,0 +1,63 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +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: { + component: `Таблица данных на базе PrimeNG pTable с поддержкой сортировки, пагинации, выбора строк и прокрутки. + +\`\`\`typescript +import { TableModule } from 'primeng/table'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-datatable' }, + }, +}; + +export default meta; +type Story = StoryObj; + +export { + Default, + StripedRows, + Selectable, + GridLines, + Pagination, + SelectionRadio, + SelectionCheckbox, + ScrollVertical, + ScrollHorizontal, + CustomBody, + LazyLoading, +}; diff --git a/src/stories/components/stepper/examples/stepper-error.component.ts b/src/stories/components/stepper/examples/stepper-error.component.ts new file mode 100644 index 0000000..d2080d4 --- /dev/null +++ b/src/stories/components/stepper/examples/stepper-error.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraStepperComponent, ExtraStepperItem } from '../../../../lib/components/stepper/stepper.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-stepper-error', + standalone: true, + imports: [ExtraStepperComponent], + template, + styles, +}) +export class StepperErrorComponent { + 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 }, + ]; +} + +export const Error: StoryObj = { + name: 'Error (Invalid step)', + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Шаг с ошибкой — визуально выделяется красным при активном состоянии.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-stepper-error', + standalone: true, + 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 }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/stepper/examples/stepper-linear.component.ts b/src/stories/components/stepper/examples/stepper-linear.component.ts new file mode 100644 index 0000000..245d6d9 --- /dev/null +++ b/src/stories/components/stepper/examples/stepper-linear.component.ts @@ -0,0 +1,60 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraStepperComponent, ExtraStepperItem } from '../../../../lib/components/stepper/stepper.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-stepper-linear', + standalone: true, + imports: [ExtraStepperComponent], + template, + styles, +}) +export class StepperLinearComponent { + 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' }, + ]; +} + +export const Linear: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Линейный режим — переход к следующему шагу только после завершения текущего.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-stepper-linear', + standalone: true, + 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' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/stepper/examples/stepper-steps-only.component.ts b/src/stories/components/stepper/examples/stepper-steps-only.component.ts new file mode 100644 index 0000000..2a7288d --- /dev/null +++ b/src/stories/components/stepper/examples/stepper-steps-only.component.ts @@ -0,0 +1,60 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraStepperComponent, ExtraStepperItem } from '../../../../lib/components/stepper/stepper.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-stepper-steps-only', + standalone: true, + imports: [ExtraStepperComponent], + template, + styles, +}) +export class StepperStepsOnlyComponent { + steps: ExtraStepperItem[] = [ + { value: 1, label: 'Stepper', caption: 'caption' }, + { value: 2, label: 'Stepper', caption: 'caption' }, + { value: 3, label: 'Stepper', caption: 'caption' }, + ]; +} + +export const StepsOnly: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Только шаги без панелей контента.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-stepper-steps-only', + standalone: true, + 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' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/stepper/examples/stepper-vertical.component.ts b/src/stories/components/stepper/examples/stepper-vertical.component.ts new file mode 100644 index 0000000..695bd9f --- /dev/null +++ b/src/stories/components/stepper/examples/stepper-vertical.component.ts @@ -0,0 +1,60 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraStepperComponent, ExtraStepperItem } from '../../../../lib/components/stepper/stepper.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-stepper-vertical', + standalone: true, + imports: [ExtraStepperComponent], + template, + styles, +}) +export class StepperVerticalComponent { + 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' }, + ]; +} + +export const Vertical: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Вертикальная ориентация степпера.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-stepper-vertical', + standalone: true, + 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' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/stepper/stepper.stories.ts b/src/stories/components/stepper/stepper.stories.ts new file mode 100644 index 0000000..41e8d72 --- /dev/null +++ b/src/stories/components/stepper/stepper.stories.ts @@ -0,0 +1,140 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +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 = ExtraStepperComponent; + +const meta: Meta = { + title: 'Components/Stepper', + component: ExtraStepperComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraStepperComponent, + StepperVerticalComponent, + StepperLinearComponent, + StepperStepsOnlyComponent, + StepperErrorComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Пошаговый навигационный компонент для отображения прогресса через последовательность шагов. + +\`\`\`typescript +import { ExtraStepperComponent, StepperItem } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-stepper' }, + }, + argTypes: { + value: { + control: 'number', + description: 'Значение активного шага', + table: { + category: 'Props', + defaultValue: { summary: '1' }, + type: { summary: 'number | undefined' }, + }, + }, + linear: { + control: 'boolean', + description: 'Запрещает переход к следующему шагу без завершения текущего', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + orientation: { + control: 'radio', + options: ['horizontal', 'vertical'], + description: 'Ориентация степпера', + table: { + category: 'Props', + defaultValue: { summary: 'horizontal' }, + type: { summary: "'horizontal' | 'vertical'" }, + }, + }, + showPanels: { + control: 'boolean', + description: 'Показывать панели контента', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + steps: { + control: 'object', + description: 'Массив шагов', + table: { + category: 'Props', + type: { summary: 'StepperItem[]' }, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default (Horizontal) ───────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Horizontal', + render: (args) => { + const parts: string[] = []; + + parts.push(`[value]="value"`); + parts.push(`[steps]="steps"`); + if (args.linear) parts.push(`[linear]="true"`); + if (args.orientation === 'vertical') parts.push(`orientation="vertical"`); + if (!args.showPanels) parts.push(`[showPanels]="false"`); + + const template = ``; + + return { props: args, template }; + }, + args: { + 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' }, + ], + linear: false, + orientation: 'horizontal', + showPanels: true, + }, + parameters: { + docs: { + description: { + story: 'Горизонтальный степпер. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Vertical ────────────────────────────────────────────────────────────────── + +export const Vertical: Story = VerticalStory; + +// ── Linear ──────────────────────────────────────────────────────────────────── + +export const Linear: Story = LinearStory; + +// ── StepsOnly ───────────────────────────────────────────────────────────────── + +export const StepsOnly: Story = StepsOnlyStory; + +// ── Error ───────────────────────────────────────────────────────────────────── + +export const Error: Story = ErrorStory;