diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/Sample.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/Sample.kt deleted file mode 100644 index 412a23fea..000000000 --- a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/Sample.kt +++ /dev/null @@ -1 +0,0 @@ -package com.suwiki.core.designsystem.component diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/dropdown/SuwikiDropDown.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/dropdown/SuwikiDropDown.kt new file mode 100644 index 000000000..f09843996 --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/dropdown/SuwikiDropDown.kt @@ -0,0 +1,137 @@ +package com.suwiki.core.designsystem.component.dropdown + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.MaterialTheme +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.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import com.suwiki.core.designsystem.R + +@Composable +fun SuwikiDropDownMenu( + label: String = "", + onClickLabel: () -> Unit = {}, + onDismissRequest: () -> Unit = {}, + expanded: Boolean = false, + content: @Composable ColumnScope.() -> Unit, +) { + Column( + horizontalAlignment = Alignment.Start, + ) { + Row( + modifier = Modifier + .clip(RoundedCornerShape(10.dp)) + .clickable(onClick = onClickLabel) + .background(Color.LightGray) + .padding( + horizontal = 9.dp, + vertical = 6.dp, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = label, + ) + Image( + painter = painterResource(id = R.drawable.ic_dropdown_arrow_down), + contentDescription = "", + ) + } + + MaterialTheme( + shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(10.dp)), + ) { + DropdownMenu( + modifier = Modifier + .width(106.dp) + .background(Color.White) + .padding(vertical = 8.dp) + .clip(RoundedCornerShape(20.dp)), + offset = DpOffset(x = 0.dp, y = 6.dp), + expanded = expanded, + onDismissRequest = onDismissRequest, + content = content, + ) + } + } +} + +@Preview(showSystemUi = true) +@Composable +fun SuwikiDropDownMenuPreview() { + var isSemesterDropDownExpanded by remember { + mutableStateOf(false) + } + + var isExamDropdownExpanded by remember { + mutableStateOf(false) + } + + Column { + SuwikiDropDownMenu( + label = "수강학기 선택", + expanded = isSemesterDropDownExpanded, + onClickLabel = { isSemesterDropDownExpanded = true }, + onDismissRequest = { isSemesterDropDownExpanded = false }, + ) { + repeat(4) { + SuwikiDropdownMenuItem( + text = "menu", + ) + } + } + + SuwikiDropDownMenu( + label = "시험유형 선택", + expanded = isExamDropdownExpanded, + onClickLabel = { isExamDropdownExpanded = true }, + onDismissRequest = { isExamDropdownExpanded = false }, + ) { + repeat(4) { + SuwikiDropdownMenuItem( + text = "menu", + ) + } + } + } +} + +@Composable +fun SuwikiDropdownMenuItem( + onClick: () -> Unit = {}, + text: String = "", +) { + DropdownMenuItem( + modifier = Modifier.fillMaxWidth(), + onClick = onClick, + text = { + Text( + text = text, + textAlign = TextAlign.Start, + ) + }, + ) +} diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/searchbar/SuwikiSearchBar.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/searchbar/SuwikiSearchBar.kt new file mode 100644 index 000000000..9421d4dfe --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/searchbar/SuwikiSearchBar.kt @@ -0,0 +1,106 @@ +package com.suwiki.core.designsystem.component.searchbar + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +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.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.suwiki.core.designsystem.R + +@Composable +fun SuwikiSearchBar( + modifier: Modifier = Modifier, + hint: String = "", + value: String = "", + maxLines: Int = 1, + minLines: Int = 1, + onValueChange: (String) -> Unit = { _ -> }, + onClickClearButton: () -> Unit = {}, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + Box( + Modifier + .background(Color.White) + .padding(vertical = 10.dp, horizontal = 24.dp), + contentAlignment = Alignment.Center, + ) { + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier + .background(Color.LightGray, shape = RoundedCornerShape(10.dp)) + .padding(8.dp), + singleLine = maxLines == 1, + maxLines = if (minLines > maxLines) minLines else maxLines, + minLines = minLines, + interactionSource = interactionSource, + cursorBrush = SolidColor(Color.Blue), + decorationBox = { innerText -> + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Image(painter = painterResource(id = R.drawable.ic_search), contentDescription = "") + + Spacer(modifier = Modifier.size(5.dp)) + + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.CenterStart, + ) { + innerText() + if (value.isEmpty()) Text(text = hint, color = Color.Gray) + } + + if (value.isNotEmpty()) { + Image( + modifier = Modifier + .clickable(onClick = onClickClearButton), + painter = painterResource(id = R.drawable.ic_textfield_clear), + contentDescription = "", + ) + } + } + }, + ) + } +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFF) +@Composable +fun SuwikiSearchBarPreview() { + var normalValue by remember { + mutableStateOf("") + } + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + SuwikiSearchBar( + hint = "Hinted search text", + value = normalValue, + onValueChange = { normalValue = it }, + onClickClearButton = { normalValue = "" }, + ) + } +} diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/searchbar/SuwikiSearchBarWithFilter.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/searchbar/SuwikiSearchBarWithFilter.kt new file mode 100644 index 000000000..92a38ba06 --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/searchbar/SuwikiSearchBarWithFilter.kt @@ -0,0 +1,122 @@ +package com.suwiki.core.designsystem.component.searchbar + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +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.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.suwiki.core.designsystem.R + +@Composable +fun SuwikiSearchBarWithFilter( + modifier: Modifier = Modifier, + hint: String = "", + value: String = "", + maxLines: Int = 1, + minLines: Int = 1, + onValueChange: (String) -> Unit = { _ -> }, + onClickClearButton: () -> Unit = {}, + onClickFilterButton: () -> Unit = {}, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + Row( + Modifier + .background(Color.LightGray) + .padding(vertical = 10.dp, horizontal = 24.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier + .weight(1f) + .shadow(elevation = 10.dp) // TODO Custom Shadow로 변경해야함 + .background(Color.White, shape = RoundedCornerShape(10.dp)) + .padding(8.dp), + singleLine = maxLines == 1, + maxLines = if (minLines > maxLines) minLines else maxLines, + minLines = minLines, + interactionSource = interactionSource, + cursorBrush = SolidColor(Color.Blue), + decorationBox = { innerText -> + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Image(painter = painterResource(id = R.drawable.ic_search), contentDescription = "") + + Spacer(modifier = Modifier.size(5.dp)) + + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.CenterStart, + ) { + innerText() + if (value.isEmpty()) Text(text = hint, color = Color.Gray) + } + + if (value.isNotEmpty()) { + Image( + modifier = Modifier + .clickable(onClick = onClickClearButton), + painter = painterResource(id = R.drawable.ic_textfield_clear), + contentDescription = "", + ) + } + } + }, + ) + + Spacer(modifier = Modifier.size(4.dp)) + + Image( + modifier = Modifier + .shadow(elevation = 10.dp) // TODO Custom Shadow로 변경해야함 + .background(Color.White, shape = RoundedCornerShape(10.dp)) + .clickable(onClick = onClickFilterButton) + .padding(8.dp), + painter = painterResource(id = R.drawable.ic_filter), + contentDescription = "", + ) + } +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFF) +@Composable +fun SuwikiSearchBarWithFilterPreview() { + var normalValue by remember { + mutableStateOf("") + } + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + SuwikiSearchBarWithFilter( + hint = "Hinted search text", + value = normalValue, + onValueChange = { normalValue = it }, + onClickClearButton = { normalValue = "" }, + ) + } +} diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSlider.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSlider.kt new file mode 100644 index 000000000..1247a04cf --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSlider.kt @@ -0,0 +1,94 @@ +package com.suwiki.core.designsystem.component.slider + +import androidx.annotation.IntRange +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +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.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import kotlin.math.round + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SuwikiSlider( + value: Float = 2.5f, + onValueChange: (Float) -> Unit = { _ -> }, + @IntRange(from = 0) + steps: Int = 9, + valueRange: ClosedFloatingPointRange = 0f..5f, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + var isHovering by rememberSaveable { + mutableStateOf(false) + } + + val label = (round(value * 100) / 100).toString() + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Slider( + modifier = Modifier.weight(1f).height(SUWIKI_THUMB_WIDTH_LABEL_HEIGHT.dp), + value = value, + onValueChange = { + isHovering = true + onValueChange(it) + }, + onValueChangeFinished = { isHovering = false }, + valueRange = valueRange, + steps = steps, + interactionSource = interactionSource, + thumb = { + if (isHovering) { + SuwikiSliderThumbWithLabel( + label = label, + ) + } else { + SuwikiSliderThumb() + } + }, + track = { + SuwikiSliderTrack( + value = value, + valueRange = valueRange, + height = 6.dp, + shape = RoundedCornerShape(4.dp), + ) + }, + ) + + Text(text = label) + } +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFF) +@Composable +fun SuwikiSliderPreview() { + var sliderPosition by rememberSaveable { + mutableFloatStateOf(2.5f) + } + + Box(modifier = Modifier.padding(vertical = 40.dp)) { + SuwikiSlider( + value = sliderPosition, + onValueChange = { sliderPosition = if (it < 0.5) 0.5F else it }, + ) + } +} diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSliderThumb.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSliderThumb.kt new file mode 100644 index 000000000..7e73dac75 --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSliderThumb.kt @@ -0,0 +1,35 @@ +package com.suwiki.core.designsystem.component.slider + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Composable +fun SuwikiSliderThumb() { + Box( + modifier = Modifier.size(28.dp), + contentAlignment = Alignment.Center, + ) { + Spacer( + modifier = Modifier + .size(16.dp) + .clip(CircleShape) + .background(Color.Blue), + ) + } +} + +@Preview +@Composable +fun SuwikiSliderThumbPreview() { + SuwikiSliderThumb() +} diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSliderThumbWithLabel.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSliderThumbWithLabel.kt new file mode 100644 index 000000000..3249f06f3 --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSliderThumbWithLabel.kt @@ -0,0 +1,50 @@ +package com.suwiki.core.designsystem.component.slider + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.suwiki.core.designsystem.R + +const val SUWIKI_THUMB_WIDTH_LABEL_HEIGHT = 88 + +@Composable +fun SuwikiSliderThumbWithLabel( + modifier: Modifier = Modifier, + label: String, +) { + Box( + modifier = modifier.height(SUWIKI_THUMB_WIDTH_LABEL_HEIGHT.dp), + ) { + Image( + painter = painterResource(id = R.drawable.ic_slider_thumb_hovered), + contentDescription = "", + ) + + Box( + modifier = Modifier.size(28.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = label, + textAlign = TextAlign.Center, + color = Color.White, + ) + } + } +} + +@Preview +@Composable +fun SuwikiSliderThumbWithLabelPreview() { + SuwikiSliderThumbWithLabel(label = "5.0") +} diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSliderTrack.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSliderTrack.kt new file mode 100644 index 000000000..7ec2bd21b --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/slider/SuwikiSliderTrack.kt @@ -0,0 +1,61 @@ +package com.suwiki.core.designsystem.component.slider + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +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.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +@ExperimentalMaterial3Api +fun SuwikiSliderTrack( + modifier: Modifier = Modifier, + value: Float, + valueRange: ClosedFloatingPointRange, + height: Dp = 6.dp, + shape: Shape = RoundedCornerShape(4.dp), +) { + Box( + modifier = modifier + .track(height = height, shape = shape) + .background(Color.LightGray), + ) { + Box( + modifier = modifier + .progress( + value = value, + valueRange = valueRange, + height = height, + shape = shape, + ) + .background(Color.Blue), + ) + } +} + +private fun Modifier.track( + height: Dp = 6.dp, + shape: Shape = CircleShape, +) = fillMaxWidth() + .heightIn(min = height) + .clip(shape) + +private fun Modifier.progress( + value: Float, + valueRange: ClosedFloatingPointRange, + height: Dp = 6.dp, + shape: Shape = CircleShape, +) = + fillMaxWidth(fraction = value / valueRange.endInclusive - valueRange.start) + .heightIn(min = height) + .clip(shape) diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/textfield/SuwikiTextFieldRegular.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/textfield/SuwikiTextFieldRegular.kt new file mode 100644 index 000000000..87bdaea6c --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/textfield/SuwikiTextFieldRegular.kt @@ -0,0 +1,157 @@ +package com.suwiki.core.designsystem.component.textfield + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.BasicTextField +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.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.suwiki.core.designsystem.R + +@Composable +fun SuwikiTextFieldRegular( + modifier: Modifier = Modifier, + label: String = "", + hint: String = "", + value: String = "", + onValueChange: (String) -> Unit = { _ -> }, + onClickClearButton: () -> Unit = {}, + helperText: String = "", + isError: Boolean = false, + maxLines: Int = 1, + minLines: Int = 1, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val isFocused by interactionSource.collectIsFocusedAsState() + + val (labelColor, helperTextColor) = when { + isError -> (Color.Red to Color.Red) + isFocused -> (Color.Blue to Color.Blue) + else -> (Color.Gray to Color.Gray) + } + + val underlineColor = when { + isError -> Color.Red + isFocused -> Color.Blue + value.isEmpty() -> Color.Gray + else -> Color.LightGray + } + + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier.fillMaxWidth(), + singleLine = maxLines == 1, + maxLines = if (minLines > maxLines) minLines else maxLines, + minLines = minLines, + interactionSource = interactionSource, + cursorBrush = SolidColor(Color.Blue), + decorationBox = { innerText -> + Column { + Text( + text = label, + color = labelColor, + ) + + Spacer(modifier = Modifier.size(2.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn( + min = 27.dp, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.CenterStart, + ) { + innerText() + if (value.isEmpty()) Text(text = hint, color = Color.LightGray) + } + + if (value.isNotEmpty()) { + Image( + modifier = Modifier.clickable(onClick = onClickClearButton), + painter = painterResource(id = R.drawable.ic_textfield_clear), + contentDescription = "", + ) + } + } + + Spacer(modifier = Modifier.height(if (isFocused || isError) 4.dp else 5.dp)) + + HorizontalDivider( + thickness = if (isFocused || isError) 2.dp else 1.dp, + color = underlineColor, + ) + + Spacer(modifier = Modifier.height(3.dp)) + + Text( + text = helperText, + color = helperTextColor, + ) + } + }, + ) +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFF) +@Composable +fun SuwikiTextFieldRegularPreview() { + var normalValue by remember { + mutableStateOf("") + } + + var errorValue by remember { + mutableStateOf("") + } + + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + SuwikiTextFieldRegular( + label = "라벨", + hint = "플레이스 홀더", + value = normalValue, + onValueChange = { normalValue = it }, + onClickClearButton = { normalValue = "" }, + helperText = "도움말 메세지", + ) + + SuwikiTextFieldRegular( + label = "라벨", + hint = "플레이스 홀더", + value = errorValue, + onValueChange = { errorValue = it }, + onClickClearButton = { errorValue = "" }, + helperText = "도움말 메세지", + isError = true, + ) + } +} diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/textfield/SuwikiTextFieldReview.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/textfield/SuwikiTextFieldReview.kt new file mode 100644 index 000000000..00ff10cbc --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/textfield/SuwikiTextFieldReview.kt @@ -0,0 +1,86 @@ +package com.suwiki.core.designsystem.component.textfield + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +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.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Composable +fun SuwikiTextFieldReview( + modifier: Modifier = Modifier, + hint: String = "", + value: String = "", + onValueChange: (String) -> Unit = { _ -> }, + isError: Boolean = false, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier.fillMaxWidth(), + interactionSource = interactionSource, + cursorBrush = SolidColor(Color.Blue), + decorationBox = { innerText -> + Box( + modifier = Modifier + .heightIn(min = 198.dp) + .background(Color.LightGray, shape = RoundedCornerShape(10.dp)) + .border( + width = 1.dp, + color = if (isError) Color.Red else Color.Unspecified, + shape = RoundedCornerShape(10.dp), + ) + .padding(horizontal = 16.dp, vertical = 24.dp), + contentAlignment = Alignment.TopStart, + ) { + innerText() + if (value.isEmpty()) Text(text = hint, color = Color.Gray) + } + }, + ) +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFF) +@Composable +fun SuwikiTextFieldReviewPreview() { + var normalValue by remember { + mutableStateOf("") + } + + Column( + modifier = Modifier.padding(20.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + SuwikiTextFieldReview( + hint = "강의평가를 작성해주세요", + value = normalValue, + onValueChange = { normalValue = it }, + ) + + SuwikiTextFieldReview( + hint = "강의평가를 작성해주세요", + value = normalValue, + isError = true, + onValueChange = { normalValue = it }, + ) + } +} diff --git a/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/textfield/SuwikiTextFieldSmall.kt b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/textfield/SuwikiTextFieldSmall.kt new file mode 100644 index 000000000..bc75ab897 --- /dev/null +++ b/core/designsystem/src/main/java/com/suwiki/core/designsystem/component/textfield/SuwikiTextFieldSmall.kt @@ -0,0 +1,128 @@ +package com.suwiki.core.designsystem.component.textfield + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.BasicTextField +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.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.suwiki.core.designsystem.R + +@Composable +fun SuwikiTextFieldSmall( + modifier: Modifier = Modifier, + hint: String = "", + value: String = "", + onValueChange: (String) -> Unit = { _ -> }, + onClickClearButton: () -> Unit = {}, + maxLines: Int = 1, + minLines: Int = 1, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val isFocused by interactionSource.collectIsFocusedAsState() + + val underlineColor = when { + isFocused -> Color.Blue + value.isEmpty() -> Color.Gray + else -> Color.LightGray + } + + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier.fillMaxWidth(), + singleLine = maxLines == 1, + maxLines = if (minLines > maxLines) minLines else maxLines, + minLines = minLines, + interactionSource = interactionSource, + cursorBrush = SolidColor(Color.Blue), + decorationBox = { innerText -> + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn( + min = 21.dp, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.CenterStart, + ) { + innerText() + if (value.isEmpty()) Text(text = hint, color = Color.LightGray) + } + + if (value.isNotEmpty()) { + Image( + modifier = Modifier + .size(21.dp) + .clickable(onClick = onClickClearButton), + painter = painterResource(id = R.drawable.ic_textfield_clear), + contentDescription = "", + ) + } + } + + Spacer(modifier = Modifier.height(5.dp)) + + HorizontalDivider( + thickness = 1.dp, + color = underlineColor, + ) + } + }, + ) +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFF) +@Composable +fun SuwikiTextFieldSmallPreview() { + var normalValue by remember { + mutableStateOf("") + } + + Column( + modifier = Modifier.padding(vertical = 10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + SuwikiTextFieldSmall( + hint = "플레이스 홀더", + value = normalValue, + onValueChange = { normalValue = it }, + onClickClearButton = { normalValue = "" }, + ) + + SuwikiTextFieldSmall( + hint = "플레이스 홀더", + value = normalValue, + onValueChange = { normalValue = it }, + onClickClearButton = { normalValue = "" }, + ) + } +} diff --git a/core/designsystem/src/main/res/drawable/ic_dropdown_arrow_down.xml b/core/designsystem/src/main/res/drawable/ic_dropdown_arrow_down.xml new file mode 100644 index 000000000..00891344a --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_dropdown_arrow_down.xml @@ -0,0 +1,11 @@ + + + diff --git a/core/designsystem/src/main/res/drawable/ic_filter.xml b/core/designsystem/src/main/res/drawable/ic_filter.xml new file mode 100644 index 000000000..2bbadb61b --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/designsystem/src/main/res/drawable/ic_search.xml b/core/designsystem/src/main/res/drawable/ic_search.xml new file mode 100644 index 000000000..a9366503c --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_search.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/designsystem/src/main/res/drawable/ic_slider_thumb_hovered.xml b/core/designsystem/src/main/res/drawable/ic_slider_thumb_hovered.xml new file mode 100644 index 000000000..4a81fe6e9 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_slider_thumb_hovered.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/core/designsystem/src/main/res/drawable/ic_textfield_clear.xml b/core/designsystem/src/main/res/drawable/ic_textfield_clear.xml new file mode 100644 index 000000000..b66edaac7 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/ic_textfield_clear.xml @@ -0,0 +1,9 @@ + + +