From 62bf075ca5e99b8ce21707c8a2c68f81a5ea8c3e Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 3 Mar 2026 10:32:26 +0000 Subject: [PATCH 01/23] Add snippets for Styles documentation --- .../snippets/designsystems/StylesSnippets.kt | 707 ++++++++++++++++++ 1 file changed, 707 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/designsystems/StylesSnippets.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/StylesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/StylesSnippets.kt new file mode 100644 index 000000000..67a122d2f --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/StylesSnippets.kt @@ -0,0 +1,707 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.designsystems + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Checkbox +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.toUpperCase +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +// [START android_compose_styles_basic_button] +@Composable +fun BasicButtonStyle() { + Button( + onClick = { }, + style = { } + ) { + Text("Click me") + } +} +// [END android_compose_styles_basic_button] + +// [START android_compose_styles_button_background] +@Composable +fun ButtonBackgroundStyle() { + Button( + onClick = { }, + style = { background(Color.Blue) } + ) { + Text("Click me") + } +} +// [END android_compose_styles_button_background] + +// [START android_compose_styles_row_styleable] +@Composable +fun RowStyleable() { + Row( + modifier = Modifier.styleable { } + ) { + Text("Content") + } +} +// [END android_compose_styles_row_styleable] + +// [START android_compose_styles_row_styleable_background] +@Composable +fun RowStyleableBackground() { + Row( + modifier = Modifier.styleable { + background(Color.Blue) + } + ) { + Text("Content") + } +} +// [END android_compose_styles_row_styleable_background] + +// [START android_compose_styles_standalone_style] +val style = Style { background(Color.Blue) } +// [END android_compose_styles_standalone_style] + +// [START android_compose_styles_standalone_usage] +@Composable +fun StandaloneStyleUsage() { + val style = Style { background(Color.Blue) } + + // built in parameter + Button(onClick = { }, style = style) { + Text("Button") + } + + // modifier styleable + val styleState = remember { MutableStyleState(null) } + Column( + Modifier.styleable(styleState, style) + ) { + Text("Column content") + } +} +// [END android_compose_styles_standalone_usage] + +// [START android_compose_styles_multiple_components] +@Composable +fun MultipleComponentsStyle() { + val style = Style { background(Color.Blue) } + + // built in parameter + Button(onClick = { }, style = style) { + Text("Button") + } + Checkbox(checked = true, onCheckedChange = { }, style = style) + + // modifier styleable + val columnStyleState = remember { MutableStyleState(null) } + Column( + Modifier.styleable(columnStyleState, style) + ) { + Text("Column") + } + val rowStyleState = remember { MutableStyleState(null) } + Row( + Modifier.styleable(rowStyleState, style) + ) { + Text("Row") + } +} +// [END android_compose_styles_multiple_components] + +// [START android_compose_styles_multiple_properties] +@Composable +fun MultiplePropertiesStyle() { + Button( + onClick = { }, + style = { + background(Color.Blue) + contentPaddingStart(16.dp) + } + ) { + Text("Button") + } +} +// [END android_compose_styles_multiple_properties] + +val TealColor = Color(0xFF008080) + +// [START android_compose_styles_overwrite_properties] +@Composable +fun OverwritePropertiesStyle() { + Button( + style = { + background(Color.Red) + // Background of Red is now overridden with TealColor instead + background(TealColor) + // All directions of padding are set to 64.dp (top, start, end, bottom) + contentPadding(64.dp) + // Top padding is now set to 16.dp, all other paddings remain at 64.dp + contentPaddingTop(16.dp) + }, + onClick = { + // + } + ) { + Text("Click me!") + } +} +// [END android_compose_styles_overwrite_properties] + +// [START android_compose_styles_merge_styles] +@Composable +fun MergeStyles() { + val style1 = Style { background(TealColor) } + val style2 = Style { contentPaddingTop(16.dp) } + + Button( + style = style1 then style2, + onClick = { + + }, + ) { + Text("Click me!") + } +} +// [END android_compose_styles_merge_styles] + +// [START android_compose_styles_merge_overwrite] +@Composable +fun MergeOverwriteStyles() { + val style1 = Style { + background(Color.Red) + contentPadding(32.dp) + } + + val style2 = Style { + contentPaddingHorizontal(8.dp) + background(Color.LightGray) + } + + Button( + style = style1 then style2, + onClick = { + + }, + ) { + Text("Click me!") + } +} +// [END android_compose_styles_merge_overwrite] + +// [START android_compose_styles_parent_styling] +@Composable +fun ParentStyling() { + val styleState = remember { MutableStyleState(null) } + Column( + modifier = Modifier.styleable(styleState) { + background(Color.LightGray) + val blue = Color(0xFF4285F4) + val purple = Color(0xFFA250EA) + val colors = listOf(blue, purple) + contentBrush(Brush.linearGradient(colors)) + }, + ) { + Text("Children inherit", style = { width(60.dp) }) + Text("certain properties") + Text("from their parents") + } +} +// [END android_compose_styles_parent_styling] + +// [START android_compose_styles_child_override] +@Composable +fun ChildOverrideStyling() { + val styleState = remember { MutableStyleState(null) } + Column( + modifier = Modifier.styleable(styleState) { + background(Color.LightGray) + val blue = Color(0xFF4285F4) + val purple = Color(0xFFA250EA) + val colors = listOf(blue, purple) + contentBrush(Brush.linearGradient(colors)) + }, + ) { + Text("Children can ", style = { + contentBrush(Brush.linearGradient(listOf(Color.Red, Color.Blue))) + }) + Text("override properties") + Text("set by their parents") + } +} +// [END android_compose_styles_child_override] + +// [START android_compose_styles_custom_extension] +fun StyleScope.outlinedBackground(color: Color) { + border(1.dp, color) + background(color) +} +// [END android_compose_styles_custom_extension] + +// [START android_compose_styles_custom_extension_usage] +val customExtensionStyle = Style { + outlinedBackground(Color.Blue) +} +// [END android_compose_styles_custom_extension_usage] + +// [START android_compose_styles_composition_local] +@Composable +fun CompositionLocalStyle() { + val buttonStyle = Style { + contentPadding(12.dp) + shape(RoundedCornerShape(50)) + background(Color.Blue) // Simplified for snippet + } +} +// [END android_compose_styles_composition_local] + +// [START android_compose_styles_design_system_component] +@Composable +fun LoginButtonSnippet(modifier: Modifier = Modifier, style: Style = Style) { + // Your custom component applying the style via the styleable modifier + // e.g., Box(modifier = modifier.styleable(styleState, style)) +} +// [END android_compose_styles_design_system_component] + +// [START android_compose_styles_theme_integration] +@Immutable +data class AppStyles( + val baseButtonStyle: Style = Style, + val baseTextStyle: Style = Style, + val baseCardStyle: Style = Style +) + +val LocalAppStyles = compositionLocalOf { AppStyles() } + +@Composable +fun CustomTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val styles = AppStyles() + CompositionLocalProvider( + LocalAppStyles provides styles, + content = content + ) +} + +@Composable +fun CustomButton( + modifier: Modifier = Modifier, + style: Style = Style, + text: String +) { + val interactionSource = remember { MutableInteractionSource() } + val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } + + Box( + modifier = modifier + .clickable(interactionSource = interactionSource, onClick = { }) + .styleable(styleState, LocalAppStyles.current.baseButtonStyle, style) + ) { + Text(text) + } +} +// [END android_compose_styles_theme_integration] + +// [START android_compose_styles_dos_expose_style] +@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 +} +// [END android_compose_styles_dos_expose_style] + +// [START android_compose_styles_dos_replace_params] +// 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) { +} +// [END android_compose_styles_dos_replace_params] + +// [START android_compose_styles_dos_wrapper] +@Composable +fun BaseButton( + modifier: Modifier = Modifier, + style: Style = Style +) { + // Uses LocalTheme.appStyles.button + incoming style +} + +// ✅ Do create wrapper composables that expose common implementations of the same component +@Composable +fun SpecialGradientButton( + modifier: Modifier = Modifier, + style: Style = Style +) { + // Uses LocalTheme.appStyles.button + LocalTheme.appStyles.gradientButton + incoming style - merge these styles +} +// [END android_compose_styles_dos_wrapper] + +// [START android_compose_styles_donts_default_style] +@Composable +fun BadButton( + modifier: Modifier = Modifier, + // ❌ DONT set a default style here as a parameter + style: Style = Style { background(Color.Red) } +) { +} +// [END android_compose_styles_donts_default_style] + +// [START android_compose_styles_base_button] +@Composable +fun MyBaseButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + style: Style = Style, + enabled: Boolean = true, + interactionSource: MutableInteractionSource? = null, + content: @Composable RowScope.() -> Unit +) { + val effectiveInteractionSource = interactionSource ?: remember { MutableInteractionSource() } + val styleState = remember(effectiveInteractionSource) { + MutableStyleState(effectiveInteractionSource) + } + styleState.isEnabled = enabled + Row( + modifier = modifier + .clickable( + enabled = enabled, + onClick = onClick, + interactionSource = effectiveInteractionSource, + indication = null, + ) + .styleable(styleState, Style, style), // Assuming some base style + content = content, + verticalAlignment = Alignment.CenterVertically + ) +} +// [END android_compose_styles_base_button] + +// [START android_compose_styles_hover_button] +@Preview +@Composable +fun Button52() { + Box( + modifier = Modifier.padding(32.dp), + contentAlignment = Alignment.Center + ) { + MyBaseButton( + onClick = {}, + style = Style { + background(Color.Transparent) + shape(RoundedCornerShape(0.dp)) + border(1.dp, Color.Black) + contentColor(Color.Black) + fontSize(16.sp) + fontWeight(FontWeight.Light) + letterSpacing(1.sp) + contentPadding(vertical = 13.dp, horizontal = 20.dp) + dropShadow( + Shadow( + spread = 0.dp, color = Color(0xFFFFE54C), + radius = 0.dp, + offset = DpOffset(7.dp, 7.dp) + ) + ) + hovered { + animate(tween(200)) { + dropShadow( + Shadow( + spread = 0.dp, color = Color(0xFFFFE54C), + radius = 0.dp, + offset = DpOffset(0.dp, 0.dp) + ) + ) + } + } + } + ) { + Text("Button 52") + } + } +} +// [END android_compose_styles_hover_button] + +// [START android_compose_styles_rounded_depth_button] +@Preview +@Composable +fun Button74() { + val density = LocalDensity.current + val buttonStyle = Style { + background(Color(0xFFFBEED0)) + border(2.dp, Color(0xFF422800)) + shape(RoundedCornerShape(30.dp)) + dropShadow( + Shadow( + color = Color(0xFF422800), offset = DpOffset(4.dp, 4.dp), + radius = 0.dp, spread = 0.dp + ) + ) + contentColor(Color(0xFF422800)) + fontWeight(FontWeight.SemiBold) + fontSize(18.sp) + contentPaddingHorizontal(25.dp) + externalPadding(8.dp) + height(50.dp) + textAlign(TextAlign.Center) + hovered { + animate { + background(Color.White) + } + } + pressed { + animate { + dropShadow( + Shadow( + color = Color(0xFF422800), + offset = DpOffset(2.dp, 2.dp), + radius = 0.dp, + spread = 0.dp + ) + ) + translation(with(density) { 2.dp.toPx() }, with(density) { 2.dp.toPx() }) + } + } + } + MyBaseButton( + onClick = {}, + style = buttonStyle + ) { + Text("Button 74") + } +} +// [END android_compose_styles_rounded_depth_button] + +// [START android_compose_styles_depth_pressed_button] +@Preview +@Composable +fun Button19() { + val interactionSource = remember { MutableInteractionSource() } + val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } + val density = LocalDensity.current + + Box( + modifier = Modifier + .padding(100.dp) + .windowInsetsPadding(WindowInsets.systemBars) + .size(200.dp, 50.dp) + .clickable(interactionSource, indication = null) {}, + contentAlignment = Alignment.Center + ) { + val edgeStyle = Style { + fillSize() + shape(RoundedCornerShape(16.dp)) + background(Color(0xFF1CB0F6)) + } + + val frontStyle = Style { + fillSize() + background(Color(0xFF1899D6)) + shape(RoundedCornerShape(16.dp)) + contentPadding(vertical = 13.dp, horizontal = 16.dp) + translationY(with(density) { (-4).dp.toPx() }) + pressed { + animate { + translationY(with(density) { (0).dp.toPx() }) + } + } + } + Box(modifier = Modifier.styleable(styleState, edgeStyle)) { + Box( + modifier = Modifier + .styleable(styleState, frontStyle), + contentAlignment = Alignment.Center + ) { + Text( + "Button 19".toUpperCase(Locale.current), + style = Style { + contentColor(Color.White) + fontSize(15.sp) + fontWeight(FontWeight.Bold) + letterSpacing(0.8.sp) + } + ) + } + } + } +} +// [END android_compose_styles_depth_pressed_button] + +// [START android_compose_styles_gradient_glow_button] +@Preview +@Composable +fun Button85() { + val infiniteTransition = rememberInfiniteTransition(label = "glowing_button_85_animation") + val animatedProgress by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 20000, easing = LinearEasing), + ), label = "progress" + ) + + val gradientColors = listOf( + Color(0xffff0000), Color(0xffff7300), Color(0xfffffb00), Color(0xff48ff00), + Color(0xff00ffd5), Color(0xff002bff), Color(0xff7a00ff), Color(0xffff00c8), + Color(0xffff0000) + ) + + val glowingBrush = remember(animatedProgress) { + object : ShaderBrush() { + override fun createShader(size: androidx.compose.ui.geometry.Size): androidx.compose.ui.graphics.Shader { + val width = size.width * 4 + val brushSize = width * animatedProgress + return androidx.compose.ui.graphics.LinearGradientShader( + colors = gradientColors, + from = androidx.compose.ui.geometry.Offset(brushSize, 0f), + to = androidx.compose.ui.geometry.Offset(brushSize + width, 0f), + tileMode = TileMode.Repeated + ) + } + } + } + + + Box( + modifier = Modifier + .padding(32.dp), + contentAlignment = Alignment.Center + ) { + MyBaseButton( + onClick = { }, + style = Style { + dropShadow( + Shadow( + brush = glowingBrush, + radius = 5.dp + ) + ) + transformOrigin(TransformOrigin.Center) + pressed { + animate { + dropShadow( + Shadow( + brush = glowingBrush, + radius = 10.dp + ) + ) + scale(0.95f) + } + + } + size(width = 200.dp, height = 50.dp) + background(Color(0xFF111111)) + shape(RoundedCornerShape(10.dp)) + contentColor(Color.White) + contentPadding(vertical = (0.6f * 14).dp, horizontal = (2f * 14).dp) + border(width = 0.dp, color = Color.Transparent) + } + ) { + Text(text = "Button 85") + } + } +} +// [END android_compose_styles_gradient_glow_button] + +// Dummy classes to satisfy the compiler/snippets if they were ever built +class Style +fun Style(block: StyleScope.() -> Unit): Style = Style() +interface StyleScope { + fun background(color: Color) + fun background(brush: Brush) + fun shape(shape: RoundedCornerShape) + fun border(width: androidx.compose.ui.unit.Dp, color: Color) + fun contentColor(color: Color) + fun fontSize(size: androidx.compose.ui.unit.TextUnit) + fun fontWeight(weight: FontWeight) + fun letterSpacing(spacing: androidx.compose.ui.unit.TextUnit) + fun contentPadding(all: androidx.compose.ui.unit.Dp) + fun contentPadding(vertical: androidx.compose.ui.unit.Dp, horizontal: androidx.compose.ui.unit.Dp) + fun contentPaddingStart(padding: androidx.compose.ui.unit.Dp) + fun contentPaddingTop(padding: androidx.compose.ui.unit.Dp) + fun contentPaddingHorizontal(padding: androidx.compose.ui.unit.Dp) + fun externalPadding(padding: androidx.compose.ui.unit.Dp) + fun dropShadow(shadow: Shadow) + fun hovered(block: StyleScope.() -> Unit) + fun pressed(block: StyleScope.() -> Unit) + fun animate(spec: androidx.compose.animation.core.AnimationSpec = tween(), block: StyleScope.() -> Unit) + fun animate(block: StyleScope.() -> Unit) + fun width(width: androidx.compose.ui.unit.Dp) + fun height(height: androidx.compose.ui.unit.Dp) + fun size(size: androidx.compose.ui.unit.Dp) + fun size(width: androidx.compose.ui.unit.Dp, height: androidx.compose.ui.unit.Dp) + fun fillSize() + fun translation(x: Float, y: Float) + fun translationY(y: Float) + fun scale(scale: Float) + fun transformOrigin(origin: TransformOrigin) + fun textAlign(align: TextAlign) + fun contentBrush(brush: Brush) +} +infix fun Style.then(other: Style): Style = Style() +fun Modifier.styleable(state: MutableStyleState, style: Style = Style, overrideStyle: Style = Style): Modifier = this +fun Modifier.styleable(block: StyleScope.() -> Unit): Modifier = this +class MutableStyleState(interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource?) { + var isEnabled: Boolean = true +} +class Shadow(val color: Color = Color.Unspecified, val brush: Brush? = null, val offset: DpOffset = DpOffset.Zero, val radius: androidx.compose.ui.unit.Dp = 0.dp, val spread: androidx.compose.ui.unit.Dp = 0.dp) From 6107458f0cca277ffd1493b48ab05f8b05a7678e Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 3 Mar 2026 12:04:51 +0000 Subject: [PATCH 02/23] Update design system style snippets to use Foundation Style API - Add `BaseText` and `BaseButton` components implementing `ExperimentalFoundationStyleApi` - Replace dummy style classes with official `androidx.compose.foundation.style` imports - Update `StylesSnippets.kt` to use the new `BaseText` and `BaseButton` implementations - Relocate style snippets to `designsystems.styles` package - Update `libs.versions.toml` to use `compose-bom-alpha` and remove explicit version overrides for core Compose libraries --- .../{ => styles}/StylesSnippets.kt | 129 +++++++----------- .../designsystems/styles/components/Button.kt | 52 +++++++ .../designsystems/styles/components/Text.kt | 37 +++++ gradle/libs.versions.toml | 10 +- 4 files changed, 146 insertions(+), 82 deletions(-) rename compose/snippets/src/main/java/com/example/compose/snippets/designsystems/{ => styles}/StylesSnippets.kt (84%) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Text.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/StylesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt similarity index 84% rename from compose/snippets/src/main/java/com/example/compose/snippets/designsystems/StylesSnippets.kt rename to compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt index 67a122d2f..c079c5bdb 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/StylesSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.example.compose.snippets.designsystems +@file:OptIn(ExperimentalFoundationStyleApi::class) + +package com.example.compose.snippets.designsystems.styles import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateFloat @@ -35,6 +37,15 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.MutableStyleState +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.StyleScope +import androidx.compose.foundation.style.fillSize +import androidx.compose.foundation.style.hovered +import androidx.compose.foundation.style.pressed +import androidx.compose.foundation.style.styleable +import androidx.compose.foundation.style.then import androidx.compose.material3.Checkbox import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -45,11 +56,16 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.Shader import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale @@ -59,15 +75,17 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.example.compose.snippets.designsystems.styles.components.BaseButton +import com.example.compose.snippets.designsystems.styles.components.BaseText // [START android_compose_styles_basic_button] @Composable fun BasicButtonStyle() { - Button( + BaseButton( onClick = { }, style = { } ) { - Text("Click me") + BaseText("Click me") } } // [END android_compose_styles_basic_button] @@ -75,11 +93,11 @@ fun BasicButtonStyle() { // [START android_compose_styles_button_background] @Composable fun ButtonBackgroundStyle() { - Button( + BaseButton( onClick = { }, style = { background(Color.Blue) } ) { - Text("Click me") + BaseText("Click me") } } // [END android_compose_styles_button_background] @@ -90,7 +108,7 @@ fun RowStyleable() { Row( modifier = Modifier.styleable { } ) { - Text("Content") + BaseText("Content") } } // [END android_compose_styles_row_styleable] @@ -103,7 +121,7 @@ fun RowStyleableBackground() { background(Color.Blue) } ) { - Text("Content") + BaseText("Content") } } // [END android_compose_styles_row_styleable_background] @@ -118,8 +136,8 @@ fun StandaloneStyleUsage() { val style = Style { background(Color.Blue) } // built in parameter - Button(onClick = { }, style = style) { - Text("Button") + BaseButton(onClick = { }, style = style) { + BaseText("Button") } // modifier styleable @@ -127,7 +145,7 @@ fun StandaloneStyleUsage() { Column( Modifier.styleable(styleState, style) ) { - Text("Column content") + BaseText("Column content") } } // [END android_compose_styles_standalone_usage] @@ -138,23 +156,23 @@ fun MultipleComponentsStyle() { val style = Style { background(Color.Blue) } // built in parameter - Button(onClick = { }, style = style) { - Text("Button") + BaseButton(onClick = { }, style = style) { + BaseText("Button") } - Checkbox(checked = true, onCheckedChange = { }, style = style) + BaseText("Different text that uses the same style parameter", style = style) // modifier styleable val columnStyleState = remember { MutableStyleState(null) } Column( Modifier.styleable(columnStyleState, style) ) { - Text("Column") + BaseText("Column") } val rowStyleState = remember { MutableStyleState(null) } Row( Modifier.styleable(rowStyleState, style) ) { - Text("Row") + BaseText("Row") } } // [END android_compose_styles_multiple_components] @@ -162,14 +180,14 @@ fun MultipleComponentsStyle() { // [START android_compose_styles_multiple_properties] @Composable fun MultiplePropertiesStyle() { - Button( + BaseButton( onClick = { }, style = { background(Color.Blue) contentPaddingStart(16.dp) } ) { - Text("Button") + BaseText("Button") } } // [END android_compose_styles_multiple_properties] @@ -179,7 +197,7 @@ val TealColor = Color(0xFF008080) // [START android_compose_styles_overwrite_properties] @Composable fun OverwritePropertiesStyle() { - Button( + BaseButton( style = { background(Color.Red) // Background of Red is now overridden with TealColor instead @@ -193,7 +211,7 @@ fun OverwritePropertiesStyle() { // } ) { - Text("Click me!") + BaseText("Click me!") } } // [END android_compose_styles_overwrite_properties] @@ -204,13 +222,13 @@ fun MergeStyles() { val style1 = Style { background(TealColor) } val style2 = Style { contentPaddingTop(16.dp) } - Button( + BaseButton( style = style1 then style2, onClick = { }, ) { - Text("Click me!") + BaseText("Click me!") } } // [END android_compose_styles_merge_styles] @@ -228,13 +246,13 @@ fun MergeOverwriteStyles() { background(Color.LightGray) } - Button( + BaseButton( style = style1 then style2, onClick = { }, ) { - Text("Click me!") + BaseText("Click me!") } } // [END android_compose_styles_merge_overwrite] @@ -252,9 +270,9 @@ fun ParentStyling() { contentBrush(Brush.linearGradient(colors)) }, ) { - Text("Children inherit", style = { width(60.dp) }) - Text("certain properties") - Text("from their parents") + BaseText("Children inherit", style = { width(60.dp) }) + BaseText("certain properties") + BaseText("from their parents") } } // [END android_compose_styles_parent_styling] @@ -272,11 +290,11 @@ fun ChildOverrideStyling() { contentBrush(Brush.linearGradient(colors)) }, ) { - Text("Children can ", style = { + BaseText("Children can ", style = { contentBrush(Brush.linearGradient(listOf(Color.Red, Color.Blue))) }) - Text("override properties") - Text("set by their parents") + BaseText("override properties") + BaseText("set by their parents") } } // [END android_compose_styles_child_override] @@ -572,7 +590,7 @@ fun Button19() { .styleable(styleState, frontStyle), contentAlignment = Alignment.Center ) { - Text( + BaseText( "Button 19".toUpperCase(Locale.current), style = Style { contentColor(Color.White) @@ -608,13 +626,13 @@ fun Button85() { val glowingBrush = remember(animatedProgress) { object : ShaderBrush() { - override fun createShader(size: androidx.compose.ui.geometry.Size): androidx.compose.ui.graphics.Shader { + override fun createShader(size: Size): Shader { val width = size.width * 4 val brushSize = width * animatedProgress - return androidx.compose.ui.graphics.LinearGradientShader( + return LinearGradientShader( colors = gradientColors, - from = androidx.compose.ui.geometry.Offset(brushSize, 0f), - to = androidx.compose.ui.geometry.Offset(brushSize + width, 0f), + from = Offset(brushSize, 0f), + to = Offset(brushSize + width, 0f), tileMode = TileMode.Repeated ) } @@ -662,46 +680,3 @@ fun Button85() { } } // [END android_compose_styles_gradient_glow_button] - -// Dummy classes to satisfy the compiler/snippets if they were ever built -class Style -fun Style(block: StyleScope.() -> Unit): Style = Style() -interface StyleScope { - fun background(color: Color) - fun background(brush: Brush) - fun shape(shape: RoundedCornerShape) - fun border(width: androidx.compose.ui.unit.Dp, color: Color) - fun contentColor(color: Color) - fun fontSize(size: androidx.compose.ui.unit.TextUnit) - fun fontWeight(weight: FontWeight) - fun letterSpacing(spacing: androidx.compose.ui.unit.TextUnit) - fun contentPadding(all: androidx.compose.ui.unit.Dp) - fun contentPadding(vertical: androidx.compose.ui.unit.Dp, horizontal: androidx.compose.ui.unit.Dp) - fun contentPaddingStart(padding: androidx.compose.ui.unit.Dp) - fun contentPaddingTop(padding: androidx.compose.ui.unit.Dp) - fun contentPaddingHorizontal(padding: androidx.compose.ui.unit.Dp) - fun externalPadding(padding: androidx.compose.ui.unit.Dp) - fun dropShadow(shadow: Shadow) - fun hovered(block: StyleScope.() -> Unit) - fun pressed(block: StyleScope.() -> Unit) - fun animate(spec: androidx.compose.animation.core.AnimationSpec = tween(), block: StyleScope.() -> Unit) - fun animate(block: StyleScope.() -> Unit) - fun width(width: androidx.compose.ui.unit.Dp) - fun height(height: androidx.compose.ui.unit.Dp) - fun size(size: androidx.compose.ui.unit.Dp) - fun size(width: androidx.compose.ui.unit.Dp, height: androidx.compose.ui.unit.Dp) - fun fillSize() - fun translation(x: Float, y: Float) - fun translationY(y: Float) - fun scale(scale: Float) - fun transformOrigin(origin: TransformOrigin) - fun textAlign(align: TextAlign) - fun contentBrush(brush: Brush) -} -infix fun Style.then(other: Style): Style = Style() -fun Modifier.styleable(state: MutableStyleState, style: Style = Style, overrideStyle: Style = Style): Modifier = this -fun Modifier.styleable(block: StyleScope.() -> Unit): Modifier = this -class MutableStyleState(interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource?) { - var isEnabled: Boolean = true -} -class Shadow(val color: Color = Color.Unspecified, val brush: Brush? = null, val offset: DpOffset = DpOffset.Zero, val radius: androidx.compose.ui.unit.Dp = 0.dp, val spread: androidx.compose.ui.unit.Dp = 0.dp) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt new file mode 100644 index 000000000..6968057d0 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt @@ -0,0 +1,52 @@ +@file:OptIn(ExperimentalFoundationStyleApi::class) + +package com.example.compose.snippets.designsystems.styles.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.MutableStyleState +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.styleable +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +val baseButtonStyle = Style { + +} + + +@ExperimentalFoundationStyleApi +@Composable +fun BaseButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + style: Style = Style, + enabled: Boolean = true, + interactionSource: MutableInteractionSource? = null, + content: @Composable RowScope.() -> Unit +) { + val effectiveInteractionSource = interactionSource ?: remember { + MutableInteractionSource() + } + val styleState = remember(effectiveInteractionSource) { + MutableStyleState(effectiveInteractionSource) + } + styleState.isEnabled = enabled + Row( + modifier = modifier + .clickable( + enabled = enabled, + onClick = onClick, + interactionSource = effectiveInteractionSource, + indication = null, + ) + .styleable(styleState, baseButtonStyle, style), + content = content, + verticalAlignment = Alignment.CenterVertically + ) +} \ No newline at end of file diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Text.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Text.kt new file mode 100644 index 000000000..83516e119 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Text.kt @@ -0,0 +1,37 @@ +package com.example.compose.snippets.designsystems.styles.components + +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.styleable +import androidx.compose.foundation.text.BasicText +import androidx.compose.foundation.text.TextAutoSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.style.TextOverflow + +@ExperimentalFoundationStyleApi +@Composable +fun BaseText( + text: String, + modifier: Modifier = Modifier, + style: Style = Style, + onTextLayout: ((TextLayoutResult) -> Unit)? = null, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + maxLines: Int = Int.MAX_VALUE, + minLines: Int = 1, + autoSize: TextAutoSize? = null, + +) { + BasicText( + text = text, + modifier = modifier.styleable(null, style), + onTextLayout = onTextLayout, + overflow = overflow, + softWrap = softWrap, + maxLines = maxLines, + minLines = minLines, + autoSize = autoSize, + ) +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a44682a23..4c0979adc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -120,10 +120,10 @@ androidx-appfunctions = { module = "androidx.appfunctions:appfunctions", version androidx-appfunctions-compiler = { module = "androidx.appfunctions:appfunctions-compiler", version.ref = "androidx-appfunctions" } androidx-appfunctions-service = { module = "androidx.appfunctions:appfunctions-service", version.ref = "androidx-appfunctions" } androidx-compose-animation-graphics = { module = "androidx.compose.animation:animation-graphics", version.ref = "compose-latest" } -androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "androidx-compose-bom" } -androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose-latest" } -androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout", version.ref = "compose-latest" } -androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "compose-latest" } +androidx-compose-bom = { module = "androidx.compose:compose-bom-alpha", version.ref = "androidx-compose-bom" } +androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" } +androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout" } +androidx-compose-material = { module = "androidx.compose.material:material" } androidx-compose-material-iconsExtended = { module = "androidx.compose.material:material-icons-extended" } androidx-compose-material-ripple = { module = "androidx.compose.material:material-ripple", version.ref = "compose-latest" } androidx-compose-material3 = { module = "androidx.compose.material3:material3" } @@ -133,7 +133,7 @@ androidx-compose-material3-adaptive-navigation = { module = "androidx.compose.ma androidx-compose-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3-adaptive-navigation-suite" } androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" } androidx-compose-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata" } -androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose-latest" } +androidx-compose-ui = { module = "androidx.compose.ui:ui" } androidx-compose-ui-googlefonts = { module = "androidx.compose.ui:ui-text-google-fonts" } androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test", version.ref = "compose-latest" } From b8ffda2f81af33f5a8f7b1e3038135e6a212bf7b Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 3 Mar 2026 13:13:49 +0000 Subject: [PATCH 03/23] Supress unused --- .../designsystems/styles/StylesSnippets.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt index c079c5bdb..a0c28ac30 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt @@ -15,6 +15,7 @@ */ @file:OptIn(ExperimentalFoundationStyleApi::class) +@file:Suppress("Unused", "UnusedVariable") package com.example.compose.snippets.designsystems.styles @@ -23,7 +24,6 @@ import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme @@ -46,8 +46,6 @@ import androidx.compose.foundation.style.hovered import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.styleable import androidx.compose.foundation.style.then -import androidx.compose.material3.Checkbox -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable @@ -367,7 +365,7 @@ fun CustomButton( .clickable(interactionSource = interactionSource, onClick = { }) .styleable(styleState, LocalAppStyles.current.baseButtonStyle, style) ) { - Text(text) + BaseText(text) } } // [END android_compose_styles_theme_integration] @@ -419,7 +417,7 @@ fun SpecialGradientButton( @Composable fun BadButton( modifier: Modifier = Modifier, - // ❌ DONT set a default style here as a parameter + // ❌ DON'T set a default style here as a parameter style: Style = Style { background(Color.Red) } ) { } @@ -494,7 +492,7 @@ fun Button52() { } } ) { - Text("Button 52") + BaseText("Button 52") } } } @@ -545,7 +543,7 @@ fun Button74() { onClick = {}, style = buttonStyle ) { - Text("Button 74") + BaseText("Button 74") } } // [END android_compose_styles_rounded_depth_button] @@ -675,7 +673,7 @@ fun Button85() { border(width = 0.dp, color = Color.Transparent) } ) { - Text(text = "Button 85") + BaseText(text = "Button 85") } } } From 2677e8a3625754f703ff5d9f4a37ed1704354441 Mon Sep 17 00:00:00 2001 From: riggaroo <9973046+riggaroo@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:17:36 +0000 Subject: [PATCH 04/23] Apply Spotless --- .../designsystems/styles/components/Button.kt | 16 ++++++++++++++++ .../designsystems/styles/components/Text.kt | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt index 6968057d0..23e8e884a 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + @file:OptIn(ExperimentalFoundationStyleApi::class) package com.example.compose.snippets.designsystems.styles.components diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Text.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Text.kt index 83516e119..cb0db2a2c 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Text.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Text.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.compose.snippets.designsystems.styles.components import androidx.compose.foundation.style.ExperimentalFoundationStyleApi From 6a6e46a810522cfddd8a975a14737f489019d5f6 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 3 Mar 2026 14:42:00 +0000 Subject: [PATCH 05/23] Add state and animation snippets --- .../designsystems/styles/StateAnimations.kt | 387 ++++++++++++++++++ .../designsystems/styles/StylesSnippets.kt | 36 +- .../designsystems/styles/components/Button.kt | 4 +- 3 files changed, 393 insertions(+), 34 deletions(-) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt new file mode 100644 index 000000000..c69dfe33c --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt @@ -0,0 +1,387 @@ +@file:OptIn(ExperimentalFoundationStyleApi::class) + +package com.example.compose.snippets.designsystems.styles + +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.MutableStyleState +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.StyleScope +import androidx.compose.foundation.style.StyleStateKey +import androidx.compose.foundation.style.focused +import androidx.compose.foundation.style.hovered +import androidx.compose.foundation.style.pressed +import androidx.compose.foundation.style.styleable +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.compose.snippets.designsystems.styles.components.BaseButton +import com.example.compose.snippets.designsystems.styles.components.BaseText + +// [START android_compose_styles_state_basic] +@Composable +private fun OpenButton() { + BaseButton( + style = { + shape(RoundedCornerShape(0.dp)) + background(Color.White) + hovered { + val lightPurple = Color(0xFFD1C4E9) + background(lightPurple) + } + focused { + val lightBlue = Color(0xFF81D4FA) + background(lightBlue) + } + }, + onClick = { }, + content = { + BaseText("Open in Studio", style = { + contentColor(Color.Black) + fontSize(26.sp) + textAlign(TextAlign.Center) + }) + } + ) +} + +// [END android_compose_styles_state_basic] + +// [START android_compose_styles_state_basic_combined_states] +@Composable +private fun OpenButton_CombinedStates() { + BaseButton( + style = { + shape(RoundedCornerShape(0.dp)) + background(Color.White) + hovered { + // light purple + val lightPurple = Color(0xFFD1C4E9) + background(lightPurple) + pressed { + // When running on a device that can hover, whilst hovering and then pressing the button this would be invoked + val lightOrange = Color(0xFFFFE0B2) + background(lightOrange) + } + } + pressed { + // when running on a device without a mouse attached, this would be invoked as you wouldn't be in a hovered state only + val lightRed = Color(0xFF66E7DC) + background(lightRed) + } + focused { + val lightBlue = Color(0xFF81D4FA) + background(lightBlue) + } + }, + onClick = { }, + content = { + BaseText("Open in Studio", style = { + contentColor(Color.Black) + fontSize(26.sp) + textAlign(TextAlign.Center) + }) + } + ) +} +// [END android_compose_styles_state_basic_combined_states] + +val baseGradientButtonStyle = Style { + contentPadding(8.dp) + background(Brush.verticalGradient(listOf(Color.Red, Color.Blue))) +} + +// [START android_compose_styles_state_custom_state_gradient] +@Composable +private fun GradientButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + style: Style = Style, + enabled: Boolean = true, + interactionSource: MutableInteractionSource? = null, + content: @Composable RowScope.() -> Unit, +) { + val interactionSource = interactionSource ?: remember { MutableInteractionSource() } + val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } + styleState.isEnabled = enabled + Row( + modifier = + modifier + .clickable( + onClick = onClick, + enabled = enabled, + interactionSource = interactionSource, + indication = null, + ) + .styleable(styleState, baseGradientButtonStyle, style), + content = content, + ) +} +// [END android_compose_styles_state_custom_state_gradient] + +// [START android_compose_styles_state_override] +@Preview +@Composable +fun LoginButton() { + val loginButtonStyle = Style { + externalPadding(8.dp) + pressed { + background( + Brush.linearGradient( + listOf(Color.Magenta, Color.Red) + ) + ) + } + } + GradientButton(onClick = { + // Login logic + }, style = loginButtonStyle) { + BaseText("Login") + } +} +// [END android_compose_styles_state_override] + +// [START android_compose_styles_animating_style ] +val animatingStyle = Style { + border(3.dp, Color.Black) + background(Color.White) + size(100.dp) + + pressed { + animate { + borderColor(Color.Magenta) + background(Color(0xFFB39DDB)) + } + } +} + +@Preview +@Composable +private fun AnimatingStyleChanges() { + val interactionSource = remember { MutableInteractionSource() } + val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } + Box(modifier = Modifier + .clickable( + interactionSource, + enabled = true, + indication = null, + onClick = { + + } + ) + .styleable(styleState, animatingStyle)) { + + } +} +// [END android_compose_styles_animating_style ] + +// [START android_compose_styles_animating_style_spec ] +val animatingStyleSpec = Style { + externalPadding(8.dp) + border(3.dp, Color.Black) + background(Color.White) + size(100.dp) + + pressed { + animate { + borderColor(Color.Magenta) + background(Color(0xFFB39DDB)) + } + animate(spring(dampingRatio = Spring.DampingRatioMediumBouncy)) { + size(120.dp) + } + } +} + +@Preview(showBackground = true) +@Composable +fun AnimatingStyleChangesSpec() { + val interactionSource = remember { MutableInteractionSource() } + val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } + Box(modifier = Modifier + .clickable( + interactionSource, + enabled = true, + indication = null, + onClick = { + + } + ) + .styleable(styleState, animatingStyleSpec)) +} +// [END android_compose_styles_animating_style_spec ] + +// [START android_compose_styles_custom_key_1] +enum class PlayerState { + Stopped, + Playing, + Paused +} + +val playerStateKey = StyleStateKey(PlayerState.Stopped) +// [END android_compose_styles_custom_key_1] + +// [START android_compose_styles_custom_key_2] +// Extension Function on MutableStyleState to query and set the current playState +var MutableStyleState.playerState + get() = this[playerStateKey] + set(value) { this[playerStateKey] = value } + +fun StyleScope.playerPlaying(value: Style) { + state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) +} +fun StyleScope.playerPaused(value: Style) { + state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) +} +// [END android_compose_styles_custom_key_2] + +// [START android_compose_styles_link_to_custom_state] +@Composable +fun MediaPlayer( + url: String, + modifier: Modifier = Modifier, + style: Style = Style, + state: PlayerState = remember { PlayerState.Paused } +) { + // Hoist style state, set playerState as a parameter, + val styleState = remember { MutableStyleState(null) } + // Set equal to incoming state to link the two together + styleState.playerState = state + Box( + //.. + ) { + ///.. + } +} +// [END android_compose_styels_link_to_custom_state] + +private object Step2StyleState { + // [START android_compose_styles_link_to_custom_state_pass] + @Composable + fun MediaPlayer( + url: String, + modifier: Modifier = Modifier, + style: Style = Style, + state: PlayerState = remember { PlayerState.Paused } + ) { + // Hoist style state, set playstate as a parameter, + val styleState = remember { MutableStyleState(null) } + // Set equal to incoming state to link the two together + styleState.playerState = state + Box( + modifier = modifier.styleable(styleState, style)) { + ///.. + } + } + // [END android_compose_styles_link_to_custom_state_pass] +} +private object Step3StyleState { + // [START android_compose_styles_link_to_custom_state_key] + @Composable + fun StyleStateKeySample() { + // Using the extension function to change the border color to green while playing + val style = Style { + borderColor(Color.Gray) + playerPlaying { + animate { + borderColor(Color.Green) + } + } + playerPaused { + animate { + borderColor(Color.Blue) + } + } + } + val styleState = remember { MutableStyleState(null) } + styleState[playerStateKey] = PlayerState.Playing + + // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. + MediaPlayer(url = "https://example.com/media/video", + style = style, + state = PlayerState.Stopped) + } + // [START android_compose_styles_link_to_custom_state_key] +} + +private object Step4FullSnippetState { + // [START android_compose_styles_state_full_snippet] + enum class PlayerState { + Stopped, + Playing, + Paused + } + val playerStateKey = StyleStateKey(PlayerState.Stopped) + var MutableStyleState.playerState + get() = this[playerStateKey] + set(value) { this[playerStateKey] = value } + + fun StyleScope.playerPlaying(value: Style) { + state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) + } + fun StyleScope.playerPaused(value: Style) { + state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) + + } + + @Composable + fun MediaPlayer( + url: String, + modifier: Modifier = Modifier, + style: Style = Style, + state: PlayerState = remember { PlayerState.Paused } + ) { + // Hoist style state, set playstate as a parameter, + val styleState = remember { MutableStyleState(null) } + // Set equal to incoming state to link the two together + styleState.playerState = state + Box( + modifier = modifier.styleable(styleState, Style { + size(100.dp) + border(2.dp, Color.Red) + + }, style, )) { + + ///.. + } + } + @Composable + fun StyleStateKeySample() { + // Using the extension function to change the border color to green while playing + val style = Style { + borderColor(Color.Gray) + playerPlaying { + animate { + borderColor(Color.Green) + } + } + playerPaused { + animate { + borderColor(Color.Blue) + } + } + } + val styleState = remember { MutableStyleState(null) } + styleState[playerStateKey] = PlayerState.Playing + + // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. + MediaPlayer(url = "https://example.com/media/video", + style = style, + state = PlayerState.Stopped) + } + // [END android_compose_styles_state_full_snippet] +} + diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt index a0c28ac30..738b3b338 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt @@ -423,36 +423,6 @@ fun BadButton( } // [END android_compose_styles_donts_default_style] -// [START android_compose_styles_base_button] -@Composable -fun MyBaseButton( - onClick: () -> Unit, - modifier: Modifier = Modifier, - style: Style = Style, - enabled: Boolean = true, - interactionSource: MutableInteractionSource? = null, - content: @Composable RowScope.() -> Unit -) { - val effectiveInteractionSource = interactionSource ?: remember { MutableInteractionSource() } - val styleState = remember(effectiveInteractionSource) { - MutableStyleState(effectiveInteractionSource) - } - styleState.isEnabled = enabled - Row( - modifier = modifier - .clickable( - enabled = enabled, - onClick = onClick, - interactionSource = effectiveInteractionSource, - indication = null, - ) - .styleable(styleState, Style, style), // Assuming some base style - content = content, - verticalAlignment = Alignment.CenterVertically - ) -} -// [END android_compose_styles_base_button] - // [START android_compose_styles_hover_button] @Preview @Composable @@ -461,7 +431,7 @@ fun Button52() { modifier = Modifier.padding(32.dp), contentAlignment = Alignment.Center ) { - MyBaseButton( + BaseButton( onClick = {}, style = Style { background(Color.Transparent) @@ -539,7 +509,7 @@ fun Button74() { } } } - MyBaseButton( + BaseButton( onClick = {}, style = buttonStyle ) { @@ -643,7 +613,7 @@ fun Button85() { .padding(32.dp), contentAlignment = Alignment.Center ) { - MyBaseButton( + BaseButton( onClick = { }, style = Style { dropShadow( diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt index 6968057d0..29ceafde8 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt @@ -21,6 +21,7 @@ val baseButtonStyle = Style { @ExperimentalFoundationStyleApi +// [START android_compose_styles_base_button] @Composable fun BaseButton( onClick: () -> Unit, @@ -49,4 +50,5 @@ fun BaseButton( content = content, verticalAlignment = Alignment.CenterVertically ) -} \ No newline at end of file +} +// [END android_compose_styles_base_button] \ No newline at end of file From 7153c1a872ec339b2f0ee3055286b9d6077e31b2 Mon Sep 17 00:00:00 2001 From: riggaroo <9973046+riggaroo@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:45:35 +0000 Subject: [PATCH 06/23] Apply Spotless --- .../designsystems/styles/StateAnimations.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt index c69dfe33c..f89046b7d 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + @file:OptIn(ExperimentalFoundationStyleApi::class) package com.example.compose.snippets.designsystems.styles From c1c9b16b54d87c3e2c7b0e5ff9c48c858d7ed792 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 3 Mar 2026 14:49:14 +0000 Subject: [PATCH 07/23] Clean up snippets and make new files --- .../snippets/designsystems/styles/DosDonts.kt | 63 +++ .../snippets/designsystems/styles/Examples.kt | 277 +++++++++++++ .../designsystems/styles/StylesSnippets.kt | 374 ++---------------- 3 files changed, 377 insertions(+), 337 deletions(-) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt new file mode 100644 index 000000000..e2263cde5 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt @@ -0,0 +1,63 @@ +@file:OptIn(ExperimentalFoundationStyleApi::class) + +package com.example.compose.snippets.designsystems.styles + +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + + +// [START android_compose_styles_dos_expose_style] +@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 +} +// [END android_compose_styles_dos_expose_style] + +// [START android_compose_styles_dos_replace_params] +// 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) { +} +// [END android_compose_styles_dos_replace_params] + +// [START android_compose_styles_dos_wrapper] +@Composable +fun BaseButton( + modifier: Modifier = Modifier, + style: Style = Style +) { + // Uses LocalTheme.appStyles.button + incoming style +} + +// ✅ Do create wrapper composables that expose common implementations of the same component +@Composable +fun SpecialGradientButton( + modifier: Modifier = Modifier, + style: Style = Style +) { + // Uses LocalTheme.appStyles.button + LocalTheme.appStyles.gradientButton + incoming style - merge these styles +} +// [END android_compose_styles_dos_wrapper] + +// [START android_compose_styles_donts_default_style] +@Composable +fun BadButton( + modifier: Modifier = Modifier, + // ❌ DON'T set a default style here as a parameter + style: Style = Style { background(Color.Red) } +) { +} +// [END android_compose_styles_donts_default_style] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt new file mode 100644 index 000000000..21b79968b --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt @@ -0,0 +1,277 @@ +@file:OptIn(ExperimentalFoundationStyleApi::class) + +package com.example.compose.snippets.designsystems.styles + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.MutableStyleState +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.fillSize +import androidx.compose.foundation.style.hovered +import androidx.compose.foundation.style.pressed +import androidx.compose.foundation.style.styleable +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.Shader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.shadow.Shadow +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.toUpperCase +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.compose.snippets.designsystems.styles.components.BaseButton +import com.example.compose.snippets.designsystems.styles.components.BaseText + + +// [START android_compose_styles_hover_button] +@Preview +@Composable +fun HoverButtonExample() { + Box( + modifier = Modifier.padding(32.dp), + contentAlignment = Alignment.Center + ) { + BaseButton( + onClick = {}, + style = Style { + background(Color.Transparent) + shape(RoundedCornerShape(0.dp)) + border(1.dp, Color.Black) + contentColor(Color.Black) + fontSize(16.sp) + fontWeight(FontWeight.Light) + letterSpacing(1.sp) + contentPadding(vertical = 13.dp, horizontal = 20.dp) + dropShadow( + Shadow( + spread = 0.dp, color = Color(0xFFFFE54C), + radius = 0.dp, + offset = DpOffset(7.dp, 7.dp) + ) + ) + hovered { + animate(tween(200)) { + dropShadow( + Shadow( + spread = 0.dp, color = Color(0xFFFFE54C), + radius = 0.dp, + offset = DpOffset(0.dp, 0.dp) + ) + ) + } + } + } + ) { + BaseText("Button 52") + } + } +} +// [END android_compose_styles_hover_button] + +// [START android_compose_styles_rounded_depth_button] +@Preview +@Composable +fun RoundedDepthButtonExample() { + val density = LocalDensity.current + val buttonStyle = Style { + background(Color(0xFFFBEED0)) + border(2.dp, Color(0xFF422800)) + shape(RoundedCornerShape(30.dp)) + dropShadow( + Shadow( + color = Color(0xFF422800), offset = DpOffset(4.dp, 4.dp), + radius = 0.dp, spread = 0.dp + ) + ) + contentColor(Color(0xFF422800)) + fontWeight(FontWeight.SemiBold) + fontSize(18.sp) + contentPaddingHorizontal(25.dp) + externalPadding(8.dp) + height(50.dp) + textAlign(TextAlign.Center) + hovered { + animate { + background(Color.White) + } + } + pressed { + animate { + dropShadow( + Shadow( + color = Color(0xFF422800), + offset = DpOffset(2.dp, 2.dp), + radius = 0.dp, + spread = 0.dp + ) + ) + translation(with(density) { 2.dp.toPx() }, with(density) { 2.dp.toPx() }) + } + } + } + BaseButton( + onClick = {}, + style = buttonStyle + ) { + BaseText("Button 74") + } +} +// [END android_compose_styles_rounded_depth_button] + +// [START android_compose_styles_depth_pressed_button] +@Preview +@Composable +fun DepthPressedButtonExample() { + val interactionSource = remember { MutableInteractionSource() } + val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } + val density = LocalDensity.current + + Box( + modifier = Modifier + .padding(100.dp) + .windowInsetsPadding(WindowInsets.systemBars) + .size(200.dp, 50.dp) + .clickable(interactionSource, indication = null) {}, + contentAlignment = Alignment.Center + ) { + val edgeStyle = Style { + fillSize() + shape(RoundedCornerShape(16.dp)) + background(Color(0xFF1CB0F6)) + } + + val frontStyle = Style { + fillSize() + background(Color(0xFF1899D6)) + shape(RoundedCornerShape(16.dp)) + contentPadding(vertical = 13.dp, horizontal = 16.dp) + translationY(with(density) { (-4).dp.toPx() }) + pressed { + animate { + translationY(with(density) { (0).dp.toPx() }) + } + } + } + Box(modifier = Modifier.styleable(styleState, edgeStyle)) { + Box( + modifier = Modifier + .styleable(styleState, frontStyle), + contentAlignment = Alignment.Center + ) { + BaseText( + "Button 19".toUpperCase(Locale.current), + style = Style { + contentColor(Color.White) + fontSize(15.sp) + fontWeight(FontWeight.Bold) + letterSpacing(0.8.sp) + } + ) + } + } + } +} +// [END android_compose_styles_depth_pressed_button] + +// [START android_compose_styles_gradient_glow_button] +@Preview +@Composable +fun GradientGlowButtonExample() { + val infiniteTransition = rememberInfiniteTransition(label = "glowing_button_85_animation") + val animatedProgress by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 20000, easing = LinearEasing), + ), label = "progress" + ) + + val gradientColors = listOf( + Color(0xffff0000), Color(0xffff7300), Color(0xfffffb00), Color(0xff48ff00), + Color(0xff00ffd5), Color(0xff002bff), Color(0xff7a00ff), Color(0xffff00c8), + Color(0xffff0000) + ) + + val glowingBrush = remember(animatedProgress) { + object : ShaderBrush() { + override fun createShader(size: Size): Shader { + val width = size.width * 4 + val brushSize = width * animatedProgress + return LinearGradientShader( + colors = gradientColors, + from = Offset(brushSize, 0f), + to = Offset(brushSize + width, 0f), + tileMode = TileMode.Repeated + ) + } + } + } + + + Box( + modifier = Modifier + .padding(32.dp), + contentAlignment = Alignment.Center + ) { + BaseButton( + onClick = { }, + style = Style { + dropShadow( + Shadow( + brush = glowingBrush, + radius = 5.dp + ) + ) + transformOrigin(TransformOrigin.Center) + pressed { + animate { + dropShadow( + Shadow( + brush = glowingBrush, + radius = 10.dp + ) + ) + scale(0.95f) + } + + } + size(width = 200.dp, height = 50.dp) + background(Color(0xFF111111)) + shape(RoundedCornerShape(10.dp)) + contentColor(Color.White) + contentPadding(vertical = (0.6f * 14).dp, horizontal = (2f * 14).dp) + border(width = 0.dp, color = Color.Transparent) + } + ) { + BaseText(text = "Button 85") + } + } +} +// [END android_compose_styles_gradient_glow_button] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt index 738b3b338..bcfb30e15 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt @@ -19,101 +19,71 @@ package com.example.compose.snippets.designsystems.styles -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.MutableStyleState import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.StyleScope -import androidx.compose.foundation.style.fillSize -import androidx.compose.foundation.style.hovered -import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.styleable import androidx.compose.foundation.style.then import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable import androidx.compose.runtime.compositionLocalOf -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.LinearGradientShader -import androidx.compose.ui.graphics.Shader -import androidx.compose.ui.graphics.ShaderBrush -import androidx.compose.ui.graphics.TileMode -import androidx.compose.ui.graphics.TransformOrigin -import androidx.compose.ui.graphics.shadow.Shadow -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.toUpperCase -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.example.compose.snippets.designsystems.styles.components.BaseButton import com.example.compose.snippets.designsystems.styles.components.BaseText -// [START android_compose_styles_basic_button] @Composable fun BasicButtonStyle() { + // [START android_compose_styles_basic_button] BaseButton( onClick = { }, style = { } ) { BaseText("Click me") } + // [END android_compose_styles_basic_button] } -// [END android_compose_styles_basic_button] -// [START android_compose_styles_button_background] @Composable fun ButtonBackgroundStyle() { + // [START android_compose_styles_button_background] BaseButton( onClick = { }, style = { background(Color.Blue) } ) { BaseText("Click me") } + // [END android_compose_styles_button_background] } -// [END android_compose_styles_button_background] -// [START android_compose_styles_row_styleable] + @Composable fun RowStyleable() { + // [START android_compose_styles_row_styleable] Row( modifier = Modifier.styleable { } ) { BaseText("Content") } + // [END android_compose_styles_row_styleable] } -// [END android_compose_styles_row_styleable] -// [START android_compose_styles_row_styleable_background] + @Composable fun RowStyleableBackground() { + // [START android_compose_styles_row_styleable_background] Row( modifier = Modifier.styleable { background(Color.Blue) @@ -121,16 +91,17 @@ fun RowStyleableBackground() { ) { BaseText("Content") } + // [END android_compose_styles_row_styleable_background] } -// [END android_compose_styles_row_styleable_background] + // [START android_compose_styles_standalone_style] val style = Style { background(Color.Blue) } // [END android_compose_styles_standalone_style] -// [START android_compose_styles_standalone_usage] @Composable fun StandaloneStyleUsage() { + // [START android_compose_styles_standalone_usage] val style = Style { background(Color.Blue) } // built in parameter @@ -145,12 +116,13 @@ fun StandaloneStyleUsage() { ) { BaseText("Column content") } + // [END android_compose_styles_standalone_usage] } -// [END android_compose_styles_standalone_usage] -// [START android_compose_styles_multiple_components] @Composable fun MultipleComponentsStyle() { + +// [START android_compose_styles_multiple_components] val style = Style { background(Color.Blue) } // built in parameter @@ -172,12 +144,14 @@ fun MultipleComponentsStyle() { ) { BaseText("Row") } + // [END android_compose_styles_multiple_components] } -// [END android_compose_styles_multiple_components] -// [START android_compose_styles_multiple_properties] + @Composable fun MultiplePropertiesStyle() { + +// [START android_compose_styles_multiple_properties] BaseButton( onClick = { }, style = { @@ -187,14 +161,16 @@ fun MultiplePropertiesStyle() { ) { BaseText("Button") } + // [END android_compose_styles_multiple_properties] } -// [END android_compose_styles_multiple_properties] + val TealColor = Color(0xFF008080) -// [START android_compose_styles_overwrite_properties] + @Composable fun OverwritePropertiesStyle() { + // [START android_compose_styles_overwrite_properties] BaseButton( style = { background(Color.Red) @@ -211,12 +187,14 @@ fun OverwritePropertiesStyle() { ) { BaseText("Click me!") } + // [END android_compose_styles_overwrite_properties] } -// [END android_compose_styles_overwrite_properties] -// [START android_compose_styles_merge_styles] + + @Composable fun MergeStyles() { + // [START android_compose_styles_merge_styles] val style1 = Style { background(TealColor) } val style2 = Style { contentPaddingTop(16.dp) } @@ -228,12 +206,12 @@ fun MergeStyles() { ) { BaseText("Click me!") } + // [END android_compose_styles_merge_styles] } -// [END android_compose_styles_merge_styles] -// [START android_compose_styles_merge_overwrite] @Composable fun MergeOverwriteStyles() { + // [START android_compose_styles_merge_overwrite] val style1 = Style { background(Color.Red) contentPadding(32.dp) @@ -252,12 +230,12 @@ fun MergeOverwriteStyles() { ) { BaseText("Click me!") } + // [END android_compose_styles_merge_overwrite] } -// [END android_compose_styles_merge_overwrite] -// [START android_compose_styles_parent_styling] @Composable fun ParentStyling() { + // [START android_compose_styles_parent_styling] val styleState = remember { MutableStyleState(null) } Column( modifier = Modifier.styleable(styleState) { @@ -272,12 +250,13 @@ fun ParentStyling() { BaseText("certain properties") BaseText("from their parents") } + // [END android_compose_styles_parent_styling] } -// [END android_compose_styles_parent_styling] -// [START android_compose_styles_child_override] + @Composable fun ChildOverrideStyling() { + // [START android_compose_styles_child_override] val styleState = remember { MutableStyleState(null) } Column( modifier = Modifier.styleable(styleState) { @@ -294,8 +273,8 @@ fun ChildOverrideStyling() { BaseText("override properties") BaseText("set by their parents") } + // [END android_compose_styles_child_override] } -// [END android_compose_styles_child_override] // [START android_compose_styles_custom_extension] fun StyleScope.outlinedBackground(color: Color) { @@ -310,16 +289,16 @@ val customExtensionStyle = Style { } // [END android_compose_styles_custom_extension_usage] -// [START android_compose_styles_composition_local] @Composable fun CompositionLocalStyle() { + // [START android_compose_styles_composition_local] val buttonStyle = Style { contentPadding(12.dp) shape(RoundedCornerShape(50)) background(Color.Blue) // Simplified for snippet } + // [END android_compose_styles_composition_local] } -// [END android_compose_styles_composition_local] // [START android_compose_styles_design_system_component] @Composable @@ -369,282 +348,3 @@ fun CustomButton( } } // [END android_compose_styles_theme_integration] - -// [START android_compose_styles_dos_expose_style] -@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 -} -// [END android_compose_styles_dos_expose_style] - -// [START android_compose_styles_dos_replace_params] -// 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) { -} -// [END android_compose_styles_dos_replace_params] - -// [START android_compose_styles_dos_wrapper] -@Composable -fun BaseButton( - modifier: Modifier = Modifier, - style: Style = Style -) { - // Uses LocalTheme.appStyles.button + incoming style -} - -// ✅ Do create wrapper composables that expose common implementations of the same component -@Composable -fun SpecialGradientButton( - modifier: Modifier = Modifier, - style: Style = Style -) { - // Uses LocalTheme.appStyles.button + LocalTheme.appStyles.gradientButton + incoming style - merge these styles -} -// [END android_compose_styles_dos_wrapper] - -// [START android_compose_styles_donts_default_style] -@Composable -fun BadButton( - modifier: Modifier = Modifier, - // ❌ DON'T set a default style here as a parameter - style: Style = Style { background(Color.Red) } -) { -} -// [END android_compose_styles_donts_default_style] - -// [START android_compose_styles_hover_button] -@Preview -@Composable -fun Button52() { - Box( - modifier = Modifier.padding(32.dp), - contentAlignment = Alignment.Center - ) { - BaseButton( - onClick = {}, - style = Style { - background(Color.Transparent) - shape(RoundedCornerShape(0.dp)) - border(1.dp, Color.Black) - contentColor(Color.Black) - fontSize(16.sp) - fontWeight(FontWeight.Light) - letterSpacing(1.sp) - contentPadding(vertical = 13.dp, horizontal = 20.dp) - dropShadow( - Shadow( - spread = 0.dp, color = Color(0xFFFFE54C), - radius = 0.dp, - offset = DpOffset(7.dp, 7.dp) - ) - ) - hovered { - animate(tween(200)) { - dropShadow( - Shadow( - spread = 0.dp, color = Color(0xFFFFE54C), - radius = 0.dp, - offset = DpOffset(0.dp, 0.dp) - ) - ) - } - } - } - ) { - BaseText("Button 52") - } - } -} -// [END android_compose_styles_hover_button] - -// [START android_compose_styles_rounded_depth_button] -@Preview -@Composable -fun Button74() { - val density = LocalDensity.current - val buttonStyle = Style { - background(Color(0xFFFBEED0)) - border(2.dp, Color(0xFF422800)) - shape(RoundedCornerShape(30.dp)) - dropShadow( - Shadow( - color = Color(0xFF422800), offset = DpOffset(4.dp, 4.dp), - radius = 0.dp, spread = 0.dp - ) - ) - contentColor(Color(0xFF422800)) - fontWeight(FontWeight.SemiBold) - fontSize(18.sp) - contentPaddingHorizontal(25.dp) - externalPadding(8.dp) - height(50.dp) - textAlign(TextAlign.Center) - hovered { - animate { - background(Color.White) - } - } - pressed { - animate { - dropShadow( - Shadow( - color = Color(0xFF422800), - offset = DpOffset(2.dp, 2.dp), - radius = 0.dp, - spread = 0.dp - ) - ) - translation(with(density) { 2.dp.toPx() }, with(density) { 2.dp.toPx() }) - } - } - } - BaseButton( - onClick = {}, - style = buttonStyle - ) { - BaseText("Button 74") - } -} -// [END android_compose_styles_rounded_depth_button] - -// [START android_compose_styles_depth_pressed_button] -@Preview -@Composable -fun Button19() { - val interactionSource = remember { MutableInteractionSource() } - val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } - val density = LocalDensity.current - - Box( - modifier = Modifier - .padding(100.dp) - .windowInsetsPadding(WindowInsets.systemBars) - .size(200.dp, 50.dp) - .clickable(interactionSource, indication = null) {}, - contentAlignment = Alignment.Center - ) { - val edgeStyle = Style { - fillSize() - shape(RoundedCornerShape(16.dp)) - background(Color(0xFF1CB0F6)) - } - - val frontStyle = Style { - fillSize() - background(Color(0xFF1899D6)) - shape(RoundedCornerShape(16.dp)) - contentPadding(vertical = 13.dp, horizontal = 16.dp) - translationY(with(density) { (-4).dp.toPx() }) - pressed { - animate { - translationY(with(density) { (0).dp.toPx() }) - } - } - } - Box(modifier = Modifier.styleable(styleState, edgeStyle)) { - Box( - modifier = Modifier - .styleable(styleState, frontStyle), - contentAlignment = Alignment.Center - ) { - BaseText( - "Button 19".toUpperCase(Locale.current), - style = Style { - contentColor(Color.White) - fontSize(15.sp) - fontWeight(FontWeight.Bold) - letterSpacing(0.8.sp) - } - ) - } - } - } -} -// [END android_compose_styles_depth_pressed_button] - -// [START android_compose_styles_gradient_glow_button] -@Preview -@Composable -fun Button85() { - val infiniteTransition = rememberInfiniteTransition(label = "glowing_button_85_animation") - val animatedProgress by infiniteTransition.animateFloat( - initialValue = 0f, - targetValue = 1f, - animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 20000, easing = LinearEasing), - ), label = "progress" - ) - - val gradientColors = listOf( - Color(0xffff0000), Color(0xffff7300), Color(0xfffffb00), Color(0xff48ff00), - Color(0xff00ffd5), Color(0xff002bff), Color(0xff7a00ff), Color(0xffff00c8), - Color(0xffff0000) - ) - - val glowingBrush = remember(animatedProgress) { - object : ShaderBrush() { - override fun createShader(size: Size): Shader { - val width = size.width * 4 - val brushSize = width * animatedProgress - return LinearGradientShader( - colors = gradientColors, - from = Offset(brushSize, 0f), - to = Offset(brushSize + width, 0f), - tileMode = TileMode.Repeated - ) - } - } - } - - - Box( - modifier = Modifier - .padding(32.dp), - contentAlignment = Alignment.Center - ) { - BaseButton( - onClick = { }, - style = Style { - dropShadow( - Shadow( - brush = glowingBrush, - radius = 5.dp - ) - ) - transformOrigin(TransformOrigin.Center) - pressed { - animate { - dropShadow( - Shadow( - brush = glowingBrush, - radius = 10.dp - ) - ) - scale(0.95f) - } - - } - size(width = 200.dp, height = 50.dp) - background(Color(0xFF111111)) - shape(RoundedCornerShape(10.dp)) - contentColor(Color.White) - contentPadding(vertical = (0.6f * 14).dp, horizontal = (2f * 14).dp) - border(width = 0.dp, color = Color.Transparent) - } - ) { - BaseText(text = "Button 85") - } - } -} -// [END android_compose_styles_gradient_glow_button] From 1e8aca95d7eefab4fa1213b9dddafc43893a81e2 Mon Sep 17 00:00:00 2001 From: riggaroo <9973046+riggaroo@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:52:31 +0000 Subject: [PATCH 08/23] Apply Spotless --- .../snippets/designsystems/styles/DosDonts.kt | 16 ++++++++++++++++ .../snippets/designsystems/styles/Examples.kt | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt index e2263cde5..05c4ecadb 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + @file:OptIn(ExperimentalFoundationStyleApi::class) package com.example.compose.snippets.designsystems.styles diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt index 21b79968b..92ffe5436 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + @file:OptIn(ExperimentalFoundationStyleApi::class) package com.example.compose.snippets.designsystems.styles From 7528f9e9f6efffa4dbf1a0ac4828af2e6f0dc5da Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 3 Mar 2026 15:53:46 +0000 Subject: [PATCH 09/23] Fix lint --- .../com/example/compose/snippets/components/DatePickers.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt index 4d59ea4ef..f5dca89af 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/DatePickers.kt @@ -61,6 +61,7 @@ import com.example.compose.snippets.ui.theme.SnippetsTheme import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import androidx.compose.ui.platform.LocalLocale @Preview @Composable @@ -105,7 +106,7 @@ fun DatePickerExamples() { // [END_EXCLUDE] if (selectedDate != null) { val date = Date(selectedDate!!) - val formattedDate = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(date) + val formattedDate = SimpleDateFormat("MMM dd, yyyy", LocalLocale.current.platformLocale).format(date) Text("Selected date: $formattedDate") } else { Text("No date selected") @@ -121,8 +122,8 @@ fun DatePickerExamples() { if (selectedDateRange.first != null && selectedDateRange.second != null) { val startDate = Date(selectedDateRange.first!!) val endDate = Date(selectedDateRange.second!!) - val formattedStartDate = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(startDate) - val formattedEndDate = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(endDate) + val formattedStartDate = SimpleDateFormat("MMM dd, yyyy", LocalLocale.current.platformLocale).format(startDate) + val formattedEndDate = SimpleDateFormat("MMM dd, yyyy", LocalLocale.current.platformLocale).format(endDate) Text("Selected date range: $formattedStartDate - $formattedEndDate") } else { Text("No date range selected") From c6299e7fa9d01f4c76dac490ec7cc7eb9e689fc8 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 4 Mar 2026 10:13:12 +0000 Subject: [PATCH 10/23] Changed tags for animating staet --- .../snippets/designsystems/styles/StateAnimations.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt index f89046b7d..a65d814f7 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt @@ -170,7 +170,7 @@ fun LoginButton() { } // [END android_compose_styles_state_override] -// [START android_compose_styles_animating_style ] +// [START android_compose_styles_animate_style_basic ] val animatingStyle = Style { border(3.dp, Color.Black) background(Color.White) @@ -202,9 +202,9 @@ private fun AnimatingStyleChanges() { } } -// [END android_compose_styles_animating_style ] +// [END android_compose_styles_animate_style_basic ] -// [START android_compose_styles_animating_style_spec ] +// [START android_compose_styles_animate_style_set_spec ] val animatingStyleSpec = Style { externalPadding(8.dp) border(3.dp, Color.Black) @@ -238,7 +238,7 @@ fun AnimatingStyleChangesSpec() { ) .styleable(styleState, animatingStyleSpec)) } -// [END android_compose_styles_animating_style_spec ] +// [END android_compose_styles_animate_style_set_spec ] // [START android_compose_styles_custom_key_1] enum class PlayerState { From fd29c161520bc2038cefc98bf5871ba10cb6980a Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 4 Mar 2026 12:02:10 +0000 Subject: [PATCH 11/23] Fix tags to not have trailing space :( --- .../snippets/designsystems/styles/StateAnimations.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt index a65d814f7..08ea50723 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt @@ -170,7 +170,7 @@ fun LoginButton() { } // [END android_compose_styles_state_override] -// [START android_compose_styles_animate_style_basic ] +// [START android_compose_styles_animate_style_basic] val animatingStyle = Style { border(3.dp, Color.Black) background(Color.White) @@ -202,9 +202,9 @@ private fun AnimatingStyleChanges() { } } -// [END android_compose_styles_animate_style_basic ] +// [END android_compose_styles_animate_style_basic] -// [START android_compose_styles_animate_style_set_spec ] +// [START android_compose_styles_animate_style_set_spec] val animatingStyleSpec = Style { externalPadding(8.dp) border(3.dp, Color.Black) @@ -238,7 +238,7 @@ fun AnimatingStyleChangesSpec() { ) .styleable(styleState, animatingStyleSpec)) } -// [END android_compose_styles_animate_style_set_spec ] +// [END android_compose_styles_animate_style_set_spec] // [START android_compose_styles_custom_key_1] enum class PlayerState { From 9ac4736249d54bb1ac7caea1c05dc9dea7ed6001 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 4 Mar 2026 14:16:58 +0000 Subject: [PATCH 12/23] Fix snippets for custom state --- .../compose/snippets/designsystems/styles/StateAnimations.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt index 08ea50723..f46614b53 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt @@ -48,6 +48,7 @@ import com.example.compose.snippets.designsystems.styles.components.BaseButton import com.example.compose.snippets.designsystems.styles.components.BaseText // [START android_compose_styles_state_basic] +@Preview @Composable private fun OpenButton() { BaseButton( @@ -282,7 +283,7 @@ fun MediaPlayer( ///.. } } -// [END android_compose_styels_link_to_custom_state] +// [END android_compose_styles_link_to_custom_state] private object Step2StyleState { // [START android_compose_styles_link_to_custom_state_pass] From b85ee90817203507160b34ab643c9d228b783860 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 5 Mar 2026 09:57:00 +0000 Subject: [PATCH 13/23] Cleanup example animations --- .../snippets/designsystems/styles/Examples.kt | 100 ++++++++++-------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt index 92ffe5436..23596507f 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt @@ -28,7 +28,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape @@ -103,6 +102,17 @@ fun HoverButtonExample() { ) } } + pressed { + animate(tween(200)) { + dropShadow( + Shadow( + spread = 0.dp, color = Color(0xFFFFE54C), + radius = 0.dp, + offset = DpOffset(0.dp, 0.dp) + ) + ) + } + } } ) { BaseText("Button 52") @@ -114,49 +124,51 @@ fun HoverButtonExample() { // [START android_compose_styles_rounded_depth_button] @Preview @Composable -fun RoundedDepthButtonExample() { - val density = LocalDensity.current - val buttonStyle = Style { - background(Color(0xFFFBEED0)) - border(2.dp, Color(0xFF422800)) - shape(RoundedCornerShape(30.dp)) - dropShadow( - Shadow( - color = Color(0xFF422800), offset = DpOffset(4.dp, 4.dp), - radius = 0.dp, spread = 0.dp +fun ShadowAnimationButton() { + Box(modifier = Modifier.padding(32.dp)) { + val density = LocalDensity.current + val buttonStyle = Style { + background(Color(0xFFFBEED0)) + border(2.dp, Color(0xFF422800)) + shape(RoundedCornerShape(30.dp)) + dropShadow( + Shadow( + color = Color(0xFF422800), offset = DpOffset(4.dp, 4.dp), + radius = 0.dp, spread = 0.dp + ) ) - ) - contentColor(Color(0xFF422800)) - fontWeight(FontWeight.SemiBold) - fontSize(18.sp) - contentPaddingHorizontal(25.dp) - externalPadding(8.dp) - height(50.dp) - textAlign(TextAlign.Center) - hovered { - animate { - background(Color.White) + contentColor(Color(0xFF422800)) + fontWeight(FontWeight.SemiBold) + fontSize(18.sp) + contentPaddingHorizontal(25.dp) + externalPadding(8.dp) + height(50.dp) + textAlign(TextAlign.Center) + hovered { + animate { + background(Color.White) + } } - } - pressed { - animate { - dropShadow( - Shadow( - color = Color(0xFF422800), - offset = DpOffset(2.dp, 2.dp), - radius = 0.dp, - spread = 0.dp + pressed { + animate { + dropShadow( + Shadow( + color = Color(0xFF422800), + offset = DpOffset(2.dp, 2.dp), + radius = 0.dp, + spread = 0.dp + ) ) - ) - translation(with(density) { 2.dp.toPx() }, with(density) { 2.dp.toPx() }) + translation(with(density) { 2.dp.toPx() }, with(density) { 2.dp.toPx() }) + } } } - } - BaseButton( - onClick = {}, - style = buttonStyle - ) { - BaseText("Button 74") + BaseButton( + onClick = {}, + style = buttonStyle + ) { + BaseText("Button 74") + } } } // [END android_compose_styles_rounded_depth_button] @@ -164,16 +176,18 @@ fun RoundedDepthButtonExample() { // [START android_compose_styles_depth_pressed_button] @Preview @Composable -fun DepthPressedButtonExample() { +fun MultipleStylesButton() { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } val density = LocalDensity.current Box( modifier = Modifier - .padding(100.dp) .windowInsetsPadding(WindowInsets.systemBars) - .size(200.dp, 50.dp) + .styleable(styleState) { + size(200.dp, 48.dp) + externalPadding(32.dp) + } .clickable(interactionSource, indication = null) {}, contentAlignment = Alignment.Center ) { @@ -187,7 +201,7 @@ fun DepthPressedButtonExample() { fillSize() background(Color(0xFF1899D6)) shape(RoundedCornerShape(16.dp)) - contentPadding(vertical = 13.dp, horizontal = 16.dp) + contentPadding(vertical = 12.dp, horizontal = 16.dp) translationY(with(density) { (-4).dp.toPx() }) pressed { animate { From f4ef61c25521c051376422733cc129d7bad6fcac Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 5 Mar 2026 09:57:15 +0000 Subject: [PATCH 14/23] Cleanup example animations --- .../example/compose/snippets/designsystems/styles/Examples.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt index 23596507f..072d9ea9f 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt @@ -183,7 +183,6 @@ fun MultipleStylesButton() { Box( modifier = Modifier - .windowInsetsPadding(WindowInsets.systemBars) .styleable(styleState) { size(200.dp, 48.dp) externalPadding(32.dp) From 17ea69aaaca98f8662d6103b73b7d91a208c73bf Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 5 Mar 2026 11:24:52 +0000 Subject: [PATCH 15/23] Switch back to minSdk 23 for Compose snippets --- compose/snippets/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/snippets/build.gradle.kts b/compose/snippets/build.gradle.kts index e21218d6c..c2a4f6eaf 100644 --- a/compose/snippets/build.gradle.kts +++ b/compose/snippets/build.gradle.kts @@ -31,7 +31,7 @@ android { defaultConfig { applicationId = "com.example.compose.snippets" - minSdk = libs.versions.minSdk.get().toInt() + minSdk = 23 targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 1 versionName = "1.0" From cbdf6bbe3c8f6b23a6f3da4c2c8575637b49d786 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 5 Mar 2026 11:38:47 +0000 Subject: [PATCH 16/23] Fix basic button --- .../designsystems/styles/StateAnimations.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt index f46614b53..5dd784f3a 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt @@ -35,6 +35,7 @@ import androidx.compose.foundation.style.focused import androidx.compose.foundation.style.hovered import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.styleable +import androidx.compose.foundation.style.then import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -47,20 +48,28 @@ import androidx.compose.ui.unit.sp import com.example.compose.snippets.designsystems.styles.components.BaseButton import com.example.compose.snippets.designsystems.styles.components.BaseText +val outlinedButtonStyle = Style { + externalPadding(48.dp) + contentPadding(12.dp) + shape(RoundedCornerShape(8.dp)) + clip(true) + border(2.dp, lightBlue) +} + // [START android_compose_styles_state_basic] +val lightBlue = Color(0xFF03A9F4) +val lightPurple = Color(0xFF9575CD) @Preview @Composable private fun OpenButton() { BaseButton( - style = { - shape(RoundedCornerShape(0.dp)) + style = outlinedButtonStyle then { background(Color.White) hovered { - val lightPurple = Color(0xFFD1C4E9) background(lightPurple) + border(2.dp, lightPurple) } focused { - val lightBlue = Color(0xFF81D4FA) background(lightBlue) } }, From f9dc2ec80c95c9f980463a1a343124222b910ac4 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 5 Mar 2026 11:46:11 +0000 Subject: [PATCH 17/23] Cleanup and move CustomStates to new file --- .../designsystems/styles/CustomStates.kt | 178 ++++++++++++++++++ .../designsystems/styles/StateAnimations.kt | 175 +---------------- 2 files changed, 184 insertions(+), 169 deletions(-) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt new file mode 100644 index 000000000..97238fe62 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt @@ -0,0 +1,178 @@ +@file:OptIn(ExperimentalFoundationStyleApi::class) + +package com.example.compose.snippets.designsystems.styles + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.MutableStyleState +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.StyleScope +import androidx.compose.foundation.style.StyleStateKey +import androidx.compose.foundation.style.styleable +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + + +// [START android_compose_styles_custom_key_1] +enum class PlayerState { + Stopped, + Playing, + Paused +} + +val playerStateKey = StyleStateKey(PlayerState.Stopped) +// [END android_compose_styles_custom_key_1] + +// [START android_compose_styles_custom_key_2] +// Extension Function on MutableStyleState to query and set the current playState +var MutableStyleState.playerState + get() = this[playerStateKey] + set(value) { this[playerStateKey] = value } + +fun StyleScope.playerPlaying(value: Style) { + state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) +} +fun StyleScope.playerPaused(value: Style) { + state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) +} +// [END android_compose_styles_custom_key_2] + +// [START android_compose_styles_link_to_custom_state] +@Composable +fun MediaPlayer( + url: String, + modifier: Modifier = Modifier, + style: Style = Style, + state: PlayerState = remember { PlayerState.Paused } +) { + // Hoist style state, set playerState as a parameter, + val styleState = remember { MutableStyleState(null) } + // Set equal to incoming state to link the two together + styleState.playerState = state + Box( + //.. + ) { + ///.. + } +} +// [END android_compose_styles_link_to_custom_state] + +private object Step2StyleState { + // [START android_compose_styles_link_to_custom_state_pass] + @Composable + fun MediaPlayer( + url: String, + modifier: Modifier = Modifier, + style: Style = Style, + state: PlayerState = remember { PlayerState.Paused } + ) { + // Hoist style state, set playstate as a parameter, + val styleState = remember { MutableStyleState(null) } + // Set equal to incoming state to link the two together + styleState.playerState = state + Box( + modifier = modifier.styleable(styleState, style)) { + ///.. + } + } + // [END android_compose_styles_link_to_custom_state_pass] +} +private object Step3StyleState { + // [START android_compose_styles_link_to_custom_state_key] + @Composable + fun StyleStateKeySample() { + // Using the extension function to change the border color to green while playing + val style = Style { + borderColor(Color.Gray) + playerPlaying { + animate { + borderColor(Color.Green) + } + } + playerPaused { + animate { + borderColor(Color.Blue) + } + } + } + val styleState = remember { MutableStyleState(null) } + styleState[playerStateKey] = PlayerState.Playing + + // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. + MediaPlayer(url = "https://example.com/media/video", + style = style, + state = PlayerState.Stopped) + } + // [END android_compose_styles_link_to_custom_state_key] +} + +private object Step4FullSnippetState { + // [START android_compose_styles_state_full_snippet] + enum class PlayerState { + Stopped, + Playing, + Paused + } + val playerStateKey = StyleStateKey(PlayerState.Stopped) + var MutableStyleState.playerState + get() = this[playerStateKey] + set(value) { this[playerStateKey] = value } + + fun StyleScope.playerPlaying(value: Style) { + state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) + } + fun StyleScope.playerPaused(value: Style) { + state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) + + } + + @Composable + fun MediaPlayer( + url: String, + modifier: Modifier = Modifier, + style: Style = Style, + state: PlayerState = remember { PlayerState.Paused } + ) { + // Hoist style state, set playstate as a parameter, + val styleState = remember { MutableStyleState(null) } + // Set equal to incoming state to link the two together + styleState.playerState = state + Box( + modifier = modifier.styleable(styleState, Style { + size(100.dp) + border(2.dp, Color.Red) + + }, style, )) { + + ///.. + } + } + @Composable + fun StyleStateKeySample() { + // Using the extension function to change the border color to green while playing + val style = Style { + borderColor(Color.Gray) + playerPlaying { + animate { + borderColor(Color.Green) + } + } + playerPaused { + animate { + borderColor(Color.Blue) + } + } + } + val styleState = remember { MutableStyleState(null) } + styleState[playerStateKey] = PlayerState.Playing + + // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. + MediaPlayer(url = "https://example.com/media/video", + style = style, + state = PlayerState.Stopped) + } + // [END android_compose_styles_state_full_snippet] +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt index 5dd784f3a..349c726a0 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt @@ -56,9 +56,12 @@ val outlinedButtonStyle = Style { border(2.dp, lightBlue) } -// [START android_compose_styles_state_basic] val lightBlue = Color(0xFF03A9F4) val lightPurple = Color(0xFF9575CD) +val lightOrange = Color(0xFFFFE0B2) +val lightRed = Color(0xFFE57373) + +// [START android_compose_styles_state_basic] @Preview @Composable private fun OpenButton() { @@ -86,30 +89,26 @@ private fun OpenButton() { // [END android_compose_styles_state_basic] +@Preview // [START android_compose_styles_state_basic_combined_states] @Composable private fun OpenButton_CombinedStates() { BaseButton( - style = { - shape(RoundedCornerShape(0.dp)) + style = outlinedButtonStyle then { background(Color.White) hovered { // light purple - val lightPurple = Color(0xFFD1C4E9) background(lightPurple) pressed { // When running on a device that can hover, whilst hovering and then pressing the button this would be invoked - val lightOrange = Color(0xFFFFE0B2) background(lightOrange) } } pressed { // when running on a device without a mouse attached, this would be invoked as you wouldn't be in a hovered state only - val lightRed = Color(0xFF66E7DC) background(lightRed) } focused { - val lightBlue = Color(0xFF81D4FA) background(lightBlue) } }, @@ -249,165 +248,3 @@ fun AnimatingStyleChangesSpec() { .styleable(styleState, animatingStyleSpec)) } // [END android_compose_styles_animate_style_set_spec] - -// [START android_compose_styles_custom_key_1] -enum class PlayerState { - Stopped, - Playing, - Paused -} - -val playerStateKey = StyleStateKey(PlayerState.Stopped) -// [END android_compose_styles_custom_key_1] - -// [START android_compose_styles_custom_key_2] -// Extension Function on MutableStyleState to query and set the current playState -var MutableStyleState.playerState - get() = this[playerStateKey] - set(value) { this[playerStateKey] = value } - -fun StyleScope.playerPlaying(value: Style) { - state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) -} -fun StyleScope.playerPaused(value: Style) { - state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) -} -// [END android_compose_styles_custom_key_2] - -// [START android_compose_styles_link_to_custom_state] -@Composable -fun MediaPlayer( - url: String, - modifier: Modifier = Modifier, - style: Style = Style, - state: PlayerState = remember { PlayerState.Paused } -) { - // Hoist style state, set playerState as a parameter, - val styleState = remember { MutableStyleState(null) } - // Set equal to incoming state to link the two together - styleState.playerState = state - Box( - //.. - ) { - ///.. - } -} -// [END android_compose_styles_link_to_custom_state] - -private object Step2StyleState { - // [START android_compose_styles_link_to_custom_state_pass] - @Composable - fun MediaPlayer( - url: String, - modifier: Modifier = Modifier, - style: Style = Style, - state: PlayerState = remember { PlayerState.Paused } - ) { - // Hoist style state, set playstate as a parameter, - val styleState = remember { MutableStyleState(null) } - // Set equal to incoming state to link the two together - styleState.playerState = state - Box( - modifier = modifier.styleable(styleState, style)) { - ///.. - } - } - // [END android_compose_styles_link_to_custom_state_pass] -} -private object Step3StyleState { - // [START android_compose_styles_link_to_custom_state_key] - @Composable - fun StyleStateKeySample() { - // Using the extension function to change the border color to green while playing - val style = Style { - borderColor(Color.Gray) - playerPlaying { - animate { - borderColor(Color.Green) - } - } - playerPaused { - animate { - borderColor(Color.Blue) - } - } - } - val styleState = remember { MutableStyleState(null) } - styleState[playerStateKey] = PlayerState.Playing - - // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. - MediaPlayer(url = "https://example.com/media/video", - style = style, - state = PlayerState.Stopped) - } - // [START android_compose_styles_link_to_custom_state_key] -} - -private object Step4FullSnippetState { - // [START android_compose_styles_state_full_snippet] - enum class PlayerState { - Stopped, - Playing, - Paused - } - val playerStateKey = StyleStateKey(PlayerState.Stopped) - var MutableStyleState.playerState - get() = this[playerStateKey] - set(value) { this[playerStateKey] = value } - - fun StyleScope.playerPlaying(value: Style) { - state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) - } - fun StyleScope.playerPaused(value: Style) { - state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) - - } - - @Composable - fun MediaPlayer( - url: String, - modifier: Modifier = Modifier, - style: Style = Style, - state: PlayerState = remember { PlayerState.Paused } - ) { - // Hoist style state, set playstate as a parameter, - val styleState = remember { MutableStyleState(null) } - // Set equal to incoming state to link the two together - styleState.playerState = state - Box( - modifier = modifier.styleable(styleState, Style { - size(100.dp) - border(2.dp, Color.Red) - - }, style, )) { - - ///.. - } - } - @Composable - fun StyleStateKeySample() { - // Using the extension function to change the border color to green while playing - val style = Style { - borderColor(Color.Gray) - playerPlaying { - animate { - borderColor(Color.Green) - } - } - playerPaused { - animate { - borderColor(Color.Blue) - } - } - } - val styleState = remember { MutableStyleState(null) } - styleState[playerStateKey] = PlayerState.Playing - - // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. - MediaPlayer(url = "https://example.com/media/video", - style = style, - state = PlayerState.Stopped) - } - // [END android_compose_styles_state_full_snippet] -} - From b88c5fa07d0012b5ea044fe83e5886c229957bcd Mon Sep 17 00:00:00 2001 From: riggaroo <9973046+riggaroo@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:49:20 +0000 Subject: [PATCH 18/23] Apply Spotless --- .../designsystems/styles/CustomStates.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt index 97238fe62..da1d9ea84 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + @file:OptIn(ExperimentalFoundationStyleApi::class) package com.example.compose.snippets.designsystems.styles From c689c05f158868537daa614a183cb2d14888a37f Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 5 Mar 2026 12:23:58 +0000 Subject: [PATCH 19/23] add info about gradient + shape not working. cleanup --- .../designsystems/styles/StateAnimations.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt index 349c726a0..0ac49ffe8 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt @@ -41,6 +41,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -125,8 +126,14 @@ private fun OpenButton_CombinedStates() { // [END android_compose_styles_state_basic_combined_states] val baseGradientButtonStyle = Style { - contentPadding(8.dp) - background(Brush.verticalGradient(listOf(Color.Red, Color.Blue))) + contentPadding(16.dp) + externalPadding(64.dp) + fontSize(16.sp) + /* There is currently a known issue where combining a shape with gradient backgrounds doesn't render properly. + b/482308908 + shape(RoundedCornerShape(16.dp)) + clip(true)*/ + background(Brush.linearGradient(listOf(lightPurple, lightBlue))) } // [START android_compose_styles_state_custom_state_gradient] @@ -151,7 +158,7 @@ private fun GradientButton( interactionSource = interactionSource, indication = null, ) - .styleable(styleState, baseGradientButtonStyle, style), + .styleable(styleState, baseGradientButtonStyle then style), content = content, ) } @@ -162,7 +169,6 @@ private fun GradientButton( @Composable fun LoginButton() { val loginButtonStyle = Style { - externalPadding(8.dp) pressed { background( Brush.linearGradient( @@ -181,6 +187,7 @@ fun LoginButton() { // [START android_compose_styles_animate_style_basic] val animatingStyle = Style { + externalPadding(48.dp) border(3.dp, Color.Black) background(Color.White) size(100.dp) @@ -215,18 +222,18 @@ private fun AnimatingStyleChanges() { // [START android_compose_styles_animate_style_set_spec] val animatingStyleSpec = Style { - externalPadding(8.dp) + externalPadding(48.dp) border(3.dp, Color.Black) background(Color.White) size(100.dp) - + transformOrigin(TransformOrigin.Center) pressed { animate { borderColor(Color.Magenta) background(Color(0xFFB39DDB)) } animate(spring(dampingRatio = Spring.DampingRatioMediumBouncy)) { - size(120.dp) + scale(1.2f) } } } From 28f20e54012eea5b4cc59bc48340eade78c974d8 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 10 Mar 2026 10:57:50 +0000 Subject: [PATCH 20/23] Fix up composition local snippet --- .../compose/snippets/designsystems/styles/StylesSnippets.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt index bcfb30e15..c45dba8ab 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StylesSnippets.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import com.example.compose.snippets.designsystems.FullyCustomDesignSystem.LocalCustomColors import com.example.compose.snippets.designsystems.styles.components.BaseButton import com.example.compose.snippets.designsystems.styles.components.BaseText @@ -295,7 +296,7 @@ fun CompositionLocalStyle() { val buttonStyle = Style { contentPadding(12.dp) shape(RoundedCornerShape(50)) - background(Color.Blue) // Simplified for snippet + background(Brush.verticalGradient(LocalCustomColors.currentValue.background)) } // [END android_compose_styles_composition_local] } From fcf06d9d0dccee0be71f175ce62ec4039c118a2e Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 10 Mar 2026 11:04:10 +0000 Subject: [PATCH 21/23] Add semantics = Role.Button --- .../compose/snippets/designsystems/styles/Examples.kt | 7 ++++++- .../snippets/designsystems/styles/components/Button.kt | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt index 072d9ea9f..a8a63e331 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/Examples.kt @@ -53,6 +53,9 @@ import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextAlign @@ -208,7 +211,9 @@ fun MultipleStylesButton() { } } } - Box(modifier = Modifier.styleable(styleState, edgeStyle)) { + Box(modifier = Modifier.semantics(properties = { + role = Role.Button + }).styleable(styleState, edgeStyle)) { Box( modifier = Modifier .styleable(styleState, frontStyle), diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt index a05c54727..fb5b37ba8 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/components/Button.kt @@ -30,12 +30,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics val baseButtonStyle = Style { } - @ExperimentalFoundationStyleApi // [START android_compose_styles_base_button] @Composable @@ -56,6 +59,9 @@ fun BaseButton( styleState.isEnabled = enabled Row( modifier = modifier + .semantics(properties = { + role = Role.Button + }) .clickable( enabled = enabled, onClick = onClick, From 133dfaaa5892acf1ae30bb8924f94dd6462ab6e5 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 10 Mar 2026 11:13:55 +0000 Subject: [PATCH 22/23] Added a do example for GoodButton --- .../snippets/designsystems/styles/DosDonts.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt index 05c4ecadb..e0560e5a5 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/DosDonts.kt @@ -18,9 +18,14 @@ package com.example.compose.snippets.designsystems.styles +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.MutableStyleState import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.styleable import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -77,3 +82,27 @@ fun BadButton( ) { } // [END android_compose_styles_donts_default_style] + +// [START android_compose_styles_do_default_style] +@Composable +fun GoodButton( + modifier: Modifier = Modifier, + // ✅ Do: always pass it as a Style, do not pass other defaults + style: Style = Style +) { + // [START_EXCLUDE] + // this is a snippet of the BaseButton - see the full snippet in /components/Button.kt + val effectiveInteractionSource = remember { + MutableInteractionSource() + } + val styleState = remember(effectiveInteractionSource) { + MutableStyleState(effectiveInteractionSource) + } + // [END_EXCLUDE] + val defaultStyle = Style { background(Color.Red) } + // ✅ Do Combine defaults inside with incoming parameter + Box(modifier = modifier.styleable(styleState, defaultStyle, style)) { + // your logic + } +} +// [END android_compose_styles_do_default_style] From 105ab9e7997efc3f6eebc2e99a892e84bd36fceb Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 17 Mar 2026 16:07:08 +0000 Subject: [PATCH 23/23] Update Compose snippets and BOM version * Bump `androidx-compose-bom` to `2026.03.00` * Refactor style state management to use `rememberUpdatedStyleState` in `StateAnimations.kt` * Remove redundant `MediaPlayer` snippet and cleanup internal object structures in `CustomStates.kt` --- .../designsystems/styles/CustomStates.kt | 23 +------------------ .../designsystems/styles/StateAnimations.kt | 6 +++-- gradle/libs.versions.toml | 2 +- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt index da1d9ea84..c033041ea 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/CustomStates.kt @@ -56,26 +56,6 @@ fun StyleScope.playerPaused(value: Style) { } // [END android_compose_styles_custom_key_2] -// [START android_compose_styles_link_to_custom_state] -@Composable -fun MediaPlayer( - url: String, - modifier: Modifier = Modifier, - style: Style = Style, - state: PlayerState = remember { PlayerState.Paused } -) { - // Hoist style state, set playerState as a parameter, - val styleState = remember { MutableStyleState(null) } - // Set equal to incoming state to link the two together - styleState.playerState = state - Box( - //.. - ) { - ///.. - } -} -// [END android_compose_styles_link_to_custom_state] - private object Step2StyleState { // [START android_compose_styles_link_to_custom_state_pass] @Composable @@ -95,8 +75,7 @@ private object Step2StyleState { } } // [END android_compose_styles_link_to_custom_state_pass] -} -private object Step3StyleState { + // [START android_compose_styles_link_to_custom_state_key] @Composable fun StyleStateKeySample() { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt index 0ac49ffe8..6b10538bf 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/designsystems/styles/StateAnimations.kt @@ -34,6 +34,7 @@ import androidx.compose.foundation.style.StyleStateKey import androidx.compose.foundation.style.focused import androidx.compose.foundation.style.hovered import androidx.compose.foundation.style.pressed +import androidx.compose.foundation.style.rememberUpdatedStyleState import androidx.compose.foundation.style.styleable import androidx.compose.foundation.style.then import androidx.compose.runtime.Composable @@ -147,8 +148,9 @@ private fun GradientButton( content: @Composable RowScope.() -> Unit, ) { val interactionSource = interactionSource ?: remember { MutableInteractionSource() } - val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } - styleState.isEnabled = enabled + val styleState = rememberUpdatedStyleState(interactionSource) { + it.isEnabled = enabled + } Row( modifier = modifier diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c0979adc..daa44f302 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ androidGradlePlugin = "9.0.1" androidx-activity-compose = "1.12.4" androidx-appcompat = "1.7.0" androidx-appfunctions = "1.0.0-alpha07" -androidx-compose-bom = "2026.02.01" +androidx-compose-bom = "2026.03.00" androidx-compose-ui-test = "1.7.0-alpha08" androidx-compose-ui-test-junit4-accessibility = "1.11.0-alpha06" androidx-constraintlayout = "2.2.1"