Skip to content

Commit

Permalink
[TEMPURA-2060] Contribute to Backpack library Badge icon with Painter (
Browse files Browse the repository at this point in the history
…#2157)

* [TEMPURA-2060] Add Painter image to composable Badge

* [TEMPURA-2060] UI test badge for custom image

* [TEMPURA-2060] defaultIconSize copied from BpkButtonImpl

* Updated snapshots

* [TEMPURA-2060] brand icon in badge story updated,

---------

Co-authored-by: Anton Sobolev <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 28, 2024
1 parent 7ec7f19 commit d46678c
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 95 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import net.skyscanner.backpack.compose.badge.BpkBadge
import net.skyscanner.backpack.compose.badge.BpkBadgeType
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.BpkSpacing
import net.skyscanner.backpack.compose.tokens.PriceTag
import net.skyscanner.backpack.compose.tokens.TickCircle
import net.skyscanner.backpack.demo.R
import net.skyscanner.backpack.demo.components.BadgeComponent
import net.skyscanner.backpack.demo.meta.ComposeStory

Expand Down Expand Up @@ -91,10 +94,18 @@ private fun BadgeRow(
BpkBadgeType.Warning,
BpkBadgeType.Destructive,
-> null

BpkBadgeType.Brand -> BpkIcon.PriceTag
else -> BpkIcon.TickCircle
},
)

BpkBadge(
text = type.toString(),
icon = painterResource(id = R.drawable.sample_icon),
modifier = Modifier
.weight(1f)
.wrapContentWidth(align = Alignment.CenterHorizontally),
type = type,
)
}
}
// }
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@

package net.skyscanner.backpack.compose.badge

import androidx.compose.ui.res.painterResource
import net.skyscanner.backpack.BpkTestVariant
import net.skyscanner.backpack.Variants
import net.skyscanner.backpack.compose.BpkSnapshotTest
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.CloseCircle
import net.skyscanner.backpack.demo.R
import org.junit.Test

class BpkBadgeTest : BpkSnapshotTest() {
Expand Down Expand Up @@ -102,4 +104,16 @@ class BpkBadgeTest : BpkSnapshotTest() {
BpkBadge(text = BpkBadgeType.Brand.toString(), type = BpkBadgeType.Brand)
}
}

@Test
@Variants(BpkTestVariant.Default, BpkTestVariant.DarkMode)
fun customIcon() {
snap {
BpkBadge(
text = BpkBadgeType.Normal.toString(),
type = BpkBadgeType.Normal,
icon = painterResource(id = R.drawable.sample_icon),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,16 @@

package net.skyscanner.backpack.compose.badge

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.graphics.painter.Painter
import net.skyscanner.backpack.compose.badge.internal.BadgeDrawable
import net.skyscanner.backpack.compose.badge.internal.BadgeIcon
import net.skyscanner.backpack.compose.badge.internal.BpkBadgeImpl
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.icon.BpkIconSize
import net.skyscanner.backpack.compose.text.BpkText
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.BpkBorderRadius
import net.skyscanner.backpack.compose.tokens.BpkSpacing
import net.skyscanner.backpack.compose.tokens.Exclamation
import net.skyscanner.backpack.compose.tokens.InformationCircle
import net.skyscanner.backpack.compose.tokens.TickCircle
import net.skyscanner.backpack.compose.tokens.internal.BpkBadgeColors

enum class BpkBadgeType {
Normal,
Expand All @@ -60,82 +47,43 @@ fun BpkBadge(
type: BpkBadgeType = BpkBadgeType.Normal,
icon: BpkIcon? = null,
) {
Row(
modifier = modifier
.semantics(mergeDescendants = true) { }
.border(1.dp, type.borderColor, BadgeShape)
.background(type.backgroundColor, BadgeShape)
.padding(horizontal = BpkSpacing.Md, vertical = BpkSpacing.Sm),
horizontalArrangement = Arrangement.spacedBy(BpkSpacing.Sm),
verticalAlignment = Alignment.CenterVertically,
) {
val contentColor = type.contentColor
val badgeIcon = icon ?: when (type) {
BpkBadgeType.Warning -> BpkIcon.InformationCircle
BpkBadgeType.Destructive -> BpkIcon.Exclamation
BpkBadgeType.Success -> BpkIcon.TickCircle
else -> icon
}
if (badgeIcon != null) {
BpkIcon(
icon = badgeIcon,
contentDescription = null,
size = BpkIconSize.Small,
tint = type.iconColor,
)
}
BpkText(
text = text,
color = contentColor,
style = BpkTheme.typography.footnote,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
val badgeIcon = icon ?: when (type) {
BpkBadgeType.Warning -> BpkIcon.InformationCircle
BpkBadgeType.Destructive -> BpkIcon.Exclamation
BpkBadgeType.Success -> BpkIcon.TickCircle
else -> icon
}
BpkBadgeImpl(
text = text,
modifier = modifier,
type = type,
icon = badgeIcon?.let {
{
BadgeIcon(
icon = badgeIcon,
type = type,
)
}
},
)
}

private val BpkBadgeType.iconColor: Color
@Composable
get() = when (this) {
BpkBadgeType.Warning -> BpkTheme.colors.statusWarningSpot
BpkBadgeType.Destructive -> BpkTheme.colors.statusDangerSpot
BpkBadgeType.Success -> BpkTheme.colors.statusSuccessSpot
else -> {
contentColor
}
}

private val BpkBadgeType.contentColor: Color
@Composable
get() = when (this) {
BpkBadgeType.Normal -> BpkTheme.colors.textPrimary
BpkBadgeType.Strong -> BpkTheme.colors.textOnDark
BpkBadgeType.Success -> BpkTheme.colors.textPrimary
BpkBadgeType.Warning -> BpkTheme.colors.textPrimary
BpkBadgeType.Destructive -> BpkTheme.colors.textPrimary
BpkBadgeType.Inverse -> BpkTheme.colors.textPrimary
BpkBadgeType.Outline -> BpkTheme.colors.textOnDark
BpkBadgeType.Brand -> BpkTheme.colors.textPrimaryInverse
}

private val BpkBadgeType.backgroundColor: Color
@Composable
get() = when (this) {
BpkBadgeType.Normal -> BpkBadgeColors.backgroundNormal
BpkBadgeType.Strong -> BpkTheme.colors.corePrimary
BpkBadgeType.Success -> BpkBadgeColors.backgroundNormal
BpkBadgeType.Warning -> BpkBadgeColors.backgroundNormal
BpkBadgeType.Destructive -> BpkBadgeColors.backgroundNormal
BpkBadgeType.Inverse -> BpkTheme.colors.surfaceDefault
BpkBadgeType.Outline -> Color.Transparent
BpkBadgeType.Brand -> BpkTheme.colors.coreAccent
}

private val BpkBadgeType.borderColor: Color
@Composable
get() = when (this) {
BpkBadgeType.Outline -> BpkTheme.colors.textOnDark
else -> Color.Transparent
}

private val BadgeShape = RoundedCornerShape(BpkBorderRadius.Xs)
@Composable
fun BpkBadge(
text: String,
icon: Painter,
modifier: Modifier = Modifier,
type: BpkBadgeType = BpkBadgeType.Normal,
) {
BpkBadgeImpl(
text = text,
modifier = modifier,
type = type,
icon = {
BadgeDrawable(
icon = icon,
type = type,
)
},
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* Backpack for Android - Skyscanner's Design System
*
* Copyright 2018 Skyscanner Ltd
*
* 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
*
* http://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 net.skyscanner.backpack.compose.badge.internal

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import net.skyscanner.backpack.compose.badge.BpkBadgeType
import net.skyscanner.backpack.compose.icon.BpkIcon
import net.skyscanner.backpack.compose.text.BpkText
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.BpkBorderRadius
import net.skyscanner.backpack.compose.tokens.BpkBorderSize
import net.skyscanner.backpack.compose.tokens.BpkSpacing
import net.skyscanner.backpack.compose.tokens.internal.BpkBadgeColors

@Composable
internal fun BpkBadgeImpl(
text: String,
modifier: Modifier = Modifier,
type: BpkBadgeType = BpkBadgeType.Normal,
icon: @Composable (RowScope.() -> Unit)?,
) {
Row(
modifier = modifier
.semantics(mergeDescendants = true) { }
.border(BpkBorderSize.Sm, type.borderColor, BadgeShape)
.background(type.backgroundColor, BadgeShape)
.padding(horizontal = BpkSpacing.Md, vertical = BpkSpacing.Sm),
horizontalArrangement = Arrangement.spacedBy(BpkSpacing.Sm),
verticalAlignment = Alignment.CenterVertically,
) {
val contentColor = type.contentColor

icon?.let { it() }

BpkText(
text = text,
color = contentColor,
style = BpkTheme.typography.footnote,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}

@Composable
internal fun BadgeIcon(
icon: BpkIcon,
type: BpkBadgeType,
modifier: Modifier = Modifier,
) {
BpkIcon(
icon = icon,
contentDescription = null,
modifier = modifier,
tint = type.iconColor,
)
}

@Composable
internal fun BadgeDrawable(
icon: Painter,
type: BpkBadgeType,
modifier: Modifier = Modifier,
) {
Icon(
painter = icon,
contentDescription = null,
tint = type.iconColor,
modifier = modifier.requiredSize(BpkSpacing.Base, BpkSpacing.Base),
)
}

private val BpkBadgeType.iconColor: Color
@Composable
get() = when (this) {
BpkBadgeType.Warning -> BpkTheme.colors.statusWarningSpot
BpkBadgeType.Destructive -> BpkTheme.colors.statusDangerSpot
BpkBadgeType.Success -> BpkTheme.colors.statusSuccessSpot
else -> {
contentColor
}
}

private val BpkBadgeType.contentColor: Color
@Composable
get() = when (this) {
BpkBadgeType.Normal -> BpkTheme.colors.textPrimary
BpkBadgeType.Strong -> BpkTheme.colors.textOnDark
BpkBadgeType.Success -> BpkTheme.colors.textPrimary
BpkBadgeType.Warning -> BpkTheme.colors.textPrimary
BpkBadgeType.Destructive -> BpkTheme.colors.textPrimary
BpkBadgeType.Inverse -> BpkTheme.colors.textPrimary
BpkBadgeType.Outline -> BpkTheme.colors.textOnDark
BpkBadgeType.Brand -> BpkTheme.colors.textPrimaryInverse
}

private val BpkBadgeType.backgroundColor: Color
@Composable
get() = when (this) {
BpkBadgeType.Normal -> BpkBadgeColors.backgroundNormal
BpkBadgeType.Strong -> BpkTheme.colors.corePrimary
BpkBadgeType.Success -> BpkBadgeColors.backgroundNormal
BpkBadgeType.Warning -> BpkBadgeColors.backgroundNormal
BpkBadgeType.Destructive -> BpkBadgeColors.backgroundNormal
BpkBadgeType.Inverse -> BpkTheme.colors.surfaceDefault
BpkBadgeType.Outline -> Color.Transparent
BpkBadgeType.Brand -> BpkTheme.colors.coreAccent
}

private val BpkBadgeType.borderColor: Color
@Composable
get() = when (this) {
BpkBadgeType.Outline -> BpkTheme.colors.textOnDark
else -> Color.Transparent
}

private val BadgeShape = RoundedCornerShape(BpkBorderRadius.Xs)
14 changes: 14 additions & 0 deletions docs/compose/Badge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,17 @@ BpkBadge(
icon = BpkIcon.TickCircle,
)
```

Example of a Badge with custom icon:

```Kotlin
import net.skyscanner.backpack.compose.badge.BpkBadge
import net.skyscanner.backpack.compose.badge.BpkBadgeType
import androidx.compose.ui.res.painterResource

BpkBadge(
text = "Badge text",
type = BpkBadgeType.Normal,
icon = painterResource(id = R.drawable.sample_icon),
)
```
Binary file modified docs/compose/Badge/screenshots/default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/compose/Badge/screenshots/default_dm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d46678c

Please sign in to comment.