Componenti Base UI
Tess UI Library fornisce un set completo di componenti base UI-agnostic che funzionano sia con PrimeNG che con Bootstrap. Tutti i componenti implementano ControlValueAccessor per integrazione seamless con Angular Forms.
Caratteristiche Comuni
Tutti i componenti base condividono:
- ✅ UI Kit Agnostic: Funzionano con PrimeNG e Bootstrap
- ✅ Forms Integration: Supporto completo per Template-driven e Reactive Forms
- ✅ Type-Safe: Full TypeScript support
- ✅ Accessibilità: ARIA attributes e keyboard navigation
- ✅ Validazione: Integrazione con Angular validators
- ✅ Standalone: Possono essere usati indipendentemente
TessButton
Pulsante unificato con supporto per loading state e icone.
Proprietà
interface TessButtonProps {
label?: string; // Testo del pulsante
type?: 'button' | 'submit' | 'reset'; // Tipo HTML
severity?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger' | 'contrast';
size?: 'small' | 'large'; // Dimensione
icon?: string; // Classe icona (es. 'pi pi-check')
iconPos?: 'left' | 'right'; // Posizione icona
loading?: boolean; // Mostra spinner
disabled?: boolean; // Disabilitato
outlined?: boolean; // Stile outlined
text?: boolean; // Stile solo testo
raised?: boolean; // Elevazione (shadow)
rounded?: boolean; // Bordi arrotondati
ariaLabel?: string; // Aria label
}
Esempi
<!-- Pulsante primario base -->
<tess-button
label="Salva"
severity="primary"
(clicked)="save()">
</tess-button>
<!-- Pulsante con icona -->
<tess-button
label="Elimina"
severity="danger"
icon="pi pi-trash"
iconPos="left"
[outlined]="true"
(clicked)="delete()">
</tess-button>
<!-- Pulsante loading -->
<tess-button
label="Caricamento..."
[loading]="isLoading"
[disabled]="isLoading"
(clicked)="submit()">
</tess-button>
<!-- Pulsante icon-only -->
<tess-button
icon="pi pi-search"
[rounded]="true"
ariaLabel="Cerca"
(clicked)="search()">
</tess-button>
<!-- Submit button in form -->
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<!-- form fields -->
<tess-button
type="submit"
label="Invia"
severity="primary"
[disabled]="form.invalid">
</tess-button>
</form>
Output Events
@Output() clicked = new EventEmitter<Event>();
TessInput
Input di testo unificato con supporto per vari tipi e validazione.
Proprietà
interface TessInputProps {
type?: 'text' | 'password' | 'email' | 'number' | 'tel' | 'url';
placeholder?: string;
disabled?: boolean;
readonly?: boolean;
required?: boolean;
maxLength?: number;
minLength?: number;
pattern?: string;
ariaLabel?: string;
autocomplete?: string;
}
Esempi
<!-- Input base con ngModel -->
<tess-input
type="text"
placeholder="Nome"
[(ngModel)]="name">
</tess-input>
<!-- Input con Reactive Forms -->
<form [formGroup]="form">
<tess-input
type="email"
placeholder="Email"
formControlName="email"
[required]="true">
</tess-input>
@if (form.get('email')?.invalid && form.get('email')?.touched) {
<small class="error">Email non valida</small>
}
</form>
<!-- Password input -->
<tess-input
type="password"
placeholder="Password"
formControlName="password"
[minLength]="8"
[required]="true">
</tess-input>
<!-- Number input -->
<tess-input
type="number"
placeholder="Età"
formControlName="age"
[required]="true">
</tess-input>
Validazione
import { Validators, FormControl } from '@angular/forms';
// Con validators built-in
email = new FormControl('', [
Validators.required,
Validators.email
]);
// Con custom validators
password = new FormControl('', [
Validators.required,
Validators.minLength(8),
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
]);
TessSelect
Dropdown select unificato per selezione singola.
Proprietà
interface TessSelectProps {
options: SelectOption[]; // Array di opzioni
placeholder?: string;
disabled?: boolean;
required?: boolean;
filter?: boolean; // Abilita filtro/ricerca
showClear?: boolean; // Mostra bottone clear
optionLabel?: string; // Campo per label
optionValue?: string; // Campo per value
ariaLabel?: string;
}
interface SelectOption {
label: string;
value: any;
disabled?: boolean;
icon?: string;
}
Esempi
<!-- Select base -->
<tess-select
[options]="countries"
placeholder="Seleziona paese"
formControlName="country">
</tess-select>
<!-- Select con filtro -->
<tess-select
[options]="cities"
placeholder="Seleziona città"
[filter]="true"
[showClear]="true"
formControlName="city">
</tess-select>
<!-- Select con oggetti complessi -->
<tess-select
[options]="users"
optionLabel="fullName"
optionValue="id"
placeholder="Seleziona utente"
formControlName="userId">
</tess-select>
// Component
countries: SelectOption[] = [
{ label: 'Italia', value: 'IT' },
{ label: 'Francia', value: 'FR' },
{ label: 'Germania', value: 'DE' },
];
users = [
{ id: 1, fullName: 'Mario Rossi', email: 'mario@example.com' },
{ id: 2, fullName: 'Luigi Verdi', email: 'luigi@example.com' },
];
TessMultiselect
Dropdown per selezione multipla.
Proprietà
interface TessMultiselectProps extends TessSelectProps {
maxSelectedLabels?: number; // Numero max label mostrate
selectedItemsLabel?: string; // Template label (es. "{0} items selected")
selectionLimit?: number; // Limite selezioni
}
Esempi
<!-- Multiselect base -->
<tess-multiselect
[options]="skills"
placeholder="Seleziona competenze"
formControlName="skills">
</tess-multiselect>
<!-- Con limite selezioni -->
<tess-multiselect
[options]="tags"
placeholder="Seleziona fino a 3 tag"
[selectionLimit]="3"
[maxSelectedLabels]="2"
selectedItemsLabel="{0} tag selezionati"
formControlName="tags">
</tess-multiselect>
<!-- Con filtro -->
<tess-multiselect
[options]="permissions"
placeholder="Seleziona permessi"
[filter]="true"
formControlName="permissions">
</tess-multiselect>
// Component
form = this.fb.group({
skills: [['angular', 'typescript']], // Valore iniziale array
tags: [[]],
permissions: [[]]
});
skills: SelectOption[] = [
{ label: 'Angular', value: 'angular' },
{ label: 'React', value: 'react' },
{ label: 'TypeScript', value: 'typescript' },
{ label: 'Node.js', value: 'nodejs' },
];
TessCheckbox
Checkbox unificato per valori booleani.
Proprietà
interface TessCheckboxProps {
label?: string;
disabled?: boolean;
required?: boolean;
binary?: boolean; // true/false invece di array
ariaLabel?: string;
}
Esempi
<!-- Checkbox singolo (boolean) -->
<tess-checkbox
label="Accetto i termini e condizioni"
[binary]="true"
formControlName="acceptTerms">
</tess-checkbox>
<!-- Checkbox in array -->
<div>
<tess-checkbox
label="Angular"
value="angular"
formControlName="frameworks">
</tess-checkbox>
<tess-checkbox
label="React"
value="react"
formControlName="frameworks">
</tess-checkbox>
<tess-checkbox
label="Vue"
value="vue"
formControlName="frameworks">
</tess-checkbox>
</div>
// Boolean checkbox
form = this.fb.group({
acceptTerms: [false, Validators.requiredTrue]
});
// Array checkbox
form = this.fb.group({
frameworks: [[]] // ['angular', 'react']
});
TessRadioGroup
Radio button group per selezione esclusiva.
Proprietà
interface TessRadioGroupProps {
options: RadioOption[];
name: string; // Nome del radio group
disabled?: boolean;
required?: boolean;
layout?: 'horizontal' | 'vertical';
}
interface RadioOption {
label: string;
value: any;
disabled?: boolean;
}
Esempi
<!-- Radio group verticale -->
<tess-radio-group
name="gender"
[options]="genderOptions"
layout="vertical"
formControlName="gender">
</tess-radio-group>
<!-- Radio group orizzontale -->
<tess-radio-group
name="size"
[options]="sizeOptions"
layout="horizontal"
formControlName="size">
</tess-radio-group>
// Component
genderOptions: RadioOption[] = [
{ label: 'Maschio', value: 'M' },
{ label: 'Femmina', value: 'F' },
{ label: 'Altro', value: 'O' },
];
sizeOptions: RadioOption[] = [
{ label: 'S', value: 'small' },
{ label: 'M', value: 'medium' },
{ label: 'L', value: 'large' },
{ label: 'XL', value: 'xlarge' },
];
TessSwitch
Toggle switch per valori on/off.
Proprietà
interface TessSwitchProps {
label?: string;
disabled?: boolean;
required?: boolean;
ariaLabel?: string;
}
Esempi
<!-- Switch base -->
<tess-switch
label="Notifiche email"
formControlName="emailNotifications">
</tess-switch>
<!-- Switch senza label -->
<tess-switch
ariaLabel="Dark mode"
formControlName="darkMode">
</tess-switch>
<!-- Lista di switches -->
<div class="settings-list">
<tess-switch
label="Newsletter settimanale"
formControlName="weeklyNewsletter">
</tess-switch>
<tess-switch
label="Aggiornamenti prodotto"
formControlName="productUpdates">
</tess-switch>
<tess-switch
label="Promozion"
formControlName="promotions">
</tess-switch>
</div>
TessTextarea
Textarea unificato per testo multilinea.
Proprietà
interface TessTextareaProps {
placeholder?: string;
rows?: number; // Numero righe visibili
cols?: number; // Numero colonne
disabled?: boolean;
readonly?: boolean;
required?: boolean;
maxLength?: number;
autoResize?: boolean; // Auto-ridimensionamento
ariaLabel?: string;
}
Esempi
<!-- Textarea base -->
<tess-textarea
placeholder="Descrizione"
[rows]="5"
formControlName="description">
</tess-textarea>
<!-- Textarea auto-resize -->
<tess-textarea
placeholder="Commento"
[rows]="3"
[autoResize]="true"
[maxLength]="500"
formControlName="comment">
</tess-textarea>
<!-- Con counter caratteri -->
<div>
<tess-textarea
placeholder="Bio"
[rows]="4"
[maxLength]="200"
formControlName="bio">
</tess-textarea>
<small class="char-counter">
{{ form.get('bio')?.value?.length || 0 }} / 200
</small>
</div>
TessDatepicker
Date picker unificato per selezione date.
Proprietà
interface TessDatepickerProps {
placeholder?: string;
disabled?: boolean;
required?: boolean;
showTime?: boolean; // Includi time picker
dateFormat?: string; // Formato visualizzazione (es. 'dd/mm/yy')
minDate?: Date; // Data minima
maxDate?: Date; // Data massima
showButtonBar?: boolean; // Mostra Today/Clear buttons
showIcon?: boolean; // Mostra icona calendario
ariaLabel?: string;
}
Esempi
<!-- Date picker base -->
<tess-datepicker
placeholder="Data di nascita"
dateFormat="dd/mm/yy"
formControlName="birthDate">
</tess-datepicker>
<!-- Date + Time picker -->
<tess-datepicker
placeholder="Data e ora appuntamento"
[showTime]="true"
[showButtonBar]="true"
[showIcon]="true"
formControlName="appointmentDate">
</tess-datepicker>
<!-- Con vincoli min/max -->
<tess-datepicker
placeholder="Data consegna"
[minDate]="today"
[maxDate]="maxDeliveryDate"
[showIcon]="true"
formControlName="deliveryDate">
</tess-datepicker>
<!-- Date range (due datepicker) -->
<div class="date-range">
<tess-datepicker
placeholder="Data inizio"
[maxDate]="form.get('endDate')?.value"
formControlName="startDate">
</tess-datepicker>
<tess-datepicker
placeholder="Data fine"
[minDate]="form.get('startDate')?.value"
formControlName="endDate">
</tess-datepicker>
</div>
// Component
today = new Date();
maxDeliveryDate = new Date();
constructor() {
this.maxDeliveryDate.setDate(this.today.getDate() + 30);
}
form = this.fb.group({
birthDate: [null],
appointmentDate: [new Date()],
deliveryDate: [null],
startDate: [null],
endDate: [null]
});
Forms Integration
Tutti i componenti supportano sia Template-driven che Reactive Forms:
Template-driven Forms
<form #f="ngForm" (ngSubmit)="onSubmit(f)">
<tess-input
name="username"
placeholder="Username"
[(ngModel)]="user.username"
required>
</tess-input>
<tess-select
name="role"
[options]="roles"
[(ngModel)]="user.role"
required>
</tess-select>
<tess-checkbox
name="active"
label="Attivo"
[(ngModel)]="user.active"
[binary]="true">
</tess-checkbox>
<tess-button
type="submit"
label="Salva"
[disabled]="f.invalid">
</tess-button>
</form>
Reactive Forms
import { FormBuilder, Validators } from '@angular/forms';
export class UserFormComponent {
form = this.fb.group({
username: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
role: ['', Validators.required],
birthDate: [null],
active: [true],
skills: [[]],
});
constructor(private fb: FormBuilder) {}
onSubmit(): void {
if (this.form.valid) {
console.log(this.form.value);
}
}
}
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<tess-input
formControlName="username"
placeholder="Username">
</tess-input>
<tess-input
type="email"
formControlName="email"
placeholder="Email">
</tess-input>
<tess-select
[options]="roles"
formControlName="role"
placeholder="Ruolo">
</tess-select>
<tess-datepicker
formControlName="birthDate"
placeholder="Data di nascita">
</tess-datepicker>
<tess-switch
formControlName="active"
label="Utente attivo">
</tess-switch>
<tess-multiselect
[options]="skillsList"
formControlName="skills"
placeholder="Competenze">
</tess-multiselect>
<tess-button
type="submit"
label="Salva"
[disabled]="form.invalid">
</tess-button>
</form>
Validazione e Error Handling
<form [formGroup]="form">
<div class="form-field">
<label for="email">Email *</label>
<tess-input
id="email"
type="email"
formControlName="email"
placeholder="email@example.com">
</tess-input>
@if (form.get('email')?.invalid && form.get('email')?.touched) {
<small class="error">
@if (form.get('email')?.errors?.['required']) {
Email è obbligatoria
}
@if (form.get('email')?.errors?.['email']) {
Email non valida
}
</small>
}
</div>
<div class="form-field">
<label for="age">Età *</label>
<tess-input
id="age"
type="number"
formControlName="age">
</tess-input>
@if (form.get('age')?.invalid && form.get('age')?.touched) {
<small class="error">
@if (form.get('age')?.errors?.['required']) {
Età è obbligatoria
}
@if (form.get('age')?.errors?.['min']) {
Età minima: {{ form.get('age')?.errors?.['min'].min }}
}
</small>
}
</div>
</form>
Styling
Tutti i componenti supportano classi CSS personalizzate:
<tess-button
label="Custom Style"
[ngClass]="'my-custom-button'">
</tess-button>
<tess-input
[ngClass]="{'error-input': hasError, 'success-input': isValid}">
</tess-input>
/* Custom styles */
.my-custom-button {
min-width: 200px;
font-weight: bold;
}
.error-input {
border-color: var(--danger-color);
}
.success-input {
border-color: var(--success-color);
}
Accessibilità
Tutti i componenti seguono le best practices di accessibilità:
- ARIA labels: Ogni componente supporta
ariaLabel - Keyboard navigation: Tab, Enter, Space, Arrow keys
- Screen reader support: Ruoli e stati ARIA appropriati
- Focus management: Indicatori focus visibili
<tess-button
icon="pi pi-search"
ariaLabel="Cerca utenti"
(clicked)="search()">
</tess-button>
<tess-input
type="email"
ariaLabel="Indirizzo email"
formControlName="email">
</tess-input>
Best Practices
- Usa formControlName: Preferisci Reactive Forms per applicazioni complesse
- Valida sempre: Aggiungi validators appropriati
- Feedback visivo: Mostra errori solo dopo touched/dirty
- Accessibility: Usa sempre label o ariaLabel
- Type-safety: Sfrutta TypeScript per form values