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
Binary file added public/assets/mascot.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions src/lib/components/card/card.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
AfterContentInit,
ChangeDetectorRef,
Component,
ContentChildren,
Input,
QueryList,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
import { Card } from 'primeng/card';
import { PrimeTemplate, SharedModule } from 'primeng/api';

@Component({
selector: 'card',
host: { style: 'display: block' },
standalone: true,
imports: [Card, SharedModule, NgTemplateOutlet],
template: `
<p-card [styleClass]="overlay ? 'shadow-md' : ''">
@if (headerTpl) {
<ng-template pTemplate="header">
<ng-container [ngTemplateOutlet]="headerTpl.template"></ng-container>
</ng-template>
}
@if (title || subtitle) {
<ng-template pTemplate="title">
<div class="p-card-caption">
@if (title) {
<div class="p-card-title m-0" data-pc-section="title">{{ title }}</div>
}
@if (subtitle) {
<div class="p-card-subtitle m-0" data-pc-section="subtitle">{{ subtitle }}</div>
}
</div>
</ng-template>
}
@if (contentTpl) {
<ng-template pTemplate="content">
<ng-container [ngTemplateOutlet]="contentTpl.template"></ng-container>
</ng-template>
}
@if (footerTpl) {
<ng-template pTemplate="footer">
<ng-container [ngTemplateOutlet]="footerTpl.template"></ng-container>
</ng-template>
}
</p-card>
`,
})
export class CardComponent implements AfterContentInit {
@Input() title = '';
@Input() subtitle = '';
@Input() overlay = false;

@ContentChildren(PrimeTemplate) templates!: QueryList<PrimeTemplate>;

headerTpl?: PrimeTemplate;
contentTpl?: PrimeTemplate;
footerTpl?: PrimeTemplate;

constructor(private cdr: ChangeDetectorRef) {}

ngAfterContentInit(): void {
this.templates.forEach(tpl => {
switch (tpl.getType()) {
case 'header': this.headerTpl = tpl; break;
case 'content': this.contentTpl = tpl; break;
case 'footer': this.footerTpl = tpl; break;
}
});
this.cdr.detectChanges();
}
}
38 changes: 38 additions & 0 deletions src/prime-preset/tokens/components/card.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Кастомная CSS-стилизация для компонента p-card.
* Публикует extend-токены как CSS-переменные и применяет глобальные стили.
* Подключается в map-tokens.ts: `import { cardCss } from './tokens/components/card'`
*/
export const cardCss = ({ dt }: { dt: (token: string) => string }): string => `
/* ─── Card extend: публикуем кастомные переменные в :root ─── */
:root {
--p-card-extend-border-color: ${dt('card.extend.borderColor')};
--p-card-extend-border-width: ${dt('card.extend.borderWidth')};
}

/* ─── Card base styles ─── */
.p-card.p-component {
border: var(--p-card-extend-border-width) solid var(--p-card-extend-border-color);
overflow: hidden;
box-shadow: none;
}

/* ─── Overlay variant ─── */
.p-card.p-component.shadow-md {
box-shadow: ${dt('overlay.popover.shadow')};
}

/* ─── Caption (Title & Subtitle wrapper) ─── */
.p-card-caption {
display: flex;
flex-direction: column;
gap: ${dt('card.caption.gap')};
}

/* ─── Subtitle typography ─── */
.p-card-subtitle {
font-family: ${dt('fonts.fontFamily.heading')};
font-size: ${dt('fonts.fontSize.200')};
font-weight: ${dt('fonts.fontWeight.regular')};
}
`;
278 changes: 278 additions & 0 deletions src/stories/components/card/card.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
import { Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { SharedModule } from 'primeng/api';
import { CardComponent } from '../../../lib/components/card/card.component';
import { ButtonComponent } from '../../../lib/components/button/button.component';
import { CardOverlayComponent } from './examples/card-overlay.component';
import { CardWithoutHeaderComponent } from './examples/card-without-header.component';
import { CardWithoutFooterComponent } from './examples/card-without-footer.component';
import { CardWithoutSubtitleComponent } from './examples/card-without-subtitle.component';

type CardArgs = CardComponent;

const meta: Meta<CardArgs> = {
title: 'Components/Panel/Card',
component: CardComponent,
tags: ['autodocs'],
decorators: [
moduleMetadata({
imports: [
CardComponent,
ButtonComponent,
SharedModule,
CardOverlayComponent,
CardWithoutHeaderComponent,
CardWithoutFooterComponent,
CardWithoutSubtitleComponent,
]
})
],
parameters: {
docs: {
description: {
component: `Гибкий контейнер для группировки контента с заголовком, подзаголовком, основным содержимым и действиями.

\`\`\`typescript
import { CardModule } from 'primeng/card';
\`\`\``,
},
},
designTokens: { prefix: '--p-card' },
},
argTypes: {
title: {
control: 'text',
description: 'Заголовок карточки',
table: {
category: 'Props',
defaultValue: { summary: '' },
type: { summary: 'string' },
},
},
subtitle: {
control: 'text',
description: 'Подзаголовок карточки',
table: {
category: 'Props',
defaultValue: { summary: '' },
type: { summary: 'string' },
},
},
overlay: {
control: 'boolean',
description: 'Тень вокруг карточки (shadow-md)',
table: {
category: 'Props',
defaultValue: { summary: 'false' },
type: { summary: 'boolean' },
},
},
},
};

export default meta;
type Story = StoryObj<CardArgs>;

// ── Default ──────────────────────────────────────────────────────────────────

export const Default: Story = {
name: 'Default',
render: (args) => {
const parts: string[] = [];

if (args.title) parts.push(`title="${args.title}"`);
if (args.subtitle) parts.push(`subtitle="${args.subtitle}"`);
if (args.overlay) parts.push(`[overlay]="true"`);

const attrs = parts.length ? `\n ${parts.join('\n ')}` : '';
const template = `<div class="bg-surface-ground">
<card${attrs} style="width: 20rem">
<ng-template pTemplate="header">
<img alt="Заголовок" src="assets/mascot.jpg" class="w-full" />
</ng-template>
<ng-template pTemplate="content">
<p class="text-sm">Контент карточки. Гибкая область для любого содержимого.</p>
</ng-template>
<ng-template pTemplate="footer">
<button label="Действие" size="small" [fluid]="true" class="w-full"></button>
</ng-template>
</card>
</div>`;

return { props: args, template };
},
args: {
title: 'Заголовок',
subtitle: 'Подзаголовок',
},
parameters: {
docs: {
description: {
story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.',
},
},
},
};

// ── Overlay ───────────────────────────────────────────────────────────────────

export const Overlay: Story = {
render: () => ({
template: `<app-card-overlay></app-card-overlay>`,
}),
parameters: {
docs: {
description: { story: 'Карточка с тенью (overlay).' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { SharedModule } from 'primeng/api';
import { CardComponent, ButtonComponent } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-card-overlay',
standalone: true,
imports: [CardComponent, ButtonComponent, SharedModule],
template: \`
<card title="Заголовок" subtitle="Подзаголовок" [overlay]="true" style="width: 20rem">
<ng-template pTemplate="header">
<div class="flex items-center justify-center h-8" style="background: var(--p-surface-100); color: var(--p-surface-400)">
<i class="ti ti-photo text-3xl"></i>
</div>
</ng-template>
<ng-template pTemplate="content">
<p class="text-sm">Карточка с тенью.</p>
</ng-template>
<ng-template pTemplate="footer">
<button label="Действие" size="small" [fluid]="true"></button>
</ng-template>
</card>
\`,
})
export class CardOverlayComponent {}
`,
},
},
},
};

// ── WithoutHeader ─────────────────────────────────────────────────────────────

export const WithoutHeader: Story = {
render: () => ({
template: `<app-card-without-header></app-card-without-header>`,
}),
parameters: {
docs: {
description: { story: 'Карточка без изображения в шапке.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { SharedModule } from 'primeng/api';
import { CardComponent, ButtonComponent } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-card-without-header',
standalone: true,
imports: [CardComponent, ButtonComponent, SharedModule],
template: \`
<card title="Заголовок" subtitle="Подзаголовок" style="width: 20rem">
<ng-template pTemplate="content">
<p class="text-sm">Карточка без изображения в шапке.</p>
</ng-template>
<ng-template pTemplate="footer">
<button label="Действие" size="small" [fluid]="true"></button>
</ng-template>
</card>
\`,
})
export class CardWithoutHeaderComponent {}
`,
},
},
},
};

// ── WithoutFooter ─────────────────────────────────────────────────────────────

export const WithoutFooter: Story = {
render: () => ({
template: `<app-card-without-footer></app-card-without-footer>`,
}),
parameters: {
docs: {
description: { story: 'Карточка без футера с действиями.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { SharedModule } from 'primeng/api';
import { CardComponent } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-card-without-footer',
standalone: true,
imports: [CardComponent, SharedModule],
template: \`
<card title="Заголовок" subtitle="Подзаголовок" style="width: 20rem">
<ng-template pTemplate="header">
<div class="flex items-center justify-center h-8" style="background: var(--p-surface-100); color: var(--p-surface-400)">
<i class="ti ti-photo text-3xl"></i>
</div>
</ng-template>
<ng-template pTemplate="content">
<p class="text-sm">Карточка без футера.</p>
</ng-template>
</card>
\`,
})
export class CardWithoutFooterComponent {}
`,
},
},
},
};

// ── WithoutSubtitle ───────────────────────────────────────────────────────────

export const WithoutSubtitle: Story = {
render: () => ({
template: `<app-card-without-subtitle></app-card-without-subtitle>`,
}),
parameters: {
docs: {
description: { story: 'Карточка без подзаголовка.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { SharedModule } from 'primeng/api';
import { CardComponent, ButtonComponent } from '@cdek-it/angular-ui-kit';

@Component({
selector: 'app-card-without-subtitle',
standalone: true,
imports: [CardComponent, ButtonComponent, SharedModule],
template: \`
<card title="Заголовок" style="width: 20rem">
<ng-template pTemplate="header">
<div class="flex items-center justify-center h-8" style="background: var(--p-surface-100); color: var(--p-surface-400)">
<i class="ti ti-photo text-3xl"></i>
</div>
</ng-template>
<ng-template pTemplate="content">
<p class="text-sm">Карточка без подзаголовка.</p>
</ng-template>
<ng-template pTemplate="footer">
<button label="Действие" size="small" [fluid]="true"></button>
</ng-template>
</card>
\`,
})
export class CardWithoutSubtitleComponent {}
`,
},
},
},
};
Loading
Loading