All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.github.lyxnx.compose.pine.Snackbar.kt Maven / Gradle / Ivy

There is a newer version: 1.2.2
Show newest version
package io.github.lyxnx.compose.pine

import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarData
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.SnackbarVisuals
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import compose.icons.TablerIcons
import compose.icons.tablericons.Outline
import compose.icons.tablericons.outline.X
import androidx.compose.material3.Snackbar as MaterialSnackbar
import androidx.compose.material3.SnackbarHost as MaterialSnackbarHost

/**
 * A predefined set of snackbar types for use in the Pine theme
 */
public enum class SnackbarType {
    /**
     * Represents a successful snackbar
     */
    Success,

    /**
     * Represents a snackbar with a warning message
     */
    Warning,

    /**
     * Represents a snackbar with an error
     */
    Error,

    /**
     * Represents a snackbar with some information
     */
    Info,

    /**
     * The default, unstyled, snackbar
     */
    Default
}

/**
 * A set of [SnackbarVisuals] for use with a [Snackbar]
 */
public data class PineSnackbarVisuals(
    override val message: String,
    override val actionLabel: String?,
    override val duration: SnackbarDuration,
    override val withDismissAction: Boolean,
    val type: SnackbarType,
) : SnackbarVisuals

/**
 * Shows or queues a snackbar using [PineSnackbarVisuals]
 *
 * @param message text to be shown in the snackbar
 * @param type the optional [SnackbarType] this snackbar represents
 * @param actionLabel optional action label to show as button in the snackbar
 * @param withDismissAction a boolean to show a dismiss action in the snackbar. This is recommended to be set to true
 * for better accessibility when a snackbar is set with a [SnackbarDuration.Indefinite]
 * @param duration duration to control how long snackbar will be shown in [SnackbarHost], either
 * [SnackbarDuration.Short], [SnackbarDuration.Long] or [SnackbarDuration.Indefinite]
 * @return [SnackbarResult.ActionPerformed] if option action has been clicked or [SnackbarResult.Dismissed] if snackbar
 * has been dismissed via timeout or by the user
 *
 * @see [SnackbarHostState.showSnackbar]
 */
public suspend fun SnackbarHostState.showSnackbar(
    message: String,
    type: SnackbarType = SnackbarType.Default,
    actionLabel: String? = null,
    withDismissAction: Boolean = false,
    duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite
): SnackbarResult = showSnackbar(PineSnackbarVisuals(message, actionLabel, duration, withDismissAction, type))

/**
 * A convenience function for creating a [SnackbarHost][MaterialSnackbarHost] that customizes the snackbars to
 * be Pine themed [Snackbar]s
 *
 * This version should be used over the material one as this will configure the snackbar in Pine styling automatically
 *
 * @param hostState state of this component to read and show [Snackbar]s accordingly
 * @param modifier the modifier to be applied to this component
 * @param snackbar the snackbar to be shown at the right time
 */
@Composable
public fun SnackbarHost(
    hostState: SnackbarHostState,
    modifier: Modifier = Modifier,
    snackbar: @Composable (SnackbarData) -> Unit = { data ->
        val type = (data.visuals as? PineSnackbarVisuals)?.type

        Snackbar(
            snackbarData = data,
            type = type ?: SnackbarType.Default
        )
    }
) {
    MaterialSnackbarHost(
        hostState = hostState,
        modifier = modifier,
        snackbar = snackbar
    )
}

/**
 * A Pine theme snackbar
 *
 * @param snackbarData data about the current snackbar showing via [SnackbarHostState]
 * @param type the type of snackbar - the colors will be decided from [SnackbarDefaults.snackbarColors]. Use the other
 * overload for custom colors
 * @param modifier modifier to apply to the snackbar
 * @param actionOnNewLine whether to place the main action and dismiss action on a separate line to the message
 * @param shape the shape of the snackbar
 */
@Composable
public fun Snackbar(
    snackbarData: SnackbarData,
    type: SnackbarType,
    modifier: Modifier = Modifier,
    actionOnNewLine: Boolean = false,
    shape: Shape = SnackbarDefaults.Shape
) {
    Snackbar(
        snackbarData = snackbarData,
        colors = SnackbarDefaults.snackbarColors(type),
        modifier = modifier,
        actionOnNewLine = actionOnNewLine,
        shape = shape
    )
}

/**
 * A Pine theme snackbar
 *
 * @param snackbarData data about the current snackbar showing via [SnackbarHostState]
 * @param colors the colors to apply to the snackbar
 * @param modifier modifier to apply to the snackbar
 * @param actionOnNewLine whether to place the main action and dismiss action on a separate line to the message
 * @param shape the shape of the snackbar
 */
@Composable
public fun Snackbar(
    snackbarData: SnackbarData,
    colors: SnackbarColors,
    modifier: Modifier = Modifier,
    actionOnNewLine: Boolean = false,
    shape: Shape = SnackbarDefaults.Shape,
) {
    // Copied from M3 to override text styles
    val actionLabel = snackbarData.visuals.actionLabel
    val actionComposable: (@Composable () -> Unit)? = if (actionLabel != null) {
        @Composable {
            TextButton(
                text = actionLabel,
                textStyle = PineTheme.typography.bodySmall.copy(fontWeight = FontWeight.Medium),
                onClick = { snackbarData.performAction() }
            )
        }
    } else {
        null
    }

    val dismissActionComposable: (@Composable () -> Unit)? =
        if (snackbarData.visuals.withDismissAction) {
            @Composable {
                IconButton(
                    onClick = { snackbarData.dismiss() },
                    content = {
                        Icon(
                            imageVector = TablerIcons.Outline.X,
                            contentDescription = null,
                        )
                    }
                )
            }
        } else {
            null
        }

    MaterialSnackbar(
        modifier = modifier.padding(12.dp),
        action = actionComposable,
        dismissAction = dismissActionComposable,
        actionOnNewLine = actionOnNewLine,
        shape = shape,
        containerColor = colors.containerColor,
        contentColor = colors.messageColor,
        actionContentColor = colors.actionColor,
        dismissActionContentColor = colors.dismissActionColor,
        content = { Text(snackbarData.visuals.message, style = PineTheme.typography.bodySmall) }
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy