Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
412 changes: 96 additions & 316 deletions README.md

Large diffs are not rendered by default.

192 changes: 192 additions & 0 deletions README_KO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Compose DateTimePicker

[![Read in English](https://img.shields.io/badge/README-English-blue)](./README.md)

Compose Multiplatform을 위한 범용적이고 커스터마이징 가능한 날짜 및 시간 선택 라이브러리입니다.
Android, iOS, Desktop (JVM), Web (Wasm) 등 다양한 플랫폼에서 일관된 UI 컴포넌트를 제공합니다.

## 주요 기능

* **멀티플랫폼 지원**: Android, iOS, Desktop (JVM), Web (Wasm) 환경을 지원하며 원활한 통합이 가능합니다.
* **TimePicker**: 12시간(오전/오후) 및 24시간 형식을 모두 지원합니다.
* **YearMonthPicker**: 년도와 월을 선택할 수 있는 전용 컴포넌트를 제공합니다.
* **커스터마이징**: 커스텀 아이템 렌더링, 스타일링, 구성 변경이 가능한 유연한 API를 제공합니다.
* **상태 관리**: `rememberTimePickerState` 및 `rememberYearMonthPickerState`를 통해 간편하게 상태를 관리할 수 있습니다.
* **접근성**: 스크린 리더 및 내비게이션 지원 등 접근성을 고려하여 설계되었습니다.

## 설치 방법

버전 카탈로그 또는 빌드 파일에 의존성을 추가하여 사용할 수 있습니다.

### 버전 카탈로그 (libs.versions.toml)

```toml
[versions]
composeDateTimePicker = "0.4.0"

[libraries]
compose-date-time-picker = { module = "io.github.kez-lab:compose-date-time-picker", version.ref = "composeDateTimePicker" }
```

### Gradle (build.gradle.kts)

```kotlin
dependencies {
implementation("io.github.kez-lab:compose-date-time-picker:0.4.0")
}
```

## 사용법

### TimePicker

시간 선택을 위해 `TimePicker`를 사용합니다. 12시간 및 24시간 형식을 지원합니다.

#### 1. 24시간 형식

```kotlin
import androidx.compose.runtime.Composable
import com.kez.picker.time.TimePicker
import com.kez.picker.rememberTimePickerState
import com.kez.picker.util.TimeFormat
import com.kez.picker.util.currentHour
import com.kez.picker.util.currentMinute

@Composable
fun TimePicker24hExample() {
val state = rememberTimePickerState(
initialHour = currentHour,
initialMinute = currentMinute,
timeFormat = TimeFormat.HOUR_24
)

TimePicker(
state = state
)
}
```

#### 2. 12시간 형식 (오전/오후)

```kotlin
import androidx.compose.runtime.Composable
import com.kez.picker.time.TimePicker
import com.kez.picker.rememberTimePickerState
import com.kez.picker.util.TimeFormat
import com.kez.picker.util.TimePeriod
import com.kez.picker.util.currentHour
import com.kez.picker.util.currentMinute

@Composable
fun TimePicker12hExample() {
// 12시간 형식 변환은 이제 state 내부에서 처리됩니다.
val state = rememberTimePickerState(
initialHour = currentHour,
initialMinute = currentMinute,
timeFormat = TimeFormat.HOUR_12
)

TimePicker(
state = state
)
}
```

### YearMonthPicker

특정 연도와 월을 선택할 때 `YearMonthPicker`를 사용합니다.

```kotlin
import androidx.compose.runtime.Composable
import com.kez.picker.date.YearMonthPicker
import com.kez.picker.rememberYearMonthPickerState
import com.kez.picker.util.currentDate

@Composable
fun YearMonthPickerExample() {
val state = rememberYearMonthPickerState(
initialYear = currentDate.year,
initialMonth = currentDate.monthNumber
)

YearMonthPicker(
state = state
)
}
```

### BottomSheet 통합

Picker 컴포넌트는 `ModalBottomSheet`나 다른 다이얼로그 컴포넌트 내에서도 원활하게 작동합니다.

```kotlin
import androidx.compose.material3.*
import androidx.compose.runtime.*
import com.kez.picker.time.TimePicker
import com.kez.picker.rememberTimePickerState
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomSheetPickerExample() {
var showBottomSheet by remember { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState()
val state = rememberTimePickerState()
val scope = rememberCoroutineScope()

Button(onClick = { showBottomSheet = true }) {
Text("시간 선택")
}

if (showBottomSheet) {
ModalBottomSheet(
onDismissRequest = { showBottomSheet = false },
sheetState = sheetState
) {
TimePicker(state = state)
// 확인 버튼 로직 등 추가 가능
}
}
}
```

## API 레퍼런스

### TimePicker

| 파라미터 | 설명 | 기본값 |
| :--- | :--- | :--- |
| `state` | Picker를 제어하기 위한 상태 객체입니다. | `rememberTimePickerState()` |
| `startTime` | Picker에 설정될 초기 시간입니다. | `currentDateTime` |
| `visibleItemsCount` | 리스트에 표시될 아이템의 개수입니다. | `3` |
| `textStyle` | 선택되지 않은 아이템의 텍스트 스타일입니다. | `16.sp` |
| `selectedTextStyle` | 선택된 아이템의 텍스트 스타일입니다. | `22.sp` |
| `dividerColor` | 구분선의 색상입니다. | `LocalContentColor.current` |

### YearMonthPicker

| 파라미터 | 설명 | 기본값 |
| :--- | :--- | :--- |
| `state` | Picker를 제어하기 위한 상태 객체입니다. | `rememberYearMonthPickerState()` |
| `startLocalDate` | Picker에 설정될 초기 날짜입니다. | `currentDate` |
| `yearItems` | 선택 가능한 연도 목록입니다. | `1900..2100` |
| `monthItems` | 선택 가능한 월 목록입니다. | `1..12` |
| `visibleItemsCount` | 리스트에 표시될 아이템의 개수입니다. | `3` |

## 라이선스

```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

The license text code block at Line 182 is missing a language specifier. Markdown linters expect fenced code blocks to declare a language.

Apply this diff to fix the code block:

-```
+```text
 Copyright 2024 KEZ Lab
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

182-182: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In README_KO.md around line 182, the fenced code block containing the license
text has no language specifier; update the opening fence from ``` to ```text so
the block becomes a text-coded fenced block (leave the license content and
closing ``` intact).

Copyright 2024 KEZ Lab

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
54 changes: 30 additions & 24 deletions datetimepicker/src/commonMain/kotlin/com/kez/picker/Picker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ import kotlin.math.abs
fun <T> Picker(
items: List<T>,
state: PickerState<T>,
startIndex: Int = 0,
modifier: Modifier = Modifier,
startIndex: Int = 0,
visibleItemsCount: Int = 3,
textStyle: TextStyle = LocalTextStyle.current,
selectedTextStyle: TextStyle = LocalTextStyle.current,
Expand All @@ -94,7 +94,8 @@ fun <T> Picker(
dividerThickness: Dp = 1.dp,
dividerShape: Shape = RoundedCornerShape(10.dp),
isDividerVisible: Boolean = true,
isInfinity: Boolean = true
isInfinity: Boolean = true,
content: @Composable ((T) -> Unit)? = null
) {
val density = LocalDensity.current
val visibleItemsMiddle = remember { visibleItemsCount / 2 }
Expand Down Expand Up @@ -205,14 +206,14 @@ fun <T> Picker(
}
}

val currentItemText = getItem(index)?.toString().orEmpty()

val item = getItem(index)
Box(
modifier = Modifier
.height(itemHeight)
.fillMaxWidth()
.clickable(
enabled = getItem(index) != null,
enabled = item != null,
role = Role.Button,
indication = null,
interactionSource = remember { MutableInteractionSource() },
Expand All @@ -230,30 +231,35 @@ fun <T> Picker(
.padding(itemPadding),
contentAlignment = Alignment.Center
) {
Text(
text = currentItemText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = textStyle.copy(
fontSize = lerp(
selectedTextStyle.fontSize,
textStyle.fontSize,
fraction
),
color = lerp(
selectedTextStyle.color,
textStyle.color,
fraction
),
),
textAlign = TextAlign.Center
)
if (item != null) {
if (content != null) {
content(item)
} else {
Text(
text = item.toString(),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = textStyle.copy(
fontSize = lerp(
selectedTextStyle.fontSize,
textStyle.fontSize,
fraction
),
color = lerp(
selectedTextStyle.color,
textStyle.color,
fraction
),
),
textAlign = TextAlign.Center
)
}
}
Comment on lines +234 to +257
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] When custom content is provided, the text styling effects (fontSize and color interpolation based on scroll position) are not applied. This could create inconsistent visual behavior compared to the default text rendering. Consider either documenting this limitation clearly or providing a way to pass the fraction value to the custom content so consumers can apply similar styling if desired.

Copilot uses AI. Check for mistakes.
}
}
}
}
}

/**
* Apply a fading edge effect to a modifier.
*
Expand Down
Loading
Loading