Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bc29dd2
[refactor] #108: 랜덤 포즈 플로팅바 배경 투명도 조절
ikseong00 Feb 14, 2026
83ae494
[design] #108: Action Bar core:designsystem 컴포넌트화
ikseong00 Feb 14, 2026
bbbfdf2
[design] #108: 필터칩 모양 커스텀 기능 추가
ikseong00 Feb 14, 2026
4fda9a3
[design] #108: NekiTextField 리팩터링 및 NekiTextFieldWithError 추가
ikseong00 Feb 14, 2026
81b8da2
[design] #108: ToolTipPopup 리팩터링
ikseong00 Feb 14, 2026
aec40c8
[design] #108: ItemOverlay 리네임 및 프리뷰 추가
ikseong00 Feb 14, 2026
1cc5636
[design] #108: TopBar 정리
ikseong00 Feb 14, 2026
4d12868
[design] #108: NekiTextButton 커스텀 색상 지원
ikseong00 Feb 14, 2026
12da32f
[design] #108: Background Modifier 정리 및 GridItemOverlay 적용
ikseong00 Feb 14, 2026
7c1d8e7
[design] #108: DoubleButtonOptionBottomSheet 레이아웃 조정
ikseong00 Feb 14, 2026
bde7d0d
[design] #108: QR 스캐너 토치 버튼 사이즈 조정
ikseong00 Feb 14, 2026
f7dfb29
[design] #108: 포즈 추천 칩 그림자 및 사이즈 조정
ikseong00 Feb 14, 2026
eebe53f
[design] #108: 아카이빙 엠티 뷰 구현
ikseong00 Feb 14, 2026
1eaa5da
[design] #108: 빈 폴더 이미지 리소스 추가
ikseong00 Feb 14, 2026
c436202
[feat] #108: 앨범에 사진이 없을 경우, 썸네일에 빈 갤러리 아이콘 표시
ikseong00 Feb 14, 2026
f062949
[design] #108: 새 앨범 추가 아이템 추가
ikseong00 Feb 14, 2026
02b9f38
[feat] #108: 공용 DropdownPopup 컴포넌트 추가 및 적용
ikseong00 Feb 14, 2026
39e1d12
[design] PhotoActionBar 아이콘 활성화 색상 변경 #108
ikseong00 Feb 14, 2026
0d4e0f1
[refactor] #108: 아카이브 앨범 아이템 Modifier 순서 변경
ikseong00 Feb 14, 2026
9989d21
[design] #108: ToolTip 닫기 버튼 클릭 이벤트 추가
ikseong00 Feb 14, 2026
5145ed5
[design] #108: 앨범 썸네일 빈 상태 조건 보완 및 clip 적용
ikseong00 Feb 14, 2026
1ab356a
[fix] #108: 선택 오버레이 border shape 파라미터 적용
ikseong00 Feb 14, 2026
eb6b42c
[design] #108: 빈 앨범 안내 문구 변경
ikseong00 Feb 14, 2026
875ad8c
[design] #108: 앨범 아이템 썸네일 cardShadow 제거
ikseong00 Feb 14, 2026
31497e7
[refactor] #108: RecommendationChip Modifier 순서 변경
ikseong00 Feb 14, 2026
748abae
Merge branch 'develop' into design/#108-design-system-change
ikseong00 Feb 15, 2026
74b6c90
Merge branch 'develop' into design/#108-design-system-change
ikseong00 Feb 15, 2026
5297d74
[chore] #108: 닉네임 글자수 제한 12자 적용
ikseong00 Feb 16, 2026
e61e664
[design] #108: 사진 상세 화면 액션바 아이콘 색상 변경
ikseong00 Feb 16, 2026
5917d82
[design] #108: NekiActionBar 뒤로가기 아이콘 크기 조정
ikseong00 Feb 16, 2026
7c756dc
[design] #108: cardShadow에 shape 파라미터 추가
ikseong00 Feb 16, 2026
c52e1a8
[chore] #108: 사용하지 않는 import 문 제거
ikseong00 Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
package com.neki.android.core.designsystem

import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.Text
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.graphics.Brush
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.neki.android.core.designsystem.ui.theme.NekiTheme

@Composable
fun NekiTextField(
textFieldState: TextFieldState,
modifier: Modifier = Modifier,
titleLabel: String? = null,
placeholder: String = "",
maxLength: Int? = null,
isError: Boolean = false,
textStyle: TextStyle = NekiTheme.typography.body16Medium.copy(
color = NekiTheme.colorScheme.gray900,
),
cursorBrush: Brush = SolidColor(NekiTheme.colorScheme.gray800),
lineLimits: TextFieldLineLimits = TextFieldLineLimits.SingleLine,
inputTransformation: InputTransformation? = null,
outputTransformation: OutputTransformation? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
) {
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()

val borderColor = when {
isError -> NekiTheme.colorScheme.primary600
isFocused -> NekiTheme.colorScheme.gray700
else -> NekiTheme.colorScheme.gray75
}

Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
titleLabel?.let {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 2.dp),
text = it,
style = NekiTheme.typography.body14Medium,
color = NekiTheme.colorScheme.gray700,
)
}

BasicTextField(
state = textFieldState,
modifier = Modifier
.fillMaxWidth()
.background(
color = NekiTheme.colorScheme.white,
shape = RoundedCornerShape(8.dp),
)
.border(
width = 1.dp,
color = borderColor,
shape = RoundedCornerShape(8.dp),
)
.padding(horizontal = 16.dp, vertical = 13.dp),
textStyle = textStyle,
inputTransformation = inputTransformation,
outputTransformation = outputTransformation,
interactionSource = interactionSource,
cursorBrush = cursorBrush,
lineLimits = lineLimits,
keyboardOptions = keyboardOptions,
decorator = { innerTextField ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Box(modifier = Modifier.weight(1f)) {
if (textFieldState.text.isEmpty()) {
Text(
text = placeholder,
style = NekiTheme.typography.body16Regular,
color = NekiTheme.colorScheme.gray300,
)
}
innerTextField()
}
maxLength?.let {
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "${textFieldState.text.length}/$maxLength",
style = NekiTheme.typography.caption12Regular,
color = NekiTheme.colorScheme.gray300,
)
}
}
},
)
}
}

@Composable
fun NekiTextFieldWithError(
textFieldState: TextFieldState,
modifier: Modifier = Modifier,
titleLabel: String? = null,
placeholder: String = "",
maxLength: Int? = null,
isError: Boolean = false,
errorMessage: String? = null,
textStyle: TextStyle = NekiTheme.typography.body16Medium.copy(
color = NekiTheme.colorScheme.gray900,
),
cursorBrush: Brush = SolidColor(NekiTheme.colorScheme.gray800),
lineLimits: TextFieldLineLimits = TextFieldLineLimits.SingleLine,
inputTransformation: InputTransformation? = null,
outputTransformation: OutputTransformation? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
NekiTextField(
textFieldState = textFieldState,
titleLabel = titleLabel,
placeholder = placeholder,
maxLength = maxLength,
isError = isError,
textStyle = textStyle,
cursorBrush = cursorBrush,
lineLimits = lineLimits,
inputTransformation = inputTransformation,
outputTransformation = outputTransformation,
keyboardOptions = keyboardOptions,
)

Text(
modifier = Modifier.heightIn(min = 16.dp),
text = if (isError) errorMessage.orEmpty() else "",
style = NekiTheme.typography.caption12Regular,
color = NekiTheme.colorScheme.primary600,
)
}
}

@Preview(showBackground = true)
@Composable
private fun NekiTextFieldPreview() {
NekiTheme {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
NekiTextField(
textFieldState = remember { TextFieldState() },
placeholder = "플레이스홀더",
)

NekiTextField(
textFieldState = remember { TextFieldState("입력된 텍스트") },
maxLength = 20,
)

NekiTextField(
textFieldState = remember { TextFieldState("에러 상태") },
isError = true,
)
}
}
}

@Preview(showBackground = true)
@Composable
private fun NekiTextFieldWithTitleLabelPreview() {
NekiTheme {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
NekiTextField(
textFieldState = remember { TextFieldState() },
titleLabel = "닉네임",
placeholder = "닉네임을 입력해주세요",
)

NekiTextField(
textFieldState = remember { TextFieldState("입력된 텍스트") },
titleLabel = "닉네임",
maxLength = 10,
)
}
}
}

@Preview(showBackground = true)
@Composable
private fun NekiTextFieldWithErrorPreview() {
NekiTheme {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
NekiTextFieldWithError(
textFieldState = remember { TextFieldState() },
placeholder = "플레이스홀더",
)

NekiTextFieldWithError(
textFieldState = remember { TextFieldState("입력된 텍스트") },
placeholder = "플레이스홀더",
)

NekiTextFieldWithError(
textFieldState = remember { TextFieldState("에러 상태") },
isError = true,
errorMessage = "올바른 값을 입력해주세요",
)
}
}
}

@Preview(showBackground = true)
@Composable
private fun NekiTextFieldWithErrorAndTitleLabelPreview() {
NekiTheme {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
NekiTextFieldWithError(
textFieldState = remember { TextFieldState() },
titleLabel = "닉네임",
placeholder = "닉네임을 입력해주세요",
maxLength = 10,
)

NekiTextFieldWithError(
textFieldState = remember { TextFieldState("입력된 텍스트") },
placeholder = "플레이스홀더",
maxLength = 10,
)

NekiTextFieldWithError(
textFieldState = remember { TextFieldState("에러 상태") },
titleLabel = "닉네임",
isError = true,
errorMessage = "이미 사용 중인 닉네임입니다",
maxLength = 10,
)
}
}
}
Loading