-
Notifications
You must be signed in to change notification settings - Fork 0
inputtext: компонент, стилизация, сторисы #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
85949ef
cbe6ed8
99c19ff
8526b89
c92fb5a
20a0331
9370287
798df8b
28036b9
a9fab15
e3dea3d
3216b4d
28735e7
aeed563
e1cb48d
885170f
d69122d
0d78e17
0acd486
efee79f
6550075
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" | ||
| 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; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| export const inputtextCss = ({ dt }: { dt: (token: string) => string }): string => ` | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. background и color поля не меняется на темной теме, включая применение флагов disabled и readonly
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Возможно проблема в самом переключателе цветов в сторибуке, но я не вижу, чтобы при переключении темы в сторибуке цвета компонента менялись
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Tenkoru фикс в виде pull от ветки /styles-debug
Tenkoru marked this conversation as resolved.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. При :focus у поля нет box-shadow
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Цвет box-shadow сейчас идентичен цвету border, в макете это #12611B
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| /* ─── Базовые стили ─── */ | ||
| .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; | ||
| } | ||
| `; | ||
| 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 }); | ||
| } | ||
| `, | ||
| }, | ||
| }, | ||
| }, | ||
| }; |
Uh oh!
There was an error while loading. Please reload this page.