Skip to content

Feature/date picker#23

Open
kez-lab wants to merge 8 commits intomainfrom
feature/date-picker
Open

Feature/date picker#23
kez-lab wants to merge 8 commits intomainfrom
feature/date-picker

Conversation

@kez-lab
Copy link
Owner

@kez-lab kez-lab commented Jan 1, 2026

This pull request introduces a new DatePicker component and enhances the generic Picker composable with improved accessibility, customization, and documentation. The key changes include the addition of the DatePicker, updates to state management, a more robust and accessible picker implementation, and improved usage documentation.

New Component & Documentation

  • Introduced DatePicker component: Added a complete date picker supporting year, month, and day selection with automatic day validation. Updated the documentation to describe its usage and parameters, including state management via rememberDatePickerState. [1] [2] [3]

Picker API & Accessibility Improvements

  • Refactored Picker API for clarity and flexibility: Replaced individual style/color parameters with PickerDefaults.colors() and PickerDefaults.textStyles(). Added input validation for parameters and improved calculation of item heights for better layout consistency. [1] [2]
  • Enhanced accessibility for Picker: Added accessibility semantics including picker labels, content descriptions, selection state, and collection info for screen readers. Each item is now described with context, improving usability for assistive technologies. [1] [2]

Usability & Customization

  • Infinite scrolling logic improved: Replaced Int.MAX_VALUE with a safer multiplier for infinite lists to prevent memory issues while maintaining a smooth user experience.
  • Preview and demo code updated: Refactored picker previews to use the new API, demonstrating color and style customization via PickerDefaults. [1] [2] [3] [4] [5] [6]

These changes collectively improve the library's feature set, accessibility, and ease of use for developers and end users.

Summary by CodeRabbit

New Features

  • Introduced DatePicker component enabling date selection with year, month, and day validation
  • Unified picker styling system with customizable colors and text styles for consistent appearance

Improvements

  • Enhanced accessibility with semantic labels and improved state management
  • Added sample screens demonstrating DatePicker and YearMonthPicker usage

✏️ Tip: You can customize this high-level summary in your review settings.

kez-lab and others added 8 commits December 13, 2025 03:48
…ating navigation and refactoring existing date picker sample.
- Add PickerStateTest with 6 tests for generic state management
- Add TimePickerStateTest with 12 tests covering 12/24-hour formats
- Add YearMonthPickerStateTest with 14 tests for year/month state
- Add PickerUtilsTest with 11 tests for utility calculations

Test coverage improved from 16 to 59 tests (+43 tests)
Supports 1.0.0 stability goals

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 1, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new DatePicker component with synchronized year/month/day selection, refactors the Picker component with centralized styling through PickerDefaults and PickerColors/PickerTextStyles, converts time utility values to functions for reactive updates, and updates existing pickers (YearMonthPicker, TimePicker) to use the new styling system. The sample app is updated to showcase the new DatePicker.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added DatePicker feature documentation with usage examples, API parameters (state, startLocalDate, yearItems, monthItems, visibleItemsCount), and DatePickerState properties (selectedYear, selectedMonth, selectedDay, maxDay).
Core Picker Refactoring
datetimepicker/src/commonMain/kotlin/com/kez/picker/Picker.kt
Major refactor: replaced individual color/style parameters with PickerColors and PickerTextStyles objects; added validation (non-empty items, odd visibleItemsCount, valid startIndex); changed infinite scrolling to use adjustedItems.size × INFINITE_SCROLL_MULTIPLIER; enhanced accessibility with Box semantics (pickerLabel, contentDescription, stateDescription, liveRegion); centralized text height calculation; added shared InteractionSource for clickable items.
Picker Defaults & Styling System
datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerDefaults.kt
New file: introduced PickerDefaults singleton object with default values (VisibleItemsCount, ItemPadding, DividerThickness, SpacingBetweenPickers, SelectedItemBackgroundShape, DividerShape); added composable factory functions colors() and textStyles() with Material3 pattern defaults; defined new public data classes PickerColors (dividerColor, selectedItemBackgroundColor, textColor, selectedTextColor) and PickerTextStyles (textStyle, selectedTextStyle).
State Management
datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerState.kt
Made PickerState.selectedItem have internal setter (public getter); internalized yearState/monthState in YearMonthPickerState and hourState/minuteState/periodState in TimePickerState; changed default parameters in rememberYearMonthPickerState and rememberTimePickerState from property access (currentDate.year) to function calls (currentDate().year).
DatePicker Feature (New)
datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePicker.kt, DatePickerState.kt
Introduced DatePicker composable with synchronized year/month/day pickers (weighted 1.2:0.8:0.8) and LaunchedEffect validation on year/month changes; introduced DatePickerState to manage year/month/day with internal PickerState containers, public read-only accessors (selectedYear, selectedMonth, selectedDay), computed maxDay, and leap-year logic; introduced rememberDatePickerState factory function.
Existing Pickers Refactoring
datetimepicker/src/commonMain/kotlin/com/kez/picker/time/TimePicker.kt, datetimepicker/src/commonMain/kotlin/com/kez/picker/date/YearMonthPicker.kt
Replaced individual color/style parameters with PickerColors/PickerTextStyles; updated default initialization from property access to function calls (currentDateTime() instead of currentDateTime); added pickerLabel for accessibility; updated child Picker invocations to use colors/textStyles instead of textStyle/dividerColor; changed itemTextAlignment to verticalAlignment.
Time Utilities
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt, TimeCalculation.kt
Converted public vals (currentDateTime, currentDate, currentYear, currentMonth, currentHour, currentMinute) to public functions that recompute values on each call for reactive updates; updated TimeCalculation to use local now = currentDateTime() variable; added LocalDate/LocalDateTime imports.
Tests (New)
datetimepicker/src/commonTest/kotlin/com/kez/picker/PickerStateTest.kt, PickerUtilsTest.kt, TimePickerStateTest.kt, YearMonthPickerStateTest.kt, date/DatePickerStateTest.kt
Added 6 new test files: PickerStateTest (6 tests for initialization/equality), PickerUtilsTest (11 tests for modulo/validation/scrolling math), TimePickerStateTest (6 tests for 24/12-hour formats), YearMonthPickerStateTest (14 tests for month/year boundaries), DatePickerStateTest (11 tests for leap-year/day-clamping logic), and updated TimeCalculationTest to use currentDay() function.
Sample App Navigation & Screens
sample/src/commonMain/kotlin/com/kez/picker/sample/App.kt, ui/navigation/Screen.kt, ui/screen/DatePickerSampleScreen.kt, YearMonthPickerSampleScreen.kt, HomeScreen.kt, BackgroundStylePickerScreen.kt, BottomSheetSampleScreen.kt, IntegratedPickerScreen.kt, TimePickerSampleScreen.kt
Updated App.kt to route Screen.DatePicker to YearMonthPickerScreen; added new Screen.DayPicker route; created new YearMonthPickerSampleScreen composable; refactored DatePickerSampleScreen from YearMonthPicker-based to DatePicker-based layout with TopAppBar; added DayPicker menu item; updated all sample screens to call date/time utility functions (currentDate(), currentHour(), currentMinute()) instead of accessing properties.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant DatePicker as DatePicker
    participant DatePickerState as DatePickerState
    participant Pickers as Year/Month/Day Pickers
    participant Validation as validate()
    
    User->>DatePicker: Selects Year or Month
    DatePicker->>Pickers: Updates yearState or monthState
    Pickers->>DatePickerState: State change detected
    DatePickerState->>Validation: LaunchedEffect triggers
    Validation->>Validation: Check maxDay for new year/month
    alt Day exceeds new maxDay
        Validation->>DatePickerState: Clamp dayState to maxDay
        DatePickerState->>Pickers: Refresh day items and scroll
    else Day is valid
        Validation->>DatePickerState: No adjustment needed
    end
    DatePickerState->>User: Display updated selectedYear/<br/>selectedMonth/selectedDay
    
    User->>DatePicker: Selects Day
    DatePicker->>Pickers: Updates dayState
    Pickers->>DatePickerState: selectedDay changes
    DatePickerState->>User: Display updated date
Loading

Estimated Code Review Effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly Related PRs

Suggested Labels

enhancement, feature, refactoring

Poem

🐰 A DatePicker hops into view,
With Year, Month, and Day dancing true,
State validated with leaps and bounds,
Defaults and colors in harmonious rounds,
The picker community grows with delight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/date picker' refers to adding a DatePicker component, which is a central part of the changeset, but is somewhat generic and doesn't capture the broader refactoring of the Picker API, accessibility enhancements, and state management improvements that are equally significant.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
README.md (1)

139-139: Fix the currentDate() function call syntax in YearMonthPickerExample.

Line 139 shows currentDate.year without parentheses, but currentDate is a function and must be called with currentDate(). This is inconsistent with the correct usage in the DatePickerExample (line 112) and the actual function definition in TimeUtil.kt.

Fix required
-        initialYear = currentDate.year,
-        initialMonth = currentDate.monthNumber
+        initialYear = currentDate().year,
+        initialMonth = currentDate().monthNumber
sample/src/commonMain/kotlin/com/kez/picker/sample/App.kt (1)

50-59: Flag naming inconsistency between routes and screens.

The route-to-screen mapping has become semantically misaligned:

  • Screen.DatePicker.routeYearMonthPickerSampleScreen
  • Screen.DayPicker.routeDatePickerSampleScreen

This may confuse developers expecting route names to match their destination screens. Consider either:

  1. Renaming the routes to match their screens (e.g., Screen.YearMonthPicker and Screen.DatePicker), or
  2. Swapping the screen assignments to restore semantic alignment.
🧹 Nitpick comments (12)
sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/BackgroundStylePickerScreen.kt (1)

54-60: Refactor to avoid multiple function calls and improve consistency.

Lines 54-55 call currentDate() twice, which could theoretically lead to inconsistent year/month values if the date changes between calls (e.g., during a month/year boundary at midnight). Additionally, currentHour is stored in a local variable while currentMinute() is called inline, creating inconsistency.

For consistency with IntegratedPickerScreen.kt (lines 74-76) and YearMonthPickerSampleScreen.kt (line 47), consider caching all current time values:

🔎 Suggested refactor
+    val currentDate = currentDate()
+    val currentHour = currentHour()
+    val currentMinute = currentMinute()
     val yearMonthState = rememberYearMonthPickerState(
-        initialYear = currentDate().year,
-        initialMonth = currentDate().month.number
+        initialYear = currentDate.year,
+        initialMonth = currentDate.month.number
     )
-    val currentHour = currentHour()
     val timeState = rememberTimePickerState(
         initialHour = currentHour,
-        initialMinute = currentMinute(),
+        initialMinute = currentMinute,
         initialPeriod = if (currentHour >= 12) TimePeriod.PM else TimePeriod.AM,
         timeFormat = TimeFormat.HOUR_12
     )
datetimepicker/src/commonTest/kotlin/com/kez/picker/PickerStateTest.kt (2)

7-14: KDoc mentions "State updates" but no corresponding tests exist.

The documentation states tests cover "State updates," but all tests only verify initial value assignments. Consider adding a test that modifies selectedItem and verifies the change is reflected, or update the KDoc to accurately describe the current coverage.


51-57: Test name may be misleading.

pickerState_sameInitialValue_areEqual tests that selectedItem values are equal, not that the state instances themselves are equal. This is fine behavior-wise, but consider renaming to pickerState_sameInitialValue_haveEqualSelectedItems for clarity.

sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/DatePickerSampleScreen.kt (1)

69-86: clip() applied without a visible background.

The Box has clip(RoundedCornerShape(16.dp)) but no background() modifier, so the clip has no visual effect. Either add a background color before clip() or remove the clip modifier.

🔎 Proposed fix
             Box(
                 modifier = Modifier
                     .fillMaxWidth()
-                    .clip(RoundedCornerShape(16.dp))
                     .padding(24.dp)
             ) {

Or add a background:

             Box(
                 modifier = Modifier
                     .fillMaxWidth()
+                    .background(MaterialTheme.colorScheme.surfaceVariant, RoundedCornerShape(16.dp))
                     .clip(RoundedCornerShape(16.dp))
                     .padding(24.dp)
             ) {
datetimepicker/src/commonTest/kotlin/com/kez/picker/YearMonthPickerStateTest.kt (1)

148-192: Special date tests are redundant with earlier boundary tests.

The "Special Date Tests" section (leap year, century year, end/start of year) largely duplicates earlier tests. YearMonthPickerState doesn't have day-level logic, so testing "leap year" or "century year" here just verifies basic initialization, which is already covered by boundary tests. Consider removing these or clarifying what unique behavior they validate.

datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePickerState.kt (2)

17-26: Multiple currentDate() calls in default parameters could return inconsistent values.

If rememberDatePickerState() is called exactly at midnight, the three currentDate() calls might span a date change, resulting in inconsistent initial values (e.g., Dec 31 year with Jan 1 month and day). Consider capturing the date once:

🔎 Proposed fix
 @Composable
 fun rememberDatePickerState(
-    initialYear: Int = currentDate().year,
-    initialMonth: Int = currentDate().month.number,
-    initialDay: Int = currentDate().day
+    initialYear: Int? = null,
+    initialMonth: Int? = null,
+    initialDay: Int? = null
 ): DatePickerState {
+    val now = currentDate()
+    val year = initialYear ?: now.year
+    val month = initialMonth ?: now.month.number
+    val day = initialDay ?: now.day
-    return remember(initialYear, initialMonth, initialDay) {
-        DatePickerState(initialYear, initialMonth, initialDay)
+    return remember(year, month, day) {
+        DatePickerState(year, month, day)
     }
 }

95-102: Silent fallback for invalid month values.

The else -> 31 branch silently handles invalid month values (outside 1-12). Consider throwing an IllegalArgumentException to fail fast, or add input validation in the constructor.

🔎 Proposed fix
     private fun daysInMonth(year: Int, month: Int): Int {
+        require(month in 1..12) { "Month must be in range 1-12, but was $month" }
         return when (month) {
             1, 3, 5, 7, 8, 10, 12 -> 31
             4, 6, 9, 11 -> 30
             2 -> if (isLeapYear(year)) 29 else 28
-            else -> 31 // Should not happen with 1-12 range
+            else -> error("Unreachable") // Now guarded by require above
         }
     }
datetimepicker/src/commonTest/kotlin/com/kez/picker/date/DatePickerStateTest.kt (1)

51-63: Consider adding tests for validate() behavior after state changes.

The current tests only verify initialization behavior. Since validate() is called when year/month changes (per DatePicker.kt), consider adding tests that simulate post-initialization state changes to ensure day clamping works correctly during runtime.

datetimepicker/src/commonMain/kotlin/com/kez/picker/date/YearMonthPicker.kt (1)

81-86: Consider adding startLocalDate as a key to remember if dynamic updates are expected.

The yearStartIndex and monthStartIndex are computed once and won't update if startLocalDate changes after initial composition. If this is intentional (start indices only matter for initial positioning), consider adding a clarifying comment. Otherwise, add startLocalDate as a key:

val yearStartIndex = remember(startLocalDate) {
    yearItems.indexOf(startLocalDate.year)
}
datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerState.kt (2)

53-60: Minor edge case: currentDate() is called twice in defaults.

If currentDate() is invoked across midnight, initialYear and initialMonth could theoretically be inconsistent (e.g., December 31st for one call, January 1st for the next). Consider computing the date once:

🔎 Proposed fix
 @Composable
 fun rememberYearMonthPickerState(
-    initialYear: Int = currentDate().year,
-    initialMonth: Int = currentDate().month.number
+    initialDate: LocalDate = currentDate(),
+    initialYear: Int = initialDate.year,
+    initialMonth: Int = initialDate.month.number
 ): YearMonthPickerState {

This is an extremely rare edge case, so it's optional to address.


102-125: Same timing edge case applies to time picker defaults.

currentHour() and currentMinute() are called separately, which could result in inconsistent time if called across a minute boundary. This is a minor edge case.

The initialPeriod derivation from initialHour (line 106) is correctly implemented.

datetimepicker/src/commonMain/kotlin/com/kez/picker/time/TimePicker.kt (1)

90-109: Same remember key consideration as YearMonthPicker.

The start index calculations won't update if startTime changes after initial composition. If this is intentional, consider adding a clarifying comment for maintainability.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58a078c and 3b298f4.

📒 Files selected for processing (25)
  • README.md
  • datetimepicker/src/commonMain/kotlin/com/kez/picker/Picker.kt
  • datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerDefaults.kt
  • datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerState.kt
  • datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePicker.kt
  • datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePickerState.kt
  • datetimepicker/src/commonMain/kotlin/com/kez/picker/date/YearMonthPicker.kt
  • datetimepicker/src/commonMain/kotlin/com/kez/picker/time/TimePicker.kt
  • datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeCalculation.kt
  • datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt
  • datetimepicker/src/commonTest/kotlin/com/kez/picker/PickerStateTest.kt
  • datetimepicker/src/commonTest/kotlin/com/kez/picker/PickerUtilsTest.kt
  • datetimepicker/src/commonTest/kotlin/com/kez/picker/TimePickerStateTest.kt
  • datetimepicker/src/commonTest/kotlin/com/kez/picker/YearMonthPickerStateTest.kt
  • datetimepicker/src/commonTest/kotlin/com/kez/picker/date/DatePickerStateTest.kt
  • datetimepicker/src/commonTest/kotlin/com/kez/picker/util/TimeCalculationTest.kt
  • sample/src/commonMain/kotlin/com/kez/picker/sample/App.kt
  • sample/src/commonMain/kotlin/com/kez/picker/sample/ui/navigation/Screen.kt
  • sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/BackgroundStylePickerScreen.kt
  • sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/BottomSheetSampleScreen.kt
  • sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/DatePickerSampleScreen.kt
  • sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/HomeScreen.kt
  • sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/IntegratedPickerScreen.kt
  • sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/TimePickerSampleScreen.kt
  • sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/YearMonthPickerSampleScreen.kt
🧰 Additional context used
🧬 Code graph analysis (12)
sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/BottomSheetSampleScreen.kt (2)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (3)
  • currentDate (52-52)
  • currentHour (80-80)
  • currentMinute (73-73)
datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerState.kt (1)
  • rememberYearMonthPickerState (52-60)
sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/TimePickerSampleScreen.kt (1)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (2)
  • currentHour (80-80)
  • currentMinute (73-73)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeCalculation.kt (1)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (1)
  • currentDateTime (44-45)
datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePicker.kt (3)
datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePickerState.kt (1)
  • rememberDatePickerState (17-26)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (1)
  • currentDate (52-52)
datetimepicker/src/commonMain/kotlin/com/kez/picker/Picker.kt (1)
  • Picker (88-332)
datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerState.kt (1)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (3)
  • currentDate (52-52)
  • currentHour (80-80)
  • currentMinute (73-73)
sample/src/commonMain/kotlin/com/kez/picker/sample/App.kt (1)
sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/YearMonthPickerSampleScreen.kt (1)
  • YearMonthPickerSampleScreen (42-121)
sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/YearMonthPickerSampleScreen.kt (4)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (1)
  • currentDate (52-52)
datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerState.kt (1)
  • rememberYearMonthPickerState (52-60)
sample/src/commonMain/kotlin/com/kez/picker/sample/StringUtils.kt (1)
  • getMonthName (27-43)
datetimepicker/src/commonMain/kotlin/com/kez/picker/date/YearMonthPicker.kt (1)
  • YearMonthPicker (53-134)
datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePickerState.kt (1)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (1)
  • currentDate (52-52)
datetimepicker/src/commonMain/kotlin/com/kez/picker/time/TimePicker.kt (1)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (1)
  • currentDateTime (44-45)
sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/IntegratedPickerScreen.kt (1)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (3)
  • currentDate (52-52)
  • currentHour (80-80)
  • currentMinute (73-73)
sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/BackgroundStylePickerScreen.kt (2)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (3)
  • currentDate (52-52)
  • currentHour (80-80)
  • currentMinute (73-73)
datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerState.kt (1)
  • rememberTimePickerState (102-125)
datetimepicker/src/commonMain/kotlin/com/kez/picker/date/YearMonthPicker.kt (2)
datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (1)
  • currentDate (52-52)
datetimepicker/src/commonMain/kotlin/com/kez/picker/Picker.kt (1)
  • Picker (88-332)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Agent
  • GitHub Check: Linux • desktop
  • GitHub Check: Linux • wasm
  • GitHub Check: macOS • iOS Simulator Test
  • GitHub Check: Linux • android
🔇 Additional comments (41)
datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerDefaults.kt (1)

21-102: LGTM! Well-designed defaults following Material3 patterns.

The centralized defaults approach is excellent:

  • Clear separation of concerns with PickerColors and PickerTextStyles
  • Appropriate use of @Composable for context-dependent defaults
  • Good use of @Immutable annotation for data classes
  • Comprehensive documentation
datetimepicker/src/commonTest/kotlin/com/kez/picker/PickerUtilsTest.kt (1)

1-143: Excellent test coverage for picker utilities.

The test suite comprehensively covers:

  • Modulo operations for cyclic behavior (positive, negative, large values)
  • Validation of odd/even visible item counts
  • Infinite scroll multiplier bounds (explicitly testing < Int.MAX_VALUE)
  • Start index calculations for both infinity and bounded modes
  • Fraction coercion and edge cases

This aligns well with the Picker.kt refactor replacing Int.MAX_VALUE with INFINITE_SCROLL_MULTIPLIER.

datetimepicker/src/commonMain/kotlin/com/kez/picker/Picker.kt (7)

109-115: Good input validation with clear error messages.

The validation requirements ensure:

  • Non-empty items list
  • Positive odd visibleItemsCount for proper center alignment
  • Valid startIndex range

Clear, actionable error messages help developers quickly identify issues.


58-63: Safer infinite scrolling with bounded multiplier.

Replacing Int.MAX_VALUE with INFINITE_SCROLL_MULTIPLIER = 1000 is a smart change that:

  • Prevents potential memory issues and integer overflow
  • Still provides a virtually infinite scrolling experience
  • Aligns with the test expectations in PickerUtilsTest.kt

180-189: Excellent accessibility improvements with semantic labels.

The picker-level semantics provide:

  • Descriptive contentDescription with current selection
  • stateDescription for screen readers
  • LiveRegionMode.Polite for non-intrusive updates
  • CollectionInfo for structural navigation

This significantly enhances the accessibility experience.


262-283: Comprehensive per-item accessibility semantics.

Each item now includes:

  • Role.Button for proper interaction semantics
  • Context-aware contentDescription with picker label and selection state
  • selected flag for current selection
  • CollectionItemInfo for position context

This provides excellent support for screen readers and assistive technologies.


228-228: Good practice: Shared InteractionSource reduces allocations.

Using a single shared MutableInteractionSource for all clickable items is more efficient than creating individual instances. Since indication = null is specified, ripple effects are disabled, making the shared source safe to use across all items.


143-147: Safe item retrieval with modulo arithmetic.

The getItem function correctly:

  • Handles empty lists with null return
  • Uses mod() for safe cyclic indexing
  • Uses getOrNull() for bounds safety

This prevents index out of bounds errors in the infinite scrolling logic.


155-171: Consider documenting the expected behavior when custom TextStyle objects have unspecified font properties.

The item height calculation defensively handles unspecified lineHeight and fontSize by falling back to 0.sp. With PickerDefaults.textStyles(), this is not a concern—both fontSize (16.sp and 22.sp) and lineHeight (inherited from LocalTextStyle.current) are specified. However, if a caller passes custom TextStyle objects with neither property explicitly set, textHeightSp could be 0.sp, resulting in minimal item heights (only padding). Either document this requirement or consider using TextStyle defaults more defensively.

datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeCalculation.kt (1)

32-40: Clean refactor using currentDateTime() function.

The change consolidates date/time extraction into a single currentDateTime() call, which:

  • Improves code clarity and maintainability
  • Ensures consistent timestamp across year/month/day extraction
  • Aligns with the broader TimeUtil API evolution in this PR
sample/src/commonMain/kotlin/com/kez/picker/sample/ui/navigation/Screen.kt (1)

8-8: LGTM!

The new DayPicker navigation route follows the established pattern and integrates cleanly with the existing Screen sealed class.

datetimepicker/src/commonTest/kotlin/com/kez/picker/util/TimeCalculationTest.kt (2)

19-19: LGTM!

Extracting currentDay upfront aligns with the existing pattern for currentYear and currentMonth, improving consistency.


33-33: LGTM!

The usage of currentDay is consistent across all test cases and maintains test correctness.

Also applies to: 54-54, 75-75, 96-96

sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/BottomSheetSampleScreen.kt (2)

62-62: LGTM!

The migration to the new function-based date/time API (currentDate(), currentHour(), currentMinute()) is clean and aligns with the PR's refactoring objectives. The local variable assignments ensure these values are captured at composition time.

Also applies to: 67-68


63-66: LGTM!

The picker state initialization correctly uses the captured date/time values and properly derives the initial period based on the hour value.

Also applies to: 69-74

sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/TimePickerSampleScreen.kt (2)

57-58: LGTM!

Capturing the current time values upfront using currentHour() and currentMinute() ensures both picker states are initialized with consistent values.


60-69: LGTM!

Both 12-hour and 24-hour TimePicker states are correctly initialized using the captured time values, with proper AM/PM period calculation for the 12-hour format.

sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/IntegratedPickerScreen.kt (1)

74-86: LGTM! Clean refactor to function-based time accessors.

The local variables ensure consistent state by calling each function once and reusing the result, avoiding potential edge cases where values might change during initialization.

sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/HomeScreen.kt (1)

82-95: LGTM! Menu structure updated correctly.

The renamed "YearMonthPicker Sample" and new "DayPicker Sample" menu items are properly configured with correct navigation routes and icons.

datetimepicker/src/commonTest/kotlin/com/kez/picker/TimePickerStateTest.kt (1)

1-203: Excellent test coverage for TimePickerState!

The test suite comprehensively covers:

  • 24-hour and 12-hour format initialization
  • Critical edge cases (midnight, noon, end of day)
  • Minute boundaries (0 and 59)
  • State independence between multiple instances
  • TimeFormat property validation

Well-structured and follows testing best practices.

sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/YearMonthPickerSampleScreen.kt (1)

47-121: LGTM! Well-implemented sample screen.

The screen demonstrates proper state management by caching currentDate() once and using derivedStateOf for reactive computed values. The UI composition follows Compose best practices with clear separation of concerns.

sample/src/commonMain/kotlin/com/kez/picker/sample/ui/screen/DatePickerSampleScreen.kt (1)

36-40: Function signature changed from internal to public without default parameter.

The function is now public and onBackPressed no longer has a default value. This is a breaking change for any existing callers that relied on the default. Verify this is intentional.

datetimepicker/src/commonTest/kotlin/com/kez/picker/YearMonthPickerStateTest.kt (1)

1-193: Good test coverage overall.

The test suite thoroughly covers initialization, boundaries, and state independence. The structure with section comments improves readability.

datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePickerState.kt (1)

39-79: Well-designed state encapsulation with automatic day validation.

The DatePickerState class properly encapsulates internal picker states while exposing read-only accessors. The init block correctly clamps the initial day, and the leap year logic is accurate.

datetimepicker/src/commonTest/kotlin/com/kez/picker/date/DatePickerStateTest.kt (1)

7-103: Solid test coverage for initialization and leap year logic.

The tests thoroughly cover maxDay calculations, initial day clamping, and leap year edge cases including century rules. The parameterized approach in testMaxDay_AllMonths and testMaxDay_February_LeapYearVariations is clean.

datetimepicker/src/commonMain/kotlin/com/kez/picker/util/TimeUtil.kt (2)

37-45: Good refactor: Converting properties to functions prevents stale time values.

The documentation clearly explains the rationale. This change ensures time-dependent defaults in picker states are always current when accessed.


54-80: Helper functions correctly derive from currentDateTime().

Each function properly computes its value from currentDateTime(), ensuring consistency and freshness. The API is clean and well-documented.

datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePicker.kt (2)

72-75: Good: State validation on year/month changes.

The LaunchedEffect correctly triggers validate() when year or month changes, ensuring the day is clamped to valid bounds (e.g., Feb 30 → Feb 28).


104-169: Well-structured picker layout with accessibility labels.

The Row layout with weighted pickers and pickerLabel for accessibility is well-implemented. Disabling infinite scroll for the day picker (isInfinity = false) is correct since day count varies by month.

datetimepicker/src/commonMain/kotlin/com/kez/picker/date/YearMonthPicker.kt (4)

20-27: LGTM! Import updates align with the new styling system.

The imports correctly bring in PickerColors, PickerDefaults, and PickerTextStyles to support the consolidated styling API.


58-72: Well-structured parameter defaults using centralized PickerDefaults.

The migration to PickerDefaults for default values provides consistent styling across picker components and simplifies customization.


101-111: Accessibility enhancement with pickerLabel is well implemented.

The addition of pickerLabel = "Year" and pickerLabel = "Month" (line 129) provides proper context for screen readers, aligning with the accessibility improvements in the broader Picker component.


150-171: Preview functions effectively demonstrate the new styling API.

The updated previews showcase how to use PickerDefaults.colors() and PickerDefaults.textStyles() for customization, serving as good documentation for API consumers.

datetimepicker/src/commonMain/kotlin/com/kez/picker/PickerState.kt (3)

28-42: Good encapsulation with internal set for selectedItem.

Making the setter internal ensures that only the Picker component can update the selected item through scrolling, preventing external code from causing inconsistent state. This is a sound API design choice.


71-90: Well-designed encapsulation for YearMonthPickerState.

Internalizing yearState and monthState while exposing read-only selectedYear and selectedMonth getters provides a clean public API and prevents external code from corrupting the picker's internal state.


138-168: Consistent encapsulation pattern applied to TimePickerState.

The internal state visibility with public read-only getters mirrors the YearMonthPickerState design. The KDoc comments are informative, especially noting the hour range differences between 12-hour and 24-hour formats.

datetimepicker/src/commonMain/kotlin/com/kez/picker/time/TimePicker.kt (5)

22-24: LGTM! Import updates consistent with the styling system refactor.


63-81: Consistent styling defaults applied from PickerDefaults.

The parameter defaults align with the YearMonthPicker and follow the centralized styling pattern established in the PR.


117-135: Period picker correctly configured with isInfinity = false.

The AM/PM picker appropriately disables infinite scrolling since there are only two options. The pickerLabel = "Period" provides good accessibility context.


138-174: Hour and Minute pickers properly updated with accessibility labels.

Both pickers include pickerLabel for screen reader context and use the new styling parameters consistently.


205-228: Preview functions effectively demonstrate the new styling API.

The TimePickerCustomColorsPreview and TimePickerLargeTextPreview provide good examples of customization using PickerDefaults.colors() and PickerDefaults.textStyles().

modifier: Modifier = Modifier,
pickerModifier: Modifier = Modifier,
state: DatePickerState = rememberDatePickerState(),
startLocalDate: LocalDate = currentDate(),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unused parameter: startLocalDate.

The startLocalDate parameter is declared but never used in the function body. Either remove it or use it for initial index calculations as the KDoc suggests.

🔎 Proposed fix - remove unused parameter
 @Composable
 fun DatePicker(
     modifier: Modifier = Modifier,
     pickerModifier: Modifier = Modifier,
     state: DatePickerState = rememberDatePickerState(),
-    startLocalDate: LocalDate = currentDate(),
     yearItems: List<Int> = YEAR_RANGE,
     monthItems: List<Int> = MONTH_RANGE,

Also update the KDoc to remove the @param startLocalDate line.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePicker.kt around
line 56, the constructor/function parameter startLocalDate is declared but never
used; remove the unused startLocalDate parameter from the signature, delete its
@param entry from the KDoc, and update any call sites to stop passing that
argument (or, alternatively, if intended to set the initial index, wire
startLocalDate into the initial index calculation where currentDate() is used);
ensure the code compiles and tests/builds after removing the parameter and
updating the documentation and callers.

Comment on lines +89 to +102
// To ensure 1:1 mapping with Picker's internal state on first render:
val yearStartIndex = remember { yearItems.indexOf(state.selectedYear) }
val monthStartIndex = remember { monthItems.indexOf(state.selectedMonth) }
// Day items change dynamically, so we can't fully pre-calculate a static list and index
// without being careful.

// Dynamic day items based on maxDay
val maxDay = state.maxDay
val dayItems = (1..maxDay).toList()
// Ensure selected day index is valid for the current dayItems
val dayStartIndex = remember(dayItems) {
val index = dayItems.indexOf(state.selectedDay)
if (index >= 0) index else 0
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

yearStartIndex and monthStartIndex are not reactive to external state changes.

Using remember {} without keys means these indices are computed only once. If the DatePickerState is externally modified or reused with different initial values, the pickers won't scroll to the correct position.

🔎 Proposed fix
-            val yearStartIndex = remember { yearItems.indexOf(state.selectedYear) }
-            val monthStartIndex = remember { monthItems.indexOf(state.selectedMonth) }
+            val yearStartIndex = remember(state.selectedYear, yearItems) { 
+                yearItems.indexOf(state.selectedYear).coerceAtLeast(0) 
+            }
+            val monthStartIndex = remember(state.selectedMonth, monthItems) { 
+                monthItems.indexOf(state.selectedMonth).coerceAtLeast(0) 
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// To ensure 1:1 mapping with Picker's internal state on first render:
val yearStartIndex = remember { yearItems.indexOf(state.selectedYear) }
val monthStartIndex = remember { monthItems.indexOf(state.selectedMonth) }
// Day items change dynamically, so we can't fully pre-calculate a static list and index
// without being careful.
// Dynamic day items based on maxDay
val maxDay = state.maxDay
val dayItems = (1..maxDay).toList()
// Ensure selected day index is valid for the current dayItems
val dayStartIndex = remember(dayItems) {
val index = dayItems.indexOf(state.selectedDay)
if (index >= 0) index else 0
}
// To ensure 1:1 mapping with Picker's internal state on first render:
val yearStartIndex = remember(state.selectedYear, yearItems) {
yearItems.indexOf(state.selectedYear).coerceAtLeast(0)
}
val monthStartIndex = remember(state.selectedMonth, monthItems) {
monthItems.indexOf(state.selectedMonth).coerceAtLeast(0)
}
// Day items change dynamically, so we can't fully pre-calculate a static list and index
// without being careful.
// Dynamic day items based on maxDay
val maxDay = state.maxDay
val dayItems = (1..maxDay).toList()
// Ensure selected day index is valid for the current dayItems
val dayStartIndex = remember(dayItems) {
val index = dayItems.indexOf(state.selectedDay)
if (index >= 0) index else 0
}
🤖 Prompt for AI Agents
In datetimepicker/src/commonMain/kotlin/com/kez/picker/date/DatePicker.kt around
lines 89 to 102, the yearStartIndex and monthStartIndex are currently wrapped in
remember { ... } with no keys so they are computed only once and won’t update
when DatePickerState changes; update the remembers to include reactive keys (at
minimum state.selectedYear and yearItems for yearStartIndex, and
state.selectedMonth and monthItems for monthStartIndex) so the indices are
recomputed when relevant state or item lists change, keeping the dayStartIndex
logic as-is.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces a new DatePicker component for selecting year, month, and day with automatic day validation, while refactoring the existing picker API to use a more consistent and accessible design pattern following Material3 guidelines. The changes include comprehensive test coverage and updated documentation.

Key changes:

  • Added DatePicker component with DatePickerState for complete date selection with leap year and month-length validation
  • Refactored picker API to use PickerDefaults.colors() and PickerDefaults.textStyles() for consistent styling across all picker components
  • Enhanced accessibility with semantic labels, content descriptions, selection states, and collection info for screen readers
  • Converted time utility properties to functions to prevent stale values in long-running applications

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
YearMonthPickerSampleScreen.kt New sample screen demonstrating standalone YearMonthPicker usage
TimePickerSampleScreen.kt Updated to use function-based time utilities instead of properties
IntegratedPickerScreen.kt Updated to use function-based time/date utilities
HomeScreen.kt Added new menu items for YearMonthPicker and DayPicker samples
DatePickerSampleScreen.kt Completely rewritten to showcase full DatePicker with year, month, and day selection
BottomSheetSampleScreen.kt Updated to use function-based utilities and removed unused import
BackgroundStylePickerScreen.kt Updated to use function-based utilities
Screen.kt Added DayPicker route for navigation
App.kt Updated routing to wire up YearMonthPickerSampleScreen and DatePickerSampleScreen
TimeCalculationTest.kt Refactored variable naming for clarity (currentDate → currentDay)
DatePickerStateTest.kt New comprehensive test suite for DatePickerState covering leap years, month validation, and day clamping
YearMonthPickerStateTest.kt New comprehensive test suite for YearMonthPickerState
TimePickerStateTest.kt New comprehensive test suite for TimePickerState covering 12/24 hour formats
PickerUtilsTest.kt New test suite for picker utility functions and calculations
PickerStateTest.kt New test suite for generic PickerState
TimeUtil.kt Converted time/date properties to functions with improved documentation
TimeCalculation.kt Updated to use function-based time utilities
TimePicker.kt Refactored to use PickerDefaults, added accessibility labels, renamed parameters
YearMonthPicker.kt Refactored to use PickerDefaults and added accessibility labels
DatePickerState.kt New state class for DatePicker with validation and leap year logic
DatePicker.kt New DatePicker component supporting year, month, and day selection
PickerState.kt Made selectedItem setter internal, changed sub-state visibility to internal, updated to use function-based utilities
PickerDefaults.kt New defaults object providing colors, text styles, and default values following Material3 patterns
Picker.kt Major refactoring with accessibility enhancements, API changes, input validation, and infinite scroll improvements
README.md Added DatePicker documentation and examples, updated API tables

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +41 to +42
var selectedItem: T by mutableStateOf(initialItem)
internal set
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The setter for selectedItem is now internal, which prevents external modification. However, this could be a breaking API change for existing users who might have been setting this property directly. Consider documenting this breaking change in the PR description or migration guide if this is intentional.

Copilot uses AI. Check for mistakes.
role = Role.Button,
indication = null,
interactionSource = remember { MutableInteractionSource() },
interactionSource = sharedInteractionSource,
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The shared MutableInteractionSource is created but not effectively utilized. Each clickable item still creates its own interaction behavior. If the intention is to share interaction state across items, this needs to be restructured. If not, this variable is unnecessary.

Suggested change
interactionSource = sharedInteractionSource,
interactionSource = remember { MutableInteractionSource() },

Copilot uses AI. Check for mistakes.
Comment on lines +155 to +165
val selectedLineHeight = selectedTextStyle.lineHeight.takeIf { it.isSpecified } ?: 0.sp
val unselectedLineHeight = textStyle.lineHeight.takeIf { it.isSpecified } ?: 0.sp
val largestLineHeight =
if (selectedLineHeight > unselectedLineHeight) selectedLineHeight else unselectedLineHeight

val selectedFontSize = selectedTextStyle.fontSize.takeIf { it.isSpecified } ?: 0.sp
val unselectedFontSize = textStyle.fontSize.takeIf { it.isSpecified } ?: 0.sp
val largestFontSize =
if (selectedFontSize > unselectedFontSize) selectedFontSize else unselectedFontSize

val textHeightSp = if (largestLineHeight > 0.sp) largestLineHeight else largestFontSize
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The calculation of textHeightSp uses largestLineHeight if specified, otherwise falls back to largestFontSize. However, when lineHeight is not specified, it defaults to 0.sp which could lead to incorrect height calculations. Consider using the font size directly when line height is not specified, or using a more appropriate fallback value.

Suggested change
val selectedLineHeight = selectedTextStyle.lineHeight.takeIf { it.isSpecified } ?: 0.sp
val unselectedLineHeight = textStyle.lineHeight.takeIf { it.isSpecified } ?: 0.sp
val largestLineHeight =
if (selectedLineHeight > unselectedLineHeight) selectedLineHeight else unselectedLineHeight
val selectedFontSize = selectedTextStyle.fontSize.takeIf { it.isSpecified } ?: 0.sp
val unselectedFontSize = textStyle.fontSize.takeIf { it.isSpecified } ?: 0.sp
val largestFontSize =
if (selectedFontSize > unselectedFontSize) selectedFontSize else unselectedFontSize
val textHeightSp = if (largestLineHeight > 0.sp) largestLineHeight else largestFontSize
val selectedEffectiveTextHeight = when {
selectedTextStyle.lineHeight.isSpecified && selectedTextStyle.lineHeight > 0.sp ->
selectedTextStyle.lineHeight
selectedTextStyle.fontSize.isSpecified && selectedTextStyle.fontSize > 0.sp ->
selectedTextStyle.fontSize
else -> 0.sp
}
val unselectedEffectiveTextHeight = when {
textStyle.lineHeight.isSpecified && textStyle.lineHeight > 0.sp ->
textStyle.lineHeight
textStyle.fontSize.isSpecified && textStyle.fontSize > 0.sp ->
textStyle.fontSize
else -> 0.sp
}
val textHeightSp =
if (selectedEffectiveTextHeight > unselectedEffectiveTextHeight) {
selectedEffectiveTextHeight
} else {
unselectedEffectiveTextHeight
}

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +77
internal val yearState = PickerState(initialYear)
internal val monthState = PickerState(initialMonth)
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The visibility of yearState and monthState has been changed to internal, making them inaccessible from external code. This is a breaking API change that prevents direct manipulation of individual picker states. If this is intentional to enforce encapsulation, it should be documented in the migration guide.

Suggested change
internal val yearState = PickerState(initialYear)
internal val monthState = PickerState(initialMonth)
val yearState = PickerState(initialYear)
val monthState = PickerState(initialMonth)

Copilot uses AI. Check for mistakes.
dividerShape: Shape = PickerDefaults.DividerShape,
isDividerVisible: Boolean = true,
isInfinity: Boolean = true,
pickerLabel: String? = null,
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The parameter name pickerLabel is used for accessibility but could be more descriptive. Consider renaming it to accessibilityLabel or contentLabel to make its purpose clearer, especially since it's specifically for accessibility features like screen readers.

Copilot uses AI. Check for mistakes.
fadingEdgeGradient = fadingEdgeGradient,
horizontalAlignment = horizontalAlignment,
itemTextAlignment = verticalAlignment,
verticalAlignment = verticalAlignment,
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The itemTextAlignment parameter has been renamed to verticalAlignment which is clearer and more consistent. However, this is a breaking API change that should be documented in the PR description or changelog to help users migrate their code.

Copilot uses AI. Check for mistakes.
val maxDay = state.maxDay
val dayItems = (1..maxDay).toList()
// Ensure selected day index is valid for the current dayItems
val dayStartIndex = remember(dayItems) {
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

Using remember(dayItems) for dayStartIndex may cause issues because dayItems is recreated every recomposition when maxDay changes. This could lead to the picker resetting to an unexpected index when the month changes. Consider using remember(state.selectedDay, dayItems.size) to be more explicit about the dependencies.

Suggested change
val dayStartIndex = remember(dayItems) {
val dayStartIndex = remember(state.selectedDay, dayItems.size) {

Copilot uses AI. Check for mistakes.
| Parameter | Description | Default |
|:--------------------|:----------------------------------------|:----------------------------|
| `state` | The state object to control the picker. | `rememberDatePickerState()` |
| `startLocalDate` | The initial date to set the picker to. | `currentDate` |
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The comment references currentDate without parentheses, but the actual API requires currentDate() as a function call. This inconsistency in documentation could confuse users.

Copilot uses AI. Check for mistakes.
Comment on lines +145 to +147
internal val hourState = PickerState(initialHour)
internal val minuteState = PickerState(initialMinute)
internal val periodState = PickerState(initialPeriod)
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The visibility of hourState, minuteState, and periodState has been changed to internal. This is a breaking API change that prevents external code from accessing these states directly. Document this change and provide guidance on how users should migrate if they were previously accessing these properties.

Copilot uses AI. Check for mistakes.
/**
* Default spacing between pickers in composite components (e.g., TimePicker, DatePicker).
*/
val SpacingBetweenPickers: Dp = 0.dp
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The default value for SpacingBetweenPickers is 0.dp, which removes spacing between picker columns. This differs from the previous default of 20.dp used in the old API. This change could affect the visual appearance of existing picker implementations and should be noted as a visual breaking change or the default should be reconsidered.

Suggested change
val SpacingBetweenPickers: Dp = 0.dp
val SpacingBetweenPickers: Dp = 20.dp

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants