diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
index 5dae9cf7..4144c8d7 100644
--- a/.idea/androidTestResultsUserPreferences.xml
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -68,6 +68,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -146,6 +172,19 @@
+
+
+
+
+
+
+
@@ -250,6 +289,19 @@
+
+
+
+
+
+
+
@@ -434,6 +486,19 @@
+
+
+
+
+
+
+
diff --git a/example/src/main/java/com/tarkalabs/ui/UIComponentListActivity.kt b/example/src/main/java/com/tarkalabs/ui/UIComponentListActivity.kt
index 1a04930e..4aacf5ab 100644
--- a/example/src/main/java/com/tarkalabs/ui/UIComponentListActivity.kt
+++ b/example/src/main/java/com/tarkalabs/ui/UIComponentListActivity.kt
@@ -37,7 +37,7 @@ class UIComponentListActivity : ComponentActivity() {
TUICheckBoxRow(
checked = status.value,
enabled = true,
- icon = TarkaIcons.CheckMark,
+ icon = TarkaIcons.CheckMark20Filled,
title = "TUICheckBoxRow",
style = TitleWithDescription("Description")
) {
diff --git a/tarkaui/src/androidTest/java/com/tarkalabs/uicomponents/TUIChipTest.kt b/tarkaui/src/androidTest/java/com/tarkalabs/uicomponents/TUIChipTest.kt
new file mode 100644
index 00000000..58aa470b
--- /dev/null
+++ b/tarkaui/src/androidTest/java/com/tarkalabs/uicomponents/TUIChipTest.kt
@@ -0,0 +1,96 @@
+package com.tarkalabs.uicomponents
+
+import android.graphics.BitmapFactory
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.platform.app.InstrumentationRegistry
+import com.tarkalabs.uicomponents.components.ChipLeadingContent
+import com.tarkalabs.uicomponents.components.ChipType
+import com.tarkalabs.uicomponents.components.TUIChip
+import com.tarkalabs.uicomponents.components.TUIChipTags
+import com.tarkalabs.uicomponents.models.TarkaIcons
+import com.tarkalabs.uicomponents.theme.TUITheme
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+class TUIChipTest {
+
+ @get:Rule
+ val composable = createComposeRule()
+ val chipTags = TUIChipTags()
+
+ val context = InstrumentationRegistry.getInstrumentation().context
+ val assetManager = context.assets
+
+ @Test
+ fun display_assist_chip_avatar() {
+ val onClick: () -> Unit = mock()
+ val bitmap = BitmapFactory.decodeStream(assetManager.open("avatarTest.webp"))
+ composable.setContent {
+ TUIChip(
+ type = ChipType.Assist(ChipLeadingContent.Image(bitmap.asImageBitmap())),
+ label = "Assist chip",
+ onClick = onClick,
+ tags = chipTags.copy(parentTag = "Assist")
+ )
+ }
+
+ composable.onNodeWithTag("Assist").assertIsDisplayed()
+ composable.onNodeWithText("Assist chip").assertIsDisplayed()
+ composable.onNodeWithTag(Tags.TAG_AVATAR, useUnmergedTree = true).assertIsDisplayed()
+ composable.onNodeWithText("Assist chip").performClick()
+ verify(onClick).invoke()
+ }
+
+ @Test
+ fun display_assist_chip_icon() {
+ composable.setContent {
+ TUIChip(type = ChipType.Assist(ChipLeadingContent.Icon(TarkaIcons.Calendar24Regular)),
+ label = "Assist chip",
+ onClick = {})
+ }
+
+ composable.onNodeWithContentDescription(
+ TarkaIcons.Calendar24Regular.contentDescription, useUnmergedTree = true
+ ).assertIsDisplayed()
+ }
+
+ @Test
+ fun display_input_chip() {
+ val onClick: () -> Unit = mock()
+ composable.setContent {
+ TUIChip(
+ type = ChipType.Input(null, true, TUITheme.colors.surface),
+ label = "Input chip",
+ onClick = onClick,
+ tags = chipTags.copy(parentTag = "Input"),
+ )
+ }
+
+ composable.onNodeWithTag("Input").assertIsDisplayed()
+ composable.onNodeWithText("Input chip").assertIsDisplayed()
+ composable.onNodeWithText("Input chip").performClick()
+ verify(onClick).invoke()
+ }
+
+ @Test
+ fun display_input_avatar() {
+ val bitmap = BitmapFactory.decodeStream(assetManager.open("avatarTest.webp"))
+ composable.setContent {
+ TUIChip(
+ type = ChipType.Input(content = ChipLeadingContent.Image(bitmap.asImageBitmap()), showTrailingDismiss = true, containerColor = TUITheme.colors.surface),
+ label = "Input chip",
+ onClick = { },
+ )
+ }
+
+ composable.onNodeWithTag(Tags.TAG_AVATAR, useUnmergedTree = true).assertIsDisplayed()
+ }
+}
\ No newline at end of file
diff --git a/tarkaui/src/androidTest/java/com/tarkalabs/uicomponents/screenshots/TUIChipScreenshotTest.kt b/tarkaui/src/androidTest/java/com/tarkalabs/uicomponents/screenshots/TUIChipScreenshotTest.kt
new file mode 100644
index 00000000..d2d770ba
--- /dev/null
+++ b/tarkaui/src/androidTest/java/com/tarkalabs/uicomponents/screenshots/TUIChipScreenshotTest.kt
@@ -0,0 +1,104 @@
+package com.tarkalabs.uicomponents.screenshots
+
+import android.graphics.BitmapFactory
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.test.platform.app.InstrumentationRegistry
+import com.tarkalabs.uicomponents.components.ChipLeadingContent
+import com.tarkalabs.uicomponents.components.ChipSize
+import com.tarkalabs.uicomponents.components.ChipType
+import com.tarkalabs.uicomponents.components.TUIChip
+import com.tarkalabs.uicomponents.models.TarkaIcons
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class TUIChipScreenshotTest(
+ private val size: ChipSize,
+ private val type: ChipType,
+ private val darkTheme: Boolean,
+ private val testName: String
+) : ComposeScreenshotComparator() {
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Collection> {
+ val context = InstrumentationRegistry.getInstrumentation().context
+ val assetManager = context.assets
+ val bitmap = BitmapFactory.decodeStream(assetManager.open("avatarTest.webp"))
+ val leadingContent = listOf(
+ null,
+ ChipLeadingContent.Image(bitmap.asImageBitmap()),
+ ChipLeadingContent.Icon(TarkaIcons.CheckMark16Filled)
+ )
+
+ val types =
+ leadingContent.map { ChipType.Assist(it) } + filterChipTypes() + leadingContent.map {
+ ChipType.Input(
+ it, false
+ )
+ } + leadingContent.map { ChipType.Input(it, true) } + listOf(
+ ChipType.Suggestion(null),
+ ChipType.Suggestion(TarkaIcons.Calendar24Regular),
+ )
+
+ val chipSizes = ChipSize.values()
+ val darkThemeValues = listOf(true, false)
+ val testData = ArrayList>()
+ for (type in types) {
+ for (chipSize in chipSizes) {
+ for (darkTheme in darkThemeValues) {
+ val testName = when (type) {
+ is ChipType.Assist -> "ChipSize_${chipSize}_chipType_${type.javaClass.simpleName}_chipType_${type.content?.javaClass?.simpleName.toString()}_darkTheme_${darkTheme}"
+ is ChipType.Filter -> "ChipSize_${chipSize}_chipType_${type.javaClass.simpleName}_chipType_${type.showLeadingCheck}_chipType_${type.showTrailingCaret}_chipType_${type.showTrailingDismiss}_chipType_${type.selected}_darkTheme_${darkTheme}"
+ is ChipType.Input -> "ChipSize_${chipSize}_chipType_${type.javaClass.simpleName}_chipType_${type.showTrailingDismiss}_chipType_${type.content?.javaClass?.simpleName}_darkTheme_${darkTheme}"
+ is ChipType.Suggestion -> "ChipSize_${chipSize}_chipType_${type.javaClass.simpleName}_chipType_${type.image.toString()}_darkTheme_${darkTheme}"
+ }
+ testData.add(
+ arrayOf(
+ chipSize, type, darkTheme, testName,
+ )
+ )
+ }
+ }
+ }
+ return testData
+ }
+ }
+
+ @Test
+ fun test_tui_chip() {
+ compareScreenshotFor(darkTheme, testName) {
+ TUIChip(
+ type = type,
+ label = type.javaClass.simpleName,
+ chipSize = size,
+ onClick = { },
+ )
+ }
+ }
+}
+
+private fun filterChipTypes(): List {
+ val filterVariations = mutableListOf()
+ val booleans = listOf(true, false)
+ booleans.forEach { selected ->
+ booleans.forEach { showLeadingCheck ->
+ booleans.forEach { showTrailingDismiss ->
+ booleans.forEach { showTrailingCaret ->
+ filterVariations.add(
+ ChipType.Filter(
+ selected = selected,
+ showLeadingCheck = showLeadingCheck,
+ showTrailingDismiss = showTrailingDismiss,
+ showTrailingCaret = showTrailingCaret,
+ badgeCount = null
+ )
+ )
+ }
+ }
+ }
+ }
+ return filterVariations
+}
\ No newline at end of file
diff --git a/tarkaui/src/main/java/com/tarkalabs/uicomponents/Tags.kt b/tarkaui/src/main/java/com/tarkalabs/uicomponents/Tags.kt
index 16daa5d9..7e3b88e2 100644
--- a/tarkaui/src/main/java/com/tarkalabs/uicomponents/Tags.kt
+++ b/tarkaui/src/main/java/com/tarkalabs/uicomponents/Tags.kt
@@ -28,5 +28,5 @@ object Tags {
const val TAG_CHECK_BOX = "check_box"
const val TAG_CHECK_BOX_ROW = "check_box_row"
const val TAG_SEARCH_BAR = "search_bar"
-
+ const val TAG_CHIP_TAG = "tui_chip"
}
\ No newline at end of file
diff --git a/tarkaui/src/main/java/com/tarkalabs/uicomponents/components/TUIChip.kt b/tarkaui/src/main/java/com/tarkalabs/uicomponents/components/TUIChip.kt
new file mode 100644
index 00000000..dd171774
--- /dev/null
+++ b/tarkaui/src/main/java/com/tarkalabs/uicomponents/components/TUIChip.kt
@@ -0,0 +1,253 @@
+package com.tarkalabs.uicomponents.components
+
+import android.util.Log
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material3.AssistChip
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FilterChip
+import androidx.compose.material3.Icon
+import androidx.compose.material3.InputChip
+import androidx.compose.material3.InputChipDefaults
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.Text
+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.ImageBitmap
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.tarkalabs.uicomponents.Tags
+import com.tarkalabs.uicomponents.components.AvatarSize.XS
+import com.tarkalabs.uicomponents.components.ChipLeadingContent.Icon
+import com.tarkalabs.uicomponents.components.ChipLeadingContent.Image
+import com.tarkalabs.uicomponents.components.ChipType.Filter
+import com.tarkalabs.uicomponents.components.IconButtonSize.M
+import com.tarkalabs.uicomponents.components.IconButtonStyle.GHOST
+import com.tarkalabs.uicomponents.models.TarkaIcon
+import com.tarkalabs.uicomponents.models.TarkaIcons
+import com.tarkalabs.uicomponents.theme.TUITheme
+
+
+/**
+ * Represents a generic chip type. The ChipType superclass serves as a generic base for the different chip types in the sealed class hierarchy.
+ */
+sealed class ChipType {
+ /**
+ * Represents an assist chip type for TUIChip.
+ *
+ * @param content The optional leading content for the chip.
+ */
+ data class Assist(val content: ChipLeadingContent? = null) : ChipType()
+
+ /**
+ * Represents an input chip type for TUIChip.
+ *
+ * @param content The optional leading content for the chip.
+ * @param showTrailingDismiss Whether to show a dismiss icon as a trailing icon.
+ * @param containerColor The color of the chip's container. If null, the default color from the theme will be used.
+ */
+ data class Input(
+ val content: ChipLeadingContent? = null,
+ val showTrailingDismiss: Boolean = false,
+ val containerColor : Color? = null
+ ) : ChipType()
+
+ /**
+ * Represents a suggestion chip type for TUIChip.
+ *
+ * @param image The optional image for the chip.
+ */
+ data class Suggestion(val image: TarkaIcon? = null) : ChipType()
+
+ /**
+ * Represents a filter chip type for TUIChip.
+ *
+ * @param selected Whether the filter is selected.
+ * @param showLeadingCheck Whether to show a check icon as a leading icon.
+ * @param showTrailingDismiss Whether to show a dismiss icon as a trailing icon.
+ * @param showTrailingCaret Whether to show a caret icon as a trailing icon.
+ * @param badgeCount The badge count to display on the chip.
+ */
+ data class Filter(
+ val selected: Boolean = false,
+ val showLeadingCheck: Boolean = false,
+ val showTrailingDismiss: Boolean = false,
+ val showTrailingCaret: Boolean = false,
+ val badgeCount: Int? = null
+ ) : ChipType()
+}
+
+sealed class ChipLeadingContent {
+ data class Image(val imageBitmap: ImageBitmap) : ChipLeadingContent()
+ data class Icon(val icon: TarkaIcon) : ChipLeadingContent()
+}
+
+enum class ChipSize(val size: Dp) {
+ SMALL(32.dp),
+ BIG(40.dp),
+}
+
+/**
+ * A customizable chip composable that can be used to represent different types of chips.
+ *
+ * @param modifier The modifier to be applied to the chip.
+ * @param type The type of chip to be rendered (Assist, Input, Filter, Suggestion).
+ * @param label The label text to be displayed on the chip.
+ * @param onClick The callback function to be invoked when the chip is clicked.
+ * @param chipSize The size of the chip (default is ChipSize.SMALL).
+ * @param tags The tags to be applied to the chip for testing purposes.
+ */
+@OptIn(ExperimentalMaterial3Api::class) @Composable fun TUIChip(
+ modifier: Modifier = Modifier,
+ type: ChipType,
+ label: String,
+ onClick: () -> Unit,
+ onDismissClick: (() -> Unit)? = null,
+ chipSize: ChipSize = ChipSize.SMALL,
+ tags: TUIChipTags = TUIChipTags()
+) {
+
+ val commonModifier = modifier
+ .testTag(tags.parentTag)
+ .height(chipSize.size)
+ val commonLabel = getCommonLabel(label)
+ val leadingIcon = getLeadingIcon()
+
+
+ when (type) {
+ is ChipType.Assist -> {
+ AssistChip(modifier = commonModifier,
+ label = commonLabel,
+ onClick = onClick,
+ leadingIcon = { leadingIcon(type.content) })
+ }
+
+ is ChipType.Input -> InputChip(modifier = commonModifier,
+ selected = false,
+ onClick = onClick,
+ label = commonLabel,
+ colors = InputChipDefaults.inputChipColors(
+ containerColor = type.containerColor ?: TUITheme.colors.onSurface
+ ),
+ leadingIcon = { leadingIcon(type.content) },
+ trailingIcon = if (type.showTrailingDismiss) {
+ {
+ TUIIconButton(icon = TarkaIcons.Dismiss20Filled,
+ iconButtonStyle = GHOST,
+ onIconClick = {
+ onDismissClick?.invoke()
+ },
+ buttonSize = M
+ )
+ }
+ } else null)
+
+ is Filter -> {
+ FilterChip(type, onClick, commonLabel, commonModifier)
+ }
+
+ is ChipType.Suggestion -> {
+ SuggestionChip(onClick = onClick,
+ label = commonLabel,
+ modifier = commonModifier,
+ icon = if (type.image != null) {
+ {
+ Icon(
+ painter = painterResource(id = type.image.iconRes),
+ contentDescription = type.image.contentDescription
+ )
+ }
+ } else null)
+ }
+ }
+}
+
+@Composable @OptIn(ExperimentalMaterial3Api::class) private fun FilterChip(
+ type: Filter, onClick: () -> Unit, commonLabel: @Composable () -> Unit, modifier: Modifier
+) {
+ Box(modifier = Modifier.wrapContentWidth()) {
+ FilterChip(selected = type.selected,
+ onClick = onClick,
+ label = commonLabel,
+ modifier = modifier,
+ leadingIcon = if (type.showLeadingCheck) {
+ {
+ Icon(
+ painter = painterResource(id = TarkaIcons.CheckMark20Filled.iconRes),
+ contentDescription = TarkaIcons.CheckMark20Filled.contentDescription
+ )
+ }
+ } else null,
+ trailingIcon = if (type.showTrailingDismiss) {
+ {
+ TUIIconButton(
+ icon = TarkaIcons.Dismiss20Filled, iconButtonStyle = GHOST
+ )
+ }
+ } else if (type.showTrailingCaret) {
+ {
+ Icon(
+ painter = painterResource(id = TarkaIcons.CaretDown20Filled.iconRes),
+ contentDescription = TarkaIcons.CaretDown20Filled.contentDescription
+ )
+ }
+ } else null)
+ if (type.badgeCount != null) {
+ TUIBadge(count = type.badgeCount, modifier = Modifier.align(Alignment.TopEnd))
+ }
+ }
+}
+
+@Composable private fun getCommonLabel(label: String) = (@Composable {
+ Text(
+ text = label, style = TUITheme.typography.button7, color = TUITheme.colors.onSurface
+ )
+})
+
+@Composable private fun getLeadingIcon(): @Composable (ChipLeadingContent?) -> Unit {
+ val leadingIcon: @Composable (ChipLeadingContent?) -> Unit = @Composable {
+ when (it) {
+ is Icon -> Icon(
+ painter = painterResource(id = it.icon.iconRes),
+ contentDescription = it.icon.contentDescription,
+ tint = TUITheme.colors.onSurface
+ )
+
+ is Image -> TUIAvatar(
+ avatarType = AvatarType.Image(it.imageBitmap), avatarSize = XS
+ )
+
+ null -> {}
+ }
+ }
+ return leadingIcon
+}
+
+data class TUIChipTags(
+ val parentTag: String = Tags.TAG_CHIP_TAG
+)
+
+@Preview @Composable fun TUIChipPreview() {
+
+ Column {
+ TUIChip(
+ type = ChipType.Input(showTrailingDismiss = true, containerColor = TUITheme.colors.surfaceVariant),
+ label = "Something",
+ onClick = { Log.e("TAG_CHIP", "TUIChipPreview: TAG_CLICKED") }
+ )
+
+ TUIChip(
+ type = ChipType.Assist(),
+ label = "Something",
+ onClick = { Log.e("TAG_CHIP", "TUIChipPreview: TAG_CLICKED") }
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/tarkaui/src/main/java/com/tarkalabs/uicomponents/models/TarkaIcon.kt b/tarkaui/src/main/java/com/tarkalabs/uicomponents/models/TarkaIcon.kt
index b63e98d3..641d9021 100644
--- a/tarkaui/src/main/java/com/tarkalabs/uicomponents/models/TarkaIcon.kt
+++ b/tarkaui/src/main/java/com/tarkalabs/uicomponents/models/TarkaIcon.kt
@@ -9,9 +9,11 @@ import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_arrow_sync_20_regu
import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_arrow_sync_circle_24_regular
import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_barcode_scanner_24_regular
import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_calendar_ltr_24_regular
+import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_caret_down_20_filled
import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_chat_bubbles_question_24_regular
import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_chat_help_20_filled
import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_checkmark_16_filled
+import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_checkmark_20_filled
import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_checkmark_circle_16_regular
import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_checkmark_starburst_24_regular
import com.microsoft.fluent.mobile.icons.R.drawable.ic_fluent_chevron_down_20_regular
@@ -85,12 +87,13 @@ object TarkaIcons {
val ReOrderDotsVertical24Regular =
TarkaIcon(ic_fluent_re_order_dots_vertical_24_regular, "Re order dots")
val CheckMark16Filled = TarkaIcon(ic_fluent_checkmark_16_filled, "Check Mark")
+ val CheckMark20Filled = TarkaIcon(ic_fluent_checkmark_20_filled, "Check Mark")
val QuestionCircle24Regular = TarkaIcon(ic_fluent_question_circle_24_regular, "Question Circle")
val DocumentText24Regular = TarkaIcon(ic_fluent_document_text_24_regular, "Document Text")
val ShieldTask24Regular = TarkaIcon(ic_fluent_shield_task_24_regular, "Document Text")
val LocalLanguage24Regular = TarkaIcon(ic_fluent_local_language_24_regular, "Language")
val BarCodeScanner24Regular = TarkaIcon(ic_fluent_barcode_scanner_24_regular, "BarCode Scanner")
val Info20Filled = TarkaIcon(ic_fluent_chat_help_20_filled, "Information")
- val ChatBubblesQuestion24Regular =
- TarkaIcon(ic_fluent_chat_bubbles_question_24_regular, "FAQ")
+ val ChatBubblesQuestion24Regular = TarkaIcon(ic_fluent_chat_bubbles_question_24_regular, "FAQ")
+ val CaretDown20Filled = TarkaIcon(ic_fluent_caret_down_20_filled, "Caret Down")
}
\ No newline at end of file