Skip to main content

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

  1. Usa formControlName: Preferisci Reactive Forms per applicazioni complesse
  2. Valida sempre: Aggiungi validators appropriati
  3. Feedback visivo: Mostra errori solo dopo touched/dirty
  4. Accessibility: Usa sempre label o ariaLabel
  5. Type-safety: Sfrutta TypeScript per form values

Vedi Anche