diff --git a/public/assets/mascot.jpg b/public/assets/mascot.jpg new file mode 100644 index 0000000..adb02e3 Binary files /dev/null and b/public/assets/mascot.jpg differ diff --git a/src/lib/components/card/card.component.ts b/src/lib/components/card/card.component.ts new file mode 100644 index 0000000..e035414 --- /dev/null +++ b/src/lib/components/card/card.component.ts @@ -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: ` + + @if (headerTpl) { + + + + } + @if (title || subtitle) { + +
+ @if (title) { +
{{ title }}
+ } + @if (subtitle) { +
{{ subtitle }}
+ } +
+
+ } + @if (contentTpl) { + + + + } + @if (footerTpl) { + + + + } +
+ `, +}) +export class CardComponent implements AfterContentInit { + @Input() title = ''; + @Input() subtitle = ''; + @Input() overlay = false; + + @ContentChildren(PrimeTemplate) templates!: QueryList; + + 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(); + } +} diff --git a/src/prime-preset/tokens/components/card.ts b/src/prime-preset/tokens/components/card.ts new file mode 100644 index 0000000..7bfac62 --- /dev/null +++ b/src/prime-preset/tokens/components/card.ts @@ -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')}; + } +`; diff --git a/src/stories/components/card/card.stories.ts b/src/stories/components/card/card.stories.ts new file mode 100644 index 0000000..1b9fc6f --- /dev/null +++ b/src/stories/components/card/card.stories.ts @@ -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 = { + 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; + +// ── 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 = `
+ + + Заголовок + + +

Контент карточки. Гибкая область для любого содержимого.

+
+ + + + +
`; + + return { props: args, template }; + }, + args: { + title: 'Заголовок', + subtitle: 'Подзаголовок', + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Overlay ─────────────────────────────────────────────────────────────────── + +export const Overlay: Story = { + render: () => ({ + template: ``, + }), + 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: \` + + +
+ +
+
+ +

Карточка с тенью.

+
+ + + +
+ \`, +}) +export class CardOverlayComponent {} + `, + }, + }, + }, +}; + +// ── WithoutHeader ───────────────────────────────────────────────────────────── + +export const WithoutHeader: Story = { + render: () => ({ + template: ``, + }), + 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: \` + + +

Карточка без изображения в шапке.

+
+ + + +
+ \`, +}) +export class CardWithoutHeaderComponent {} + `, + }, + }, + }, +}; + +// ── WithoutFooter ───────────────────────────────────────────────────────────── + +export const WithoutFooter: Story = { + render: () => ({ + template: ``, + }), + 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: \` + + +
+ +
+
+ +

Карточка без футера.

+
+
+ \`, +}) +export class CardWithoutFooterComponent {} + `, + }, + }, + }, +}; + +// ── WithoutSubtitle ─────────────────────────────────────────────────────────── + +export const WithoutSubtitle: Story = { + render: () => ({ + template: ``, + }), + 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: \` + + +
+ +
+
+ +

Карточка без подзаголовка.

+
+ + + +
+ \`, +}) +export class CardWithoutSubtitleComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/card/examples/card-overlay.component.ts b/src/stories/components/card/examples/card-overlay.component.ts new file mode 100644 index 0000000..b266546 --- /dev/null +++ b/src/stories/components/card/examples/card-overlay.component.ts @@ -0,0 +1,70 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { SharedModule } from 'primeng/api'; +import { CardComponent } from '../../../../lib/components/card/card.component'; +import { ButtonComponent } from '../../../../lib/components/button/button.component'; + +const template = ` +
+ + + Заголовок + + +

Карточка с тенью.

+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-card-overlay', + standalone: true, + imports: [CardComponent, ButtonComponent, SharedModule], + template, + styles, +}) +export class CardOverlayComponent {} + +export const Overlay: StoryObj = { + render: () => ({ + template: ``, + }), + 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: \` + + + Заголовок + + +

Карточка с тенью.

+
+ + + +
+ \`, +}) +export class CardOverlayComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/card/examples/card-without-footer.component.ts b/src/stories/components/card/examples/card-without-footer.component.ts new file mode 100644 index 0000000..e412af6 --- /dev/null +++ b/src/stories/components/card/examples/card-without-footer.component.ts @@ -0,0 +1,63 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { SharedModule } from 'primeng/api'; +import { CardComponent } from '../../../../lib/components/card/card.component'; + +const template = ` +
+ + + Заголовок + + +

Карточка без футера.

+
+
+
+`; +const styles = ''; + +@Component({ + selector: 'app-card-without-footer', + standalone: true, + imports: [CardComponent, SharedModule], + template, + styles, +}) +export class CardWithoutFooterComponent {} + +export const WithoutFooter: StoryObj = { + render: () => ({ + template: ``, + }), + 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: \` + + + Заголовок + + +

Карточка без футера.

+
+
+ \`, +}) +export class CardWithoutFooterComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/card/examples/card-without-header.component.ts b/src/stories/components/card/examples/card-without-header.component.ts new file mode 100644 index 0000000..e7a9625 --- /dev/null +++ b/src/stories/components/card/examples/card-without-header.component.ts @@ -0,0 +1,64 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { SharedModule } from 'primeng/api'; +import { CardComponent } from '../../../../lib/components/card/card.component'; +import { ButtonComponent } from '../../../../lib/components/button/button.component'; + +const template = ` +
+ + +

Карточка без изображения в шапке.

+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-card-without-header', + standalone: true, + imports: [CardComponent, ButtonComponent, SharedModule], + template, + styles, +}) +export class CardWithoutHeaderComponent {} + +export const WithoutHeader: StoryObj = { + render: () => ({ + template: ``, + }), + 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: \` + + +

Карточка без изображения в шапке.

+
+ + + +
+ \`, +}) +export class CardWithoutHeaderComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/card/examples/card-without-subtitle.component.ts b/src/stories/components/card/examples/card-without-subtitle.component.ts new file mode 100644 index 0000000..0d7d91a --- /dev/null +++ b/src/stories/components/card/examples/card-without-subtitle.component.ts @@ -0,0 +1,70 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { SharedModule } from 'primeng/api'; +import { CardComponent } from '../../../../lib/components/card/card.component'; +import { ButtonComponent } from '../../../../lib/components/button/button.component'; + +const template = ` +
+ + + Заголовок + + +

Карточка без подзаголовка.

+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-card-without-subtitle', + standalone: true, + imports: [CardComponent, ButtonComponent, SharedModule], + template, + styles, +}) +export class CardWithoutSubtitleComponent {} + +export const WithoutSubtitle: StoryObj = { + render: () => ({ + template: ``, + }), + 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: \` + + + Заголовок + + +

Карточка без подзаголовка.

+
+ + + +
+ \`, +}) +export class CardWithoutSubtitleComponent {} + `, + }, + }, + }, +};