From 3f1f6197def73b0166f0fba18731de27ff20c1ce Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Tue, 7 Apr 2026 17:42:43 +0700 Subject: [PATCH 1/2] =?UTF-8?q?rating:=20=D1=81=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D1=81=D1=8B,=20=D0=BE=D0=B1=D1=91=D1=80=D1=82=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/components/rating/rating.component.ts | 68 ++++++++++ .../examples/rating-disabled.component.ts | 51 ++++++++ .../examples/rating-readonly.component.ts | 51 ++++++++ .../components/rating/rating.stories.ts | 120 ++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 src/lib/components/rating/rating.component.ts create mode 100644 src/stories/components/rating/examples/rating-disabled.component.ts create mode 100644 src/stories/components/rating/examples/rating-readonly.component.ts create mode 100644 src/stories/components/rating/rating.stories.ts diff --git a/src/lib/components/rating/rating.component.ts b/src/lib/components/rating/rating.component.ts new file mode 100644 index 00000000..cbb6a242 --- /dev/null +++ b/src/lib/components/rating/rating.component.ts @@ -0,0 +1,68 @@ +import { Component, EventEmitter, Input, Output, forwardRef } from '@angular/core'; +import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Rating } from 'primeng/rating'; + +export type RatingValue = number | null; + +@Component({ + selector: 'rating', + standalone: true, + imports: [Rating, FormsModule], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RatingComponent), + multi: true, + }, + ], + template: ` + + `, +}) +export class RatingComponent implements ControlValueAccessor { + @Input() stars = 5; + @Input() readonly = false; + @Input() disabled = false; + @Input() autofocus = false; + + @Output() onRate = new EventEmitter(); + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + modelValue: RatingValue = null; + + private onChange: (value: RatingValue) => void = () => {}; + private onTouched: () => void = () => {}; + + handleChange(value: RatingValue): void { + this.modelValue = value; + this.onChange(value); + this.onTouched(); + } + + writeValue(value: RatingValue): void { + this.modelValue = value; + } + + registerOnChange(fn: (value: RatingValue) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/stories/components/rating/examples/rating-disabled.component.ts b/src/stories/components/rating/examples/rating-disabled.component.ts new file mode 100644 index 00000000..e2bf475b --- /dev/null +++ b/src/stories/components/rating/examples/rating-disabled.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { RatingComponent } from '../../../../lib/components/rating/rating.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-rating-disabled', + standalone: true, + imports: [RatingComponent, FormsModule], + template, +}) +export class RatingDisabledComponent { + value = 2; +} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Заблокированное состояние — компонент недоступен для взаимодействия.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { RatingComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-rating-disabled', + standalone: true, + imports: [RatingComponent, FormsModule], + template: \` + + \`, +}) +export class RatingDisabledComponent { + value = 2; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/rating/examples/rating-readonly.component.ts b/src/stories/components/rating/examples/rating-readonly.component.ts new file mode 100644 index 00000000..6b82adaf --- /dev/null +++ b/src/stories/components/rating/examples/rating-readonly.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { RatingComponent } from '../../../../lib/components/rating/rating.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-rating-readonly', + standalone: true, + imports: [RatingComponent, FormsModule], + template, +}) +export class RatingReadonlyComponent { + value = 4; +} + +export const ReadOnly: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Режим только для чтения — значение отображается, но не может быть изменено.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { RatingComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-rating-readonly', + standalone: true, + imports: [RatingComponent, FormsModule], + template: \` + + \`, +}) +export class RatingReadonlyComponent { + value = 4; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/rating/rating.stories.ts b/src/stories/components/rating/rating.stories.ts new file mode 100644 index 00000000..39605e07 --- /dev/null +++ b/src/stories/components/rating/rating.stories.ts @@ -0,0 +1,120 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { RatingComponent } from '../../../lib/components/rating/rating.component'; +import { RatingReadonlyComponent, ReadOnly } from './examples/rating-readonly.component'; +import { RatingDisabledComponent, Disabled } from './examples/rating-disabled.component'; + +type RatingArgs = RatingComponent; + +const meta: Meta = { + title: 'Prime/Form/Rating', + component: RatingComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + RatingComponent, + FormsModule, + RatingReadonlyComponent, + RatingDisabledComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-rating' }, + docs: { + description: { + component: `Компонент для отображения и выбора оценки в виде звёзд.`, + }, + }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + stars: { + control: 'number', + description: 'Количество отображаемых звёзд', + table: { + category: 'Props', + defaultValue: { summary: '5' }, + type: { summary: 'number' }, + }, + }, + readonly: { + control: 'boolean', + description: 'Режим только для чтения — значение нельзя изменить', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает возможность взаимодействия', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + autofocus: { table: { disable: true } }, + + // ── Events ─────────────────────────────────────────────── + onRate: { + control: false, + description: 'Событие изменения оценки', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onFocus: { + control: false, + description: 'Событие фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onBlur: { + control: false, + description: 'Событие потери фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, + args: { + stars: 5, + readonly: false, + disabled: false, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = [`[(ngModel)]="value"`]; + if (args.stars !== 5) parts.push(`[stars]="${args.stars}"`); + if (args.readonly) parts.push(`[readonly]="true"`); + if (args.disabled) parts.push(`[disabled]="true"`); + + const template = ``; + return { props: { ...args, value: 3 }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { ReadOnly, Disabled }; From e1b38eafdd8b548dd6afcb657017df6fc0e8af98 Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Tue, 7 Apr 2026 21:09:59 +0700 Subject: [PATCH 2/2] =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=BF=D0=BF=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/components/rating/rating.stories.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stories/components/rating/rating.stories.ts b/src/stories/components/rating/rating.stories.ts index 39605e07..2d544ebe 100644 --- a/src/stories/components/rating/rating.stories.ts +++ b/src/stories/components/rating/rating.stories.ts @@ -7,7 +7,7 @@ import { RatingDisabledComponent, Disabled } from './examples/rating-disabled.co type RatingArgs = RatingComponent; const meta: Meta = { - title: 'Prime/Form/Rating', + title: 'Components/Form/Rating', component: RatingComponent, tags: ['autodocs'], decorators: [