Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package com.team.prezel.core.designsystem.component.snackbar

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.SnackbarData
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarVisuals
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
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.ThemePreview
import com.team.prezel.core.designsystem.theme.PrezelColorScheme
import com.team.prezel.core.designsystem.theme.PrezelTheme

@Immutable
internal object PrezelSnackbarDefaults {
val IconSize = 20.dp

val ContentPadding = PaddingValues(
start = 16.dp,
end = 8.dp,
top = 6.dp,
bottom = 6.dp,
)

val Shape: Shape = RoundedCornerShape(12.dp)

val IconMessageSpacing = 8.dp
val MessageActionSpacing = 16.dp

val ActionTouchPadding = PaddingValues(
horizontal = 12.dp,
vertical = 8.dp,
)
Comment thread
HamBeomJoon marked this conversation as resolved.
Outdated
}

@Composable
fun PrezelSnackbar(
data: SnackbarData,
modifier: Modifier = Modifier,
leadingIcon: IconSource? = null,
) {
Surface(
modifier = modifier,
shape = PrezelSnackbarDefaults.Shape,
color = PrezelColorScheme.Dark.bgMedium,
) {
Comment thread
HamBeomJoon marked this conversation as resolved.
Outdated
PrezelSnackbarContent(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snackbar(
    ...
    컨테이너 글자 아이콘 등 색상 정의
    ...
    content: @Composable () -> Unit
)

해당 컴포넌트를 사용해서 디자인만 입혀도 될 것 같은데 스낵바 뷰 자체를 구현하신 이유가 궁금해요

지금 보면 기존 스낵바에서 처리해주고 있는 로직들이 모두 포함되고 있지 않아 보여요

디자인 제약 상 위 컴포넌트를 사용하지 못한다면 기존 스낵바에서 처리해주는 로직을 모두 포함하고 있는지 검토 부탁드려요.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존 Material Snackbar는 레이아웃과 spacing이 강하게 고정되어 있어서 Prezel 디자인 요구사항(leading icon, spacing, touch 영역, minHeight)을 구현하기 힘들다 생각해서 따로 만들었습니다.

기존 스낵바에서 처리해주고 있는 로직

은 다른 리뷰에서말한 action 클릭 결과같은걸 말하는걸까요?

data = data,
leadingIcon = leadingIcon,
)
}
}

@Composable
fun PrezelSnackbarContent(
data: SnackbarData,
modifier: Modifier = Modifier,
leadingIcon: IconSource? = null,
) {
Row(
modifier = modifier
.fillMaxWidth()
.heightIn(min = 48.dp)
.padding(PrezelSnackbarDefaults.ContentPadding),
verticalAlignment = Alignment.CenterVertically,
) {
leadingIcon?.let {
PrezelSnackbarLeadingIcon(icon = it)
Spacer(Modifier.width(PrezelSnackbarDefaults.IconMessageSpacing))
}

Text(
text = data.visuals.message,
modifier = Modifier
.padding(top = 8.dp, bottom = 8.dp, end = 8.dp)
Comment thread
HamBeomJoon marked this conversation as resolved.
Outdated
.weight(1f),
color = PrezelColorScheme.Dark.textLarge,
style = PrezelTextStyles.Body3Regular.toTextStyle(),
)

data.visuals.actionLabel?.let { label ->
Spacer(Modifier.width(PrezelSnackbarDefaults.MessageActionSpacing))

Text(
text = label,
modifier = Modifier
.clickable(role = Role.Button, onClick = { data.performAction() })
.padding(PrezelSnackbarDefaults.ActionTouchPadding),
color = PrezelColorScheme.Dark.interactiveRegular,
style = PrezelTextStyles.Body3Medium.toTextStyle(),
)
}
Copy link
Copy Markdown
Member

@moondev03 moondev03 Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data.performAction()를 호출하지 않아도
SnackbarResult가 맞게 반환되나요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log찍어본 결과 onClick = { data.performAction() }으로 잘 반환됩니다

}
}

@Composable
private fun PrezelSnackbarLeadingIcon(
icon: IconSource,
modifier: Modifier = Modifier,
) {
Icon(
painter = icon.painter(),
contentDescription = icon.contentDescription(),
modifier = modifier.size(PrezelSnackbarDefaults.IconSize),
tint = PrezelColorScheme.Dark.iconLarge,
)
}

@ThemePreview
@Composable
private fun PrezelSnackBarPreview_Cases() {
Comment thread
HamBeomJoon marked this conversation as resolved.
PrezelTheme {
Column(
modifier = Modifier
.background(PrezelTheme.colors.bgRegular)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Comment thread
HamBeomJoon marked this conversation as resolved.
Outdated
Text("Action O / Icon O")
PrezelSnackbar(
data = previewData(message = "Message", actionLabel = "Action"),
leadingIcon = DrawableIcon(PrezelIcons.Blank),
modifier = Modifier.fillMaxWidth(),
)

Text("Action O / Icon X")
PrezelSnackbar(
data = previewData(message = "Message Message Message Message Message", actionLabel = "Action"),
leadingIcon = null,
modifier = Modifier.fillMaxWidth(),
)

Text("Action X / Icon O")
PrezelSnackbar(
data = previewData(message = "Message", actionLabel = null),
leadingIcon = DrawableIcon(PrezelIcons.Blank),
modifier = Modifier.fillMaxWidth(),
)

Text("Action X / Icon X")
PrezelSnackbar(
data = previewData(message = "Message Message Message Message Message", actionLabel = null),
leadingIcon = null,
modifier = Modifier.fillMaxWidth(),
)
}
}
}

@Composable
private fun previewData(
message: String,
actionLabel: String?,
): SnackbarData =
PreviewSnackbarData(
visuals = PreviewSnackbarVisuals(
message = message,
actionLabel = actionLabel,
),
)

private data class PreviewSnackbarVisuals(
override val message: String,
override val actionLabel: String? = null,
override val withDismissAction: Boolean = false,
override val duration: SnackbarDuration = SnackbarDuration.Short,
) : SnackbarVisuals

private class PreviewSnackbarData(
override val visuals: SnackbarVisuals,
) : SnackbarData {
override fun performAction() {}

override fun dismiss() {}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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.SnackbarVisuals
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.team.prezel.core.designsystem.icon.IconSource

data class PrezelSnackbarVisuals(
override val message: String,
override val actionLabel: String?,
override val withDismissAction: Boolean,
override val duration: SnackbarDuration,
val leadingIcon: IconSource?,
) : SnackbarVisuals

suspend fun SnackbarHostState.showPrezelSnackbar(
message: String,
leadingIcon: IconSource?,
actionLabel: String? = null,
withDismissAction: Boolean = false,
Comment thread
HamBeomJoon marked this conversation as resolved.
Outdated
duration: SnackbarDuration = SnackbarDuration.Short,
) {
Comment thread
HamBeomJoon marked this conversation as resolved.
showSnackbar(
visuals = PrezelSnackbarVisuals(
message = message,
actionLabel = actionLabel,
withDismissAction = withDismissAction,
duration = duration,
leadingIcon = leadingIcon,
),
)
}

@Composable
fun PrezelSnackbarHost(
hostState: SnackbarHostState,
modifier: Modifier = Modifier,
) {
SnackbarHost(
hostState = hostState,
modifier = modifier,
) { data ->
val visuals = data.visuals
val leadingIcon = (visuals as? PrezelSnackbarVisuals)?.leadingIcon
Comment thread
HamBeomJoon marked this conversation as resolved.
Outdated

PrezelSnackbar(
data = data,
leadingIcon = leadingIcon,
)
}
}
Loading