diff --git a/catalog/src/main/kotlin/com/adevinta/spark/catalog/examples/samples/tags/TagsExamples.kt b/catalog/src/main/kotlin/com/adevinta/spark/catalog/examples/samples/tags/TagsExamples.kt index 9adbaecca..e3ff49085 100644 --- a/catalog/src/main/kotlin/com/adevinta/spark/catalog/examples/samples/tags/TagsExamples.kt +++ b/catalog/src/main/kotlin/com/adevinta/spark/catalog/examples/samples/tags/TagsExamples.kt @@ -27,14 +27,17 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.runtime.Composable import androidx.compose.ui.unit.dp +import com.adevinta.spark.ExperimentalSparkApi import com.adevinta.spark.catalog.model.Example import com.adevinta.spark.catalog.util.SampleSourceUrl +import com.adevinta.spark.components.tags.Highlight +import com.adevinta.spark.components.tags.HighlightBadge +import com.adevinta.spark.components.tags.Tag import com.adevinta.spark.components.tags.TagFilled -import com.adevinta.spark.components.tags.TagHighlight -import com.adevinta.spark.components.tags.TagHighlightBadge import com.adevinta.spark.components.tags.TagIntent import com.adevinta.spark.components.tags.TagOutlined import com.adevinta.spark.components.tags.TagTinted +import com.adevinta.spark.components.text.Text import com.adevinta.spark.icons.LeboncoinIcons import com.adevinta.spark.icons.RocketOutline import com.adevinta.spark.icons.SparkIcon @@ -44,7 +47,7 @@ import kotlinx.collections.immutable.persistentListOf private const val TagsExampleDescription = "Tags examples" private const val TagsExampleSourceUrl = "$SampleSourceUrl/TagSamples.kt" -@OptIn(ExperimentalLayoutApi::class) +@OptIn(ExperimentalLayoutApi::class, ExperimentalSparkApi::class) public val TagsExamples: ImmutableList = persistentListOf( Example( id = "filled", @@ -105,8 +108,18 @@ public val TagsExamples: ImmutableList = persistentListOf( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - TagHighlight() - TagHighlightBadge() + Tag.Highlight() + Tag.HighlightBadge() + } + }, + Example( + id = "ai", + name = "Tag \"AI\"", + description = "The AI tag highlights content generated or curated by AI.", + sourceUrl = TagsExampleSourceUrl, + ) { + Tag.Ai { + Text("À la une") } }, Example( diff --git a/spark-screenshot-testing/src/test/kotlin/com/adevinta/spark/tags/TagDocumentationScreenshots.kt b/spark-screenshot-testing/src/test/kotlin/com/adevinta/spark/tags/TagDocumentationScreenshots.kt index 8027a4381..558e6b77b 100644 --- a/spark-screenshot-testing/src/test/kotlin/com/adevinta/spark/tags/TagDocumentationScreenshots.kt +++ b/spark-screenshot-testing/src/test/kotlin/com/adevinta/spark/tags/TagDocumentationScreenshots.kt @@ -27,12 +27,13 @@ import androidx.compose.ui.unit.dp import com.adevinta.spark.DefaultTestDevices import com.adevinta.spark.ExperimentalSparkApi import com.adevinta.spark.SparkTheme +import com.adevinta.spark.components.tags.Highlight +import com.adevinta.spark.components.tags.HighlightBadge +import com.adevinta.spark.components.tags.Tag import com.adevinta.spark.components.tags.TagFilled -import com.adevinta.spark.components.tags.TagHighlight -import com.adevinta.spark.components.tags.TagHighlightBadge -import com.adevinta.spark.components.tags.TagIntent import com.adevinta.spark.components.tags.TagOutlined import com.adevinta.spark.components.tags.TagTinted +import com.adevinta.spark.components.text.Text import com.adevinta.spark.icons.FireFill import com.adevinta.spark.icons.LeboncoinIcons import com.adevinta.spark.paparazziRule @@ -81,12 +82,19 @@ internal class TagDocumentationScreenshots { } @Test - fun tagHighlight() = paparazzi.sparkDocSnapshot { - TagHighlight() + fun highlight() = paparazzi.sparkDocSnapshot { + Tag.Highlight() } @Test - fun tagHighlightBadge() = paparazzi.sparkDocSnapshot({ SparkTheme.colors.backgroundVariant }) { - TagHighlightBadge() + fun highlightBadge() = paparazzi.sparkDocSnapshot({ SparkTheme.colors.backgroundVariant }) { + Tag.HighlightBadge() + } + + @Test + fun tagAi() = paparazzi.sparkDocSnapshot { + Tag.Ai { + Text("À la une") + } } } diff --git a/spark-screenshot-testing/src/test/kotlin/com/adevinta/spark/tags/TagsScreenshot.kt b/spark-screenshot-testing/src/test/kotlin/com/adevinta/spark/tags/TagsScreenshot.kt index 4fbc532ce..bba043a57 100644 --- a/spark-screenshot-testing/src/test/kotlin/com/adevinta/spark/tags/TagsScreenshot.kt +++ b/spark-screenshot-testing/src/test/kotlin/com/adevinta/spark/tags/TagsScreenshot.kt @@ -36,9 +36,10 @@ import com.adevinta.spark.DefaultTestDevices import com.adevinta.spark.ExperimentalSparkApi import com.adevinta.spark.SparkTheme import com.adevinta.spark.components.surface.Surface +import com.adevinta.spark.components.tags.Highlight +import com.adevinta.spark.components.tags.HighlightBadge +import com.adevinta.spark.components.tags.Tag import com.adevinta.spark.components.tags.TagFilled -import com.adevinta.spark.components.tags.TagHighlight -import com.adevinta.spark.components.tags.TagHighlightBadge import com.adevinta.spark.components.tags.TagIntent import com.adevinta.spark.components.tags.TagOutlined import com.adevinta.spark.components.tags.TagTinted @@ -175,13 +176,13 @@ internal class TagsScreenshot { text = "TagHighlight", style = SparkTheme.typography.headline2, ) - TagHighlight() + Tag.Highlight() Text( text = "TagHighlightBadge", style = SparkTheme.typography.headline2, ) - TagHighlightBadge() + Tag.HighlightBadge() } } diff --git a/spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagHighlight.png b/spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_highlight.png similarity index 100% rename from spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagHighlight.png rename to spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_highlight.png diff --git a/spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagHighlightBadge.png b/spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_highlightBadge.png similarity index 100% rename from spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagHighlightBadge.png rename to spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_highlightBadge.png diff --git a/spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagAi.png b/spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagAi.png new file mode 100644 index 000000000..036a78626 --- /dev/null +++ b/spark-screenshot-testing/src/test/snapshots/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagAi.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bbdeb8738cd085aa9d50f2849d9e5b6a819f2caaa2f4ee681d13ab5a521a850 +size 13311 diff --git a/spark/src/main/kotlin/com/adevinta/spark/components/chips/ChipDefaults.kt b/spark/src/main/kotlin/com/adevinta/spark/components/chips/ChipDefaults.kt index b9b5de9bf..061e7540b 100644 --- a/spark/src/main/kotlin/com/adevinta/spark/components/chips/ChipDefaults.kt +++ b/spark/src/main/kotlin/com/adevinta/spark/components/chips/ChipDefaults.kt @@ -25,26 +25,12 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.ui.unit.dp public object ChipDefaults { - /** - * The padding between the elements in the chip. - */ - private val HorizontalElementsPadding = 8.dp /** * The size of the leading icon in the chip. */ internal val LeadingIconSize = 16.dp - /** - * Horizontal padding for chip content. - */ - internal val ChipPadding = PaddingValues(horizontal = HorizontalElementsPadding) - - /** - * Spacing between the leading icon and the label. - */ - internal val LeadingIconEndSpacing = 4.dp - /** * The outlined chip's border size */ @@ -59,4 +45,8 @@ public object ChipDefaults { * The chip's default height */ internal val MinHeight = 32.dp + + private val HorizontalElementsPadding = 8.dp + + internal val ChipPadding = PaddingValues(horizontal = HorizontalElementsPadding) } diff --git a/spark/src/main/kotlin/com/adevinta/spark/components/tags/Tag.kt b/spark/src/main/kotlin/com/adevinta/spark/components/tags/Tag.kt index 257536db0..188e2eebb 100644 --- a/spark/src/main/kotlin/com/adevinta/spark/components/tags/Tag.kt +++ b/spark/src/main/kotlin/com/adevinta/spark/components/tags/Tag.kt @@ -46,17 +46,19 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.adevinta.spark.ExperimentalSparkApi import com.adevinta.spark.InternalSparkApi import com.adevinta.spark.PreviewTheme import com.adevinta.spark.SparkTheme -import com.adevinta.spark.components.chips.ChipDefaults.LeadingIconEndSpacing import com.adevinta.spark.components.icons.Icon import com.adevinta.spark.components.surface.Surface -import com.adevinta.spark.components.tags.TagDefaults.LeadingIconSize +import com.adevinta.spark.components.tags.TagTokens.LeadingIconSize import com.adevinta.spark.components.text.Text import com.adevinta.spark.icons.AccessoriesCriteria +import com.adevinta.spark.icons.BroomstickSparks import com.adevinta.spark.icons.LeboncoinIcons import com.adevinta.spark.icons.SparkIcon +import com.adevinta.spark.icons.Sparks import com.adevinta.spark.tokens.contentColorFor import com.adevinta.spark.tokens.highlight import com.adevinta.spark.tools.modifiers.SlotArea @@ -93,13 +95,14 @@ internal fun BaseSparkTag( border = border, ) { ProvideTextStyle( - value = SparkTheme.typography.caption.highlight, + value = TagTokens.contentTypo, ) { Row( Modifier .defaultMinSize(minHeight = size.minHeight.dp) - .padding(vertical = size.verticalPadding.dp, horizontal = HorizontalPadding), - horizontalArrangement = Arrangement.spacedBy(LeadingIconEndSpacing, Alignment.Start), + .padding(TagTokens.contentPadding) + .padding(vertical = size.verticalPadding.dp), + horizontalArrangement = Arrangement.spacedBy(TagTokens.ContentSpacing, Alignment.Start), verticalAlignment = Alignment.CenterVertically, ) { AnimatedVisibility( @@ -161,6 +164,44 @@ internal fun BaseSparkTag( ) } +public object Tag { + /** + * A tag that marks AI-generated or AI-assisted content. Styled with the Spark AI colour tokens + * (`ai`, `aiContainer`, `onAiContainer`). + * + * ![Tag Ai](https://leboncoin.github.io/spark-android/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagAi.png) + * + * @param modifier Modifier for the tag container. + * @param leadingIcon The icon displayed before the content. Defaults to [TagDefaults.Ai.icon]. + * @param atEnd Whether the animated vector should be rendered at the end of its animations. + * @param content The main content of the tag, usually a [Text]. + */ + @ExperimentalSparkApi + @Composable + public fun Ai( + modifier: Modifier = Modifier, + leadingIcon: SparkIcon = TagDefaults.Ai.icon, + atEnd: Boolean = false, + content: @Composable RowScope.() -> Unit, + ) { + BaseSparkTag( + colors = TagColors( + backgroundColor = TagTokens.Ai.containerColor, + contentColor = TagTokens.Ai.contentColor, + ), + modifier = modifier, + border = BorderStroke( + TagTokens.OutlinedBorderSize, + TagTokens.Ai.borderColor, + ), + tint = TagTokens.Ai.iconTint, + leadingIcon = leadingIcon, + ) { + content() + } + } +} + @Composable private fun RowScope.AnimatedVisibility( value: T?, @@ -263,15 +304,6 @@ internal fun SparkTag( public data class TagColors(val backgroundColor: Color, val contentColor: Color) public object TagDefaults { - /** - * The outlined tag's border size - */ - internal val OutlinedBorderSize = 1.dp - - /** - * The size of a tag's leading icon - */ - internal val LeadingIconSize = 12.dp @Composable @InternalSparkApi @@ -306,16 +338,18 @@ public object TagDefaults { contentColor = contentColor, ) } -} -private val HorizontalPadding = 8.dp + public object Ai { + public val icon: SparkIcon = LeboncoinIcons.Sparks + } +} @Preview( group = "Tags", name = "Tag", ) @Composable -private fun SparkTagPreview() { +private fun PreviewSparkTag() { PreviewTheme { val colors = TagDefaults.filledColors() BaseSparkTag(colors = colors, atEnd = false) { @@ -332,3 +366,20 @@ private fun SparkTagPreview() { } } } + +@Preview( + group = "Tags", + name = "Ai", +) +@Composable +private fun PreviewAiTag() { + PreviewTheme { + val colors = TagDefaults.filledColors() + Tag.Ai(atEnd = false) { + Text("À la une") + } + Tag.Ai(leadingIcon = LeboncoinIcons.BroomstickSparks, atEnd = false) { + Text("À la deux") + } + } +} diff --git a/spark/src/main/kotlin/com/adevinta/spark/components/tags/Tag.md b/spark/src/main/kotlin/com/adevinta/spark/components/tags/Tag.md index aaadc232f..fb89f6b2d 100644 --- a/spark/src/main/kotlin/com/adevinta/spark/components/tags/Tag.md +++ b/spark/src/main/kotlin/com/adevinta/spark/components/tags/Tag.md @@ -56,5 +56,17 @@ The Highlight tag allows you to temporarily highlight new features of the produc - Implement with an End-of-Life Remote Config to prevent indefinite production presence ```kotlin -TagHighlight() +Tag.Highlight() +``` + +### Tag "Ai" + +The Ai tag marks AI-generated or AI-assisted content. Use it to clearly signal to users when content has been produced or enhanced by AI. + +![](../../images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagAi.png) + +```kotlin +Tag.Ai { + Text("À la une") +} ``` diff --git a/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagHighlight.kt b/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagHighlight.kt index 8f745b9c0..aa7ad2393 100644 --- a/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagHighlight.kt +++ b/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagHighlight.kt @@ -48,15 +48,15 @@ import com.adevinta.spark.tools.modifiers.invisibleSemantic * Ensure that this component is being used with a End of life Remote Config so that it doesn't remain in * production indefinitely. * - * ![Tag Highlight](https://leboncoin.github.io/spark-android/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagHighlight.png) + * ![Tag Highlight](https://leboncoin.github.io/spark-android/images/com.adevinta.spark.tags_TagDocumentationScreenshots_highlight.png) * * @param modifier Optional modifier for the badge container * - * @see TagHighlightBadge + * @see Tag.HighlightBadge */ @ExperimentalSparkApi @Composable -public fun TagHighlight( +public fun Tag.Highlight( modifier: Modifier = Modifier, ) { BaseSparkTag( @@ -81,15 +81,15 @@ public fun TagHighlight( * Ensure that this component is being used with a End of life Remote Config so that it doesn't remain in * production indefinitely. * - * ![Tag Highlight](https://leboncoin.github.io/spark-android/images/com.adevinta.spark.tags_TagDocumentationScreenshots_tagHighlightbadge.png) + * ![Tag Highlight](https://leboncoin.github.io/spark-android/images/com.adevinta.spark.tags_TagDocumentationScreenshots_highlightBadge.png) * * @param modifier Optional modifier for the badge container * - * @see TagHighlight + * @see Tag.Highlight */ @ExperimentalSparkApi @Composable -public fun TagHighlightBadge( +public fun Tag.HighlightBadge( modifier: Modifier = Modifier, ) { BaseSparkTag( @@ -117,7 +117,7 @@ public fun TagHighlightBadge( @Composable private fun PreviewTagHighlight() { PreviewTheme { - TagHighlight() - TagHighlightBadge() + Tag.Highlight() + Tag.HighlightBadge() } } diff --git a/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagIntent.kt b/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagIntent.kt index 330feface..1a7167573 100644 --- a/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagIntent.kt +++ b/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagIntent.kt @@ -29,18 +29,6 @@ import com.adevinta.spark.components.IntentColors * TagIntent is used to define the intent of the tag. */ public enum class TagIntent { - /** - * Used to match default color of such UI controls as toggles, Slider, etc. - */ - @Deprecated( - message = "Basic has been removed with the rebranding, use Support instead", - replaceWith = ReplaceWith("TagIntent.Support"), - level = DeprecationLevel.ERROR, - ) - Basic { - @Composable - override fun colors(): IntentColor = IntentColors.Support.colors() - }, /** * Used to make UI component visually accentuated. diff --git a/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagOutlined.kt b/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagOutlined.kt index 9616e38c5..fb94b0002 100644 --- a/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagOutlined.kt +++ b/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagOutlined.kt @@ -61,7 +61,7 @@ public fun TagOutlined( colors = colors, modifier = modifier, border = BorderStroke( - TagDefaults.OutlinedBorderSize, + TagTokens.OutlinedBorderSize, colors.contentColor, ), leadingIcon = leadingIcon, @@ -101,7 +101,7 @@ public fun TagOutlined( colors = colors, modifier = modifier, border = BorderStroke( - TagDefaults.OutlinedBorderSize, + TagTokens.OutlinedBorderSize, colors.contentColor, ), leadingIcon = leadingIcon, @@ -140,7 +140,7 @@ public fun TagOutlined( colors = colors, modifier = modifier, border = BorderStroke( - TagDefaults.OutlinedBorderSize, + TagTokens.OutlinedBorderSize, colors.contentColor, ), leadingIcon = leadingIcon, diff --git a/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagTokens.kt b/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagTokens.kt index a1a0bc147..365b20d78 100644 --- a/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagTokens.kt +++ b/spark/src/main/kotlin/com/adevinta/spark/components/tags/TagTokens.kt @@ -21,10 +21,18 @@ */ package com.adevinta.spark.components.tags +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.adevinta.spark.ExperimentalSparkApi import com.adevinta.spark.LocalSparkFeatureFlag import com.adevinta.spark.SparkTheme +import com.adevinta.spark.tokens.highlight /** * Component tokens for tag components. Centralises all flag-driven token resolution so that @@ -45,4 +53,45 @@ public object TagTokens { } else { SparkTheme.shapes.full } + + /** + * The outlined tag's border size + */ + public val OutlinedBorderSize: Dp = 1.dp + + /** + * The size of a tag's leading icon + */ + public val LeadingIconSize: Dp = 12.dp + + /** + * The size of a tag's leading icon + */ + public val ContentSpacing: Dp = 4.dp + + public val contentTypo: TextStyle + @Composable + get() = SparkTheme.typography.caption.highlight + + public val contentPadding: PaddingValues = PaddingValues(horizontal = 8.dp) + + @ExperimentalSparkApi + public object Ai { + public val borderColor: Color + @Composable + @ReadOnlyComposable + get() = SparkTheme.colors.ai + public val containerColor: Color + @Composable + @ReadOnlyComposable + get() = SparkTheme.colors.aiContainer + public val contentColor: Color + @Composable + @ReadOnlyComposable + get() = SparkTheme.colors.onAiContainer + public val iconTint: Color + @Composable + @ReadOnlyComposable + get() = SparkTheme.colors.ai + } }