Skip to content
3 changes: 1 addition & 2 deletions build-logic/src/main/kotlin/NDGLFeaturePlugin.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import convention.configureComposeAndroid
import convention.configureCoroutineAndroid
import convention.configureFirebase
import convention.configureHiltAndroid
import org.gradle.api.Plugin
import org.gradle.api.Project
Expand All @@ -18,6 +16,7 @@ class NDGLFeaturePlugin : Plugin<Project> {

dependencies {
"implementation"(project(":navigation"))
"implementation"(project(":core:base"))
"implementation"(project(":core:ui"))
"implementation"(project(":core:util"))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.yapp.ui.base
package com.yapp.ndgl.core.base

interface UiState

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.yapp.ui.base
package com.yapp.ndgl.core.base

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package com.yapp.ndgl.core.ui.designsystem

import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.yapp.ndgl.core.ui.R
import com.yapp.ndgl.core.ui.theme.NDGLTheme

object NDGLCTAButtonAttr {
enum class Type {
PRIMARY,
SECONDARY,
DESTRUCTIVE,
}

enum class Size(
val height: Dp,
val horizontalPadding: Dp,
val horizontalSpacing: Dp,
val iconSize: Dp,
) {
LARGE(
height = 56.dp,
horizontalPadding = 24.dp,
horizontalSpacing = 8.dp,
iconSize = 24.dp,
),
MEDIUM(
height = 40.dp,
horizontalPadding = 24.dp,
horizontalSpacing = 8.dp,
iconSize = 20.dp,
),
SMALL(
height = 32.dp,
horizontalPadding = 12.dp,
horizontalSpacing = 4.dp,
iconSize = 16.dp,
),
}

enum class Status {
ACTIVE,
DISABLED,
}
}

@Composable
fun NDGLCTAButton(
type: NDGLCTAButtonAttr.Type,
size: NDGLCTAButtonAttr.Size,
status: NDGLCTAButtonAttr.Status,
label: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
@DrawableRes leadingIcon: Int? = null,
@DrawableRes trailingIcon: Int? = null,
) {
val contentColor = type.contentColor(status)

Row(
modifier = modifier
.height(size.height)
.clip(RoundedCornerShape(8.dp))
.background(type.containerColor(status))
.clickable(
enabled = status != NDGLCTAButtonAttr.Status.DISABLED,
onClick = onClick,
)
.padding(horizontal = size.horizontalPadding, vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(
space = size.horizontalSpacing,
alignment = Alignment.CenterHorizontally,
),
verticalAlignment = Alignment.CenterVertically,
) {
leadingIcon?.let { icon ->
Icon(
imageVector = ImageVector.vectorResource(icon),
contentDescription = null,
modifier = Modifier.size(size.iconSize),
tint = contentColor,
)
}

Text(
text = label,
style = size.labelStyle(),
color = contentColor,
)

trailingIcon?.let { icon ->
Icon(
imageVector = ImageVector.vectorResource(icon),
contentDescription = null,
modifier = Modifier.size(size.iconSize),
tint = contentColor,
)
}
}
}

@Composable
private fun NDGLCTAButtonAttr.Type.containerColor(
status: NDGLCTAButtonAttr.Status,
): Color {
if (status == NDGLCTAButtonAttr.Status.DISABLED) return NDGLTheme.colors.black100
return when (this) {
NDGLCTAButtonAttr.Type.PRIMARY -> NDGLTheme.colors.black900
NDGLCTAButtonAttr.Type.SECONDARY -> NDGLTheme.colors.black50
NDGLCTAButtonAttr.Type.DESTRUCTIVE -> NDGLTheme.colors.red50
}
}

@Composable
private fun NDGLCTAButtonAttr.Type.contentColor(
status: NDGLCTAButtonAttr.Status,
): Color {
if (status == NDGLCTAButtonAttr.Status.DISABLED) return NDGLTheme.colors.black300
return when (this) {
NDGLCTAButtonAttr.Type.PRIMARY -> NDGLTheme.colors.white
NDGLCTAButtonAttr.Type.SECONDARY -> NDGLTheme.colors.black700
NDGLCTAButtonAttr.Type.DESTRUCTIVE -> NDGLTheme.colors.red500
}
}

@Composable
private fun NDGLCTAButtonAttr.Size.labelStyle(): TextStyle {
return when (this) {
NDGLCTAButtonAttr.Size.LARGE -> NDGLTheme.typography.bodyLgSemiBold
NDGLCTAButtonAttr.Size.MEDIUM -> NDGLTheme.typography.bodyMdSemiBold
NDGLCTAButtonAttr.Size.SMALL -> NDGLTheme.typography.bodySmSemiBold
}
}

@Preview(showBackground = true)
@Composable
private fun NDGLCTAButtonPrimaryLargePreview() {
NDGLTheme {
NDGLCTAButton(
type = NDGLCTAButtonAttr.Type.PRIMARY,
size = NDGLCTAButtonAttr.Size.LARGE,
status = NDGLCTAButtonAttr.Status.ACTIVE,
label = "Primary Large",
leadingIcon = R.drawable.ic_24_pin,
onClick = {},
)
}
}

@Preview
@Composable
private fun NDGLCTAButtonSecondaryMediumPreview() {
NDGLTheme {
NDGLCTAButton(
type = NDGLCTAButtonAttr.Type.SECONDARY,
size = NDGLCTAButtonAttr.Size.MEDIUM,
status = NDGLCTAButtonAttr.Status.ACTIVE,
label = "Secondary Medium",
leadingIcon = R.drawable.ic_20_tv,
onClick = {},
)
}
}

@Preview
@Composable
private fun NDGLCTAButtonDestructiveSmallPreview() {
NDGLTheme {
NDGLCTAButton(
type = NDGLCTAButtonAttr.Type.DESTRUCTIVE,
size = NDGLCTAButtonAttr.Size.SMALL,
status = NDGLCTAButtonAttr.Status.ACTIVE,
label = "Destructive Small",
leadingIcon = R.drawable.ic_20_tv,
onClick = {},
modifier = Modifier.fillMaxWidth(),
)
}
}

@Preview
@Composable
private fun NDGLCTAButtonDisabledPreview() {
NDGLTheme {
NDGLCTAButton(
type = NDGLCTAButtonAttr.Type.PRIMARY,
size = NDGLCTAButtonAttr.Size.LARGE,
status = NDGLCTAButtonAttr.Status.DISABLED,
label = "Disabled",
leadingIcon = R.drawable.ic_24_pin,
onClick = {},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.yapp.ndgl.core.ui.designsystem

import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.yapp.ndgl.core.ui.R
import com.yapp.ndgl.core.ui.theme.NDGLTheme
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf

object NDGLChipTabAttr {
data class Tab(
val tag: String,
val name: String,
@param:DrawableRes val leadingIcon: Int? = null,
)
}

@Composable
fun NDGLChipTab(
tabs: PersistentList<NDGLChipTabAttr.Tab>,
selectedIndex: Int,
onTabSelected: (Int) -> Unit,
modifier: Modifier = Modifier,
) {
val scrollState = rememberScrollState()

Row(
modifier = modifier.horizontalScroll(scrollState),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
tabs.forEachIndexed { index, tab ->
NDGLChipTabItem(
isSelected = index == selectedIndex,
name = tab.name,
leadingIcon = tab.leadingIcon,
onTabSelected = { onTabSelected(index) },
)
}
}
}

@Composable
private fun NDGLChipTabItem(
isSelected: Boolean,
name: String,
onTabSelected: () -> Unit,
@DrawableRes leadingIcon: Int?,
) {
Row(
modifier = Modifier
.clip(CircleShape)
.chipStyle(isSelected)
.clickable(onClick = onTabSelected)
.widthIn(min = 72.dp)
.padding(horizontal = 14.dp, vertical = 6.dp),
horizontalArrangement = Arrangement.spacedBy(
space = 4.dp,
alignment = Alignment.CenterHorizontally,
),
verticalAlignment = Alignment.CenterVertically,
) {
leadingIcon?.let { icon ->
Icon(
imageVector = ImageVector.vectorResource(icon),
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = isSelected.chipContentColor(),
)
}

Text(
text = name,
style = NDGLTheme.typography.bodyMdMedium,
color = isSelected.chipContentColor(),
)
}
}

@Composable
private fun Modifier.chipStyle(
isSelected: Boolean,
): Modifier = this.then(
if (isSelected) {
Modifier.background(NDGLTheme.colors.black900, CircleShape)
} else {
Modifier
.background(NDGLTheme.colors.white, CircleShape)
.border(
width = 1.dp,
color = NDGLTheme.colors.black200,
shape = CircleShape,
)
},
)

@Composable
private fun Boolean.chipContentColor() = if (this) {
NDGLTheme.colors.white
} else {
NDGLTheme.colors.black400
}

@Preview(showBackground = true)
@Composable
private fun NDGLChipTabPreview() {
NDGLTheme {
var selectedIndex by remember { mutableIntStateOf(0) }

NDGLChipTab(
tabs = persistentListOf(
NDGLChipTabAttr.Tab(
tag = "1",
name = "1일차",
leadingIcon = R.drawable.ic_20_tv,
),
NDGLChipTabAttr.Tab(
tag = "1",
name = "2일차",
leadingIcon = R.drawable.ic_20_tv,
),
NDGLChipTabAttr.Tab(
tag = "1",
name = "3일차",
),
),
selectedIndex = selectedIndex,
onTabSelected = { selectedIndex = it },
)
}
}
Loading