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
8.9 KiB
Plaintext

---
title: Penataan
description: Pengaturan gaya kondisional dan komposabel dengan kelas Tailwind.
---
Perpustakaan komponen modern membutuhkan sistem penataan yang fleksibel yang dapat menangani kebutuhan kompleks tanpa mengorbankan pengalaman pengembang. Kombinasi Tailwind CSS dengan penggabungan kelas yang cerdas telah muncul sebagai pola yang kuat untuk membangun komponen yang dapat dikustomisasi.
Pendekatan ini menyelesaikan ketegangan fundamental antara menyediakan pengaturan bawaan yang masuk akal dan memungkinkan kustomisasi penuh - sebuah tantangan yang telah lama mengganggu pustaka komponen.
## Masalah dengan penataan tradisional
Pendekatan CSS tradisional sering menyebabkan perang spesifisitas, konflik gaya, dan penimpaan yang tidak dapat diprediksi. Ketika Anda meneruskan `className="bg-blue-500"` ke sebuah komponen yang sudah memiliki `bg-red-500`, mana yang menang?
Tanpa penanganan yang tepat, kedua kelas tersebut akan berlaku dan hasilnya bergantung pada banyak faktor - urutan sumber CSS, spesifisitas kelas, algoritma penggabungan kelas bundler, dll.
## Menggabungkan kelas secara cerdas
Perpustakaan `tailwind-merge` menyelesaikan ini dengan memahami struktur kelas Tailwind dan menyelesaikan konflik secara cerdas. Ketika dua kelas menargetkan properti CSS yang sama, ia hanya menyimpan yang terakhir.
```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"
```
Ini bekerja untuk semua utilitas 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"
```
Perpustakaan ini juga memahami sistem modifier 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"
```
## Kelas kondisional
Seringkali Anda perlu menerapkan kelas secara kondisional berdasarkan props atau state. Perpustakaan `clsx` menyediakan API yang bersih untuk ini:
```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'
);
```
Polanya yang umum adalah menggabungkan sekumpulan kelas bawaan dengan props yang masuk, serta logika kustom apa pun yang kita miliki:
```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}
/>
);
};
```
## Fungsi utilitas `cn`
Fungsi `cn`, yang dipopulerkan oleh [shadcn/ui](https://ui.shadcn.com/), menggabungkan `clsx` dan `tailwind-merge` sehingga Anda mendapatkan logika kondisional dan penggabungan cerdas sekaligus:
```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));
}
```
Kekuatan datang dari pengurutan - gaya dasar terlebih dahulu, kondisi kedua, override pengguna terakhir. Ini memastikan perilaku yang dapat diprediksi sambil mempertahankan kustomisasi penuh.
## Class Variance Authority (CVA)
Untuk komponen kompleks dengan banyak varian, mengelola kelas kondisional secara manual menjadi tidak praktis. [Class Variance Authority (CVA)](https://cva.style/docs) menyediakan API deklaratif untuk mendefinisikan varian komponen.
Sebagai contoh, berikut kutipan dari komponen [Button](https://ui.shadcn.com/docs/components/button) dari 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",
},
}
)
```
## Praktik terbaik
### 1. Urutan itu penting
Selalu terapkan kelas dalam urutan ini:
1. Gaya dasar (selalu diterapkan)
2. Gaya varian (berdasarkan props)
3. Gaya kondisional (berdasarkan state)
4. Override pengguna (prop className)
```tsx
className={cn(
'base-styles', // 1. Base
variant && variantStyles, // 2. Variants
isActive && 'active', // 3. Conditionals
className // 4. User overrides
)}
```
### 2. Dokumentasikan varian Anda
Gunakan TypeScript dan JSDoc untuk mendokumentasikan apa yang dilakukan setiap varian:
```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. Ekstrak pola yang berulang
Jika Anda mendapati diri menulis logika kondisional yang sama berulang kali, ekstraklah:
```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)}
```
## Panduan migrasi
Jika Anda bermigrasi dari pendekatan penataan yang berbeda, berikut cara mengadaptasi pola umum:
### Dari 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
)} />
```
### Dari 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}
/>
);
}
```
## Pertimbangan kinerja
Baik `clsx` maupun `tailwind-merge` sangat dioptimalkan, tetapi perhatikan tips berikut:
1. **Definisikan varian di luar komponen** - Varian CVA sebaiknya didefinisikan di luar komponen untuk menghindari pembuatan ulang setiap render.
2. **Memoisasikan komputasi yang kompleks** - Jika Anda memiliki logika kondisional yang mahal, pertimbangkan untuk mememoisasinya:
```tsx
const className = useMemo(
() => cn(
baseStyles,
expensiveComputation(props),
className
),
[props, className]
);
```
3. **Gunakan variabel CSS untuk nilai dinamis** - Alih-alih menghasilkan kelas secara dinamis, gunakan variabel CSS:
```tsx title="Prefer CSS variables"
// Good
<div
className="bg-[var(--color)]"
style={{ '--color': dynamicColor } as React.CSSProperties}
/>
// Avoid
<div className={`bg-[${dynamicColor}]`} />
```
Kombinasi Tailwind CSS, penggabungan kelas yang cerdas, dan API varian memberikan fondasi yang kuat untuk penataan komponen. Pendekatan ini dapat diskalakan dari tombol sederhana hingga sistem desain kompleks sambil mempertahankan prediktabilitas dan pengalaman pengembang.