diff --git a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/snackbar/PrezelSnackbar.kt b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/snackbar/PrezelSnackbar.kt new file mode 100644 index 0000000..51d087e --- /dev/null +++ b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/snackbar/PrezelSnackbar.kt @@ -0,0 +1,142 @@ +package com.team.prezel.core.designsystem.component.snackbar + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Icon +import androidx.compose.material3.Snackbar +import androidx.compose.material3.SnackbarData +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarVisuals +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.team.prezel.core.designsystem.component.button.PrezelButton +import com.team.prezel.core.designsystem.component.button.PrezelButtonSize +import com.team.prezel.core.designsystem.component.button.PrezelButtonStyle +import com.team.prezel.core.designsystem.component.button.PrezelButtonType +import com.team.prezel.core.designsystem.foundation.typography.PrezelTextStyles +import com.team.prezel.core.designsystem.icon.DrawableIcon +import com.team.prezel.core.designsystem.icon.IconSource +import com.team.prezel.core.designsystem.icon.PrezelIcons +import com.team.prezel.core.designsystem.preview.PreviewScaffold +import com.team.prezel.core.designsystem.preview.ThemePreview +import com.team.prezel.core.designsystem.theme.PrezelColorScheme +import com.team.prezel.core.designsystem.theme.PrezelTheme + +@Composable +fun PrezelSnackbar( + data: SnackbarData, + modifier: Modifier = Modifier, +) { + val visuals = data.visuals + val leadingIcon = visuals.leadingIconOrNull() + val actionLabel = visuals.actionLabel + + Snackbar( + modifier = modifier, + shape = PrezelTheme.shapes.V12, + containerColor = PrezelColorScheme.Dark.bgMedium, + contentColor = PrezelColorScheme.Dark.textLarge, + action = actionLabel?.let { label -> + { + PrezelButton( + text = label, + onClick = { data.performAction() }, + style = PrezelButtonStyle(buttonType = PrezelButtonType.GHOST, buttonSize = PrezelButtonSize.SMALL), + ) + } + }, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + leadingIcon?.let { + PrezelSnackbarLeadingIcon(icon = it) + Spacer(Modifier.width(PrezelTheme.spacing.V8)) + } + + Text( + text = visuals.message, + style = PrezelTextStyles.Body3Regular.toTextStyle(), + ) + } + } +} + +@Composable +private fun PrezelSnackbarLeadingIcon( + icon: IconSource, + modifier: Modifier = Modifier, +) { + Icon( + painter = icon.painter(), + contentDescription = icon.contentDescription(), + modifier = modifier.size(20.dp), + tint = PrezelColorScheme.Dark.iconLarge, + ) +} + +private fun SnackbarVisuals.leadingIconOrNull(): IconSource? = (this as? PrezelSnackbarVisuals)?.leadingIcon + +@ThemePreview +@Composable +private fun PrezelSnackBarPreview_Cases() { + PrezelTheme { + PreviewScaffold { + Text("Action O / Icon O") + PrezelSnackbar( + data = previewData(message = "Message", actionLabel = "Action", leadingIcon = DrawableIcon(PrezelIcons.Blank)), + ) + + Text("Action X / Icon O") + PrezelSnackbar( + data = previewData( + message = "Message Message Message ", + actionLabel = null, + leadingIcon = DrawableIcon(PrezelIcons.Blank), + ), + ) + + Text("Action O / Icon X") + PrezelSnackbar( + data = previewData(message = "Message Message Message Message Message", actionLabel = "Action"), + ) + + Text("Action X / Icon X") + PrezelSnackbar( + data = previewData(message = "Message Message Message Message Message", actionLabel = null), + ) + } + } +} + +@Composable +private fun previewData( + message: String, + actionLabel: String?, + leadingIcon: IconSource? = null, +): SnackbarData = + PreviewSnackbarData( + visuals = PrezelSnackbarVisuals( + message = message, + actionLabel = actionLabel, + withDismissAction = false, + duration = SnackbarDuration.Short, + leadingIcon = leadingIcon, + ), + ) + +@Suppress("EmptyFunctionBlock") +private class PreviewSnackbarData( + override val visuals: SnackbarVisuals, +) : SnackbarData { + override fun dismiss() {} + + override fun performAction() {} +} diff --git a/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/snackbar/SnackbarHost.kt b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/snackbar/SnackbarHost.kt new file mode 100644 index 0000000..3a9dfc9 --- /dev/null +++ b/Prezel/core/designsystem/src/main/java/com/team/prezel/core/designsystem/component/snackbar/SnackbarHost.kt @@ -0,0 +1,58 @@ +package com.team.prezel.core.designsystem.component.snackbar + +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.runtime.Composable +import androidx.compose.ui.Modifier +import com.team.prezel.core.designsystem.icon.IconSource + +internal data class PrezelSnackbarVisuals( + override val message: String, + override val actionLabel: String?, + override val withDismissAction: Boolean = false, + override val duration: SnackbarDuration, + val leadingIcon: IconSource?, +) : SnackbarVisuals + +suspend fun SnackbarHostState.showPrezelSnackbar( + message: String, + leadingIcon: IconSource? = null, + actionLabel: String? = null, + duration: SnackbarDuration = SnackbarDuration.Short, + onAction: (() -> Unit)? = null, + onDismiss: (() -> Unit)? = null, +) { + require((actionLabel == null) == (onAction == null)) { + "actionLabel과 onAction은 둘 다 있거나 둘 다 없어야 합니다." + } + + val result = showSnackbar( + visuals = PrezelSnackbarVisuals( + message = message, + actionLabel = actionLabel, + duration = duration, + leadingIcon = leadingIcon, + ), + ) + + when (result) { + SnackbarResult.ActionPerformed -> onAction?.invoke() + SnackbarResult.Dismissed -> onDismiss?.invoke() + } +} + +@Composable +fun PrezelSnackbarHost( + hostState: SnackbarHostState, + modifier: Modifier = Modifier, +) { + SnackbarHost( + hostState = hostState, + modifier = modifier, + ) { data -> + PrezelSnackbar(data = data) + } +}