From 27664721489c159252dc38ba013397f698ba3ec7 Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 20 Apr 2026 15:25:04 +0700 Subject: [PATCH 1/8] =?UTF-8?q?password:=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F,=20=D1=81=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D1=81=D1=8B,=20=D0=BE=D0=B1=D1=91=D1=80=D1=82=D0=B8?= =?UTF-8?q?=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/password/password.component.ts | 99 ++++++++++ src/prime-preset/map-tokens.ts | 5 + .../tokens/components/password.ts | 56 ++++++ .../examples/password-disabled.component.ts | 51 +++++ .../examples/password-feedback.component.ts | 51 +++++ .../password-float-label.component.ts | 98 ++++++++++ .../examples/password-invalid.component.ts | 51 +++++ .../examples/password-toggle.component.ts | 51 +++++ .../components/password/password.stories.ts | 179 ++++++++++++++++++ 9 files changed, 641 insertions(+) create mode 100644 src/lib/components/password/password.component.ts create mode 100644 src/prime-preset/tokens/components/password.ts create mode 100644 src/stories/components/password/examples/password-disabled.component.ts create mode 100644 src/stories/components/password/examples/password-feedback.component.ts create mode 100644 src/stories/components/password/examples/password-float-label.component.ts create mode 100644 src/stories/components/password/examples/password-invalid.component.ts create mode 100644 src/stories/components/password/examples/password-toggle.component.ts create mode 100644 src/stories/components/password/password.stories.ts diff --git a/src/lib/components/password/password.component.ts b/src/lib/components/password/password.component.ts new file mode 100644 index 00000000..2568dd5c --- /dev/null +++ b/src/lib/components/password/password.component.ts @@ -0,0 +1,99 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, forwardRef } from '@angular/core'; +import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Password } from 'primeng/password'; + +export type PasswordSize = 'small' | 'base' | 'large' | 'xlarge'; + +@Component({ + selector: 'password', + host: { style: 'display: block' }, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [Password, FormsModule], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => PasswordComponent), + multi: true, + }, + ], + template: ` + + `, +}) +export class PasswordComponent implements ControlValueAccessor { + @Input() feedback = true; + @Input() toggleMask = false; + @Input() disabled = false; + @Input() placeholder: string | undefined = undefined; + @Input() size: PasswordSize = 'base'; + @Input() variant: 'filled' | 'outlined' = 'outlined'; + @Input() fluid = false; + @Input() invalid = false; + @Input() inputId: string | undefined = undefined; + @Input() inputStyleClass: string | undefined = undefined; + @Input() ariaLabel: string | undefined = undefined; + @Input() ariaLabelledBy: string | undefined = undefined; + @Input() autofocus = false; + + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + get primeSize(): 'small' | 'large' | undefined { + if (this.size === 'small') return 'small'; + if (this.size === 'large' || this.size === 'xlarge') return 'large'; + return undefined; + } + + get sizeClass(): string { + return this.size === 'xlarge' ? 'p-inputtext-xlg' : ''; + } + + get computedInputStyleClass(): string { + return [this.sizeClass, this.inputStyleClass].filter(Boolean).join(' '); + } + + modelValue: string | null = null; + + private _onChange: (value: string | null) => void = () => {}; + private _onTouched: () => void = () => {}; + + handleChange(value: string | null): void { + this.modelValue = value; + this._onChange(value); + this._onTouched(); + } + + writeValue(value: string | null): void { + this.modelValue = value; + } + + registerOnChange(fn: (value: string | null) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/prime-preset/map-tokens.ts b/src/prime-preset/map-tokens.ts index d0a44965..631c7f97 100644 --- a/src/prime-preset/map-tokens.ts +++ b/src/prime-preset/map-tokens.ts @@ -11,6 +11,7 @@ import { inputtextCss } from './tokens/components/inputtext'; import { progressspinnerCss } from './tokens/components/progressspinner'; import { tagCss } from './tokens/components/tag'; import { timelineCss } from './tokens/components/timeline'; +import { passwordCss } from './tokens/components/password'; import { tooltipCss } from './tokens/components/tooltip'; import { megamenuCss } from './tokens/components/megamenu'; @@ -35,6 +36,10 @@ const presetTokens: Preset = { ...(tokens.components.button as unknown as ComponentsDesignTokens['button']), css: buttonCss, }, + password: { + ...(tokens.components.password as unknown as ComponentsDesignTokens['password']), + css: passwordCss, + }, progressspinner: { ...(tokens.components.progressspinner as unknown as ComponentsDesignTokens['progressspinner']), css: progressspinnerCss, diff --git a/src/prime-preset/tokens/components/password.ts b/src/prime-preset/tokens/components/password.ts new file mode 100644 index 00000000..01100dff --- /dev/null +++ b/src/prime-preset/tokens/components/password.ts @@ -0,0 +1,56 @@ +/** + * Кастомная CSS-стилизация для компонента p-password. + * Подключается в map-tokens.ts: `import { passwordCss } from './components/password'` + */ +export const passwordCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Иконки управления ─── */ + .p-password-toggle-mask-icon, + .p-icon.p-password-toggle-mask-icon.p-password-unmask-icon { + cursor: pointer; + } + + /* ─── Оверлей и индикатор ─── */ + .p-password-overlay { + border-width: ${dt('password.extend.borderWidth')}; + } + + .p-password-meter-text { + color: ${dt('text.color')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.medium')}; + } + + /* ─── Кастомный контент (правила пароля) ─── */ + .p-password-rules { + display: flex; + flex-direction: column; + gap: ${dt('password.content.gap')}; + margin: 0; + padding: 0; + list-style: none; + } + + .p-password-rule { + display: flex; + align-items: center; + gap: ${dt('content.gap.200')}; + font-size: ${dt('fonts.fontSize.100')}; + } + + /* ─── Состояния иконок правил ─── */ + .p-password-rule i { + font-size: ${dt('fonts.fontSize.200')}; + } + + .p-password-rule .ti-circle { + color: ${dt('surface.400')}; + } + + .p-password-rule .ti-circle-check { + color: ${dt('password.colorScheme.light.strength.strongBackground')}; + } + + .p-password-rule .ti-circle-x { + color: ${dt('password.colorScheme.light.strength.weakBackground')}; + } +`; diff --git a/src/stories/components/password/examples/password-disabled.component.ts b/src/stories/components/password/examples/password-disabled.component.ts new file mode 100644 index 00000000..4c13c6df --- /dev/null +++ b/src/stories/components/password/examples/password-disabled.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { PasswordComponent } from '../../../../lib/components/password/password.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-password-disabled', + standalone: true, + imports: [PasswordComponent, FormsModule], + template, +}) +export class PasswordDisabledComponent { + value: string | null = 'secret123'; +} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Поле ввода пароля в отключённом состоянии.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { PasswordComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-password-disabled', + standalone: true, + imports: [PasswordComponent, FormsModule], + template: \` + + \`, +}) +export class PasswordDisabledComponent { + value: string | null = 'secret123'; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/examples/password-feedback.component.ts b/src/stories/components/password/examples/password-feedback.component.ts new file mode 100644 index 00000000..829aacf4 --- /dev/null +++ b/src/stories/components/password/examples/password-feedback.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { PasswordComponent } from '../../../../lib/components/password/password.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-password-feedback', + standalone: true, + imports: [PasswordComponent, FormsModule], + template, +}) +export class PasswordFeedbackComponent { + value: string | null = null; +} + +export const Feedback: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Индикатор надёжности пароля с визуальной шкалой (слабый / средний / сильный).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { PasswordComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-password-feedback', + standalone: true, + imports: [PasswordComponent, FormsModule], + template: \` + + \`, +}) +export class PasswordFeedbackComponent { + value: string | null = null; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/examples/password-float-label.component.ts b/src/stories/components/password/examples/password-float-label.component.ts new file mode 100644 index 00000000..ca9a37e9 --- /dev/null +++ b/src/stories/components/password/examples/password-float-label.component.ts @@ -0,0 +1,98 @@ +import { Component, Input } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { Password } from 'primeng/password'; +import { FloatLabel as PrimeFloatLabel } from 'primeng/floatlabel'; + +@Component({ + selector: 'app-password-float-label', + standalone: true, + imports: [Password, PrimeFloatLabel, FormsModule], + template: ` +
+ + + + +
+ `, +}) +export class PasswordFloatLabelComponent { + @Input() feedback = true; + @Input() toggleMask = false; + @Input() disabled = false; + @Input() invalid = false; + @Input() fluid = false; + @Input() placeholder: string | undefined = undefined; + value = ''; +} + +export const FloatLabel: StoryObj = { + name: 'FloatLabel', + render: (args) => { + const parts: string[] = []; + + if (!args.feedback) parts.push(`[feedback]="false"`); + if (args.toggleMask) parts.push(`[toggleMask]="true"`); + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.invalid) parts.push(`[invalid]="true"`); + if (args.fluid) parts.push(`[fluid]="true"`); + + const attrs = parts.length ? `\n ${parts.join('\n ')}` : ''; + + return { + props: args, + template: ``, + }; + }, + args: { + feedback: true, + toggleMask: false, + placeholder: undefined, + disabled: false, + invalid: false, + fluid: false, + }, + parameters: { + docs: { + description: { + story: + 'Интеграция с `p-floatlabel` — плавающая метка внутри поля. Кликните на поле чтобы увидеть анимацию.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { Password } from 'primeng/password'; +import { FloatLabel } from 'primeng/floatlabel'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-password-float-label', + standalone: true, + imports: [Password, FloatLabel, FormsModule], + template: \` + + + + + \`, +}) +export class PasswordFloatLabelComponent { + value = ''; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/examples/password-invalid.component.ts b/src/stories/components/password/examples/password-invalid.component.ts new file mode 100644 index 00000000..dace649f --- /dev/null +++ b/src/stories/components/password/examples/password-invalid.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { PasswordComponent } from '../../../../lib/components/password/password.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-password-invalid', + standalone: true, + imports: [PasswordComponent, FormsModule], + template, +}) +export class PasswordInvalidComponent { + value: string | null = null; +} + +export const Invalid: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Поле ввода пароля в состоянии ошибки валидации.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { PasswordComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-password-invalid', + standalone: true, + imports: [PasswordComponent, FormsModule], + template: \` + + \`, +}) +export class PasswordInvalidComponent { + value: string | null = null; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/examples/password-toggle.component.ts b/src/stories/components/password/examples/password-toggle.component.ts new file mode 100644 index 00000000..0fdb915e --- /dev/null +++ b/src/stories/components/password/examples/password-toggle.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { PasswordComponent } from '../../../../lib/components/password/password.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-password-toggle', + standalone: true, + imports: [PasswordComponent, FormsModule], + template, +}) +export class PasswordToggleComponent { + value: string | null = null; +} + +export const ToggleMask: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Возможность показать/скрыть введённый пароль по иконке.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { PasswordComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-password-toggle', + standalone: true, + imports: [PasswordComponent, FormsModule], + template: \` + + \`, +}) +export class PasswordToggleComponent { + value: string | null = null; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/password.stories.ts b/src/stories/components/password/password.stories.ts new file mode 100644 index 00000000..3dc2c98d --- /dev/null +++ b/src/stories/components/password/password.stories.ts @@ -0,0 +1,179 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { PasswordComponent } from '../../../lib/components/password/password.component'; +import { PasswordToggleComponent, ToggleMask } from './examples/password-toggle.component'; +import { PasswordFeedbackComponent, Feedback } from './examples/password-feedback.component'; +import { PasswordDisabledComponent, Disabled } from './examples/password-disabled.component'; +import { PasswordInvalidComponent, Invalid } from './examples/password-invalid.component'; +import { PasswordFloatLabelComponent, FloatLabel } from './examples/password-float-label.component'; + +const meta: Meta = { + title: 'Components/Form/Password', + component: PasswordComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + PasswordComponent, + FormsModule, + PasswordToggleComponent, + PasswordFeedbackComponent, + PasswordDisabledComponent, + PasswordInvalidComponent, + PasswordFloatLabelComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-password' }, + docs: { + description: { + component: `Поле ввода пароля с поддержкой индикатора надёжности и переключения видимости. + +\`\`\`typescript +import { PasswordComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + }, + argTypes: { + feedback: { + control: 'boolean', + description: 'Показывать индикатор надёжности пароля', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + toggleMask: { + control: 'boolean', + description: 'Возможность показать/скрыть пароль', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + placeholder: { + control: 'text', + description: 'Текст-подсказка', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' }, + }, + }, + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'], + description: 'Размер поля', + table: { + category: 'Props', + defaultValue: { summary: 'base' }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает возможность взаимодействия', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + invalid: { + control: 'boolean', + description: 'Подсвечивает поле как невалидное', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + fluid: { + control: 'boolean', + description: 'Растягивает поле на всю ширину контейнера', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + // Hidden props + variant: { table: { disable: true } }, + inputId: { table: { disable: true } }, + inputStyleClass: { table: { disable: true } }, + ariaLabel: { table: { disable: true } }, + ariaLabelledBy: { table: { disable: true } }, + autofocus: { table: { disable: true } }, + primeSize: { table: { disable: true } }, + sizeClass: { table: { disable: true } }, + computedInputStyleClass: { table: { disable: true } }, + + // Events + onFocus: { + control: false, + description: 'Событие фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onBlur: { + control: false, + description: 'Событие потери фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, + args: { + feedback: true, + toggleMask: false, + placeholder: 'Введите пароль', + size: 'base', + disabled: false, + invalid: false, + fluid: false, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (!args.feedback) parts.push(`[feedback]="false"`); + if (args.toggleMask) parts.push(`[toggleMask]="true"`); + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`); + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.invalid) parts.push(`[invalid]="true"`); + if (args.fluid) parts.push(`[fluid]="true"`); + + parts.push(`[(ngModel)]="value"`); + + const template = parts.length > 1 + ? `` + : ``; + + return { props: { ...args, value: null }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { ToggleMask, Feedback, Disabled, Invalid, FloatLabel }; From 6256c6d4b4af6d72de6530198f420b70ecbe2d67 Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 20 Apr 2026 15:39:30 +0700 Subject: [PATCH 2/8] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/password/password.component.ts | 19 ++-- .../examples/password-template.component.ts | 98 +++++++++++++++++++ .../components/password/password.stories.ts | 22 +++-- 3 files changed, 124 insertions(+), 15 deletions(-) create mode 100644 src/stories/components/password/examples/password-template.component.ts diff --git a/src/lib/components/password/password.component.ts b/src/lib/components/password/password.component.ts index 2568dd5c..22f93645 100644 --- a/src/lib/components/password/password.component.ts +++ b/src/lib/components/password/password.component.ts @@ -26,6 +26,10 @@ export type PasswordSize = 'small' | 'base' | 'large' | 'xlarge'; [inputStyleClass]="computedInputStyleClass" [disabled]="disabled" [placeholder]="placeholder" + [promptLabel]="promptLabel" + [weakLabel]="weakLabel" + [mediumLabel]="mediumLabel" + [strongLabel]="strongLabel" [variant]="variant" [fluid]="fluid" [invalid]="invalid" @@ -47,6 +51,10 @@ export class PasswordComponent implements ControlValueAccessor { @Input() variant: 'filled' | 'outlined' = 'outlined'; @Input() fluid = false; @Input() invalid = false; + @Input() promptLabel = 'Введите пароль'; + @Input() weakLabel = 'Слабый'; + @Input() mediumLabel = 'Средний'; + @Input() strongLabel = 'Надёжный'; @Input() inputId: string | undefined = undefined; @Input() inputStyleClass: string | undefined = undefined; @Input() ariaLabel: string | undefined = undefined; @@ -56,14 +64,11 @@ export class PasswordComponent implements ControlValueAccessor { @Output() onFocus = new EventEmitter(); @Output() onBlur = new EventEmitter(); - get primeSize(): 'small' | 'large' | undefined { - if (this.size === 'small') return 'small'; - if (this.size === 'large' || this.size === 'xlarge') return 'large'; - return undefined; - } - get sizeClass(): string { - return this.size === 'xlarge' ? 'p-inputtext-xlg' : ''; + if (this.size === 'small') return 'p-inputtext-sm'; + if (this.size === 'large') return 'p-inputtext-lg'; + if (this.size === 'xlarge') return 'p-inputtext-lg p-inputtext-xlg'; + return ''; } get computedInputStyleClass(): string { diff --git a/src/stories/components/password/examples/password-template.component.ts b/src/stories/components/password/examples/password-template.component.ts new file mode 100644 index 00000000..5d761fa0 --- /dev/null +++ b/src/stories/components/password/examples/password-template.component.ts @@ -0,0 +1,98 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { Password } from 'primeng/password'; +import { Divider } from 'primeng/divider'; + +@Component({ + selector: 'app-password-template', + standalone: true, + imports: [Password, Divider, FormsModule], + template: ` +
+ + +
Создание пароля
+
+ + +
    +
  • Минимум одна строчная буква
  • +
  • Минимум одна заглавная буква
  • +
  • Минимум одна цифра
  • +
  • Не менее 8 символов
  • +
+
+
+
+ `, +}) +export class PasswordTemplateComponent { + value: string | null = null; +} + +export const Template: StoryObj = { + name: 'Template', + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Кастомный контент через `ng-template`: заголовок, разделитель и список требований к паролю.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { Password } from 'primeng/password'; +import { Divider } from 'primeng/divider'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-password-template', + standalone: true, + imports: [Password, Divider, FormsModule], + template: \` + + +
Создание пароля
+
+ + +
    +
  • Минимум одна строчная буква
  • +
  • Минимум одна заглавная буква
  • +
  • Минимум одна цифра
  • +
  • Не менее 8 символов
  • +
+
+
+ \`, +}) +export class PasswordTemplateComponent { + value: string | null = null; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/password.stories.ts b/src/stories/components/password/password.stories.ts index 3dc2c98d..4008f882 100644 --- a/src/stories/components/password/password.stories.ts +++ b/src/stories/components/password/password.stories.ts @@ -6,6 +6,7 @@ import { PasswordFeedbackComponent, Feedback } from './examples/password-feedbac import { PasswordDisabledComponent, Disabled } from './examples/password-disabled.component'; import { PasswordInvalidComponent, Invalid } from './examples/password-invalid.component'; import { PasswordFloatLabelComponent, FloatLabel } from './examples/password-float-label.component'; +import { PasswordTemplateComponent, Template } from './examples/password-template.component'; const meta: Meta = { title: 'Components/Form/Password', @@ -21,6 +22,7 @@ const meta: Meta = { PasswordDisabledComponent, PasswordInvalidComponent, PasswordFloatLabelComponent, + PasswordTemplateComponent, ], }), ], @@ -34,6 +36,7 @@ const meta: Meta = { import { PasswordComponent } from '@cdek-it/angular-ui-kit'; \`\`\``, }, + story: { height: '280px' }, }, }, argTypes: { @@ -103,12 +106,15 @@ import { PasswordComponent } from '@cdek-it/angular-ui-kit'; }, // Hidden props variant: { table: { disable: true } }, + promptLabel: { table: { disable: true } }, + weakLabel: { table: { disable: true } }, + mediumLabel: { table: { disable: true } }, + strongLabel: { table: { disable: true } }, inputId: { table: { disable: true } }, inputStyleClass: { table: { disable: true } }, ariaLabel: { table: { disable: true } }, ariaLabelledBy: { table: { disable: true } }, autofocus: { table: { disable: true } }, - primeSize: { table: { disable: true } }, sizeClass: { table: { disable: true } }, computedInputStyleClass: { table: { disable: true } }, @@ -150,13 +156,13 @@ export const Default: Story = { render: (args) => { const parts: string[] = []; - if (!args.feedback) parts.push(`[feedback]="false"`); - if (args.toggleMask) parts.push(`[toggleMask]="true"`); - if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + if (!args.feedback) parts.push(`[feedback]="false"`); + if (args.toggleMask) parts.push(`[toggleMask]="true"`); + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`); - if (args.disabled) parts.push(`[disabled]="true"`); - if (args.invalid) parts.push(`[invalid]="true"`); - if (args.fluid) parts.push(`[fluid]="true"`); + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.invalid) parts.push(`[invalid]="true"`); + if (args.fluid) parts.push(`[fluid]="true"`); parts.push(`[(ngModel)]="value"`); @@ -176,4 +182,4 @@ export const Default: Story = { }; // ── Re-exports from example components ──────────────────────────────────── -export { ToggleMask, Feedback, Disabled, Invalid, FloatLabel }; +export { ToggleMask, Feedback, Disabled, Invalid, FloatLabel, Template }; From 7591ddbed0a33975135521d115fd42dc03c3b67a Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 27 Apr 2026 22:09:25 +0700 Subject: [PATCH 3/8] =?UTF-8?q?password:=20=D1=86=D0=B2=D0=B5=D1=82=20mete?= =?UTF-8?q?r-text=20=D0=BD=D0=B0=20text.mutedColor,=20focus=20box-shadow,?= =?UTF-8?q?=20invalid=20focus,=20floatlabel=20=D1=81=D1=82=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tokens/components/password.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/prime-preset/tokens/components/password.ts b/src/prime-preset/tokens/components/password.ts index 01100dff..2bb0a00c 100644 --- a/src/prime-preset/tokens/components/password.ts +++ b/src/prime-preset/tokens/components/password.ts @@ -15,11 +15,45 @@ export const passwordCss = ({ dt }: { dt: (token: string) => string }): string = } .p-password-meter-text { - color: ${dt('text.color')}; + color: ${dt('text.mutedColor')}; font-size: ${dt('fonts.fontSize.200')}; font-weight: ${dt('fonts.fontWeight.medium')}; } + /* ─── Focus ─── */ + .p-password:has(.p-inputtext:enabled:focus) { + box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('inputtext.focusRing.color')}; + border-radius: ${dt('inputtext.root.borderRadius')}; + } + + /* ─── Invalid + Focus ─── */ + .p-password:has(.p-inputtext.p-invalid:focus) { + box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('focusRing.extend.invalid')}; + border-radius: ${dt('inputtext.root.borderRadius')}; + } + + .p-password:has(.p-inputtext.p-invalid:focus) .p-inputtext { + border-color: ${dt('inputtext.root.invalidBorderColor')}; + } + + /* ─── FloatLabel ─── */ + .p-floatlabel:has(.p-password) label { + font-family: ${dt('fonts.fontFamily.base')}; + font-weight: ${dt('floatlabel.root.fontWeight')}; + line-height: ${dt('fonts.lineHeight.250')}; + color: ${dt('floatlabel.root.color')}; + } + + .p-floatlabel:has(.p-password) .p-floatlabel-active label { + font-weight: ${dt('floatlabel.root.active.fontWeight')}; + } + + .p-floatlabel-in .p-password .p-inputtext { + font-family: ${dt('fonts.fontFamily.base')}; + padding-block-start: ${dt('floatlabel.in.input.paddingTop')}; + padding-block-end: ${dt('floatlabel.in.input.paddingBottom')}; + } + /* ─── Кастомный контент (правила пароля) ─── */ .p-password-rules { display: flex; From 2707e745b82cefa2ee3d3202f9a6ad6889cd0ad7 Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 27 Apr 2026 22:09:29 +0700 Subject: [PATCH 4/8] =?UTF-8?q?password:=20floatLabel=20=D0=B8=20label=20p?= =?UTF-8?q?rops=20=D1=81=20p-floatlabel=20=D0=BE=D0=B1=D1=91=D1=80=D1=82?= =?UTF-8?q?=D0=BA=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/password/password.component.ts | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/src/lib/components/password/password.component.ts b/src/lib/components/password/password.component.ts index 22f93645..9dba3fc4 100644 --- a/src/lib/components/password/password.component.ts +++ b/src/lib/components/password/password.component.ts @@ -1,6 +1,8 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, forwardRef } from '@angular/core'; import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NgTemplateOutlet } from '@angular/common'; import { Password } from 'primeng/password'; +import { FloatLabel } from 'primeng/floatlabel'; export type PasswordSize = 'small' | 'base' | 'large' | 'xlarge'; @@ -9,7 +11,7 @@ export type PasswordSize = 'small' | 'base' | 'large' | 'xlarge'; host: { style: 'display: block' }, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [Password, FormsModule], + imports: [Password, FormsModule, FloatLabel, NgTemplateOutlet], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -18,28 +20,39 @@ export type PasswordSize = 'small' | 'base' | 'large' | 'xlarge'; }, ], template: ` - + @if (floatLabel) { + + + + + } @else { + + } + + + + `, }) export class PasswordComponent implements ControlValueAccessor { @@ -51,6 +64,8 @@ export class PasswordComponent implements ControlValueAccessor { @Input() variant: 'filled' | 'outlined' = 'outlined'; @Input() fluid = false; @Input() invalid = false; + @Input() floatLabel = false; + @Input() label = ''; @Input() promptLabel = 'Введите пароль'; @Input() weakLabel = 'Слабый'; @Input() mediumLabel = 'Средний'; From ec63673825b7bac687c35eb338aee44f86062b9f Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 27 Apr 2026 22:09:34 +0700 Subject: [PATCH 5/8] =?UTF-8?q?password:=20=D1=81=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=20FloatLabel=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=91=D1=80=D1=82=D0=BA=D1=83,=20argTypes=20floatLabel=20?= =?UTF-8?q?=D0=B8=20label?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../password-float-label.component.ts | 58 ++++++------------- .../components/password/password.stories.ts | 22 +++++++ 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/stories/components/password/examples/password-float-label.component.ts b/src/stories/components/password/examples/password-float-label.component.ts index ca9a37e9..e75551fd 100644 --- a/src/stories/components/password/examples/password-float-label.component.ts +++ b/src/stories/components/password/examples/password-float-label.component.ts @@ -1,28 +1,26 @@ import { Component, Input } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { StoryObj } from '@storybook/angular'; -import { Password } from 'primeng/password'; -import { FloatLabel as PrimeFloatLabel } from 'primeng/floatlabel'; +import { PasswordComponent } from '../../../../lib/components/password/password.component'; @Component({ selector: 'app-password-float-label', standalone: true, - imports: [Password, PrimeFloatLabel, FormsModule], + imports: [PasswordComponent, FormsModule], template: `
- - - - +
`, }) @@ -33,6 +31,7 @@ export class PasswordFloatLabelComponent { @Input() invalid = false; @Input() fluid = false; @Input() placeholder: string | undefined = undefined; + @Input() label = 'Пароль'; value = ''; } @@ -62,36 +61,17 @@ export const FloatLabel: StoryObj = { disabled: false, invalid: false, fluid: false, + label: 'Пароль', }, parameters: { docs: { description: { story: - 'Интеграция с `p-floatlabel` — плавающая метка внутри поля. Кликните на поле чтобы увидеть анимацию.', + 'Интеграция с `floatLabel` — плавающая метка внутри поля. Кликните на поле чтобы увидеть анимацию.', }, source: { - language: 'ts', - code: ` -import { Component } from '@angular/core'; -import { Password } from 'primeng/password'; -import { FloatLabel } from 'primeng/floatlabel'; -import { FormsModule } from '@angular/forms'; - -@Component({ - selector: 'app-password-float-label', - standalone: true, - imports: [Password, FloatLabel, FormsModule], - template: \` - - - - - \`, -}) -export class PasswordFloatLabelComponent { - value = ''; -} - `, + language: 'html', + code: ``, }, }, }, diff --git a/src/stories/components/password/password.stories.ts b/src/stories/components/password/password.stories.ts index 4008f882..22960f5a 100644 --- a/src/stories/components/password/password.stories.ts +++ b/src/stories/components/password/password.stories.ts @@ -104,6 +104,24 @@ import { PasswordComponent } from '@cdek-it/angular-ui-kit'; type: { summary: 'boolean' }, }, }, + floatLabel: { + control: 'boolean', + description: 'Плавающая метка внутри поля', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + label: { + control: 'text', + description: 'Текст плавающей метки (используется с floatLabel)', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, // Hidden props variant: { table: { disable: true } }, promptLabel: { table: { disable: true } }, @@ -144,6 +162,8 @@ import { PasswordComponent } from '@cdek-it/angular-ui-kit'; disabled: false, invalid: false, fluid: false, + floatLabel: false, + label: '', }, }; @@ -163,6 +183,8 @@ export const Default: Story = { if (args.disabled) parts.push(`[disabled]="true"`); if (args.invalid) parts.push(`[invalid]="true"`); if (args.fluid) parts.push(`[fluid]="true"`); + if (args.floatLabel) parts.push(`[floatLabel]="true"`); + if (args.label) parts.push(`label="${args.label}"`); parts.push(`[(ngModel)]="value"`); From 21de7851eef7c21325cd35f6ac42cf46c20b9cea Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Mon, 27 Apr 2026 22:22:18 +0700 Subject: [PATCH 6/8] =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BB=D0=B5=D0=B9=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=BC=20password?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/prime-preset/map-tokens.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/prime-preset/map-tokens.ts b/src/prime-preset/map-tokens.ts index ce029c7e..434098f8 100644 --- a/src/prime-preset/map-tokens.ts +++ b/src/prime-preset/map-tokens.ts @@ -9,6 +9,7 @@ import { cardCss } from './tokens/components/card'; import { checkboxCss } from './tokens/components/checkbox'; import { inputtextCss } from './tokens/components/inputtext'; import { progressspinnerCss } from './tokens/components/progressspinner'; +import { passwordCss } from './tokens/components/password'; import { tagCss } from './tokens/components/tag'; import { textareaCss } from './tokens/components/textarea'; import { tooltipCss } from './tokens/components/tooltip'; From fcf206aa4d92ef34775a93591defda2e66753278 Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Tue, 28 Apr 2026 18:57:11 +0700 Subject: [PATCH 7/8] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=8C=D1=8E=20=D1=81=D1=82=D0=BE=D1=80=D0=B8=D1=81?= =?UTF-8?q?=D0=BE=D0=B2;=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/password/password.component.ts | 23 +++- .../tokens/components/password.ts | 15 ++- src/prime-preset/tokens/tokens.json | 10 ++ .../examples/password-template.component.ts | 110 +++++++++++++----- .../components/password/password.stories.ts | 4 + 5 files changed, 124 insertions(+), 38 deletions(-) diff --git a/src/lib/components/password/password.component.ts b/src/lib/components/password/password.component.ts index 9dba3fc4..2b1ebe0e 100644 --- a/src/lib/components/password/password.component.ts +++ b/src/lib/components/password/password.component.ts @@ -1,7 +1,8 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, forwardRef } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ContentChild, EventEmitter, Input, Output, TemplateRef, forwardRef } from '@angular/core'; import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import { NgTemplateOutlet } from '@angular/common'; import { Password } from 'primeng/password'; +import { PrimeTemplate } from 'primeng/api'; import { FloatLabel } from 'primeng/floatlabel'; export type PasswordSize = 'small' | 'base' | 'large' | 'xlarge'; @@ -11,7 +12,7 @@ export type PasswordSize = 'small' | 'base' | 'large' | 'xlarge'; host: { style: 'display: block' }, standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [Password, FormsModule, FloatLabel, NgTemplateOutlet], + imports: [Password, FormsModule, FloatLabel, NgTemplateOutlet, PrimeTemplate], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -48,14 +49,29 @@ export type PasswordSize = 'small' | 'base' | 'large' | 'xlarge'; [inputId]="inputId" [ariaLabel]="ariaLabel" [ariaLabelledBy]="ariaLabelledBy" + [appendTo]="appendTo" [autofocus]="autofocus" (onFocus)="onFocus.emit($event)" (onBlur)="onBlur.emit($event)" - > + > + @if (headerTemplate) { + + + + } + @if (footerTemplate) { + + + + } + `, }) export class PasswordComponent implements ControlValueAccessor { + @ContentChild('header') headerTemplate: TemplateRef | null = null; + @ContentChild('footer') footerTemplate: TemplateRef | null = null; + @Input() feedback = true; @Input() toggleMask = false; @Input() disabled = false; @@ -74,6 +90,7 @@ export class PasswordComponent implements ControlValueAccessor { @Input() inputStyleClass: string | undefined = undefined; @Input() ariaLabel: string | undefined = undefined; @Input() ariaLabelledBy: string | undefined = undefined; + @Input() appendTo: any = 'body'; @Input() autofocus = false; @Output() onFocus = new EventEmitter(); diff --git a/src/prime-preset/tokens/components/password.ts b/src/prime-preset/tokens/components/password.ts index 2bb0a00c..b682ad02 100644 --- a/src/prime-preset/tokens/components/password.ts +++ b/src/prime-preset/tokens/components/password.ts @@ -7,6 +7,7 @@ export const passwordCss = ({ dt }: { dt: (token: string) => string }): string = .p-password-toggle-mask-icon, .p-icon.p-password-toggle-mask-icon.p-password-unmask-icon { cursor: pointer; + color: ${dt('password.icon.color')}; } /* ─── Оверлей и индикатор ─── */ @@ -15,9 +16,11 @@ export const passwordCss = ({ dt }: { dt: (token: string) => string }): string = } .p-password-meter-text { - color: ${dt('text.mutedColor')}; + font-family: ${dt('fonts.fontFamily.base')}; font-size: ${dt('fonts.fontSize.200')}; - font-weight: ${dt('fonts.fontWeight.medium')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.250')}; + color: ${dt('password.overlay.color')}; } /* ─── Focus ─── */ @@ -67,8 +70,12 @@ export const passwordCss = ({ dt }: { dt: (token: string) => string }): string = .p-password-rule { display: flex; align-items: center; - gap: ${dt('content.gap.200')}; - font-size: ${dt('fonts.fontSize.100')}; + gap: ${dt('password.content.gap')}; + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.250')}; + color: ${dt('password.overlay.color')}; } /* ─── Состояния иконок правил ─── */ diff --git a/src/prime-preset/tokens/tokens.json b/src/prime-preset/tokens/tokens.json index 20c71fb1..d760d1d5 100644 --- a/src/prime-preset/tokens/tokens.json +++ b/src/prime-preset/tokens/tokens.json @@ -3960,6 +3960,16 @@ "icon": { "color": "{form.placeholderColor}" } + }, + "dark": { + "strength": { + "weakBackground": "{error.500}", + "mediumBackground": "{warn.500}", + "strongBackground": "{success.600}" + }, + "icon": { + "color": "{form.placeholderColor}" + } } }, "meter": { diff --git a/src/stories/components/password/examples/password-template.component.ts b/src/stories/components/password/examples/password-template.component.ts index 5d761fa0..063b24b5 100644 --- a/src/stories/components/password/examples/password-template.component.ts +++ b/src/stories/components/password/examples/password-template.component.ts @@ -1,16 +1,16 @@ import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { StoryObj } from '@storybook/angular'; -import { Password } from 'primeng/password'; +import { PasswordComponent } from '../../../../lib/components/password/password.component'; import { Divider } from 'primeng/divider'; @Component({ selector: 'app-password-template', standalone: true, - imports: [Password, Divider, FormsModule], + imports: [PasswordComponent, Divider, FormsModule], template: `
- - -
Создание пароля
-
-
    -
  • Минимум одна строчная буква
  • -
  • Минимум одна заглавная буква
  • -
  • Минимум одна цифра
  • -
  • Не менее 8 символов
  • -
+
+
+ + Минимум одна строчная буква +
+
+ + Минимум одна заглавная буква +
+
+ + Минимум одна цифра +
+
+ + Не менее 8 символов +
+
-
+
`, }) export class PasswordTemplateComponent { value: string | null = null; + + get hasLowercase(): boolean { + return /[a-z]/.test(this.value ?? ''); + } + + get hasUppercase(): boolean { + return /[A-Z]/.test(this.value ?? ''); + } + + get hasDigit(): boolean { + return /\d/.test(this.value ?? ''); + } + + get hasMinLength(): boolean { + return (this.value ?? '').length >= 8; + } } export const Template: StoryObj = { @@ -49,22 +73,21 @@ export const Template: StoryObj = { controls: { disable: true }, docs: { description: { - story: 'Кастомный контент через `ng-template`: заголовок, разделитель и список требований к паролю.', + story: 'Кастомный контент через `ng-template`: разделитель и список требований к паролю с tabler-иконками.', }, source: { language: 'ts', code: ` import { Component } from '@angular/core'; -import { Password } from 'primeng/password'; -import { Divider } from 'primeng/divider'; import { FormsModule } from '@angular/forms'; +import { PasswordComponent } from '@cdek-it/angular-ui-kit'; +import { Divider } from 'primeng/divider'; @Component({ - selector: 'app-password-template', standalone: true, - imports: [Password, Divider, FormsModule], + imports: [PasswordComponent, Divider, FormsModule], template: \` - - -
Создание пароля
-
-
    -
  • Минимум одна строчная буква
  • -
  • Минимум одна заглавная буква
  • -
  • Минимум одна цифра
  • -
  • Не менее 8 символов
  • -
+
+
+ + Минимум одна строчная буква +
+
+ + Минимум одна заглавная буква +
+
+ + Минимум одна цифра +
+
+ + Не менее 8 символов +
+
-
+ \`, }) -export class PasswordTemplateComponent { +export class PasswordTemplateExample { value: string | null = null; + + get hasLowercase(): boolean { + return /[a-z]/.test(this.value ?? ''); + } + + get hasUppercase(): boolean { + return /[A-Z]/.test(this.value ?? ''); + } + + get hasDigit(): boolean { + return /\\d/.test(this.value ?? ''); + } + + get hasMinLength(): boolean { + return (this.value ?? '').length >= 8; + } } `, }, diff --git a/src/stories/components/password/password.stories.ts b/src/stories/components/password/password.stories.ts index 22960f5a..00306792 100644 --- a/src/stories/components/password/password.stories.ts +++ b/src/stories/components/password/password.stories.ts @@ -25,6 +25,10 @@ const meta: Meta = { PasswordTemplateComponent, ], }), + (story) => ({ + ...story(), + template: `
${story().template}
`, + }), ], parameters: { designTokens: { prefix: '--p-password' }, From a571d3cd196bea61e0538a71f05c27f0370a158b Mon Sep 17 00:00:00 2001 From: Danil Khaliulin Date: Tue, 28 Apr 2026 21:44:04 +0700 Subject: [PATCH 8/8] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=BF=D1=81=D0=B0=20floatLabel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/components/password/password.stories.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/stories/components/password/password.stories.ts b/src/stories/components/password/password.stories.ts index 00306792..b7367373 100644 --- a/src/stories/components/password/password.stories.ts +++ b/src/stories/components/password/password.stories.ts @@ -167,7 +167,7 @@ import { PasswordComponent } from '@cdek-it/angular-ui-kit'; invalid: false, fluid: false, floatLabel: false, - label: '', + label: 'Пароль', }, }; @@ -182,13 +182,16 @@ export const Default: Story = { if (!args.feedback) parts.push(`[feedback]="false"`); if (args.toggleMask) parts.push(`[toggleMask]="true"`); - if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + if (args.floatLabel) { + parts.push(`[floatLabel]="true"`); + if (args.label) parts.push(`label="${args.label}"`); + } else { + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + } if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`); if (args.disabled) parts.push(`[disabled]="true"`); if (args.invalid) parts.push(`[invalid]="true"`); if (args.fluid) parts.push(`[fluid]="true"`); - if (args.floatLabel) parts.push(`[floatLabel]="true"`); - if (args.label) parts.push(`label="${args.label}"`); parts.push(`[(ngModel)]="value"`);