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)
+}