Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/lib/components/menu/menu.component.ts
Original file line number Diff line number Diff line change
@@ -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 MenuModel extends MenuItem {
caption?: string;
}

@Component({
selector: 'menu',
host: { style: 'display: contents' },
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

для чего?

Copy link
Copy Markdown
Author

@khaliulin khaliulin Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AxyIX делает хост-элемент menu прозрачным для layout (flex/grid родителя его не видят). Без этого menu был бы блочным элементом, ломающим позиционирование.

standalone: true,
imports: [Menu, PrimeTemplate, NgTemplateOutlet],
template: `
<p-menu #menuRef [model]="model" [popup]="popup" [appendTo]="popup ? 'body' : null">
<ng-template pTemplate="item" let-item>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

опять же вопрос с кастомизациями. разработчикам не придется свои шаблоны передавать?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AxyIX добавлен itemTemplate для кастомизации элемента меню 7896268

@if (itemTemplate) {
<ng-container [ngTemplateOutlet]="itemTemplate"
[ngTemplateOutletContext]="{ $implicit: item }">
</ng-container>
} @else {
<a
class="p-menu-item-link"
role="menuitem"
tabindex="0"
[class.p-disabled]="item.disabled"
[attr.href]="item.url || null"
[attr.target]="item.target || null"
(click)="!item.disabled && item.command && item.command({ originalEvent: $event, item: item })"
>
@if (item.icon) {
<span [class]="item.icon + ' p-menu-item-icon'"></span>
}
@if ($any(item).caption) {
<div class="menu-item-label">
<span class="p-menu-item-label">{{ item.label }}</span>
<small class="menu-item-caption">{{ $any(item).caption }}</small>
</div>
} @else {
<span class="p-menu-item-label">{{ item.label }}</span>
}
</a>
}
</ng-template>
</p-menu>
`,
})
export class MenuComponent {
@ViewChild('menuRef') menuRef!: Menu;

@Input() model: MenuModel[] = [];
@Input() popup = false;
@Input() itemTemplate: TemplateRef<any> | null = null;

toggle(event: Event): void {
this.menuRef.toggle(event);
}
}
67 changes: 67 additions & 0 deletions src/prime-preset/tokens/components/menu.ts
Original file line number Diff line number Diff line change
@@ -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')};
}
`;
23 changes: 23 additions & 0 deletions src/stories/components/menu/examples/menu-basic.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Component } from '@angular/core';
import { MenuComponent, MenuModel } from '../../../../lib/components/menu/menu.component';

const template = `
<div class="bg-surface-ground">
<menu [model]="items"></menu>
</div>
`;

@Component({
selector: 'app-menu-basic',
standalone: true,
imports: [MenuComponent],
template,
})
export class MenuBasicComponent {
items: MenuModel[] = [
{ label: 'Новый заказ' },
{ label: 'Поиск отправления' },
{ separator: true },
{ label: 'Экспорт' },
];
}
35 changes: 35 additions & 0 deletions src/stories/components/menu/examples/menu-custom.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component } from '@angular/core';
import { MenuComponent, MenuModel } from '../../../../lib/components/menu/menu.component';

const template = `
<div class="bg-surface-ground">
<menu [model]="items"></menu>
</div>
`;

@Component({
selector: 'app-menu-custom',
standalone: true,
imports: [MenuComponent],
template,
})
export class MenuCustomComponent {
items: MenuModel[] = [
{
label: 'Создать отправление',
caption: 'Оформление нового заказа',
icon: 'ti ti-file-plus',
},
{
label: 'Найти посылку',
caption: 'Поиск по трек-номеру',
icon: 'ti ti-map-pin',
},
{ separator: true },
{
label: 'Экспорт данных',
caption: 'Выгрузка в CSV или Excel',
icon: 'ti ti-download',
},
];
}
35 changes: 35 additions & 0 deletions src/stories/components/menu/examples/menu-grouped.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component } from '@angular/core';
import { MenuComponent, MenuModel } from '../../../../lib/components/menu/menu.component';

const template = `
<div class="bg-surface-ground">
<menu [model]="items"></menu>
</div>
`;

@Component({
selector: 'app-menu-grouped',
standalone: true,
imports: [MenuComponent],
template,
})
export class MenuGroupedComponent {
items: MenuModel[] = [
{
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' },
],
},
];
}
31 changes: 31 additions & 0 deletions src/stories/components/menu/examples/menu-popup.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Component, ViewChild } from '@angular/core';
import { Button } from 'primeng/button';
import { MenuComponent, MenuModel } from '../../../../lib/components/menu/menu.component';

const template = `
<div class="bg-surface-ground">
<p-button label="Действия с заказом" severity="contrast" (onClick)="toggle($event)"></p-button>
<menu #menuRef [model]="items" [popup]="true"></menu>
</div>
`;

@Component({
selector: 'app-menu-popup',
standalone: true,
imports: [MenuComponent, Button],
template,
})
export class MenuPopupComponent {
@ViewChild('menuRef') menuRef!: MenuComponent;

items: MenuModel[] = [
{ 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);
}
}
25 changes: 25 additions & 0 deletions src/stories/components/menu/examples/menu-with-icons.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Component } from '@angular/core';
import { MenuComponent, MenuModel } from '../../../../lib/components/menu/menu.component';

const template = `
<div class="bg-surface-ground">
<menu [model]="items"></menu>
</div>
`;

@Component({
selector: 'app-menu-with-icons',
standalone: true,
imports: [MenuComponent],
template,
})
export class MenuWithIconsComponent {
items: MenuModel[] = [
{ 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' },
];
}
Loading
Loading