Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
85949ef
feat(inputtext): компонент, стилизация, сторисы
Apr 16, 2026
cbe6ed8
стилизация размеров компонента
Apr 16, 2026
99c19ff
фикс padding для float label
Apr 16, 2026
8526b89
экспорт StoryObj с render: (args) => ({ props: { ...args, value }, te…
Apr 16, 2026
c92fb5a
добавлен tabindex=0 и обработчики клавиатуры на кнопку очистки
Apr 20, 2026
20a0331
удалён параметр variant — не актуален
Apr 20, 2026
9370287
добавлена поддержка тёмной темы для disabled/readonly background и color
Apr 20, 2026
798df8b
border и box-shadow остаются красными при focus+invalid
Apr 20, 2026
28036b9
добавлен box-shadow при фокусе
Apr 20, 2026
a9fab15
добавлен ngClass !w-full на iconfield при fluid
Apr 20, 2026
e3dea3d
удалены disabled/invalid из @Input, используются через formControl
Apr 21, 2026
3216b4d
удалены disabled/invalid/ngModel из stories, используются через formC…
Apr 21, 2026
28735e7
добавлен story FloatLabel + Invalid
Apr 21, 2026
aeed563
modified .gitignore
Apr 21, 2026
e1cb48d
Merge branch 'feature/styles-debug' into form.inputtext
Apr 21, 2026
885170f
цвет и ширина для focusRing для состояния focus
Apr 21, 2026
d69122d
inputtext: disabled/invalid в stories через FormControl + NgControl в…
Apr 21, 2026
0d78e17
раскомментирован source.code в stories Disabled/Invalid
Apr 21, 2026
0acd486
макрер обязательного поля для FloatLabel
Apr 21, 2026
efee79f
в Controls чекбокс required для переключения маркера required
Apr 21, 2026
6550075
focusRing для invalid при focus
Apr 21, 2026
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
130 changes: 130 additions & 0 deletions src/lib/components/inputtext/inputtext.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Component, Input, Output, EventEmitter, forwardRef, inject, Injector, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
import { NgClass } from '@angular/common';
import { InputText } from 'primeng/inputtext';
import { IconField } from 'primeng/iconfield';
import { InputIcon } from 'primeng/inputicon';

export type InputTextSize = 'small' | 'base' | 'large' | 'xlarge';


@Component({
selector: 'input-text',
standalone: true,
imports: [InputText, IconField, InputIcon, NgClass],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputTextComponent),
multi: true,
},
],
template: `
@if (showClear) {
<p-iconfield [ngClass]="{'!w-full': fluid}">
<input
pInputText
[ngClass]="sizeClass"
[pSize]="primeSize"
[disabled]="disabled"
[readOnly]="readonly"
[invalid]="invalid"
[placeholder]="placeholder"
[fluid]="fluid"
[value]="modelValue"
(input)="onInput($event)"
(blur)="onTouched()"
/>
<p-inputicon
class="ti ti-x"
Comment thread
Tenkoru marked this conversation as resolved.
tabindex="0"
[style.visibility]="modelValue ? 'visible' : 'hidden'"
[style.pointerEvents]="modelValue ? 'auto' : 'none'"
(click)="clearValue()"
(keydown.enter)="clearValue()"
(keydown.space)="clearValue()"
></p-inputicon>
</p-iconfield>
} @else {
<input
pInputText
[ngClass]="sizeClass"
[pSize]="primeSize"
[disabled]="disabled"
[readOnly]="readonly"
[invalid]="invalid"
[placeholder]="placeholder"
[fluid]="fluid"
[value]="modelValue"
(input)="onInput($event)"
(blur)="onTouched()"
/>
}
`,
})
export class InputTextComponent implements ControlValueAccessor, OnInit {
private readonly _injector = inject(Injector);
private _ngControl: NgControl | null = null;

ngOnInit(): void {
this._ngControl = this._injector.get(NgControl, null, { self: true, optional: true });
}

@Input() placeholder = '';
@Input() size: InputTextSize = 'base';
@Input() readonly = false;
@Input() showClear = false;
@Input() fluid = false;

disabled = false;

get invalid(): boolean {
return this._ngControl?.invalid ?? false;
}

@Output() onClear = new EventEmitter<void>();

modelValue = '';

private _onChange: (value: string) => void = () => {};

get primeSize(): 'small' | 'large' | undefined {
if (this.size === 'small') return 'small';
if (this.size === 'large' || this.size === 'xlarge') return 'large';
return undefined;
}

get sizeClass(): Record<string, boolean> {
return { 'p-inputtext-xlg': this.size === 'xlarge' };
}

onInput(event: Event): void {
const value = (event.target as HTMLInputElement).value;
this.modelValue = value;
this._onChange(value);
}

onTouched: () => void = () => {};

clearValue(): void {
this.modelValue = '';
this._onChange('');
this.onClear.emit();
}

writeValue(value: string): void {
this.modelValue = value ?? '';
}

registerOnChange(fn: (value: string) => void): void {
this._onChange = fn;
}

registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}

setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
}
5 changes: 5 additions & 0 deletions src/prime-preset/map-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import tokens from './tokens/tokens.json';
import { avatarCss } from './tokens/components/avatar';
import { buttonCss } from './tokens/components/button';
import { checkboxCss } from './tokens/components/checkbox';
import { inputtextCss } from './tokens/components/inputtext';
import { progressspinnerCss } from './tokens/components/progressspinner';
import { tagCss } from './tokens/components/tag';
import { tooltipCss } from './tokens/components/tooltip';
Expand All @@ -31,6 +32,10 @@ const presetTokens: Preset<AuraBaseDesignTokens> = {
...(tokens.components.progressspinner as unknown as ComponentsDesignTokens['progressspinner']),
css: progressspinnerCss,
},
inputtext: {
...(tokens.components.inputtext as unknown as ComponentsDesignTokens['inputtext']),
css: inputtextCss,
},
tag: {
...(tokens.components.tag as unknown as ComponentsDesignTokens['tag']),
css: tagCss,
Expand Down
49 changes: 49 additions & 0 deletions src/prime-preset/tokens/components/inputtext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export const inputtextCss = ({ dt }: { dt: (token: string) => string }): string => `

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.

background и color поля не меняется на темной теме, включая применение флагов disabled и readonly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@Tenkoru Добавлены CSS-правила для disabled/readonly с dt-токенами для dark theme — 9370287

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
Contributor Author

Choose a reason for hiding this comment

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

@Tenkoru фикс в виде pull от ветки /styles-debug

Comment thread
Tenkoru marked this conversation as resolved.
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.

При :focus у поля нет box-shadow

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

11004194:
@Tenkoru Добавлен box-shadow при фокусе — 28036b9

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.

Цвет box-shadow сейчас идентичен цвету border, в макете это #12611B

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@Tenkoru цвет и ширина для focusRing для состояния focus 885170f

/* ─── Базовые стили ─── */
.p-inputtext {
border-width: ${dt('inputtext.extend.borderWidth')};
line-height: ${dt('fonts.lineHeight.250')};
}

/* ─── Disabled ─── */
.p-inputtext:disabled {
background: ${dt('inputtext.root.disabledBackground')};
color: ${dt('inputtext.root.disabledColor')};
}

/* ─── Readonly ─── */
.p-inputtext:enabled:read-only {
background: ${dt('inputtext.extend.readonlyBackground')};
color: ${dt('inputtext.root.color')};
}

/* ─── Focus ─── */
.p-inputtext:enabled:focus {
box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('inputtext.focusRing.color')};
}

/* ─── Invalid + Focus ─── */
.p-inputtext.p-invalid:focus {
border-color: ${dt('inputtext.root.invalidBorderColor')};
box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('focusRing.extend.invalid')};
}

/* ─── Extra Large ─── */
.p-inputtext.p-inputtext-xlg {
font-size: ${dt('inputtext.extend.extXlg.fontSize')};
padding: ${dt('inputtext.extend.extXlg.paddingY')} ${dt('inputtext.extend.extXlg.paddingX')};
}

/* ─── IconField ─── */
.p-iconfield[data-pc-name="iconfield"] {
width: fit-content;
}

.p-iconfield .p-inputicon {
font-size: ${dt('inputtext.extend.iconSize')};
width: ${dt('inputtext.extend.iconSize')};
height: ${dt('inputtext.extend.iconSize')};
cursor: pointer;
}
`;
24 changes: 12 additions & 12 deletions src/prime-preset/tokens/tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -2909,7 +2909,7 @@
"top": "{form.padding.400}"
}
},
"inside": {
"in": {
"input": {
"paddingTop": "{form.padding.700}",
"paddingBottom": "{form.padding.300}"
Expand Down Expand Up @@ -3120,9 +3120,9 @@
"iconSize": "{form.icon.300}",
"borderWidth": "{form.borderWidth}",
"extXlg": {
"fontSize": "{form.fontSize}",
"paddingX": "{form.paddingX}",
"paddingY": "{form.paddingY}"
"fontSize": "{form.xlg.fontSize}",
"paddingX": "{form.padding.300}",
"paddingY": "{form.padding.600}"
}
},
"root": {
Expand All @@ -3140,19 +3140,19 @@
"placeholderColor": "{form.placeholderColor}",
"invalidPlaceholderColor": "{form.invalidPlaceholderColor}",
"shadow": "0",
"paddingX": "{form.paddingX}",
"paddingY": "{form.paddingY}",
"paddingX": "{form.padding.300}",
"paddingY": "{form.padding.300}",
"borderRadius": "{form.borderRadius.200}",
"transitionDuration": "{form.transitionDuration}",
"sm": {
"fontSize": "{form.fontSize}",
"paddingX": "{form.paddingX}",
"paddingY": "{form.paddingY}"
"fontSize": "{fonts.fontSize.300}",
"paddingX": "{form.padding.300}",
"paddingY": "{form.padding.200}"
},
"lg": {
"fontSize": "{form.fontSize}",
"paddingX": "{form.paddingX}",
"paddingY": "{form.paddingY}"
"fontSize": "{fonts.fontSize.300}",
"paddingX": "{form.padding.300}",
"paddingY": "{form.padding.400}"
},
"focusRing": {
"width": "{form.focusRing.width}",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { StoryObj } from '@storybook/angular';
import { InputTextComponent } from '../../../../lib/components/inputtext/inputtext.component';

type Story = StoryObj<InputTextComponent>;

export const ClearButton: Story = {
name: 'ClearButton',
render: (args) => ({
props: { ...args },
template: `
<input-text
[size]="size"
[showClear]="showClear"
[readonly]="readonly"
[fluid]="fluid"
[placeholder]="placeholder"
></input-text>
`,
}),
args: {
showClear: true,
placeholder: 'Введите текст...',
},
parameters: {
docs: {
description: {
story: 'Поле с кнопкой очистки через `showClear`. Иконка × появляется при вводе первого символа.',
},
source: {
language: 'ts',
code: `
import { InputTextComponent } from '@cdek-it/angular-ui-kit';

// template:
// <input-text [showClear]="true" placeholder="Введите текст..."></input-text>
`,
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { StoryObj } from '@storybook/angular';
import { InputTextComponent } from '../../../../lib/components/inputtext/inputtext.component';

export const Disabled: StoryObj = {
name: 'Disabled',
render: (args) => {
const control = new FormControl({ value: '', disabled: true });
return {
props: { ...args, control },
template: `<input-text [formControl]="control" placeholder="Введите текст..."></input-text>`,
};
},
decorators: [
(story: any) => ({
...story(),
moduleMetadata: {
imports: [InputTextComponent, ReactiveFormsModule],
},
}),
],
parameters: {
controls: { disable: true },
docs: {
description: { story: 'Отключённое состояние — управляется через FormControl.' },
source: {
language: 'ts',
code: `
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { InputTextComponent } from '@cdek-it/angular-ui-kit';

@Component({
standalone: true,
imports: [InputTextComponent, ReactiveFormsModule],
template: \`<input-text [formControl]="control" placeholder="Введите текст..."></input-text>\`,
})
export class DisabledExample {
control = new FormControl({ value: '', disabled: true });
}
`,
},
},
},
};
Loading
Loading