diff --git a/fabs/.gitignore b/fabs/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/fabs/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/fabs/README.txt b/fabs/README.txt new file mode 100644 index 00000000..d0bbf1a4 --- /dev/null +++ b/fabs/README.txt @@ -0,0 +1,96 @@ +# Floating Action Button components + +## FAB design specs + +You can find the design specs on [decathlon.design](https://www.decathlon.design/). + +## Usage + +If you want to use components of this module in your android mobile application, you should +first add the Gradle dependency in your Gradle file: + +```kotlin +implementation("com.decathlon.vitamin.compose:fabs:") +``` + +### Primary + +**FAB** + +```kotlin +object VitaminFabs { + @Composable + fun Primary( + icon: Painter, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: FabsColor = VitaminFabsColors.primary(), + sizes: FabSizes = VitaminFabSizes.default(), + ripple: RippleTheme = VitaminTheme.ripples.brand, + onClick: () -> Unit + ) +} +``` + +Use FABs for primary, positive actions. + +The minimal usage of the component is the fab with an icon. + +```kotlin +VitaminFabs.Primary( + icon = rememberVectorPainter(image = Icons.Filled.Add) +) { + // Click event +} +``` + +Parameters | Descriptions +-- | -- +`icon: Painter` | The icon to be displayed inside the FAB +`modifier: Modifier = Modifier` | The `Modifier` to be applied to the component +`enabled: Boolean = true` | True if you can click on the FAB, otherwise false +`colors: FabsColors = VitaminFabsColors.primary()` | The colors of the background and the content in enabled and disabled +`sizes: FabSizes = VitaminFabSizes.medium()` | The sizes for the icon and the component itself +`ripple: RippleTheme = VitaminTheme.ripples.brand` | The ripple effect applied on the component +`onClick: () -> Unit` | The callback to be called when the user click on the FAB + + +**FAB extended** + +```kotlin +object VitaminFabsExtended%%%%%%%%%%%%%%%%%%%%%%%%%%% { + @Composable + fun Primary( + text: String, + modifier: Modifier = Modifier, + enabled: Boolean = true, + icon: Painter? = null, + colors: FabsColors = VitaminFabsColors.primary(), + sizes: FabExtendedSizes = VitaminFabExtendedSizes.default(), + ripple: RippleTheme = VitaminTheme.ripples.brand, + onClick: () -> Unit + ) +} + + +The minimal usage of the component is the fab extended with a label. You can also add an icon. + +```kotlin +VitaminFabsExtended.Primary( + text = "Label" +) { + // Click event +} +``` + +``` +Parameters | Descriptions +-- | -- +`text: String` | The text inside the FAB extended +`modifier: Modifier = Modifier` | The `Modifier` to be applied to the component +`icon: Painter` | The optional icon to be displayed inside the FAB extended on the right left side of the text +`enabled: Boolean = true` | True if you can click on the FAB, otherwise false +`colors: FabsColors = VitaminFabsColors.primary()` | The colors of the background and the content in enabled and disabled +`sizes: FabSizes = VitaminFabExtendedSizes.default()` | The sizes for the icon, the text and the component itself +`ripple: RippleTheme = VitaminTheme.ripples.brand` | The ripple effect applied on the component +`onClick: () -> Unit` | The callback to be called when the user click on the FAB extended diff --git a/fabs/build.gradle.kts b/fabs/build.gradle.kts new file mode 100644 index 00000000..9181975e --- /dev/null +++ b/fabs/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("com.android.library") + id("kotlin-android") + id("VitaminComposeLibraryPlugin") + id("com.vanniktech.maven.publish") + id("app.cash.paparazzi") +} + +dependencies { + api(project(":foundation")) + implementation(AndroidX.compose.ui.tooling) + testImplementation("com.google.testparameterinjector:test-parameter-injector:1.8") +} diff --git a/fabs/gradle.properties b/fabs/gradle.properties new file mode 100644 index 00000000..2dbee2da --- /dev/null +++ b/fabs/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=fabs +POM_NAME=Vitamin Fabs +POM_DESCRIPTION=Fab components with Vitamin design \ No newline at end of file diff --git a/fabs/src/main/AndroidManifest.xml b/fabs/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8d161820 --- /dev/null +++ b/fabs/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/NoRippleInteractionSource.kt b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/NoRippleInteractionSource.kt new file mode 100644 index 00000000..cc5321c7 --- /dev/null +++ b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/NoRippleInteractionSource.kt @@ -0,0 +1,12 @@ +package com.decathlon.vitamin.compose.fabs + +import androidx.compose.foundation.interaction.Interaction +import androidx.compose.foundation.interaction.MutableInteractionSource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +internal class NoRippleInteractionSource : MutableInteractionSource { + override val interactions: Flow = emptyFlow() + override suspend fun emit(interaction: Interaction) { /*not used in this context*/ } + override fun tryEmit(interaction: Interaction) = true +} diff --git a/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabExtendedSizes.kt b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabExtendedSizes.kt new file mode 100644 index 00000000..6719a062 --- /dev/null +++ b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabExtendedSizes.kt @@ -0,0 +1,47 @@ +package com.decathlon.vitamin.compose.fabs + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.decathlon.vitamin.compose.foundation.VitaminTheme + +@Immutable +data class FabExtendedSizes( + val textStyle: TextStyle, + val height: Dp, + val iconSize: Dp, + val horizontalPadding: Dp, + val iconStartPadding: Dp, + val iconEndPadding: Dp +) + +object VitaminFabExtendedSizes { + @Composable + fun default( + textStyle: TextStyle = VitaminTheme.typography.button, + height: Dp = 48.dp, + iconSize: Dp = 24.dp, + textHorizontalPadding: Dp = 20.dp, + iconStartPadding: Dp = 16.dp, + iconEndPadding: Dp = 8.dp + ): FabExtendedSizes = remember( + textStyle, + height, + iconSize, + textHorizontalPadding, + iconStartPadding, + iconEndPadding + ) { + FabExtendedSizes( + textStyle = textStyle, + height = height, + iconSize = iconSize, + horizontalPadding = textHorizontalPadding, + iconStartPadding = iconStartPadding, + iconEndPadding = iconEndPadding + ) + } +} diff --git a/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabSizes.kt b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabSizes.kt new file mode 100644 index 00000000..1c4ac6b4 --- /dev/null +++ b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabSizes.kt @@ -0,0 +1,43 @@ +package com.decathlon.vitamin.compose.fabs + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Immutable +data class FabSizes( + val size: Dp, + val iconSize: Dp +) + +object VitaminFabSizes { + @Composable + fun mini( + size: Dp = 40.dp, + iconSize: Dp = 24.dp + ): FabSizes = remember( + size, + iconSize + ) { + FabSizes( + size = size, + iconSize = iconSize + ) + } + + @Composable + fun default( + size: Dp = 56.dp, + iconSize: Dp = 24.dp + ): FabSizes = remember( + size, + iconSize + ) { + FabSizes( + size = size, + iconSize = iconSize + ) + } +} diff --git a/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabs.kt b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabs.kt new file mode 100644 index 00000000..00a8f44d --- /dev/null +++ b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabs.kt @@ -0,0 +1,128 @@ +package com.decathlon.vitamin.compose.fabs + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.size +import androidx.compose.material.FloatingActionButton +import androidx.compose.material.FloatingActionButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.ripple.LocalRippleTheme +import androidx.compose.material.ripple.RippleTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.decathlon.vitamin.compose.foundation.VitaminTheme + +object VitaminFabs { + + /** + * The FAB must be used for primary, positive actions + * @param icon The icon to be displayed inside the FAB + * @param modifier The [Modifier] to be applied to the component + * @param enabled True if you can click on the FAB, otherwise false + * @param colors The colors of the background and the content in enabled and disabled + * @param sizes The sizes for the icon and the component itself + * @param ripple The ripple effect applied on the component + * @param onClick The callback to be called when the user click on the FAB + */ + @Composable + fun Primary( + icon: Painter, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: FabsColors = VitaminFabsColors.primary(), + sizes: FabSizes = VitaminFabSizes.default(), + ripple: RippleTheme = VitaminTheme.ripples.brand, + onClick: () -> Unit + ) { + CompositionLocalProvider(LocalRippleTheme provides ripple) { + FloatingActionButton( + interactionSource = if (enabled) { + MutableInteractionSource() + } else { + NoRippleInteractionSource() + }, + elevation = if (enabled) { + FloatingActionButtonDefaults.elevation() + } else { + FloatingActionButtonDefaults.elevation( + defaultElevation = 0.dp, + pressedElevation = 0.dp, + focusedElevation = 0.dp, + hoveredElevation = 0.dp + ) + }, + contentColor = if (enabled) { + colors.contentColor + } else { + colors.disabledContentColor + }, + backgroundColor = if (enabled) { + colors.backgroundColor + } else { + colors.disabledBackgroundColor + }, + modifier = modifier + .size(sizes.size), + onClick = { + if (enabled) { + onClick() + } + } + ) { + Icon( + painter = icon, + contentDescription = null, + modifier = Modifier.size(sizes.iconSize) + ) + } + } + } +} + +@Preview +@Composable +private fun VitaminEnabledFabPreview() { + VitaminTheme { + VitaminFabs.Primary(icon = rememberVectorPainter(image = Icons.Filled.Add)) {} + } +} + +@Preview +@Composable +private fun VitaminDisabledFabPreview() { + VitaminTheme { + VitaminFabs.Primary( + icon = rememberVectorPainter(image = Icons.Filled.Add), + enabled = false + ) {} + } +} + +@Preview +@Composable +private fun VitaminEnabledMiniFabPreview() { + VitaminTheme { + VitaminFabs.Primary( + sizes = VitaminFabSizes.mini(), + icon = rememberVectorPainter(image = Icons.Filled.Add) + ) {} + } +} + +@Preview +@Composable +private fun VitaminDisabledMiniFabPreview() { + VitaminTheme { + VitaminFabs.Primary( + sizes = VitaminFabSizes.mini(), + icon = rememberVectorPainter(image = Icons.Filled.Add), + enabled = false + ) {} + } +} diff --git a/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabsColors.kt b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabsColors.kt new file mode 100644 index 00000000..0b240ed7 --- /dev/null +++ b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabsColors.kt @@ -0,0 +1,38 @@ +package com.decathlon.vitamin.compose.fabs + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import com.decathlon.vitamin.compose.foundation.VitaminTheme +import com.decathlon.vitamin.compose.foundation.VtmnStatesDisabled + +@Immutable +data class FabsColors( + val contentColor: Color, + val backgroundColor: Color, + val disabledContentColor: Color, + val disabledBackgroundColor: Color +) + +object VitaminFabsColors { + @Composable + fun primary( + contentColor: Color = VitaminTheme.colors.vtmnContentPrimaryReversed, + backgroundColor: Color = VitaminTheme.colors.vtmnBackgroundBrandPrimary, + disabledContentColor: Color = VitaminTheme.colors.vtmnContentAction.copy(alpha = VtmnStatesDisabled), + disabledBackgroundColor: Color = VitaminTheme.colors.vtmnBackgroundBrandPrimary.copy(alpha = 0.12f), + ): FabsColors = remember( + contentColor, + backgroundColor, + disabledContentColor, + disabledBackgroundColor + ) { + FabsColors( + contentColor = contentColor, + backgroundColor = backgroundColor, + disabledContentColor = disabledContentColor, + disabledBackgroundColor = disabledBackgroundColor + ) + } +} diff --git a/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabsExtended.kt b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabsExtended.kt new file mode 100644 index 00000000..712bb93e --- /dev/null +++ b/fabs/src/main/java/com/decathlon/vitamin/compose/fabs/VitaminFabsExtended.kt @@ -0,0 +1,137 @@ +package com.decathlon.vitamin.compose.fabs + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.width +import androidx.compose.material.FloatingActionButton +import androidx.compose.material.FloatingActionButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.ripple.LocalRippleTheme +import androidx.compose.material.ripple.RippleTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.decathlon.vitamin.compose.foundation.VitaminTheme + +object VitaminFabsExtended { + + /** + * The FAB extended must be used for primary, positive actions + * @param text The text inside the FAB extended + * @param modifier The [Modifier] to be applied to the component + * @param icon The optional icon to be displayed inside the FAB extended on the right left side of the text + * @param enabled True if you can click on the FAB extended, otherwise false + * @param colors The colors of the background and the content in enabled and disabled + * @param sizes The sizes for the icon, the text and the component itself + * @param ripple The ripple effect applied on the component + * @param onClick The callback to be called when the user click on the FAB extended + */ + @Composable + fun Primary( + text: String, + modifier: Modifier = Modifier, + enabled: Boolean = true, + icon: Painter? = null, + colors: FabsColors = VitaminFabsColors.primary(), + sizes: FabExtendedSizes = VitaminFabExtendedSizes.default(), + ripple: RippleTheme = VitaminTheme.ripples.brand, + onClick: () -> Unit + ) { + IntrinsicSize.Min + CompositionLocalProvider(LocalRippleTheme provides ripple) { + FloatingActionButton( + interactionSource = if (enabled) { + MutableInteractionSource() + } else { + NoRippleInteractionSource() + }, + elevation = if (enabled) { + FloatingActionButtonDefaults.elevation() + } else { + FloatingActionButtonDefaults.elevation( + defaultElevation = 0.dp, pressedElevation = 0.dp + ) + }, + contentColor = if (enabled) { + colors.contentColor + } else { + colors.disabledContentColor + }, + backgroundColor = if (enabled) { + colors.backgroundColor + } else { + colors.disabledBackgroundColor + }, + modifier = modifier.sizeIn( + minWidth = sizes.height, + minHeight = sizes.height + ), + onClick = { + if (enabled) { + onClick() + } + } + ) { + val startPadding = + if (icon == null) { + sizes.horizontalPadding + } else { + sizes.iconStartPadding + } + Row( + modifier = Modifier.padding( + start = startPadding, + end = sizes.horizontalPadding, + ), + verticalAlignment = Alignment.CenterVertically + ) { + if (icon != null) { + Icon( + painter = icon, + contentDescription = null, + modifier = Modifier.size(sizes.iconSize) + ) + Spacer(Modifier.width(sizes.iconEndPadding)) + } + Text(text = text, style = sizes.textStyle) + } + } + } + } +} + +@Preview +@Composable +private fun VitaminEnabledFabExtendedPreview() { + VitaminTheme { + VitaminFabsExtended.Primary( + icon = rememberVectorPainter(image = Icons.Filled.Add), + text = "Label" + ) {} + } +} + +@Preview +@Composable +private fun VitaminDisabledFabExtendedPreview() { + VitaminTheme { + VitaminFabsExtended.Primary( + icon = rememberVectorPainter(image = Icons.Filled.Add), + text = "Label", + enabled = false + ) {} + } +} diff --git a/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/VitaminFabsExtendedPrimaryTest.kt b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/VitaminFabsExtendedPrimaryTest.kt new file mode 100644 index 00000000..74b71d9a --- /dev/null +++ b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/VitaminFabsExtendedPrimaryTest.kt @@ -0,0 +1,73 @@ +package com.decathlon.vitamin.compose.fabs + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.unit.dp +import app.cash.paparazzi.Paparazzi +import com.decathlon.vitamin.compose.fabs.utils.FabVariantsFactory +import com.decathlon.vitamin.compose.fabs.utils.Theme +import com.decathlon.vitamin.compose.fabs.utils.Variant +import com.decathlon.vitamin.compose.foundation.VitaminTheme +import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(TestParameterInjector::class) +class VitaminFabsExtendedPrimaryTest( + @TestParameter val variant: Variant +) { + @get:Rule + val paparazzi = Paparazzi() + + @Suppress("LongMethod") + @Test + fun default(@TestParameter theme: Theme) { + paparazzi.snapshot { + VitaminTheme(theme == Theme.Dark) { + Scaffold { padding -> + Column( + modifier = Modifier.padding(padding), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text(text = variant.name, style = VitaminTheme.typography.subtitle1) + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + FabVariantsFactory.FabExtended( + variant = variant, + size = VitaminFabExtendedSizes.default() + ) + FabVariantsFactory.FabExtended( + variant = variant, + size = VitaminFabExtendedSizes.default(), + enabled = false + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + FabVariantsFactory.FabExtended( + icon = rememberVectorPainter(image = Icons.Filled.Add), + variant = variant, + size = VitaminFabExtendedSizes.default() + ) + FabVariantsFactory.FabExtended( + icon = rememberVectorPainter(image = Icons.Filled.Add), + variant = variant, + size = VitaminFabExtendedSizes.default(), + enabled = false + ) + } + } + } + } + } + } +} diff --git a/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/VitaminFabsPrimaryTest.kt b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/VitaminFabsPrimaryTest.kt new file mode 100644 index 00000000..da47be15 --- /dev/null +++ b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/VitaminFabsPrimaryTest.kt @@ -0,0 +1,93 @@ +package com.decathlon.vitamin.compose.fabs + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.unit.dp +import app.cash.paparazzi.Paparazzi +import com.decathlon.vitamin.compose.fabs.utils.FabVariantsFactory +import com.decathlon.vitamin.compose.fabs.utils.Theme +import com.decathlon.vitamin.compose.fabs.utils.Variant +import com.decathlon.vitamin.compose.foundation.VitaminTheme +import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(TestParameterInjector::class) +class VitaminFabsPrimaryTest( + @TestParameter val variant: Variant +) { + + @get:Rule + val paparazzi = Paparazzi() + + @Suppress("LongMethod") + @Test + fun default(@TestParameter theme: Theme) { + paparazzi.snapshot { + VitaminTheme(theme == Theme.Dark) { + Scaffold { padding -> + Column( + modifier = Modifier.padding(padding), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text(text = variant.name, style = VitaminTheme.typography.subtitle1) + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + FabVariantsFactory.Fab( + icon = rememberVectorPainter(image = Icons.Filled.Add), + variant = variant, + size = VitaminFabSizes.default() + ) + FabVariantsFactory.Fab( + icon = rememberVectorPainter(image = Icons.Filled.Add), + variant = variant, + size = VitaminFabSizes.default(), + enabled = false + ) + } + } + } + } + } + } + + @Suppress("LongMethod") + @Test + fun mini(@TestParameter theme: Theme) { + paparazzi.snapshot { + VitaminTheme(theme == Theme.Dark) { + Scaffold { padding -> + Column( + modifier = Modifier.padding(padding), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text(text = variant.name, style = VitaminTheme.typography.subtitle1) + Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + FabVariantsFactory.Fab( + icon = rememberVectorPainter(image = Icons.Filled.Add), + variant = variant, + size = VitaminFabSizes.mini() + ) + FabVariantsFactory.Fab( + icon = rememberVectorPainter(image = Icons.Filled.Add), + variant = variant, + size = VitaminFabSizes.mini(), + enabled = false + ) + } + } + } + } + } + } +} diff --git a/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/utils/FabVariantsFactory.kt b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/utils/FabVariantsFactory.kt new file mode 100644 index 00000000..eabbc9eb --- /dev/null +++ b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/utils/FabVariantsFactory.kt @@ -0,0 +1,58 @@ +package com.decathlon.vitamin.compose.fabs.utils + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import com.decathlon.vitamin.compose.fabs.FabExtendedSizes +import com.decathlon.vitamin.compose.fabs.FabSizes +import com.decathlon.vitamin.compose.fabs.VitaminFabs +import com.decathlon.vitamin.compose.fabs.VitaminFabsExtended + +object FabVariantsFactory { + + @Suppress("LongMethod") + @Composable + fun Fab( + icon: Painter, + variant: Variant, + size: FabSizes, + modifier: Modifier = Modifier, + enabled: Boolean = true + ) { + when (variant) { + Variant.Primary -> VitaminFabs.Primary( + icon = icon, + sizes = size, + enabled = enabled, + onClick = { + // Nothing to do here + }, + modifier = modifier + ) + } + } + + @Suppress("LongMethod") + @Composable + fun FabExtended( + modifier: Modifier = Modifier, + text: String = "Label", + icon: Painter? = null, + variant: Variant, + size: FabExtendedSizes, + enabled: Boolean = true + ) { + when (variant) { + Variant.Primary -> VitaminFabsExtended.Primary( + text = text, + icon = icon, + sizes = size, + enabled = enabled, + onClick = { + // Nothing to do here + }, + modifier = modifier + ) + } + } +} diff --git a/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/utils/Theme.kt b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/utils/Theme.kt new file mode 100644 index 00000000..a10ce55d --- /dev/null +++ b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/utils/Theme.kt @@ -0,0 +1,3 @@ +package com.decathlon.vitamin.compose.fabs.utils + +enum class Theme { Light, Dark } diff --git a/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/utils/Variant.kt b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/utils/Variant.kt new file mode 100644 index 00000000..e3fb88bf --- /dev/null +++ b/fabs/src/test/kotlin/com/decathlon/vitamin/compose/fabs/utils/Variant.kt @@ -0,0 +1,5 @@ +package com.decathlon.vitamin.compose.fabs.utils + +enum class Variant { + Primary +} diff --git a/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsExtendedPrimaryTest_default[Primary,Dark].png b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsExtendedPrimaryTest_default[Primary,Dark].png new file mode 100644 index 00000000..8fcd799c Binary files /dev/null and b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsExtendedPrimaryTest_default[Primary,Dark].png differ diff --git a/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsExtendedPrimaryTest_default[Primary,Light].png b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsExtendedPrimaryTest_default[Primary,Light].png new file mode 100644 index 00000000..350b4f0d Binary files /dev/null and b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsExtendedPrimaryTest_default[Primary,Light].png differ diff --git a/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_default[Primary,Dark].png b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_default[Primary,Dark].png new file mode 100644 index 00000000..d3055bef Binary files /dev/null and b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_default[Primary,Dark].png differ diff --git a/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_default[Primary,Light].png b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_default[Primary,Light].png new file mode 100644 index 00000000..9b51408a Binary files /dev/null and b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_default[Primary,Light].png differ diff --git a/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_mini[Primary,Dark].png b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_mini[Primary,Dark].png new file mode 100644 index 00000000..8499bb92 Binary files /dev/null and b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_mini[Primary,Dark].png differ diff --git a/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_mini[Primary,Light].png b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_mini[Primary,Light].png new file mode 100644 index 00000000..de27e374 Binary files /dev/null and b/fabs/src/test/snapshots/images/com.decathlon.vitamin.compose.fabs_VitaminFabsPrimaryTest_mini[Primary,Light].png differ diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 2eaf2674..68824558 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -69,6 +69,7 @@ dependencies { implementation(project(":snackbars")) implementation(project(":badges")) implementation(project(":quantity-pickers")) + implementation(project(":fabs")) implementation(AndroidX.appCompat) implementation(Google.Android.material) diff --git a/sample/src/main/java/com/decathlon/compose/sample/MainActivity.kt b/sample/src/main/java/com/decathlon/compose/sample/MainActivity.kt index a44a050c..5b6d52d9 100644 --- a/sample/src/main/java/com/decathlon/compose/sample/MainActivity.kt +++ b/sample/src/main/java/com/decathlon/compose/sample/MainActivity.kt @@ -14,6 +14,7 @@ import com.decathlon.compose.sample.screens.Badges import com.decathlon.compose.sample.screens.Buttons import com.decathlon.compose.sample.screens.Checkboxes import com.decathlon.compose.sample.screens.Dividers +import com.decathlon.compose.sample.screens.Fabs import com.decathlon.compose.sample.screens.Modals import com.decathlon.compose.sample.screens.Prices import com.decathlon.compose.sample.screens.Progress @@ -67,7 +68,8 @@ class MainActivity : AppCompatActivity() { Dividers, Snackbars, Badges, - QuantityPicker + QuantityPicker, + Fabs ) } NavHost(navController = navController, startDestination = "dashboard") { diff --git a/sample/src/main/java/com/decathlon/compose/sample/screens/Fabs.kt b/sample/src/main/java/com/decathlon/compose/sample/screens/Fabs.kt new file mode 100644 index 00000000..aca66b79 --- /dev/null +++ b/sample/src/main/java/com/decathlon/compose/sample/screens/Fabs.kt @@ -0,0 +1,98 @@ +package com.decathlon.compose.sample.screens + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.decathlon.compose.sample.components.SampleScaffold +import com.decathlon.vitamin.compose.fabs.VitaminFabSizes +import com.decathlon.vitamin.compose.fabs.VitaminFabs +import com.decathlon.vitamin.compose.fabs.VitaminFabsExtended +import com.decathlon.vitamin.compose.foundation.VitaminTheme + +object Fabs : Screen { + override val name: String + get() = "Floating Action Buttons" + + override val navigationKey: String + get() = "fabs" + + @SuppressWarnings("LongMethod") + @Composable + override fun Screen() { + SampleScaffold(title = name) { + Column(modifier = Modifier.fillMaxSize()) { + Text( + modifier = Modifier.padding(start = 16.dp, top = 16.dp), + text = "Regular fab", + style = VitaminTheme.typography.h6 + ) + + Row(modifier = Modifier.padding(start = 16.dp, top = 16.dp)) { + VitaminFabs.Primary( + modifier = Modifier.padding(end = 16.dp), + icon = rememberVectorPainter(image = Icons.Filled.Add) + ) {} + VitaminFabs.Primary( + icon = rememberVectorPainter(image = Icons.Filled.Add), + enabled = false + ) {} + } + + Text( + modifier = Modifier.padding(start = 16.dp, top = 32.dp), + text = "Mini fab", + style = VitaminTheme.typography.h6 + ) + + Row(modifier = Modifier.padding(start = 16.dp, top = 16.dp)) { + VitaminFabs.Primary( + modifier = Modifier.padding(end = 16.dp), + icon = rememberVectorPainter(image = Icons.Filled.Add), + sizes = VitaminFabSizes.mini() + ) {} + VitaminFabs.Primary( + icon = rememberVectorPainter(image = Icons.Filled.Add), + sizes = VitaminFabSizes.mini(), + enabled = false + ) {} + } + + Text( + modifier = Modifier.padding(start = 16.dp, top = 32.dp), + text = "Extended fab", + style = VitaminTheme.typography.h6 + ) + + Row(modifier = Modifier.padding(start = 16.dp, top = 16.dp)) { + VitaminFabsExtended.Primary( + modifier = Modifier.padding(end = 16.dp), + text = "Label", + icon = rememberVectorPainter(image = Icons.Filled.Add) + ) {} + VitaminFabsExtended.Primary( + text = "Label", + icon = rememberVectorPainter(image = Icons.Filled.Add), + enabled = false + ) {} + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun DefaultPreview() { + VitaminTheme { + Fabs.Screen() + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 651ed43c..996f847b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,5 +34,6 @@ include(":prices") include(":menus") include(":snackbars") include(":badges") +include(":fabs") include(":quantity-pickers") include(":vitamin")