Cosa fare e cosa non fare con gli stili

Questa pagina descrive le best practice per lavorare con gli stili che garantiscono la coerenza in tutto il codebase, nonché i principi che abbiamo seguito durante la progettazione delle API.

Che cosa fare

Segui queste best practice:

Consigliato: utilizza gli stili per gli elementi visivi e i modificatori per i comportamenti

Utilizza l'API Styles per la configurazione visiva (sfondi, spaziatura interna, bordi) e riserva i modificatori per comportamenti come la logica di clic, il rilevamento dei gesti o l'accessibilità.

Azione consigliata: esporre i parametri di stile nei sistemi di progettazione

Per i tuoi componenti del sistema di progettazione personalizzati, devi esporre un oggetto Style dopo il parametro del modificatore.

@Composable
fun GradientButton(
    modifier: Modifier = Modifier,
    // ✅ DO: for design system components, expose a style modifier to consumers to be able to customize the components
    style: Style = Style
) {
    // Consume the style
}

Azione consigliata: sostituisci i parametri basati sulla visualizzazione con uno stile

Valuta la possibilità di sostituire i parametri degli elementi componibili con un singolo parametro Style. Ad esempio:

// Before
@Composable
fun OldButton(background: Color, fontColor: Color) {
}

// After
// ✅ DO: Replace visual-based parameters with a style that includes same properties
@Composable
fun NewButton(style: Style = Style) {
}

Consigli: dai la priorità agli stili per le animazioni

Utilizza il blocco animate integrato per lo stile basato sullo stato con animazioni per migliorare le prestazioni rispetto ai modificatori.

Azione consigliata: sfrutta la strategia "L'ultima scrittura vince"

Sfrutta il fatto che le proprietà style vengono sovrascritte anziché impilate. Utilizzalo per sostituire i bordi o gli sfondi predefiniti dei componenti senza avere bisogno di più parametri.

Che cosa non fare

I seguenti pattern sono sconsigliati:

Non utilizzare gli stili per la logica di interazione

Non tentare di gestire il rilevamento di onClick o dei gesti all'interno di uno stile. Gli stili sono limitati alle configurazioni visive basate sullo stato, pertanto non devono gestire la logica di business. Devono invece avere solo un aspetto visivo diverso in base allo stato.

Sbagliato: fornire uno stile predefinito come parametro predefinito

I parametri di stile devono sempre essere dichiarati utilizzando style: Style = Style:

@Composable
fun BadButton(
    modifier: Modifier = Modifier,
    // ❌ DON'T set a default style here as a parameter
    style: Style = Style { background(Color.Red) }
) {
}

Per includere un parametro "default", unisci lo stile del parametro in entrata con quello predefinito:

@Composable
fun GoodButton(
    modifier: Modifier = Modifier,
    // ✅ Do: always pass it as a Style, do not pass other defaults
    style: Style = Style
) {
    // ...
    val defaultStyle = Style { background(Color.Red) }
    // ✅ Do Combine defaults inside with incoming parameter
    Box(modifier = modifier.styleable(styleState, defaultStyle, style)) {
      // your logic
    }
}

Non: fornire parametri di stile agli elementi componibili basati sul layout

Anche se puoi fornire uno stile a qualsiasi composable, non è previsto che i composable basati sul layout o a livello di schermo accettino uno stile. Dal punto di vista del consumatore, non è chiaro cosa farebbe uno stile a questo livello. Gli stili sono progettati per i componenti, non necessariamente per i layout.

Non: creare stili in Composizione

CompositionLocals vengono letti nel punto in cui viene definito lo stile, non dove viene utilizzato. Quando lo stile viene effettivamente utilizzato, lo stato di CompositionLocal potrebbe essere cambiato, con conseguente stile impreciso.

// DON'T - Create styles in Composition that access composition locals in this way - this will likely lead to issues when style is used / accessed, as it would not get updated when the value changes.
@Composable
fun containerStyle(): Style {
    val background = MaterialTheme.colorScheme.background
    val onBackground = MaterialTheme.colorScheme.onBackground
    return Style {
        background(background)
        contentColor(onBackground)
    }
}

// Do: Instead, Create StyleScope extension functions for your subsystems to access themed composition Locals
val StyleScope.colors: JetsnackColors
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.colors

val StyleScope.typography: androidx.compose.material3.Typography
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.typography
val StyleScope.shapes: Shapes
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.shapes
// Access CompositionLocals
val button = Style {
    background(colors.brandSecondary)
    shape(shapes.small)
}

Azione consigliata: crea uno stile per le modifiche ai valori del sottosistema

Ad esempio, se passi dalla modalità Buio alla modalità Luce e viceversa, esegui una query sui valori dei temi esistenti (tramite CompositionLocal) per modificare Style in modo dinamico:

// Do: Use CompositionLocals or themed values to create a single style
val buttonStyle = Style {
    background(colors.brandSecondary)
    shape(shapes.small)
}

Azione consigliata: sostituisci gli stili interi quando il componente differisce fondamentalmente tra le definizioni dei temi

Puoi sostituire interi oggetti di stile a livello di tema se si tratta di temi fondamentalmente diversi.

Ad esempio, se stai creando un'app con temi diversi per prodotto/pagina o offerta e molte proprietà di uno stile sono diverse, è accettabile sostituire interi set di stili a livello di tema.

// DO Switch out whole styles when many properties differ - if Product A and Product B are two white labelled apps that provide different Themes.
val productBThemedButton = Style {
    shape(shapes.small)
    background(colors.brandSecondary)
    // other properties are fundamentally different
}

val productAThemedButton = Style {
    shape(shapes.large)
    background(colors.brand)
    // other properties are fundamentally different
}