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.1 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Tyylittely
description: Ehdollinen ja yhdisteltävä tyylitys Tailwind-luokkien avulla.
---
Nykyaikaiset komponenttikirjastot tarvitsevat joustavia tyylijärjestelmiä, jotka pystyvät käsittelemään monimutkaisia vaatimuksia tinkimättä kehittäjäkokemuksesta. Tailwind CSS:n yhdistäminen älykkääseen luokkien yhdistelyyn on muodostunut tehokkaaksi malliksi räätälöitävien komponenttien rakentamiseen.
Tämä lähestymistapa ratkaisee perustavanlaatuisen jännitteen järkevistä oletusarvoista ja täydellisestä mukautettavuudesta - haaste, joka on vaivannut komponenttikirjastoja vuosia.
## Ongelma perinteisen tyylityksen kanssa
Perinteiset CSS-lähestymistavat johtavat usein spesifisyyssotiin, tyylikonflikteihin ja ennakoimattomiin ylikirjoituksiin. Kun välität komponentille `className="bg-blue-500"`, mutta komponentissa on jo `bg-red-500`, kumpi voittaa?
Ilman asianmukaista käsittelyä molemmat luokat voivat päteä ja lopputulos riippuu monista tekijöistä CSS:n lähdejärjestyksestä, luokkien spesifisyydestä, bundlerin luokkien yhdistelyalgoritmista jne.
## Luokkien älykäs yhdistäminen
`tailwind-merge`-kirjasto ratkaisee tämän ymmärtämällä Tailwindin luokkarakenteen ja ratkaisemalla konfliktit älykkäästi. Kun kaksi luokkaa kohdistuvat samaan CSS-ominaisuuteen, se pitää vain viimeisen.
```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"
```
Tämä toimii kaikkien Tailwind-apuohjelmien kanssa:
```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"
```
Kirjasto ymmärtää myös Tailwindin modifikaattorijärjestelmän:
```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"
```
## Ehdolliset luokat
Usein tarvitsee soveltaa luokkia ehdollisesti propsien tai tilan perusteella. `clsx`-kirjasto tarjoaa selkeän rajapinnan tähän:
```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'
);
```
Yleinen käytäntö on yhdistää oletusarvoinen luokkajoukko saapuviin props-arvoihin sekä mahdolliseen omaan logiikkaamme:
```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}
/>
);
};
```
## `cn`-apuohjelma
`cn`-funktio, jonka tunnetuksi teki [shadcn/ui](https://ui.shadcn.com/), yhdistää `clsx`- ja `tailwind-merge`-kirjastot tarjoten sekä ehdollisen logiikan että älykkään yhdistelyn:
```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));
}
```
Teho syntyy järjestyksestä - perusmuotoilu ensin, ehdolliset tyylit toisena, käyttäjän ylikirjoitukset viimeisenä. Tämä varmistaa ennakoitavan käyttäytymisen samalla kun säilytetään täysi mukautettavuus.
## Class Variance Authority (CVA)
Monimutkaisille komponenteille, joilla on paljon variantteja, ehdollisten luokkien manuaalinen hallinta käy nopeasti työlääksi. Class Variance Authority (CVA) tarjoaa deklaratiivisen rajapinnan komponenttivarianttien määrittelyyn.
Esimerkiksi tässä on ote shadcn/ui:n [Button](https://ui.shadcn.com/docs/components/button)-komponentista:
```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",
},
}
)
```
## Parhaat käytännöt
### 1. Järjestyksellä on merkitystä
Sovella luokkia aina tässä järjestyksessä:
1. Perusmuotoilut (aina käytössä)
2. Varianttien tyylit (propsien perusteella)
3. Ehdolliset tyylit (tilan perusteella)
4. Käyttäjän ylikirjoitukset (className-prop)
```tsx
className={cn(
'base-styles', // 1. Base
variant && variantStyles, // 2. Variants
isActive && 'active', // 3. Conditionals
className // 4. User overrides
)}
```
### 2. Dokumentoi varianttisi
Käytä TypeScriptiä ja JSDocia kuvaamaan, mitä kukin variantti tekee:
```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. Ota toistuvat mallit talteen
Jos huomaat kirjoittavasi samaa ehdollista logiikkaa toistuvasti, erottele se:
```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)}
```
## Migraatio-opas
Jos siirryt eri tyylityslähestymistavasta, tässä on miten sopeuttaa yleisiä malleja:
### CSS Modulesista
```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
)} />
```
### styled-componentsista
```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}
/>
);
}
```
## Suorituskykylähtökohdat
Sekä `clsx` että `tailwind-merge` ovat erittäin optimoituja, mutta pidä nämä vinkit mielessä:
1. **Määrittele variantit komponentin ulkopuolella** - CVA-variantit tulisi määritellä komponentin ulkopuolelle, jotta niitä ei luoda uudelleen jokaisella renderöinnillä.
2. **Muistimuistioi (memoize) monimutkaiset laskelmat** - Jos sinulla on raskasta ehdollista logiikkaa, harkitse muistimuistion käyttöä:
```tsx
const className = useMemo(
() => cn(
baseStyles,
expensiveComputation(props),
className
),
[props, className]
);
```
3. **Käytä CSS-muuttujia dynaamisille arvoille** - Dynaamisten arvojen luokkien generoimisen sijaan käytä CSS-muuttujia:
```tsx title="Prefer CSS variables"
// Good
<div
className="bg-[var(--color)]"
style={{ '--color': dynamicColor } as React.CSSProperties}
/>
// Avoid
<div className={`bg-[${dynamicColor}]`} />
```
Tailwind CSS:n, älykkään luokkien yhdistelyn ja variantti-rajapintojen yhdistelmä tarjoaa vankan perustan komponenttien tyylittämiselle. Tämä lähestymistapa skaalautuu yksinkertaisista painikkeista monimutkaisiin design-järjestelmiin säilyttäen ennakoitavuuden ja hyvän kehittäjäkokemuksen.