Files
Hayden Bleasel 55fae375a4 Update docs, internationalization (#35)
* Update Geistdocs

* Update geistdocs.tsx

* Run translation script

* Delete toc.tsx

* Update route.ts

* Update route.ts
2025-12-04 23:57:35 -08:00

280 lines
9.0 KiB
Plaintext

---
title: Stilizzazione
description: Stilizzazione condizionale e componibile con classi Tailwind.
---
Le moderne librerie di componenti hanno bisogno di sistemi di stilizzazione flessibili che possano gestire requisiti complessi senza compromettere l'esperienza dello sviluppatore. La combinazione di Tailwind CSS con un'unione intelligente delle classi è emersa come un pattern potente per costruire componenti personalizzabili.
Questo approccio risolve la tensione fondamentale tra fornire valori predefiniti sensati e consentire una personalizzazione completa - una sfida che ha afflitto le librerie di componenti per anni.
## Il problema degli stili tradizionali
Gli approcci CSS tradizionali spesso portano a guerre di specificità, conflitti di stile e override imprevedibili. Quando passi `className="bg-blue-500"` a un componente che ha già `bg-red-500`, quale prevale?
Senza un'adeguata gestione, entrambe le classi si applicano e il risultato dipende da molti fattori - l'ordine di sorgente del CSS, la specificità delle classi, l'algoritmo di unione delle classi del bundler, ecc.
## Unire le classi in modo intelligente
La libreria `tailwind-merge` risolve questo comprendendo la struttura delle classi di Tailwind e risolvendo i conflitti in modo intelligente. Quando due classi mirano alla stessa proprietà CSS, mantiene solo l'ultima.
```tsx title="Without tailwind-merge"
// Both bg-red-500 and bg-blue-500 apply - unpredictable result
<Button className="bg-blue-500" />
// Renders: className="bg-red-500 bg-blue-500"
```
```tsx title="With tailwind-merge"
import { twMerge } from 'tailwind-merge';
// bg-blue-500 wins as it comes last
const className = twMerge('bg-red-500', 'bg-blue-500');
// Returns: "bg-blue-500"
```
Questo funziona per tutte le utility di Tailwind:
```tsx
twMerge('px-4 py-2', 'px-8'); // Returns: "py-2 px-8"
twMerge('text-sm', 'text-lg'); // Returns: "text-lg"
twMerge('hover:bg-red-500', 'hover:bg-blue-500'); // Returns: "hover:bg-blue-500"
```
La libreria comprende anche il sistema di modificatori di Tailwind:
```tsx
// Modifiers are handled correctly
twMerge('hover:bg-red-500 focus:bg-red-500', 'hover:bg-blue-500');
// Returns: "focus:bg-red-500 hover:bg-blue-500"
```
## Classi condizionali
Spesso è necessario applicare classi condizionalmente in base a props o state. La libreria `clsx` fornisce un'API pulita per questo:
```tsx title="Using clsx"
import clsx from 'clsx';
// Basic conditionals
clsx('base', isActive && 'active');
// Returns: "base active" (if isActive is true)
// Object syntax
clsx('base', {
'active': isActive,
'disabled': isDisabled,
});
// Arrays
clsx(['base', isLarge ? 'text-lg' : 'text-sm']);
// Mixed
clsx(
'base',
['array-item'],
{ 'object-conditional': true },
isActive && 'conditional'
);
```
Un pattern comune è unire un set predefinito di classi con le props in arrivo, oltre a qualsiasi logica personalizzata che abbiamo:
```tsx title="component.tsx"
const Component = ({ className, ...props }: ComponentProps) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div
className={cn(
"rounded-lg border bg-white shadow-sm",
isOpen && "bg-blue-500",
className
)}
{...props}
/>
);
};
```
## La funzione di utilità `cn`
La funzione `cn`, resa popolare da [shadcn/ui](https://ui.shadcn.com/), combina `clsx` e `tailwind-merge` per offrirti sia la logica condizionale sia l'unione intelligente:
```tsx title="lib/utils.ts"
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
La potenza deriva dall'ordine - prima gli stili base, poi i condizionali, infine le sovrascritture dell'utente. Questo garantisce un comportamento prevedibile mantenendo la piena personalizzazione.
## Class Variance Authority (CVA)
Per componenti complessi con molte varianti, gestire manualmente le classi condizionali diventa ingombrante. [Class Variance Authority (CVA)](https://cva.style/docs) fornisce un'API dichiarativa per definire le varianti dei componenti.
Ad esempio, ecco un estratto dal componente [Button](https://ui.shadcn.com/docs/components/button) di shadcn/ui:
```tsx title="@/components/ui/button.tsx"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
```
## Migliori pratiche
### 1. L'ordine è importante
Applica sempre le classi in questo ordine:
1. Stili di base (sempre applicati)
2. Stili delle varianti (in base alle props)
3. Stili condizionali (in base allo state)
4. Sovrascritture dell'utente (prop className)
```tsx
className={cn(
'base-styles', // 1. Base
variant && variantStyles, // 2. Variants
isActive && 'active', // 3. Conditionals
className // 4. User overrides
)}
```
### 2. Documenta le tue varianti
Usa TypeScript e JSDoc per documentare cosa fa ogni variante:
```tsx
type ButtonProps = {
/**
* The visual style of the button
* @default "primary"
*/
variant?: 'primary' | 'secondary' | 'destructive' | 'ghost';
/**
* The size of the button
* @default "md"
*/
size?: 'sm' | 'md' | 'lg';
};
```
### 3. Estrai pattern ripetuti
Se ti ritrovi a scrivere ripetutamente la stessa logica condizionale, estraila:
```tsx title="utils/styles.ts"
export const focusRing = 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500';
export const disabled = 'disabled:pointer-events-none disabled:opacity-50';
// Use in components
className={cn(focusRing, disabled, className)}
```
## Guida alla migrazione
Se stai migrando da un diverso approccio di stilizzazione, ecco come adattare i pattern comuni:
### Da CSS Modules
```tsx title="Before - CSS Modules"
import styles from './Button.module.css';
<button className={`${styles.button} ${styles[variant]} ${className}`} />
```
```tsx title="After - cn + Tailwind"
import { cn } from '@/lib/utils';
<button className={cn(
'px-4 py-2 rounded-lg',
variant === 'primary' && 'bg-blue-500 text-white',
className
)} />
```
### Da styled-components
```tsx title="Before - styled-components"
const Button = styled.button<{ $primary?: boolean }>`
padding: 8px 16px;
background: ${props => props.$primary ? 'blue' : 'gray'};
`;
```
```tsx title="After - cn + Tailwind"
function Button({ primary, className, ...props }) {
return (
<button
className={cn(
'px-4 py-2',
primary ? 'bg-blue-500' : 'bg-gray-500',
className
)}
{...props}
/>
);
}
```
## Considerazioni sulle prestazioni
Sia `clsx` che `tailwind-merge` sono altamente ottimizzati, ma tieni a mente questi consigli:
1. **Definire le varianti al di fuori dei componenti** - Le varianti CVA dovrebbero essere definite al di fuori del componente per evitare la ricreazione ad ogni render.
2. **Memoizza i calcoli complessi** - Se hai logica condizionale costosa, considera di memoizzarla:
```tsx
const className = useMemo(
() => cn(
baseStyles,
expensiveComputation(props),
className
),
[props, className]
);
```
3. **Usa variabili CSS per valori dinamici** - Invece di generare classi dinamicamente, usa variabili CSS:
```tsx title="Prefer CSS variables"
// Good
<div
className="bg-[var(--color)]"
style={{ '--color': dynamicColor } as React.CSSProperties}
/>
// Avoid
<div className={`bg-[${dynamicColor}]`} />
```
La combinazione di Tailwind CSS, un'unione intelligente delle classi e le API per le varianti fornisce una base solida per la stilizzazione dei componenti. Questo approccio scala dai semplici pulsanti a sistemi di design complessi mantenendo prevedibilità ed esperienza dello sviluppatore.