diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/BasicButton.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/BasicButton.kt index 80bac04b..fea811d9 100644 --- a/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/BasicButton.kt +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/BasicButton.kt @@ -1,6 +1,5 @@ package com.susu.core.designsystem.component.button -import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement @@ -10,7 +9,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -19,7 +17,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -31,7 +28,7 @@ import com.susu.core.ui.extension.susuClickable fun BasicButton( modifier: Modifier = Modifier, shape: Shape = RectangleShape, - text: String = "", + text: String? = null, textStyle: TextStyle = TextStyle.Default, contentColor: Color = Color.Unspecified, rippleColor: Color = Color.Unspecified, @@ -39,10 +36,8 @@ fun BasicButton( borderWidth: Dp = 0.dp, backgroundColor: Color = Color.Unspecified, padding: PaddingValues = PaddingValues(0.dp), - @DrawableRes leftIcon: Int? = null, - leftIconContentDescription: String? = null, - @DrawableRes rightIcon: Int? = null, - rightIconContentDescription: String? = null, + leftIcon: (@Composable () -> Unit)? = null, + rightIcon: (@Composable () -> Unit)? = null, iconSpacing: Dp = 0.dp, isActive: Boolean = true, onClick: () -> Unit = {}, @@ -66,33 +61,22 @@ fun BasicButton( ) { Row( modifier + .height(textStyle.lineHeight.value.dp) .wrapContentWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(iconSpacing), ) { - leftIcon?.let { - Icon( - modifier = Modifier.height(textStyle.lineHeight.value.dp), - painter = painterResource(id = it), - contentDescription = leftIconContentDescription, - tint = contentColor, - ) - } + leftIcon?.invoke() - Text( - text = text, - style = textStyle, - color = contentColor, - ) - - rightIcon?.let { - Icon( - modifier = Modifier.height(textStyle.lineHeight.value.dp), - painter = painterResource(id = it), - contentDescription = rightIconContentDescription, - tint = contentColor, + text?.let { + Text( + text = it, + style = textStyle, + color = contentColor, ) } + + rightIcon?.invoke() } } } diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuFilledButton.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuFilledButton.kt index 0d9e67e6..25028202 100644 --- a/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuFilledButton.kt +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuFilledButton.kt @@ -1,15 +1,17 @@ package com.susu.core.designsystem.component.button -import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.susu.core.designsystem.R @@ -47,13 +49,11 @@ enum class FilledButtonColor( fun SusuFilledButton( modifier: Modifier = Modifier, shape: Shape = RoundedCornerShape(4.dp), - text: String = "", + text: String? = null, color: FilledButtonColor, style: @Composable () -> ButtonStyle, - @DrawableRes leftIcon: Int? = null, - leftIconContentDescription: String? = null, - @DrawableRes rightIcon: Int? = null, - rightIconContentDescription: String? = null, + leftIcon: (@Composable () -> Unit)? = null, + rightIcon: (@Composable () -> Unit)? = null, isActive: Boolean = true, onClick: () -> Unit = {}, ) { @@ -68,9 +68,7 @@ fun SusuFilledButton( rippleColor = rippleColor, backgroundColor = if (isActive) activeBackgroundColor else inactiveBackgroundColor, leftIcon = leftIcon, - leftIconContentDescription = leftIconContentDescription, rightIcon = rightIcon, - rightIconContentDescription = rightIconContentDescription, padding = paddingValues, iconSpacing = iconSpacing, isActive = isActive, @@ -181,8 +179,22 @@ private fun SusuFilledButtonPreview( style = large, color = color, text = "Button", - rightIcon = R.drawable.ic_arrow_left, - leftIcon = R.drawable.ic_arrow_left, + leftIcon = { + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(id = R.drawable.ic_arrow_left), + contentDescription = "", + tint = color.activeContentColor, + ) + }, + rightIcon = { + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(id = R.drawable.ic_arrow_left), + contentDescription = "", + tint = color.activeContentColor, + ) + }, ) SusuFilledButton( diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuGhostButton.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuGhostButton.kt index b003883f..02f3bcf1 100644 --- a/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuGhostButton.kt +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuGhostButton.kt @@ -1,15 +1,17 @@ package com.susu.core.designsystem.component.button -import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.susu.core.designsystem.R @@ -47,13 +49,11 @@ enum class GhostButtonColor( fun SusuGhostButton( modifier: Modifier = Modifier, shape: Shape = RoundedCornerShape(4.dp), - text: String = "", + text: String? = null, color: GhostButtonColor, style: @Composable () -> ButtonStyle, - @DrawableRes leftIcon: Int? = null, - leftIconContentDescription: String? = null, - @DrawableRes rightIcon: Int? = null, - rightIconContentDescription: String? = null, + leftIcon: (@Composable () -> Unit)? = null, + rightIcon: (@Composable () -> Unit)? = null, isActive: Boolean = true, onClick: () -> Unit = {}, ) { @@ -68,9 +68,7 @@ fun SusuGhostButton( rippleColor = rippleColor, backgroundColor = if (isActive) activeBackgroundColor else inactiveBackgroundColor, leftIcon = leftIcon, - leftIconContentDescription = leftIconContentDescription, rightIcon = rightIcon, - rightIconContentDescription = rightIconContentDescription, padding = paddingValues, iconSpacing = iconSpacing, isActive = isActive, @@ -181,8 +179,22 @@ private fun SusuGhostButtonPreview( style = large, color = color, text = "Button", - rightIcon = R.drawable.ic_arrow_left, - leftIcon = R.drawable.ic_arrow_left, + leftIcon = { + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(id = R.drawable.ic_arrow_left), + contentDescription = "", + tint = color.activeContentColor, + ) + }, + rightIcon = { + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(id = R.drawable.ic_arrow_left), + contentDescription = "", + tint = color.activeContentColor, + ) + }, ) SusuGhostButton( diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuLinedButton.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuLinedButton.kt index 906b92ae..8bf8ef3f 100644 --- a/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuLinedButton.kt +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/component/button/SusuLinedButton.kt @@ -1,15 +1,17 @@ package com.susu.core.designsystem.component.button -import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.susu.core.designsystem.R @@ -54,13 +56,11 @@ enum class LinedButtonColor( fun SusuLinedButton( modifier: Modifier = Modifier, shape: Shape = RoundedCornerShape(4.dp), - text: String = "", + text: String? = null, color: LinedButtonColor, style: @Composable () -> ButtonStyle, - @DrawableRes leftIcon: Int? = null, - leftIconContentDescription: String? = null, - @DrawableRes rightIcon: Int? = null, - rightIconContentDescription: String? = null, + leftIcon: (@Composable () -> Unit)? = null, + rightIcon: (@Composable () -> Unit)? = null, isActive: Boolean = true, onClick: () -> Unit = {}, ) { @@ -77,9 +77,7 @@ fun SusuLinedButton( rippleColor = rippleColor, backgroundColor = if (isActive) activeBackgroundColor else inactiveBackgroundColor, leftIcon = leftIcon, - leftIconContentDescription = leftIconContentDescription, rightIcon = rightIcon, - rightIconContentDescription = rightIconContentDescription, padding = paddingValues, iconSpacing = iconSpacing, isActive = isActive, @@ -190,8 +188,22 @@ private fun SusuLinedButtonPreview( style = large, color = color, text = "Button", - rightIcon = R.drawable.ic_arrow_left, - leftIcon = R.drawable.ic_arrow_left, + leftIcon = { + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(id = R.drawable.ic_arrow_left), + contentDescription = "", + tint = color.activeContentColor, + ) + }, + rightIcon = { + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(id = R.drawable.ic_arrow_left), + contentDescription = "", + tint = color.activeContentColor, + ) + }, ) SusuLinedButton( diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/SusuTextFieldButton.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/SusuTextFieldButton.kt new file mode 100644 index 00000000..a9fb2336 --- /dev/null +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/SusuTextFieldButton.kt @@ -0,0 +1,426 @@ +package com.susu.core.designsystem.component.textfieldbutton + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.susu.core.designsystem.R +import com.susu.core.designsystem.component.textfieldbutton.style.InnerButtonStyle +import com.susu.core.designsystem.component.textfieldbutton.style.LargeTextFieldButtonStyle +import com.susu.core.designsystem.component.textfieldbutton.style.SmallTextFieldButtonStyle +import com.susu.core.designsystem.component.textfieldbutton.style.TextFieldButtonStyle +import com.susu.core.designsystem.theme.SusuTheme +import com.susu.core.ui.extension.disabledHorizontalPointerInputScroll +import com.susu.core.ui.extension.susuClickable + +@Composable +fun SusuTextFieldFillMaxButton( + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(4.dp), + text: String = "", + onTextChange: (String) -> Unit = {}, + placeholder: String = "", + maxLines: Int = 1, + minLines: Int = 1, + overflow: TextOverflow = TextOverflow.Clip, + style: @Composable () -> TextFieldButtonStyle, + color: TextFieldButtonColor = TextFieldButtonColor.Black, + isSaved: Boolean = false, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + showCloseIcon: Boolean = true, + showClearIcon: Boolean = true, + onClickClearIcon: () -> Unit = {}, + onClickCloseIcon: () -> Unit = {}, + onClickButton: () -> Unit = {}, +) { + val (backgroundColor, textColor) = with(color) { + if (isSaved) { + (savedBackgroundColor to savedTextColor) + } else { + (editBackgroundColor to editTextColor) + } + } + + with(style()) { + BasicTextField( + modifier = modifier + .fillMaxWidth(), + value = text, + onValueChange = onTextChange, + enabled = isSaved.not(), + singleLine = maxLines == 1, + maxLines = if (minLines > maxLines) minLines else maxLines, + minLines = minLines, + textStyle = textStyle.copy(textAlign = TextAlign.Center, color = textColor), + interactionSource = interactionSource, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + decorationBox = { innerTextField -> + Row( + modifier = Modifier + .clip(shape) + .background(backgroundColor) + .padding(paddingValues), + horizontalArrangement = Arrangement.spacedBy(iconSpacing), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.Center, + ) { + if (isSaved.not()) { + innerTextField() + } + + /** + * Problem : innerTextField 수정 중에 saved가 되면 underline이 생기는 현상. + * Solution : saved 상태에서는 Text 컴포저블 함수가 보이게 함. + */ + if (isSaved) { + Text( + text = text, + color = textColor, + style = textStyle, + textAlign = TextAlign.Center, + overflow = overflow, + maxLines = maxLines, + minLines = minLines, + ) + } + + if (text.isEmpty()) { + Text( + text = placeholder, + color = color.placeholderColor, + style = textStyle, + textAlign = TextAlign.Center, + overflow = overflow, + maxLines = maxLines, + minLines = minLines, + ) + } + } + + InnerButtons( + showCloseIcon = showCloseIcon, + showClearIcon = showClearIcon, + isSaved = isSaved, + isActive = text.isNotEmpty(), + color = color.buttonColor, + buttonStyle = innerButtonStyle, + clearIconSize = clearIconSize, + closeIconSize = closeIconSize, + onClickClearIcon = onClickClearIcon, + onClickCloseIcon = onClickCloseIcon, + onClickButton = onClickButton, + ) + } + }, + ) + } +} + +@Composable +fun SusuTextFieldWrapContentButton( + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(4.dp), + text: String = "", + onTextChange: (String) -> Unit = {}, + placeholder: String = "", + maxLines: Int = 1, + minLines: Int = 1, + overflow: TextOverflow = TextOverflow.Clip, + style: @Composable () -> TextFieldButtonStyle, + color: TextFieldButtonColor = TextFieldButtonColor.Black, + isSaved: Boolean = false, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + showCloseIcon: Boolean = true, + showClearIcon: Boolean = true, + onClickClearIcon: () -> Unit = {}, + onClickCloseIcon: () -> Unit = {}, + onClickButton: () -> Unit = {}, +) { + val (backgroundColor, textColor) = with(color) { + if (isSaved) { + (savedBackgroundColor to savedTextColor) + } else { + (editBackgroundColor to editTextColor) + } + } + + with(style()) { + Row( + modifier = modifier + .clip(shape) + .background(backgroundColor) + .padding(paddingValues), + horizontalArrangement = Arrangement.spacedBy(iconSpacing), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + contentAlignment = Alignment.Center, + ) { + BasicTextField( + modifier = Modifier + .disabledHorizontalPointerInputScroll() + .widthIn(min = 2.dp) + /** + * BasicTextField의 기본 width를 없애기 위해 IntrinsicSize.Min을 사용함. + * see -> https://stackoverflow.com/questions/67719981/resizeable-basictextfield-in-jetpack-compose + */ + .width(IntrinsicSize.Min), + value = text, + enabled = isSaved.not(), + onValueChange = onTextChange, + singleLine = maxLines == 1, + maxLines = if (minLines > maxLines) minLines else maxLines, + minLines = minLines, + textStyle = textStyle + .copy(color = Color.Transparent), + interactionSource = interactionSource, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + decorationBox = { innerTextField -> + /** + * Problem : 만약 커서가 맨 끝으로 가게 된다면 innerTextField의 텍스트가 짤리는 현상 발생. + * Why : IntrinsicSize.Min을 사용하게 되면 innerTextField의 width는 텍스트의 너비 만큼만 설정이 되었기 때문. (커서의 너비는 포함되지 않음.) + * Solution : + * innerTextField의 color를 Transparent로 설정해 텍스트가 짤리는 현상을 안보이게 함. + * 눈에 보이는 텍스트는 Text 컴포저블 함수로 보이게 함. + */ + Text( + text = text, + color = textColor, + style = textStyle, + overflow = overflow, + maxLines = maxLines, + minLines = minLines, + ) + innerTextField() + }, + ) + + if (text.isEmpty()) { + Text( + text = placeholder, + color = color.placeholderColor, + style = textStyle, + textAlign = TextAlign.Center, + overflow = overflow, + maxLines = maxLines, + minLines = minLines, + ) + } + } + + InnerButtons( + showCloseIcon = showCloseIcon, + showClearIcon = showClearIcon, + isSaved = isSaved, + isActive = text.isNotEmpty(), + color = color.buttonColor, + buttonStyle = innerButtonStyle, + clearIconSize = clearIconSize, + closeIconSize = closeIconSize, + onClickClearIcon = onClickClearIcon, + onClickCloseIcon = onClickCloseIcon, + onClickButton = onClickButton, + ) + } + } +} + +@Composable +private fun InnerButtons( + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(4.dp), + isSaved: Boolean, + isActive: Boolean, + showCloseIcon: Boolean = true, + showClearIcon: Boolean = true, + color: TextButtonInnerButtonColor, + clearIconSize: Dp = 0.dp, + closeIconSize: Dp = 0.dp, + buttonStyle: @Composable () -> InnerButtonStyle, + onClickClearIcon: () -> Unit = {}, + onClickCloseIcon: () -> Unit = {}, + onClickButton: () -> Unit = {}, +) { + val (innerButtonTextColor, innerButtonBackgroundColor) = with(color) { + if (isActive || isSaved) { + (activeContentColor to activeBackgroundColor) + } else { + (inactiveContentColor to inactiveBackgroundColor) + } + } + + if (isSaved.not()) { + Box(modifier = Modifier.size(clearIconSize)) { + if (showClearIcon) { + Image( + modifier = Modifier + .clip(CircleShape) + .size(clearIconSize) + .susuClickable(onClick = onClickClearIcon), + painter = painterResource(id = R.drawable.ic_clear), + contentDescription = "", + ) + } + } + } + + if (isSaved && showCloseIcon.not()) { + Box(modifier = Modifier.size(closeIconSize)) + } + + with(buttonStyle()) { + Box( + modifier = modifier + .clip(shape) + .background(innerButtonBackgroundColor) + .susuClickable( + runIf = isActive || isSaved, + rippleColor = color.rippleColor, + onClick = onClickButton, + ) + .padding(paddingValues), + ) { + Text( + text = if (isSaved) stringResource(R.string.word_edit) else stringResource(R.string.word_save), + style = textStyle, + color = innerButtonTextColor, + ) + } + } + + if (isSaved && showCloseIcon) { + Image( + modifier = Modifier + .clip(CircleShape) + .size(closeIconSize) + .susuClickable(onClick = onClickCloseIcon), + painter = painterResource(id = R.drawable.ic_close), + contentDescription = "", + ) + } +} + +@Preview(showBackground = true, backgroundColor = 0x000000) +@Composable +fun TextFieldButtonPreview() { + SusuTheme { + var text by remember { + mutableStateOf("Button") + } + + var isSaved by remember { + mutableStateOf(false) + } + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Text(text = "텍스트 길이에 딱 맞는 너비 (wrap)") + SusuTextFieldWrapContentButton( + text = text, + onTextChange = { text = it }, + placeholder = "", + maxLines = 1, + minLines = 1, + showClearIcon = true, + style = LargeTextFieldButtonStyle.height46, + onClickButton = { isSaved = isSaved.not() }, + onClickClearIcon = { text = "" }, + isSaved = isSaved, + ) + + HorizontalDivider() + + Text(text = "최대 너비 (fillMaxWidth)") + SusuTextFieldFillMaxButton( + text = text, + onTextChange = { text = it }, + placeholder = "Button", + maxLines = 1, + minLines = 1, + style = LargeTextFieldButtonStyle.height46, + onClickButton = { isSaved = isSaved.not() }, + onClickClearIcon = { text = "" }, + isSaved = isSaved, + ) + + HorizontalDivider() + + Text(text = "텍스트가 없는 경우 : placeHolder 길이에 딱 맞는 너비\n텍스트가 있는 경우 : 텍스트가 차지하는 너비") + SusuTextFieldWrapContentButton( + text = text, + onTextChange = { text = it }, + placeholder = "Button", + maxLines = 1, + minLines = 1, + showClearIcon = false, + showCloseIcon = false, + color = TextFieldButtonColor.Orange, + style = LargeTextFieldButtonStyle.height46, + onClickButton = { isSaved = isSaved.not() }, + onClickClearIcon = { text = "" }, + isSaved = isSaved, + ) + + HorizontalDivider() + + Text(text = "고정된 너비 : 200dp") + SusuTextFieldFillMaxButton( + modifier = Modifier.width(200.dp), + text = text, + overflow = TextOverflow.Ellipsis, + onTextChange = { text = it }, + placeholder = "Button", + maxLines = 1, + minLines = 1, + color = TextFieldButtonColor.Orange, + style = SmallTextFieldButtonStyle.height32, + onClickButton = { isSaved = isSaved.not() }, + onClickClearIcon = { text = "" }, + isSaved = isSaved, + ) + } + } +} diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/TextFieldButtonColor.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/TextFieldButtonColor.kt new file mode 100644 index 00000000..0426a23f --- /dev/null +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/TextFieldButtonColor.kt @@ -0,0 +1,49 @@ +package com.susu.core.designsystem.component.textfieldbutton + +import androidx.compose.ui.graphics.Color +import com.susu.core.designsystem.theme.Gray10 +import com.susu.core.designsystem.theme.Gray100 +import com.susu.core.designsystem.theme.Gray30 +import com.susu.core.designsystem.theme.Orange60 + +enum class TextFieldButtonColor( + val buttonColor: TextButtonInnerButtonColor, + val savedBackgroundColor: Color, + val editBackgroundColor: Color, + val editTextColor: Color, + val savedTextColor: Color, + val placeholderColor: Color, +) { + Black( + buttonColor = TextButtonInnerButtonColor.Black, + savedBackgroundColor = Gray10, + editBackgroundColor = Gray10, + editTextColor = Gray100, + savedTextColor = Gray100, + placeholderColor = Gray30, + ), + Orange( + buttonColor = TextButtonInnerButtonColor.Black, + savedBackgroundColor = Orange60, + editBackgroundColor = Gray10, + editTextColor = Gray100, + savedTextColor = Gray10, + placeholderColor = Gray30, + ), +} + +enum class TextButtonInnerButtonColor( + val activeContentColor: Color, + val inactiveContentColor: Color, + val activeBackgroundColor: Color, + val inactiveBackgroundColor: Color, + val rippleColor: Color, +) { + Black( + activeContentColor = Gray10, + inactiveContentColor = Gray10, + activeBackgroundColor = Gray100, + inactiveBackgroundColor = Gray30, + rippleColor = Gray10, + ), +} diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/style/InnerButtonStyle.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/style/InnerButtonStyle.kt new file mode 100644 index 00000000..63c8c69b --- /dev/null +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/style/InnerButtonStyle.kt @@ -0,0 +1,44 @@ +package com.susu.core.designsystem.component.textfieldbutton.style + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import com.susu.core.designsystem.theme.SusuTheme + +data class InnerButtonStyle( + val paddingValues: PaddingValues = PaddingValues(0.dp), + val textStyle: TextStyle = TextStyle.Default, +) + +object XSmallInnerButtonStyle { + val height28: @Composable () -> InnerButtonStyle = { + InnerButtonStyle( + paddingValues = PaddingValues( + horizontal = SusuTheme.spacing.spacing_xxs, + vertical = SusuTheme.spacing.spacing_xxxxs, + ), + textStyle = SusuTheme.typography.title_xxxs, + ) + } + + val height24: @Composable () -> InnerButtonStyle = { + InnerButtonStyle( + paddingValues = PaddingValues( + horizontal = SusuTheme.spacing.spacing_xxs, + vertical = SusuTheme.spacing.spacing_xxxxxs, + ), + textStyle = SusuTheme.typography.title_xxxs, + ) + } + + val height20: @Composable () -> InnerButtonStyle = { + InnerButtonStyle( + paddingValues = PaddingValues( + horizontal = SusuTheme.spacing.spacing_xxs, + vertical = SusuTheme.spacing.spacing_xxxxxs, + ), + textStyle = SusuTheme.typography.title_xxxxs, + ) + } +} diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/style/TextFieldButtonStyle.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/style/TextFieldButtonStyle.kt new file mode 100644 index 00000000..eb9147c7 --- /dev/null +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/component/textfieldbutton/style/TextFieldButtonStyle.kt @@ -0,0 +1,221 @@ +package com.susu.core.designsystem.component.textfieldbutton.style + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.susu.core.designsystem.theme.SusuTheme + +data class TextFieldButtonStyle( + val paddingValues: PaddingValues = PaddingValues(0.dp), + val iconSpacing: Dp = 0.dp, + val textStyle: TextStyle = TextStyle.Default, + val innerButtonStyle: @Composable () -> InnerButtonStyle, + val clearIconSize: Dp = 0.dp, + val closeIconSize: Dp = 0.dp, +) + +object LargeTextFieldButtonStyle { + + val height62: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_xl, + end = SusuTheme.spacing.spacing_m, + top = SusuTheme.spacing.spacing_m, + bottom = SusuTheme.spacing.spacing_m, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_s, + innerButtonStyle = XSmallInnerButtonStyle.height28, + clearIconSize = 24.dp, + closeIconSize = 24.dp, + ) + } + + val height54: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_l, + end = SusuTheme.spacing.spacing_xm, + top = SusuTheme.spacing.spacing_s, + bottom = SusuTheme.spacing.spacing_s, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_s, + innerButtonStyle = XSmallInnerButtonStyle.height28, + clearIconSize = 24.dp, + closeIconSize = 24.dp, + ) + } + + val height46: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_m, + end = SusuTheme.spacing.spacing_s, + top = SusuTheme.spacing.spacing_xs, + bottom = SusuTheme.spacing.spacing_xs, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_s, + innerButtonStyle = XSmallInnerButtonStyle.height28, + clearIconSize = 24.dp, + closeIconSize = 24.dp, + ) + } +} + +object MediumTextFieldButtonStyle { + + val height60: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_xl, + end = SusuTheme.spacing.spacing_m, + top = SusuTheme.spacing.spacing_m, + bottom = SusuTheme.spacing.spacing_m, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_xs, + innerButtonStyle = XSmallInnerButtonStyle.height28, + clearIconSize = 24.dp, + closeIconSize = 24.dp, + ) + } + + val height52: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_l, + end = SusuTheme.spacing.spacing_xm, + top = SusuTheme.spacing.spacing_s, + bottom = SusuTheme.spacing.spacing_s, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_xs, + innerButtonStyle = XSmallInnerButtonStyle.height28, + clearIconSize = 24.dp, + closeIconSize = 24.dp, + ) + } + + val height44: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_m, + end = SusuTheme.spacing.spacing_s, + top = SusuTheme.spacing.spacing_xxs, + bottom = SusuTheme.spacing.spacing_xxs, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_xs, + innerButtonStyle = XSmallInnerButtonStyle.height24, + clearIconSize = 20.dp, + closeIconSize = 20.dp, + ) + } +} + +object SmallTextFieldButtonStyle { + + val height48: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_m, + end = SusuTheme.spacing.spacing_s, + top = SusuTheme.spacing.spacing_s, + bottom = SusuTheme.spacing.spacing_s, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_xxs, + innerButtonStyle = XSmallInnerButtonStyle.height24, + clearIconSize = 20.dp, + closeIconSize = 20.dp, + ) + } + + val height40: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_s, + end = SusuTheme.spacing.spacing_xxs, + top = SusuTheme.spacing.spacing_xxs, + bottom = SusuTheme.spacing.spacing_xxs, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_xxs, + innerButtonStyle = XSmallInnerButtonStyle.height24, + clearIconSize = 20.dp, + closeIconSize = 20.dp, + ) + } + + val height32: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_xxs, + end = SusuTheme.spacing.spacing_xxs, + top = SusuTheme.spacing.spacing_xxxxs, + bottom = SusuTheme.spacing.spacing_xxxxs, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_xxs, + innerButtonStyle = XSmallInnerButtonStyle.height20, + clearIconSize = 16.dp, + closeIconSize = 20.dp, + ) + } +} + +object XSmallTextFieldButtonStyle { + + val height44: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_m, + end = SusuTheme.spacing.spacing_m, + top = SusuTheme.spacing.spacing_s, + bottom = SusuTheme.spacing.spacing_s, + ), + iconSpacing = SusuTheme.spacing.spacing_xxs, + textStyle = SusuTheme.typography.title_xxxs, + innerButtonStyle = XSmallInnerButtonStyle.height20, + clearIconSize = 20.dp, + closeIconSize = 20.dp, + ) + } + + val height36: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_xxs, + end = SusuTheme.spacing.spacing_xxs, + top = SusuTheme.spacing.spacing_xxs, + bottom = SusuTheme.spacing.spacing_xxs, + ), + iconSpacing = SusuTheme.spacing.spacing_xxxs, + textStyle = SusuTheme.typography.title_xxxs, + innerButtonStyle = XSmallInnerButtonStyle.height20, + clearIconSize = 16.dp, + closeIconSize = 16.dp, + ) + } + + val height28: @Composable () -> TextFieldButtonStyle = { + TextFieldButtonStyle( + paddingValues = PaddingValues( + start = SusuTheme.spacing.spacing_xxs, + end = SusuTheme.spacing.spacing_xxxxs, + top = SusuTheme.spacing.spacing_xxxxs, + bottom = SusuTheme.spacing.spacing_xxxxs, + ), + iconSpacing = SusuTheme.spacing.spacing_xxxxs, + textStyle = SusuTheme.typography.title_xxxs, + innerButtonStyle = XSmallInnerButtonStyle.height20, + clearIconSize = 16.dp, + closeIconSize = 16.dp, + ) + } +} diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/theme/Spacing.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/theme/Spacing.kt index 3851b804..0b09b85f 100644 --- a/core/designsystem/src/main/java/com/susu/core/designsystem/theme/Spacing.kt +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/theme/Spacing.kt @@ -12,6 +12,7 @@ internal val Spacing = SusuSpacing( spacing_xxs = 8.dp, spacing_xs = 10.dp, spacing_s = 12.dp, + spacing_xm = 14.dp, spacing_m = 16.dp, spacing_l = 20.dp, spacing_xl = 24.dp, @@ -30,6 +31,7 @@ data class SusuSpacing( val spacing_xs: Dp, val spacing_s: Dp, val spacing_m: Dp, + val spacing_xm: Dp, val spacing_l: Dp, val spacing_xl: Dp, val spacing_xxl: Dp, @@ -46,6 +48,7 @@ val LocalSpacing = staticCompositionLocalOf { spacing_xs = Dp.Unspecified, spacing_s = Dp.Unspecified, spacing_m = Dp.Unspecified, + spacing_xm = Dp.Unspecified, spacing_l = Dp.Unspecified, spacing_xl = Dp.Unspecified, spacing_xxl = Dp.Unspecified, diff --git a/core/designsystem/src/main/java/com/susu/core/designsystem/theme/Theme.kt b/core/designsystem/src/main/java/com/susu/core/designsystem/theme/Theme.kt index 4ea4783d..57520bba 100644 --- a/core/designsystem/src/main/java/com/susu/core/designsystem/theme/Theme.kt +++ b/core/designsystem/src/main/java/com/susu/core/designsystem/theme/Theme.kt @@ -48,6 +48,8 @@ object SusuTheme { * * spacing_s = 12.dp, * + * spacing_xm = 14.dp, + * * spacing_m = 16.dp, * * spacing_l = 20.dp, diff --git a/core/designsystem/src/main/res/drawable/ic_clear.xml b/core/designsystem/src/main/res/drawable/ic_clear.xml new file mode 100644 index 00000000..fef3a2ae --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_clear.xml @@ -0,0 +1,12 @@ + + + + diff --git a/core/designsystem/src/main/res/drawable/ic_close.xml b/core/designsystem/src/main/res/drawable/ic_close.xml new file mode 100644 index 00000000..bb21304f --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml new file mode 100644 index 00000000..a0872b86 --- /dev/null +++ b/core/designsystem/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + 편집 + 저장 + diff --git a/core/ui/src/main/java/com/susu/core/ui/extension/Modifier.kt b/core/ui/src/main/java/com/susu/core/ui/extension/Modifier.kt index d0de0994..90e1690a 100644 --- a/core/ui/src/main/java/com/susu/core/ui/extension/Modifier.kt +++ b/core/ui/src/main/java/com/susu/core/ui/extension/Modifier.kt @@ -7,7 +7,12 @@ import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.unit.Velocity import com.susu.core.ui.util.MultipleEventsCutter import com.susu.core.ui.util.get @@ -56,3 +61,12 @@ fun Modifier.susuClickable( ) } } + +// https://stackoverflow.com/questions/73309395/strange-scroll-in-the-basictextfield-with-intrinsicsize-min +fun Modifier.disabledHorizontalPointerInputScroll(disabled: Boolean = true) = + if (disabled) this.nestedScroll(HorizontalScrollConsumer) else this + +private val HorizontalScrollConsumer = object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource) = available.copy(y = 0f) + override suspend fun onPreFling(available: Velocity) = available.copy(y = 0f) +}