feat(design-system): add reusable FormField wrapper primitive
Standalone, OnPush. Label/hint/error slots, required marker, aria-describedby wiring, role=alert on error. Colors via existing tenant CSS vars with fallbacks.
This commit is contained in:
23
src/app/shared/ui/form-field/form-field.component.html
Normal file
23
src/app/shared/ui/form-field/form-field.component.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<div class="app-form-field">
|
||||
@if (label()) {
|
||||
<label class="app-form-field__label" [for]="fieldId">
|
||||
{{ label() }}
|
||||
@if (required()) {
|
||||
<span class="app-form-field__required" aria-hidden="true">*</span>
|
||||
}
|
||||
</label>
|
||||
}
|
||||
|
||||
<div
|
||||
class="app-form-field__control"
|
||||
[attr.aria-describedby]="error() ? errorId : (hint() ? hintId : null)"
|
||||
>
|
||||
<ng-content />
|
||||
</div>
|
||||
|
||||
@if (error()) {
|
||||
<p class="app-form-field__error" [id]="errorId" role="alert">{{ error() }}</p>
|
||||
} @else if (hint()) {
|
||||
<p class="app-form-field__hint" [id]="hintId">{{ hint() }}</p>
|
||||
}
|
||||
</div>
|
||||
32
src/app/shared/ui/form-field/form-field.component.scss
Normal file
32
src/app/shared/ui/form-field/form-field.component.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
.app-form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs, 0.25rem);
|
||||
}
|
||||
|
||||
.app-form-field__label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #1f322d);
|
||||
}
|
||||
|
||||
.app-form-field__required {
|
||||
color: var(--error-color, #c0392b);
|
||||
margin-left: 0.125rem;
|
||||
}
|
||||
|
||||
.app-form-field__control {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.app-form-field__hint {
|
||||
margin: 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
}
|
||||
|
||||
.app-form-field__error {
|
||||
margin: 0;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--error-color, #c0392b);
|
||||
}
|
||||
21
src/app/shared/ui/form-field/form-field.component.ts
Normal file
21
src/app/shared/ui/form-field/form-field.component.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||
|
||||
let nextFormFieldId = 0;
|
||||
|
||||
@Component({
|
||||
selector: 'app-form-field',
|
||||
standalone: true,
|
||||
templateUrl: './form-field.component.html',
|
||||
styleUrl: './form-field.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FormFieldComponent {
|
||||
readonly label = input<string | null>(null);
|
||||
readonly hint = input<string | null>(null);
|
||||
readonly error = input<string | null>(null);
|
||||
readonly required = input(false);
|
||||
|
||||
protected readonly fieldId = `app-form-field-${++nextFormFieldId}`;
|
||||
protected readonly hintId = `${this.fieldId}-hint`;
|
||||
protected readonly errorId = `${this.fieldId}-error`;
|
||||
}
|
||||
Reference in New Issue
Block a user