diff --git a/.idea/other.xml b/.idea/other.xml
index 4604c446..94c96f63 100644
--- a/.idea/other.xml
+++ b/.idea/other.xml
@@ -69,6 +69,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -91,6 +113,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -102,6 +135,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -146,6 +190,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -235,6 +290,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/kotlin/com/yourssu/handy/demo/ButtonPreview.kt b/app/src/main/kotlin/com/yourssu/handy/demo/ButtonPreview.kt
new file mode 100644
index 00000000..74929549
--- /dev/null
+++ b/app/src/main/kotlin/com/yourssu/handy/demo/ButtonPreview.kt
@@ -0,0 +1,462 @@
+package com.yourssu.handy.demo
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color.Companion.White
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.yourssu.handy.compose.HandyTheme
+import com.yourssu.handy.compose.button.BoxButton
+import com.yourssu.handy.compose.button.BoxButtonSize
+import com.yourssu.handy.compose.button.BoxButtonType
+import com.yourssu.handy.compose.button.TextButton
+import com.yourssu.handy.compose.button.TextButtonSize
+import com.yourssu.handy.compose.button.TextButtonType
+import com.yourssu.handy.compose.icons.HandyIcons
+import com.yourssu.handy.compose.icons.line.Add
+import com.yourssu.handy.compose.icons.line.ExternalLink
+
+@Preview
+@Composable
+private fun BoxButtonPreview() {
+ HandyTheme {
+ Column(
+ modifier = Modifier.background(White),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+
+ BoxButton(
+ onClick = {},
+ text = "isDisabled",
+ enabled = false,
+ sizeType = BoxButtonSize.XL,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "XL Button",
+ sizeType = BoxButtonSize.XL,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "L Button",
+ sizeType = BoxButtonSize.L,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.M,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "S Button",
+ sizeType = BoxButtonSize.S,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "XS Button",
+ sizeType = BoxButtonSize.XS,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "XXS Button",
+ sizeType = BoxButtonSize.XXS,
+ )
+ }
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+
+ ) {
+ BoxButton(
+ onClick = {},
+ buttonType = BoxButtonType.Secondary,
+ text = "isDisabled",
+ enabled = false,
+ sizeType = BoxButtonSize.XL,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "XL Button",
+ buttonType = BoxButtonType.Secondary,
+ sizeType = BoxButtonSize.XL,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "L Button",
+ buttonType = BoxButtonType.Secondary,
+ sizeType = BoxButtonSize.L,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ buttonType = BoxButtonType.Secondary,
+ sizeType = BoxButtonSize.M,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "S Button",
+ buttonType = BoxButtonType.Secondary,
+ sizeType = BoxButtonSize.S,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "XS Button",
+ buttonType = BoxButtonType.Secondary,
+ sizeType = BoxButtonSize.XS,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "XXS Button",
+ buttonType = BoxButtonType.Secondary,
+ sizeType = BoxButtonSize.XXS,
+ )
+ }
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ BoxButton(
+ onClick = {},
+ buttonType = BoxButtonType.Tertiary,
+ text = "isDisabled",
+ enabled = false,
+ sizeType = BoxButtonSize.XL,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "XL Button",
+ buttonType = BoxButtonType.Tertiary,
+ sizeType = BoxButtonSize.XL,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "L Button",
+ buttonType = BoxButtonType.Tertiary,
+ sizeType = BoxButtonSize.L,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ buttonType = BoxButtonType.Tertiary,
+ sizeType = BoxButtonSize.M,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "S Button",
+ buttonType = BoxButtonType.Tertiary,
+ sizeType = BoxButtonSize.S,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "XS Button",
+ buttonType = BoxButtonType.Tertiary,
+ sizeType = BoxButtonSize.XS,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "XXS Button",
+ buttonType = BoxButtonType.Tertiary,
+ sizeType = BoxButtonSize.XXS,
+ )
+ }
+ }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ leftIcon = HandyIcons.Line.Add,
+ enabled = false,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ rightIcon = HandyIcons.Line.Add,
+ enabled = false,
+ )
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ leftIcon = HandyIcons.Line.Add,
+ rightIcon = HandyIcons.Line.Add,
+ enabled = false,
+ )
+ }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ leftIcon = HandyIcons.Line.Add,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ rightIcon = HandyIcons.Line.Add,
+ )
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ leftIcon = HandyIcons.Line.Add,
+ rightIcon = HandyIcons.Line.Add,
+ )
+ }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ buttonType = BoxButtonType.Secondary,
+ leftIcon = HandyIcons.Line.Add,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ buttonType = BoxButtonType.Secondary,
+ rightIcon = HandyIcons.Line.Add,
+ )
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ buttonType = BoxButtonType.Secondary,
+ leftIcon = HandyIcons.Line.Add,
+ rightIcon = HandyIcons.Line.Add,
+ )
+ }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ){
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ buttonType = BoxButtonType.Tertiary,
+ leftIcon = HandyIcons.Line.Add,
+ )
+
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ buttonType = BoxButtonType.Tertiary,
+ rightIcon = HandyIcons.Line.Add,
+ )
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.S,
+ buttonType = BoxButtonType.Tertiary,
+ leftIcon = HandyIcons.Line.Add,
+ rightIcon = HandyIcons.Line.Add,
+ )
+ }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ BoxButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = BoxButtonSize.M,
+ leftIcon = HandyIcons.Line.Add,
+ horizontalPadding = 100.dp
+ )
+ }
+ }
+ }
+}
+
+@Composable
+@Preview
+fun TextButtonPreview() {
+ HandyTheme {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+
+ ) {
+ TextButton(
+ onClick = {},
+ text = "L Button",
+ enabled = false,
+ sizeType = TextButtonSize.L,
+ buttonType = TextButtonType.Primary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+ TextButton(
+ onClick = {},
+ text = "L Button",
+ sizeType = TextButtonSize.L,
+ buttonType = TextButtonType.Primary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Primary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+ TextButton(
+ onClick = {},
+ text = "S Button",
+ sizeType = TextButtonSize.S,
+ buttonType = TextButtonType.Primary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+ }
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ TextButton(
+ onClick = {},
+ text = "L Button",
+ enabled = false,
+ sizeType = TextButtonSize.L,
+ buttonType = TextButtonType.Secondary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+ TextButton(
+ onClick = {},
+ text = "L Button",
+ sizeType = TextButtonSize.L,
+ buttonType = TextButtonType.Secondary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Secondary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+ TextButton(
+ onClick = {},
+ text = "S Button",
+ sizeType = TextButtonSize.S,
+ buttonType = TextButtonType.Secondary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+ }
+ }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ){
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Primary,
+ )
+
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Primary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Primary,
+ rightIcon = HandyIcons.Line.ExternalLink,
+ )
+
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Primary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ rightIcon = HandyIcons.Line.ExternalLink,
+ )
+ }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Secondary,
+ )
+
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Secondary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ )
+
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Secondary,
+ rightIcon = HandyIcons.Line.ExternalLink,
+ )
+
+ TextButton(
+ onClick = {},
+ text = "M Button",
+ sizeType = TextButtonSize.M,
+ buttonType = TextButtonType.Secondary,
+ leftIcon = HandyIcons.Line.ExternalLink,
+ rightIcon = HandyIcons.Line.ExternalLink,
+ )
+ }
+ }
+ }
+}
diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/Surface.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/Surface.kt
index e02d6f9e..85010e8e 100644
--- a/compose/src/main/kotlin/com/yourssu/handy/compose/Surface.kt
+++ b/compose/src/main/kotlin/com/yourssu/handy/compose/Surface.kt
@@ -1,9 +1,11 @@
package com.yourssu.handy.compose
import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Indication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.toggleable
@@ -11,8 +13,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
@@ -20,6 +24,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.semantics.isContainer
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
import com.yourssu.handy.compose.foundation.LocalContentColor
@@ -92,6 +97,8 @@ fun Surface(
backgroundColor: Color = HandyTheme.colors.bgBasicDefault,
contentColor: Color = LocalContentColor.current,
border: BorderStroke? = null,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource()},
+ indication: Indication? = null,
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
@@ -105,6 +112,54 @@ fun Surface(
border = border,
)
.clickable(
+ interactionSource = interactionSource,
+ indication = indication,
+ enabled = enabled,
+ onClick = onClick,
+ ),
+ propagateMinConstraints = true,
+ ) {
+ content()
+ }
+ }
+}
+
+@Composable
+fun Surface(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ rounding: Dp? = null,
+ shape: Shape = rounding?.let { RoundedCornerShape(it) } ?: RectangleShape,
+ backgroundColor: Color = HandyTheme.colors.bgBasicDefault,
+ contentColor: Color = LocalContentColor.current,
+ border: BorderStroke? = null,
+ shadowColor: Color = Color.Transparent,
+ shadowElevation: Dp = 0.dp,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ indication: Indication? = null,
+ content: @Composable () -> Unit,
+) {
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
+ ) {
+ Box(
+ modifier = modifier
+ .shadow(
+ elevation = shadowElevation,
+ shape = shape,
+ clip = false,
+ ambientColor = shadowColor,
+ spotColor = shadowColor
+ )
+ .surface(
+ shape = shape,
+ backgroundColor = backgroundColor,
+ border = border,
+ )
+ .clickable(
+ interactionSource = interactionSource,
+ indication = indication,
enabled = enabled,
onClick = onClick,
),
@@ -128,6 +183,7 @@ fun Surface(
* @param backgroundColor Surface 배경 색상. 기본값 : bgBasicDefault(#0xFFFFFFFF)
* @param contentColor Surface 내부 content 색상
* @param border Surface 테두리 굵기
+ * @param interactionSource Surface 상호작용 소스
* @param content Surface 내부 content
**/
@Composable
@@ -141,6 +197,8 @@ fun Surface(
backgroundColor: Color = HandyTheme.colors.bgBasicDefault,
contentColor: Color = LocalContentColor.current,
border: BorderStroke? = null,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource()},
+ indication: Indication? = null,
content: @Composable () -> Unit
) {
CompositionLocalProvider(
@@ -155,6 +213,8 @@ fun Surface(
border = border,
)
.selectable(
+ interactionSource = interactionSource,
+ indication = indication,
selected = selected,
onClick = onClick,
enabled = enabled,
@@ -179,6 +239,7 @@ fun Surface(
* @param backgroundColor : Surface 배경 색상. 기본값 : bgBasicDefault(#0xFFFFFFFF)
* @param contentColor : Surface 내부 content 색상
* @param border : Surface 테두리 굵기
+ * @param interactionSource : Surface 상호작용 소스
* @param content : Surface 내부 content
**/
@@ -193,6 +254,8 @@ fun Surface(
backgroundColor: Color = HandyTheme.colors.bgBasicDefault,
contentColor: Color = LocalContentColor.current,
border: BorderStroke? = null,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ indication: Indication? = null,
content: @Composable () -> Unit
) {
CompositionLocalProvider(
@@ -207,6 +270,8 @@ fun Surface(
border = border,
)
.toggleable(
+ interactionSource = interactionSource,
+ indication = indication,
value = checked,
enabled = enabled,
onValueChange = onCheckedChange
diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/button/BaseButton.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/button/BaseButton.kt
new file mode 100644
index 00000000..5e14a8df
--- /dev/null
+++ b/compose/src/main/kotlin/com/yourssu/handy/compose/button/BaseButton.kt
@@ -0,0 +1,81 @@
+package com.yourssu.handy.compose.button
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsPressedAsState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.yourssu.handy.compose.HandyTheme
+import com.yourssu.handy.compose.Surface
+
+/**
+ * BaseButton : ripple 효과가 없는 Composable 함수 입니다.
+ *
+ * BoxButton, TextButton의 베이스가 됩니다.
+ *
+ * @param onClick Button 클릭 시 실행되는 함수
+ * @param colors Button 색상 상태
+ * @param enabled Surface 클릭 가능 여부
+ * @param showBorder Button 테두리 표시 여부
+ * @param rounding Button 모서리의 둥글기
+ * @param contentPadding Button 내부 content padding
+ * @param interactionSource Button 상호작용 소스
+ * @param minWidth Button 최소 너비
+ * @param minHeight Button 최소 높이
+ * @param content Button 내부 content
+ **/
+@Composable
+internal fun BaseButton(
+ onClick: () -> Unit,
+ colors: ButtonColorState,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ showBorder: Boolean = false,
+ rounding: Dp = 8.dp,
+ contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ content: @Composable RowScope.() -> Unit,
+) {
+ val localPressed by interactionSource.collectIsPressedAsState()
+ val buttonColors = colors.apply { pressed = localPressed }
+ val contentColor by buttonColors.contentColor(enabled)
+
+ Surface(
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ rounding = rounding,
+ border = if (showBorder) BorderStroke(1.dp, HandyTheme.colors.lineBasicMedium) else null,
+ backgroundColor = buttonColors.backgroundColor(enabled).value,
+ contentColor = contentColor,
+ interactionSource = interactionSource,
+ ) {
+ Row(
+ modifier = Modifier.padding(contentPadding),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
+ content = content,
+ )
+ }
+}
+
+object ButtonDefaults {
+ private val ButtonHorizontalPadding = 20.dp
+ private val ButtonVerticalPadding = 14.dp
+
+ val ContentPadding = PaddingValues(
+ horizontal = ButtonHorizontalPadding,
+ vertical = ButtonVerticalPadding,
+ )
+}
diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/button/BoxButton.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/button/BoxButton.kt
new file mode 100644
index 00000000..e6f69c29
--- /dev/null
+++ b/compose/src/main/kotlin/com/yourssu/handy/compose/button/BoxButton.kt
@@ -0,0 +1,181 @@
+package com.yourssu.handy.compose.button
+
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.yourssu.handy.compose.HandyTheme
+import com.yourssu.handy.compose.Icon
+import com.yourssu.handy.compose.IconSize
+import com.yourssu.handy.compose.Text
+import com.yourssu.handy.compose.foundation.HandyTypography
+
+
+enum class BoxButtonType {
+ Primary,
+ Secondary,
+ Tertiary;
+}
+
+enum class BoxButtonSize {
+ XXS,
+ XS,
+ S,
+ M,
+ L,
+ XL;
+}
+
+/**
+ * BoxButton : Box 형태의 Button 입니다.
+ * type : Primary, Secondary, Tertiary
+ * size : XXS, XS, S, M, L, XL
+ *
+ * @param onClick Button 클릭 시 실행되는 함수
+ * @param text Button 내부 text
+ * @param leftIcon Button 왼쪽에 표시되는 Icon
+ * @param rightIcon Button 오른쪽에 표시되는 Icon
+ * @param enabled Button 활성화 여부, default true
+ * @param sizeType Button 사이즈
+ * @param buttonType Button 타입 (Primary, Secondary, Tertiary)
+ * @param interactionSource Button 상호작용 소스
+ * @param horizontalPadding Button 내부 content padding
+ **/
+
+@Composable
+fun BoxButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ text: String,
+ leftIcon: ImageVector? = null,
+ rightIcon: ImageVector? = null,
+ enabled: Boolean = true,
+ sizeType: BoxButtonSize = BoxButtonSize.M,
+ buttonType: BoxButtonType = BoxButtonType.Primary,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ horizontalPadding: Dp = boxButtonSizeStateBySize(size = sizeType).horizontalPadding,
+) {
+ val roundingDp = boxButtonSizeStateBySize(size = sizeType).round
+ val (typo, iconSize, height) = boxButtonSizeStateBySize(size = sizeType)
+
+ BaseButton(
+ onClick = onClick,
+ colors = boxButtonColorByType(
+ type = buttonType,
+ ),
+ modifier = Modifier.height(height),
+ enabled = enabled,
+ showBorder = (buttonType == BoxButtonType.Tertiary),
+ interactionSource = interactionSource,
+ rounding = roundingDp,
+ contentPadding = PaddingValues(
+ horizontal = horizontalPadding,
+ ),
+ ) {
+ leftIcon?.let {
+ Icon(
+ imageVector = leftIcon,
+ iconSize = iconSize,
+ modifier = Modifier.padding(end = 4.dp)
+ )
+ }
+
+ Text(
+ text = text,
+ style = typo,
+ )
+
+ rightIcon?.let {
+ Icon(
+ imageVector = rightIcon,
+ iconSize = iconSize,
+ modifier = Modifier.padding(start = 4.dp)
+ )
+ }
+ }
+}
+
+
+@Composable
+private fun boxButtonColorByType(
+ type: BoxButtonType,
+): ButtonColorState = when (type) {
+ BoxButtonType.Primary -> ButtonColorState(
+ contentColor = HandyTheme.colors.textBasicWhite,
+ disabledContentColor = HandyTheme.colors.textBasicDisabled,
+ bgColor = HandyTheme.colors.buttonBoxPrimaryEnabled,
+ disabledBgColor = HandyTheme.colors.buttonBoxPrimaryDisabled,
+ )
+
+ BoxButtonType.Secondary -> ButtonColorState(
+ contentColor = HandyTheme.colors.textBrandSecondary,
+ disabledContentColor = HandyTheme.colors.textBasicDisabled,
+ bgColor = HandyTheme.colors.buttonBoxSecondaryEnabled,
+ disabledBgColor = HandyTheme.colors.buttonBoxSecondaryDisabled,
+ )
+
+ BoxButtonType.Tertiary -> ButtonColorState(
+ bgColor = HandyTheme.colors.buttonBoxTertiaryEnabled,
+ contentColor = HandyTheme.colors.textBasicPrimary,
+ disabledBgColor = HandyTheme.colors.buttonBoxTertiaryDisabled,
+ disabledContentColor = HandyTheme.colors.textBasicDisabled,
+ )
+}
+
+private fun boxButtonSizeStateBySize(
+ size: BoxButtonSize,
+): ButtonStyleProperties = when (size) {
+ BoxButtonSize.XL -> ButtonStyleProperties(
+ typo = HandyTypography.B1Sb16,
+ iconSize = IconSize.S,
+ height = 56.dp,
+ horizontalPadding = 20.dp,
+ round = 16.dp
+ )
+
+ BoxButtonSize.L -> ButtonStyleProperties(
+ typo = HandyTypography.B1Sb16,
+ iconSize = IconSize.S,
+ height = 52.dp,
+ horizontalPadding = 20.dp,
+ round = 16.dp
+ )
+
+ BoxButtonSize.M -> ButtonStyleProperties(
+ typo = HandyTypography.B1Sb16,
+ iconSize = IconSize.S,
+ height = 48.dp,
+ horizontalPadding = 16.dp,
+ round = 14.dp
+ )
+
+ BoxButtonSize.S -> ButtonStyleProperties(
+ typo = HandyTypography.B3Sb14,
+ iconSize = IconSize.XS,
+ height = 40.dp,
+ horizontalPadding = 16.dp,
+ round = 12.dp
+ )
+
+ BoxButtonSize.XS -> ButtonStyleProperties(
+ typo = HandyTypography.B5Sb12,
+ iconSize = IconSize.XXS,
+ height = 32.dp,
+ horizontalPadding = 8.dp,
+ round = 10.dp
+ )
+
+ BoxButtonSize.XXS -> ButtonStyleProperties(
+ typo = HandyTypography.B5Sb12,
+ iconSize = IconSize.XXS,
+ height = 24.dp,
+ horizontalPadding = 8.dp,
+ round = 8.dp
+ )
+}
\ No newline at end of file
diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/button/ButtonState.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/button/ButtonState.kt
new file mode 100644
index 00000000..2ec5c6c5
--- /dev/null
+++ b/compose/src/main/kotlin/com/yourssu/handy/compose/button/ButtonState.kt
@@ -0,0 +1,81 @@
+package com.yourssu.handy.compose.button
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.yourssu.handy.compose.HandyTheme
+import com.yourssu.handy.compose.IconSize
+import com.yourssu.handy.compose.foundation.HandyTextStyle
+
+@Immutable
+class ButtonColorState(
+ val contentColor: Color = Color.Unspecified,
+ val disabledContentColor: Color = Color.Unspecified,
+ val bgColor: Color = Color.Transparent,
+ val disabledBgColor: Color = Color.Transparent,
+ val shadowColor: Color = Color.Transparent,
+ pressed: Boolean = false,
+) {
+ var pressed by mutableStateOf(pressed)
+ internal set
+
+ @Composable
+ fun contentColor(enabled: Boolean): State =
+ rememberUpdatedState(
+ when {
+ !enabled -> disabledContentColor
+ pressed -> pressedColorFor(contentColor)
+ else -> contentColor
+ }
+ )
+
+ @Composable
+ fun backgroundColor(enabled: Boolean): State =
+ rememberUpdatedState(
+ when {
+ !enabled -> disabledBgColor
+ pressed -> pressedColorFor(bgColor)
+ else -> bgColor
+ }
+ )
+
+ @Composable
+ fun shadowColor(enabled: Boolean): State =
+ rememberUpdatedState(
+ when {
+ !enabled -> Color.Transparent
+ else -> shadowColor
+ }
+ )
+}
+
+@Composable
+private fun pressedColorFor(color: Color): Color {
+ return when (color) {
+ HandyTheme.colors.buttonBoxPrimaryEnabled -> HandyTheme.colors.buttonBoxPrimaryPressed
+ HandyTheme.colors.buttonBoxSecondaryEnabled -> HandyTheme.colors.buttonBoxSecondaryPressed
+ HandyTheme.colors.buttonBoxTertiaryEnabled -> HandyTheme.colors.buttonBoxTertiaryPressed
+ HandyTheme.colors.buttonTextPrimaryEnabled -> HandyTheme.colors.buttonTextPrimaryPressed
+ HandyTheme.colors.buttonTextSecondaryEnabled -> HandyTheme.colors.buttonTextSecondaryPressed
+ HandyTheme.colors.buttonFabPrimaryEnabled -> HandyTheme.colors.buttonFabPrimaryPressed
+ HandyTheme.colors.buttonFabSecondaryEnabled -> HandyTheme.colors.buttonFabSecondaryPressed
+ else -> color
+ }
+}
+
+@Immutable
+data class ButtonStyleProperties(
+ val typo: HandyTextStyle = HandyTextStyle.Default,
+ val iconSize: IconSize = IconSize.S,
+ val height: Dp = 0.dp,
+ val horizontalPadding: Dp = 0.dp,
+ val betweenSpace: Dp = 0.dp,
+ val round: Dp = 0.dp,
+)
diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/button/TextButton.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/button/TextButton.kt
new file mode 100644
index 00000000..0445c5d7
--- /dev/null
+++ b/compose/src/main/kotlin/com/yourssu/handy/compose/button/TextButton.kt
@@ -0,0 +1,145 @@
+package com.yourssu.handy.compose.button
+
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
+import com.yourssu.handy.compose.HandyTheme
+import com.yourssu.handy.compose.Icon
+import com.yourssu.handy.compose.IconSize
+import com.yourssu.handy.compose.Text
+import com.yourssu.handy.compose.foundation.HandyTypography
+
+enum class TextButtonType {
+ Primary,
+ Secondary;
+}
+
+enum class TextButtonSize {
+ S,
+ M,
+ L;
+}
+
+/**
+ * BaseButton : ripple 효과가 없는 Composable 함수 입니다.
+ *
+ * BoxButton, TextButton의 베이스가 됩니다.
+ *
+ * @param onClick Button 클릭 시 실행되는 함수
+ * @param text Button 내부 text
+ * @param leftIcon Button 왼쪽에 표시되는 Icon
+ * @param rightIcon Button 오른쪽에 표시되는 Icon
+ * @param enabled Button 활성화 여부 default true
+ * @param sizeType Button 사이즈
+ * @param buttonType Button 타입 (Primary, Secondary)
+ * @param interactionSource Button 상호작용 소스
+ **/
+@Composable
+fun TextButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ text: String,
+ leftIcon: ImageVector? = null,
+ rightIcon: ImageVector? = null,
+ sizeType: TextButtonSize = TextButtonSize.M,
+ enabled: Boolean = false,
+ buttonType: TextButtonType = TextButtonType.Primary,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+) {
+ val roundingDp = textButtonSizeStateBySize(size = sizeType).round
+ val (typo, iconSize, height, horizontalPadding) = textButtonSizeStateBySize(size = sizeType)
+
+ BaseButton(
+ onClick = onClick,
+ colors = textButtonColorByType(
+ type = buttonType,
+ ),
+ modifier = Modifier
+ .then(modifier)
+ .height(height),
+ enabled = enabled,
+ interactionSource = interactionSource,
+ rounding = roundingDp,
+ contentPadding = PaddingValues(
+ horizontal = horizontalPadding,
+ ),
+ ) {
+ leftIcon?.let {
+ Icon(
+ imageVector = leftIcon,
+ iconSize = iconSize,
+ modifier = Modifier.padding(end = 4.dp)
+ )
+ }
+
+ Text(
+ text = text,
+ style = typo,
+ )
+
+ rightIcon?.let {
+ Icon(
+ imageVector = rightIcon,
+ iconSize = iconSize,
+ modifier = Modifier.padding(start = 4.dp)
+ )
+ }
+ }
+}
+
+
+@Composable
+private fun textButtonColorByType(
+ type: TextButtonType,
+): ButtonColorState = when (type) {
+ TextButtonType.Primary -> ButtonColorState(
+ bgColor = HandyTheme.colors.buttonTextPrimaryEnabled,
+ contentColor = HandyTheme.colors.textBrandPrimary,
+ disabledBgColor = HandyTheme.colors.buttonTextPrimaryEnabled,
+ disabledContentColor = HandyTheme.colors.textBasicDisabled,
+ )
+
+ TextButtonType.Secondary -> ButtonColorState(
+ bgColor = HandyTheme.colors.buttonTextSecondaryEnabled,
+ contentColor = HandyTheme.colors.textBasicTertiary,
+ disabledBgColor = HandyTheme.colors.buttonTextSecondaryDisabled,
+ disabledContentColor = HandyTheme.colors.textBasicDisabled,
+ )
+}
+
+
+@Composable
+private fun textButtonSizeStateBySize(
+ size: TextButtonSize,
+): ButtonStyleProperties = when (size) {
+ TextButtonSize.L -> ButtonStyleProperties(
+ typo = HandyTypography.B3Sb14,
+ iconSize = IconSize.S,
+ height = 36.dp,
+ horizontalPadding = 8.dp,
+ round = 8.dp
+ )
+
+ TextButtonSize.M -> ButtonStyleProperties(
+ typo = HandyTypography.B3Sb14,
+ iconSize = IconSize.XS,
+ height = 32.dp,
+ horizontalPadding = 8.dp,
+ round = 8.dp
+ )
+
+ TextButtonSize.S -> ButtonStyleProperties(
+ typo = HandyTypography.B5Sb12,
+ iconSize = IconSize.XXS,
+ height = 24.dp,
+ horizontalPadding = 6.dp,
+ round = 8.dp
+ )
+
+}
\ No newline at end of file
diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/PrimitiveColors.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/PrimitiveColors.kt
index 01e5e7b7..5739e773 100644
--- a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/PrimitiveColors.kt
+++ b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/PrimitiveColors.kt
@@ -37,4 +37,9 @@ internal val ColorNeutralTransparent = Color(0x00FFFFFF)
// Status
internal val ColorStatusRedMain = Color(0xFFFF5C5C)
-internal val ColorStatusRedSub = Color(0xFFFFEBEB)
\ No newline at end of file
+internal val ColorStatusRedSub = Color(0xFFFFEBEB)
+
+
+
+internal val ColorFabPrimaryShadow = Color(0x6E768740).copy(alpha = 0.25f)
+internal val ColorFabSecondaryShadow = Color(0xB5B9C440).copy(alpha = 0.25f)
\ No newline at end of file
diff --git a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/SemanticColors.kt b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/SemanticColors.kt
index 4ac83cdd..d668f4f4 100644
--- a/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/SemanticColors.kt
+++ b/compose/src/main/kotlin/com/yourssu/handy/compose/foundation/SemanticColors.kt
@@ -37,12 +37,12 @@ data class ColorScheme(
val textStatusNegative: Color = ColorStatusRedMain,
val textStatusPositive: Color = ColorViolet500,
- // Line / Basic
+ // Tertiary / Basic
val lineBasicLight: Color = ColorGray100,
val lineBasicMedium: Color = ColorGray200,
val lineBasicStrong: Color = ColorGray300,
- // Line / Status
+ // Tertiary / Status
val lineStatusNegative: Color = ColorStatusRedMain,
val lineStatusPositive: Color = ColorViolet500,