diff --git a/src/lib/components/listbox/listbox.component.ts b/src/lib/components/listbox/listbox.component.ts index 9ca7d02..2ed2f0e 100644 --- a/src/lib/components/listbox/listbox.component.ts +++ b/src/lib/components/listbox/listbox.component.ts @@ -1,18 +1,15 @@ import { - AfterContentInit, ChangeDetectionStrategy, Component, - ContentChildren, - ChangeDetectorRef, EventEmitter, Input, Output, - QueryList, + TemplateRef, forwardRef } from '@angular/core'; import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Listbox, ListboxChangeEvent } from 'primeng/listbox'; -import { PrimeTemplate, SharedModule } from 'primeng/api'; +import { SharedModule } from 'primeng/api'; import { NgTemplateOutlet } from '@angular/common'; @Component({ @@ -41,22 +38,18 @@ import { NgTemplateOutlet } from '@angular/common'; (onFocus)="onFocus.emit($event)" (onBlur)="onBlurHandler($event)" > - - - - - - @if (itemTpl) { + @if (itemTemplate) { + - } - + + } ` }) -export class ExtraListboxComponent implements ControlValueAccessor, AfterContentInit { +export class ExtraListboxComponent implements ControlValueAccessor { @Input() options: any[] = []; @Input() optionLabel = 'label'; @Input() optionValue: string | undefined = undefined; @@ -69,14 +62,13 @@ export class ExtraListboxComponent implements ControlValueAccessor, AfterContent @Input() optionGroupChildren: string | undefined = undefined; @Input() scrollHeight = '200px'; @Input() emptyMessage: string | undefined = undefined; + @Input() itemTemplate: TemplateRef | null = null; @Output() onFocus = new EventEmitter(); @Output() onBlur = new EventEmitter(); protected modelValue: any = null; - @ContentChildren(PrimeTemplate) templates!: QueryList; - itemTpl?: PrimeTemplate; private _disabled = false; private _onChange: (value: any) => void = () => {}; @@ -86,18 +78,6 @@ export class ExtraListboxComponent implements ControlValueAccessor, AfterContent return this._disabled; } - constructor(private cdr: ChangeDetectorRef) {} - - ngAfterContentInit(): void { - this.templates.forEach((tpl) => { - switch (tpl.getType()) { - case 'item': - this.itemTpl = tpl; - break; - } - }); - this.cdr.detectChanges(); - } onChangeHandler(event: ListboxChangeEvent): void { // Обновляем внутреннее значение и уведомляем форму об изменении. diff --git a/src/lib/components/menu/menu.component.ts b/src/lib/components/menu/menu.component.ts new file mode 100644 index 0000000..21b8798 --- /dev/null +++ b/src/lib/components/menu/menu.component.ts @@ -0,0 +1,59 @@ +import { Component, Input, TemplateRef, ViewChild } from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; +import { Menu } from 'primeng/menu'; +import { MenuItem, PrimeTemplate } from 'primeng/api'; + +export interface ExtraMenuModel extends MenuItem { + caption?: string; +} + +@Component({ + selector: 'extra-menu', + host: { style: 'display: contents' }, + standalone: true, + imports: [Menu, PrimeTemplate, NgTemplateOutlet], + template: ` + + + @if (itemTemplate) { + + + } @else { + + @if (item.icon) { + + } + @if ($any(item).caption) { + + {{ item.label }} + {{ $any(item).caption }} + + } @else { + {{ item.label }} + } + + } + + + `, +}) +export class ExtraMenuComponent { + @ViewChild('menuRef') menuRef!: Menu; + + @Input() model: ExtraMenuModel[] = []; + @Input() popup = false; + @Input() itemTemplate: TemplateRef | null = null; + + toggle(event: Event): void { + this.menuRef.toggle(event); + } +} diff --git a/src/lib/components/menu/ng-package.json b/src/lib/components/menu/ng-package.json new file mode 100644 index 0000000..ecdf8fe --- /dev/null +++ b/src/lib/components/menu/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/menu/public_api.ts b/src/lib/components/menu/public_api.ts new file mode 100644 index 0000000..50e5b52 --- /dev/null +++ b/src/lib/components/menu/public_api.ts @@ -0,0 +1,4 @@ +export * from './menu.component'; + + + diff --git a/src/lib/providers/prime-preset/tokens/components/menu.ts b/src/lib/providers/prime-preset/tokens/components/menu.ts new file mode 100644 index 0000000..2ef5fcf --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/menu.ts @@ -0,0 +1,67 @@ +export const menuCss = ({ dt }: { dt: (token: string) => string }): string => ` + .p-menu.p-component { + padding: ${dt('menu.extend.paddingY')} ${dt('menu.extend.paddingX')}; + } + + .p-menu .p-menu-item-content .p-menu-item-link .p-menu-item-label { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.400')}; + } + + .p-menu .p-menu-item-content .menu-item-label { + display: flex; + flex-direction: column; + gap: ${dt('menu.extend.extItem.caption.gap')}; + } + + .p-menu .p-menu-item-content .menu-item-caption { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + color: ${dt('menu.colorScheme.light.extend.extItem.caption.color')}; + } + + .p-menu .p-menu-item:not(.p-disabled) .p-menu-item-content:hover, + .p-menu .p-menu-item:not(.p-disabled) .p-menu-item-content:hover .p-menu-item-link, + .p-menu .p-menu-item:not(.p-disabled) .p-menu-item-content:hover .p-menu-item-label, + .p-menu .p-menu-item:not(.p-disabled) .p-menu-item-content:hover .p-menu-item-icon { + background: ${dt('menu.colorScheme.light.item.focusBackground')}; + color: ${dt('menu.colorScheme.light.item.focusColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked > .p-menu-item-content, + .p-menu .p-menu-item.p-focus > .p-menu-item-content { + background: ${dt('menu.extend.extItem.activeBackground')}; + color: ${dt('menu.extend.extItem.activeColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked > .p-menu-item-content .p-menu-item-link, + .p-menu .p-menu-item.p-menuitem-checked > .p-menu-item-content .p-menu-item-label, + .p-menu .p-menu-item.p-focus > .p-menu-item-content .p-menu-item-link, + .p-menu .p-menu-item.p-focus > .p-menu-item-content .p-menu-item-label { + color: ${dt('menu.extend.extItem.activeColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked > .p-menu-item-content .p-menu-item-icon, + .p-menu .p-menu-item.p-focus > .p-menu-item-content .p-menu-item-icon { + color: ${dt('menu.colorScheme.light.extend.extItem.icon.activeColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked:not(.p-disabled) > .p-menu-item-content:hover { + background: ${dt('menu.colorScheme.light.item.focusBackground')}; + color: ${dt('menu.colorScheme.light.item.focusColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked:not(.p-disabled) > .p-menu-item-content:hover .p-menu-item-icon { + color: ${dt('menu.colorScheme.light.item.focusColor')}; + } + + .p-menu .p-menu-submenu-label { + text-transform: uppercase; + font-size: ${dt('fonts.fontSize.200')}; + font-family: ${dt('fonts.fontFamily.heading')}; + line-height: ${dt('fonts.lineHeight.400')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/tokens.json b/src/lib/providers/prime-preset/tokens/tokens.json index 2e49ff4..5824c87 100644 --- a/src/lib/providers/prime-preset/tokens/tokens.json +++ b/src/lib/providers/prime-preset/tokens/tokens.json @@ -3381,7 +3381,7 @@ }, "submenuLabel": { "padding": "{navigation.submenuLabel.padding}", - "fontWeight": "{fonts.fontWeight.demibold}", + "fontWeight": "{fonts.fontWeight.regular}", "background": "{navigation.submenuLabel.background}", "color": "{navigation.submenuLabel.color}" }, diff --git a/src/stories/components/listbox/examples/listbox-custom.component.ts b/src/stories/components/listbox/examples/listbox-custom.component.ts index be05b8e..1643bed 100644 --- a/src/stories/components/listbox/examples/listbox-custom.component.ts +++ b/src/stories/components/listbox/examples/listbox-custom.component.ts @@ -2,7 +2,6 @@ import { Component } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { StoryObj } from '@storybook/angular'; import { ExtraListboxComponent } from '../../../../lib/components/listbox/listbox.component'; -import { SharedModule } from 'primeng/api'; const options = [ { name: 'Profile', description: 'Manage your account', icon: 'ti ti-user' }, @@ -11,24 +10,22 @@ const options = [ ]; const template = ` - - - - - {{ item.name }} - {{ item.description }} - - - + + + + + + {{ item.name }} + {{ item.description }} + + `; -const styles = ''; @Component({ selector: 'app-listbox-custom', standalone: true, - imports: [ExtraListboxComponent, SharedModule, ReactiveFormsModule], + imports: [ExtraListboxComponent, ReactiveFormsModule], template, - styles, }) export class ListboxCustomComponent { ctrl = new FormControl(null); @@ -42,29 +39,28 @@ export const Custom: StoryObj = { parameters: { controls: { disable: true }, docs: { - description: { story: 'Кастомный шаблон элемента с иконкой и подписью.' }, + description: { story: 'Кастомный шаблон элемента с иконкой и подписью через `itemTemplate`.' }, source: { language: 'ts', code: ` import { Component } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { Listbox } from 'primeng/listbox'; -import { SharedModule } from 'primeng/api'; +import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; @Component({ selector: 'app-listbox-custom', standalone: true, - imports: [ExtraListboxComponent, SharedModule, ReactiveFormsModule], + imports: [ExtraListboxComponent, ReactiveFormsModule], template: \` - - - - - {{ item.name }} - {{ item.description }} - - - + + + + + + {{ item.name }} + {{ item.description }} + + \`, }) export class ListboxCustomComponent { @@ -80,3 +76,5 @@ export class ListboxCustomComponent { }, }, }; + + diff --git a/src/stories/components/listbox/listbox.stories.ts b/src/stories/components/listbox/listbox.stories.ts index a6f7be6..0e0933d 100644 --- a/src/stories/components/listbox/listbox.stories.ts +++ b/src/stories/components/listbox/listbox.stories.ts @@ -13,22 +13,22 @@ type ListboxArgs = ExtraListboxComponent; const meta: Meta = { title: 'Components/Form/Listbox', - component: ExtraListboxComponent, + component: ExtraListboxComponent, tags: ['autodocs'], decorators: [ - moduleMetadata({ - imports: [ - ExtraListboxComponent, - ReactiveFormsModule, - Listbox, + moduleMetadata({ + imports: [ + ExtraListboxComponent, + ReactiveFormsModule, + Listbox, ListboxCheckmarkComponent, ListboxFilterComponent, ListboxMultipleComponent, ListboxGroupedComponent, ListboxCustomComponent, - ListboxDisabledComponent, - ], - }), + ListboxDisabledComponent + ] + }) ], parameters: { designTokens: { prefix: '--p-listbox' }, @@ -38,9 +38,9 @@ const meta: Meta = { \`\`\`typescript import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; -\`\`\``, - }, - }, +\`\`\`` + } + } }, argTypes: { options: { table: { disable: true } }, @@ -53,8 +53,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; table: { category: 'Props', defaultValue: { summary: 'label' }, - type: { summary: 'string' }, - }, + type: { summary: 'string' } + } }, optionValue: { control: 'text', @@ -62,8 +62,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; table: { category: 'Props', defaultValue: { summary: 'undefined' }, - type: { summary: 'string' }, - }, + type: { summary: 'string' } + } }, multiple: { control: 'boolean', @@ -71,8 +71,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; table: { category: 'Props', defaultValue: { summary: 'false' }, - type: { summary: 'boolean' }, - }, + type: { summary: 'boolean' } + } }, filter: { control: 'boolean', @@ -80,8 +80,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; table: { category: 'Props', defaultValue: { summary: 'false' }, - type: { summary: 'boolean' }, - }, + type: { summary: 'boolean' } + } }, filterPlaceHolder: { control: 'text', @@ -89,8 +89,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; table: { category: 'Props', defaultValue: { summary: 'undefined' }, - type: { summary: 'string' }, - }, + type: { summary: 'string' } + } }, checkmark: { control: 'boolean', @@ -98,8 +98,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; table: { category: 'Props', defaultValue: { summary: 'false' }, - type: { summary: 'boolean' }, - }, + type: { summary: 'boolean' } + } }, group: { control: 'boolean', @@ -107,8 +107,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; table: { category: 'Props', defaultValue: { summary: 'false' }, - type: { summary: 'boolean' }, - }, + type: { summary: 'boolean' } + } }, scrollHeight: { control: 'text', @@ -116,8 +116,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; table: { category: 'Props', defaultValue: { summary: '200px' }, - type: { summary: 'string' }, - }, + type: { summary: 'string' } + } }, emptyMessage: { control: 'text', @@ -125,8 +125,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; table: { category: 'Props', defaultValue: { summary: 'undefined' }, - type: { summary: 'string' }, - }, + type: { summary: 'string' } + } }, // ── Events ─────────────────────────────────────────────── onFocus: { @@ -134,17 +134,17 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; description: 'Событие фокуса', table: { category: 'Events', - type: { summary: 'EventEmitter' }, - }, + type: { summary: 'EventEmitter' } + } }, onBlur: { control: false, description: 'Событие потери фокуса', table: { category: 'Events', - type: { summary: 'EventEmitter' }, - }, - }, + type: { summary: 'EventEmitter' } + } + } }, args: { optionLabel: 'label', @@ -152,8 +152,8 @@ import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; filter: false, checkmark: false, group: false, - scrollHeight: '200px', - }, + scrollHeight: '200px' + } }; export default meta; @@ -164,7 +164,7 @@ const defaultOptions = [ { label: 'Rome', value: 'RM' }, { label: 'London', value: 'LDN' }, { label: 'Istanbul', value: 'IST' }, - { label: 'Paris', value: 'PRS' }, + { label: 'Paris', value: 'PRS' } ]; // ── Default ────────────────────────────────────────────────────────────────── @@ -175,7 +175,7 @@ export const Default: Story = { props: { ...args, ctrl: new FormControl(null), - options: defaultOptions, + options: defaultOptions }, template: ` `, +>` }), parameters: { docs: { description: { - story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', - }, - }, - }, + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.' + } + } + } }; // ── Варианты ───────────────────────────────────────────────────────────────── diff --git a/src/stories/components/menu/examples/menu-basic.component.ts b/src/stories/components/menu/examples/menu-basic.component.ts new file mode 100644 index 0000000..48d59ee --- /dev/null +++ b/src/stories/components/menu/examples/menu-basic.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; + +const template = ` + + + +`; + +@Component({ + selector: 'app-menu-basic', + standalone: true, + imports: [ExtraMenuComponent], + template, +}) +export class MenuBasicComponent { + items: ExtraMenuModel[] = [ + { label: 'Новый заказ' }, + { label: 'Поиск отправления' }, + { separator: true }, + { label: 'Экспорт' }, + ]; +} diff --git a/src/stories/components/menu/examples/menu-custom.component.ts b/src/stories/components/menu/examples/menu-custom.component.ts new file mode 100644 index 0000000..1223747 --- /dev/null +++ b/src/stories/components/menu/examples/menu-custom.component.ts @@ -0,0 +1,71 @@ +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; + +const template = ` + + + + + + + @if (item.icon) { + + } + + {{ item.label }} + @if (item.caption) { + {{ item.caption }} + } + + @if (item.badge) { + + {{ item.badge }} + + } + + +`; + +@Component({ + selector: 'app-menu-custom', + standalone: true, + imports: [ExtraMenuComponent], + template, +}) +export class MenuCustomComponent { + items: ExtraMenuModel[] = [ + { + label: 'Создать отправление', + caption: 'Оформление нового заказа', + icon: 'ti ti-file-plus', + badge: 'Новое', + }, + { + label: 'Найти посылку', + caption: 'Поиск по трек-номеру', + icon: 'ti ti-map-pin', + }, + { separator: true }, + { + label: 'Экспорт данных', + caption: 'Выгрузка в CSV или Excel', + icon: 'ti ti-download', + }, + { + label: 'Удалить', + caption: 'Действие недоступно', + icon: 'ti ti-trash', + disabled: true, + }, + ]; +} diff --git a/src/stories/components/menu/examples/menu-grouped.component.ts b/src/stories/components/menu/examples/menu-grouped.component.ts new file mode 100644 index 0000000..3d838ce --- /dev/null +++ b/src/stories/components/menu/examples/menu-grouped.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; + +const template = ` + + + +`; + +@Component({ + selector: 'app-menu-grouped', + standalone: true, + imports: [ExtraMenuComponent], + template, +}) +export class MenuGroupedComponent { + items: ExtraMenuModel[] = [ + { + label: 'Заказы', + items: [ + { label: 'Новый заказ', icon: 'ti ti-plus' }, + { label: 'Список заказов', icon: 'ti ti-list' }, + { label: 'Архив', icon: 'ti ti-archive' }, + ], + }, + { + label: 'Отправления', + items: [ + { label: 'Создать накладную', icon: 'ti ti-file-invoice' }, + { label: 'Отследить посылку', icon: 'ti ti-map-pin' }, + { label: 'Отменить отправление', icon: 'ti ti-ban' }, + ], + }, + ]; +} diff --git a/src/stories/components/menu/examples/menu-popup.component.ts b/src/stories/components/menu/examples/menu-popup.component.ts new file mode 100644 index 0000000..fd222ee --- /dev/null +++ b/src/stories/components/menu/examples/menu-popup.component.ts @@ -0,0 +1,31 @@ +import { Component, ViewChild } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; + +const template = ` + + + + +`; + +@Component({ + selector: 'app-menu-popup', + standalone: true, + imports: [ExtraMenuComponent, ExtraButtonComponent], + template, +}) +export class MenuPopupComponent { + @ViewChild('menuRef') menuRef!: ExtraMenuComponent; + + items: ExtraMenuModel[] = [ + { label: 'Создать отправление', icon: 'ti ti-file-plus' }, + { label: 'Найти по трек-номеру', icon: 'ti ti-search' }, + { separator: true }, + { label: 'Экспорт данных', icon: 'ti ti-download' }, + ]; + + toggle(event: Event): void { + this.menuRef.toggle(event); + } +} diff --git a/src/stories/components/menu/examples/menu-with-icons.component.ts b/src/stories/components/menu/examples/menu-with-icons.component.ts new file mode 100644 index 0000000..061da8d --- /dev/null +++ b/src/stories/components/menu/examples/menu-with-icons.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; + +const template = ` + + + +`; + +@Component({ + selector: 'app-menu-with-icons', + standalone: true, + imports: [ExtraMenuComponent], + template, +}) +export class MenuWithIconsComponent { + items: ExtraMenuModel[] = [ + { label: 'Создать отправление', icon: 'ti ti-file-plus' }, + { label: 'Открыть список заказов', icon: 'ti ti-folder-open' }, + { label: 'Сохранить черновик', icon: 'ti ti-device-floppy' }, + { separator: true }, + { label: 'Распечатать накладную', icon: 'ti ti-printer' }, + { label: 'Экспорт данных', icon: 'ti ti-download' }, + ]; +} diff --git a/src/stories/components/menu/menu.stories.ts b/src/stories/components/menu/menu.stories.ts new file mode 100644 index 0000000..4234914 --- /dev/null +++ b/src/stories/components/menu/menu.stories.ts @@ -0,0 +1,311 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraMenuComponent } from '../../../lib/components/menu/menu.component'; +import { MenuPopupComponent } from './examples/menu-popup.component'; +import { MenuBasicComponent } from './examples/menu-basic.component'; +import { MenuWithIconsComponent } from './examples/menu-with-icons.component'; +import { MenuGroupedComponent } from './examples/menu-grouped.component'; +import { MenuCustomComponent } from './examples/menu-custom.component'; + +const meta: Meta = { + title: 'Components/Menu/Menu', + component: ExtraMenuComponent, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: `Компонент навигационного меню. Поддерживает режим popup (по нажатию на триггер) и inline-отображение, группировку пунктов и пункты с описанием (caption). + +\`\`\`typescript +import { ExtraMenuComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-menu' }, + }, + argTypes: { + model: { + control: false, + description: 'Массив пунктов меню.', + table: { + category: 'Props', + type: { summary: 'ExtraMenuModel[]' }, + }, + }, + popup: { + control: 'boolean', + description: 'Режим popup — меню отображается при вызове метода toggle().', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Popup ───────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Popup', + decorators: [moduleMetadata({ imports: [MenuPopupComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Меню вызывается по нажатию на кнопку. Используйте метод toggle() для показа/скрытия.', + }, + source: { + language: 'ts', + code: ` +import { Component, ViewChild } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel, ButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-popup', + standalone: true, + imports: [ExtraMenuComponent, ButtonComponent], + template: \` + + + \`, +}) +export class MenuPopupComponent { + @ViewChild('menuRef') menuRef!: ExtraMenuComponent; + + items: ExtraMenuModel[] = [ + { label: 'Создать отправление', icon: 'ti ti-file-plus' }, + { label: 'Найти по трек-номеру', icon: 'ti ti-search' }, + { separator: true }, + { label: 'Экспорт данных', icon: 'ti ti-download' }, + ]; + + toggle(event: Event): void { + this.menuRef.toggle(event); + } +} + `, + }, + }, + }, +}; + +// ── Basic ───────────────────────────────────────────────────────────────────── + +export const Basic: Story = { + name: 'Basic', + decorators: [moduleMetadata({ imports: [MenuBasicComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Базовый вариант inline-меню без иконок.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-basic', + standalone: true, + imports: [ExtraMenuComponent], + template: \` + + \`, +}) +export class MenuBasicComponent { + items: ExtraMenuModel[] = [ + { label: 'Новый заказ' }, + { label: 'Поиск отправления' }, + { separator: true }, + { label: 'Экспорт' }, + ]; +} + `, + }, + }, + }, +}; + +// ── WithIcons ───────────────────────────────────────────────────────────────── + +export const WithIcons: Story = { + name: 'WithIcons', + decorators: [moduleMetadata({ imports: [MenuWithIconsComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Пункты меню с иконками.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-with-icons', + standalone: true, + imports: [ExtraMenuComponent], + template: \` + + \`, +}) +export class MenuWithIconsComponent { + items: ExtraMenuModel[] = [ + { label: 'Создать отправление', icon: 'ti ti-file-plus' }, + { label: 'Открыть список заказов', icon: 'ti ti-folder-open' }, + { label: 'Сохранить черновик', icon: 'ti ti-device-floppy' }, + { separator: true }, + { label: 'Распечатать накладную', icon: 'ti ti-printer' }, + { label: 'Экспорт данных', icon: 'ti ti-download' }, + ]; +} + `, + }, + }, + }, +}; + +// ── Grouped ─────────────────────────────────────────────────────────────────── + +export const Grouped: Story = { + name: 'Grouped', + decorators: [moduleMetadata({ imports: [MenuGroupedComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Группировка пунктов меню через label у родительского элемента.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-grouped', + standalone: true, + imports: [ExtraMenuComponent], + template: \` + + \`, +}) +export class MenuGroupedComponent { + items: ExtraMenuModel[] = [ + { + label: 'Заказы', + items: [ + { label: 'Новый заказ', icon: 'ti ti-plus' }, + { label: 'Список заказов', icon: 'ti ti-list' }, + { label: 'Архив', icon: 'ti ti-archive' }, + ], + }, + { + label: 'Отправления', + items: [ + { label: 'Создать накладную', icon: 'ti ti-file-invoice' }, + { label: 'Отследить посылку', icon: 'ti ti-map-pin' }, + { label: 'Отменить отправление', icon: 'ti ti-ban' }, + ], + }, + ]; +} + `, + }, + }, + }, +}; + +// ── Custom ──────────────────────────────────────────────────────────────────── + +export const Custom: Story = { + name: 'Custom', + decorators: [moduleMetadata({ imports: [MenuCustomComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Кастомизация отображения пунктов меню через входной параметр `itemTemplate`. Передайте `ng-template` с произвольной разметкой — он получит объект пункта меню через `let-item`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-custom', + standalone: true, + imports: [ExtraMenuComponent], + template: \` + + + + + @if (item.icon) { + + } + + {{ item.label }} + @if (item.caption) { + {{ item.caption }} + } + + @if (item.badge) { + + {{ item.badge }} + + } + + + \`, +}) +export class MenuCustomComponent { + items: ExtraMenuModel[] = [ + { + label: 'Создать отправление', + caption: 'Оформление нового заказа', + icon: 'ti ti-file-plus', + badge: 'Новое', + }, + { + label: 'Найти посылку', + caption: 'Поиск по трек-номеру', + icon: 'ti ti-map-pin', + }, + { separator: true }, + { + label: 'Экспорт данных', + caption: 'Выгрузка в CSV или Excel', + icon: 'ti ti-download', + }, + { + label: 'Удалить', + caption: 'Действие недоступно', + icon: 'ti ti-trash', + disabled: true, + }, + ]; +} + `, + }, + }, + }, +};