@expo/ui 57.0.15 → 57.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/ui/DatePickerView.kt +29 -18
  4. package/android/src/main/java/expo/modules/ui/DateRangePickerView.kt +191 -0
  5. package/android/src/main/java/expo/modules/ui/ExpoUIModule.kt +25 -0
  6. package/android/src/main/java/expo/modules/ui/HostView.kt +8 -1
  7. package/android/src/main/java/expo/modules/ui/RadioButtonView.kt +23 -1
  8. package/android/src/main/java/expo/modules/ui/VerticalSliderView.kt +145 -0
  9. package/android/src/main/java/expo/modules/ui/textfield/BasicTextField.kt +2 -0
  10. package/build/community/bottom-sheet/BottomSheet.android.d.ts.map +1 -1
  11. package/build/community/bottom-sheet/BottomSheet.ios.d.ts.map +1 -1
  12. package/build/jetpack-compose/DatePicker/index.d.ts +84 -0
  13. package/build/jetpack-compose/DatePicker/index.d.ts.map +1 -1
  14. package/build/jetpack-compose/RadioButton/index.d.ts +19 -0
  15. package/build/jetpack-compose/RadioButton/index.d.ts.map +1 -1
  16. package/build/jetpack-compose/Slider/index.d.ts +21 -0
  17. package/build/jetpack-compose/Slider/index.d.ts.map +1 -1
  18. package/build/swift-ui/modifiers/background.d.ts +32 -5
  19. package/build/swift-ui/modifiers/background.d.ts.map +1 -1
  20. package/build/swift-ui/modifiers/containerBackground.d.ts +9 -4
  21. package/build/swift-ui/modifiers/containerBackground.d.ts.map +1 -1
  22. package/build/swift-ui/modifiers/index.d.ts +45 -10
  23. package/build/swift-ui/modifiers/index.d.ts.map +1 -1
  24. package/build/swift-ui/modifiers/presentationModifiers.d.ts +6 -3
  25. package/build/swift-ui/modifiers/presentationModifiers.d.ts.map +1 -1
  26. package/expo-module.config.json +1 -1
  27. package/ios/Modifiers/BackgroundModifier.swift +10 -10
  28. package/ios/Modifiers/ContainerBackgroundModifier.swift +4 -4
  29. package/ios/Modifiers/PresentationModifiers.swift +3 -3
  30. package/ios/Modifiers/ScrollClipDisabledModifier.swift +16 -0
  31. package/ios/Modifiers/ViewModifierRegistry.swift +18 -8
  32. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{57.0.15/expo.modules.ui-57.0.15-sources.jar → 57.0.17/expo.modules.ui-57.0.17-sources.jar} +0 -0
  33. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17-sources.jar.md5 +1 -0
  34. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17-sources.jar.sha1 +1 -0
  35. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17-sources.jar.sha256 +1 -0
  36. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17-sources.jar.sha512 +1 -0
  37. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.aar +0 -0
  38. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.aar.md5 +1 -0
  39. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.aar.sha1 +1 -0
  40. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.aar.sha256 +1 -0
  41. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.aar.sha512 +1 -0
  42. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{57.0.15/expo.modules.ui-57.0.15.module → 57.0.17/expo.modules.ui-57.0.17.module} +22 -22
  43. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.module.md5 +1 -0
  44. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.module.sha1 +1 -0
  45. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.module.sha256 +1 -0
  46. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.module.sha512 +1 -0
  47. package/local-maven-repo/expo/modules/ui/expo.modules.ui/{57.0.15/expo.modules.ui-57.0.15.pom → 57.0.17/expo.modules.ui-57.0.17.pom} +1 -1
  48. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.pom.md5 +1 -0
  49. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.pom.sha1 +1 -0
  50. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.pom.sha256 +1 -0
  51. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.17/expo.modules.ui-57.0.17.pom.sha512 +1 -0
  52. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml +4 -4
  53. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.md5 +1 -1
  54. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha1 +1 -1
  55. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha256 +1 -1
  56. package/local-maven-repo/expo/modules/ui/expo.modules.ui/maven-metadata.xml.sha512 +1 -1
  57. package/package.json +4 -4
  58. package/spm.config.json +1 -1
  59. package/src/community/bottom-sheet/BottomSheet.android.tsx +5 -3
  60. package/src/community/bottom-sheet/BottomSheet.ios.tsx +4 -3
  61. package/src/jetpack-compose/DatePicker/index.tsx +152 -0
  62. package/src/jetpack-compose/RadioButton/index.tsx +20 -0
  63. package/src/jetpack-compose/Slider/index.tsx +33 -2
  64. package/src/swift-ui/modifiers/background.ts +49 -7
  65. package/src/swift-ui/modifiers/containerBackground.ts +10 -5
  66. package/src/swift-ui/modifiers/index.ts +62 -8
  67. package/src/swift-ui/modifiers/presentationModifiers.ts +7 -4
  68. package/src/universal/transformStyle.ios.ts +2 -2
  69. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15-sources.jar.md5 +0 -1
  70. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15-sources.jar.sha1 +0 -1
  71. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15-sources.jar.sha256 +0 -1
  72. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15-sources.jar.sha512 +0 -1
  73. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.aar +0 -0
  74. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.aar.md5 +0 -1
  75. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.aar.sha1 +0 -1
  76. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.aar.sha256 +0 -1
  77. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.aar.sha512 +0 -1
  78. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.module.md5 +0 -1
  79. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.module.sha1 +0 -1
  80. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.module.sha256 +0 -1
  81. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.module.sha512 +0 -1
  82. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.pom.md5 +0 -1
  83. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.pom.sha1 +0 -1
  84. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.pom.sha256 +0 -1
  85. package/local-maven-repo/expo/modules/ui/expo.modules.ui/57.0.15/expo.modules.ui-57.0.15.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,36 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 57.0.17 — 2026-09-08
14
+
15
+ ### 🎉 New features
16
+
17
+ - [Android] Added the `enabled` and `colors` props to the Jetpack Compose `RadioButton` component. ([#49852](https://github.com/expo/expo/pull/49852) by [@felipe-software](https://github.com/felipe-software))
18
+ - [iOS] Taught the `presentationBackground` modifier to paint with any `ShapeStyle`, matching SwiftUI, so a sheet can be backed by a material, a gradient or a hierarchical style instead of only a color. iOS 26 renders a material as a flat color rather than a translucent blur. ([#49767](https://github.com/expo/expo/pull/49767) by [@Den1Marshall](https://github.com/Den1Marshall))
19
+ - [iOS] Added the `scrollClipDisabled` modifier, which lets content that draws outside a scrollable view's bounds, such as a shadow or a scaled-up card, stay visible instead of being clipped. ([#49780](https://github.com/expo/expo/pull/49780) by [@Den1Marshall](https://github.com/Den1Marshall))
20
+ - [iOS] Taught the `tint`, `border`, `strokeBorder` and `containerBackground` modifiers to paint with any `ShapeStyle`, matching SwiftUI, where all four take a `ShapeStyle` rather than a color. The `color` parameter of `border` and `strokeBorder` is deprecated in favor of `content`, the name SwiftUI gives it. ([#49838](https://github.com/expo/expo/pull/49838) by [@Den1Marshall](https://github.com/Den1Marshall))
21
+ - [Android] Added the Jetpack Compose `DateRangePicker` and `DateRangePickerDialog` components. ([#49788](https://github.com/expo/expo/pull/49788) by [@felipe-software](https://github.com/felipe-software))
22
+
23
+ ### 🐛 Bug fixes
24
+
25
+ - [Android][iOS] Fix `community/bottom-sheet` content shrinking to its own width instead of filling the sheet when the sheet sizes to its content. ([#49742](https://github.com/expo/expo/issues/49742) by [@agung-adhinata](https://github.com/agung-adhinata)) ([#49762](https://github.com/expo/expo/pull/49762) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
26
+ - [iOS] Fixed `PlatformColor` and `DynamicColorIOS` values dropping the `backgroundColor` and `borderColor` of a universal component. Both were stringified to `"[object Object]"`, which the native color converter rejects, so the modifier was discarded without a trace. ([#49746](https://github.com/expo/expo/pull/49746) by [@Den1Marshall](https://github.com/Den1Marshall))
27
+
28
+ ## 57.0.16 — 2026-09-04
29
+
30
+ ### 🎉 New features
31
+
32
+ - [iOS] Taught the `background` modifier to paint with any `ShapeStyle`, so a view can be filled with a material, a gradient or a hierarchical style instead of only a color, and added the `ignoresSafeAreaEdges` option from SwiftUI's signature. Deprecated `backgroundOverlay` in favor of the `background` modifier and the `Background` component. ([#49621](https://github.com/expo/expo/pull/49621) by [@Den1Marshall](https://github.com/Den1Marshall))
33
+ - [android] Added the Jetpack Compose `VerticalSlider` component with support for reversed direction and custom thumb and track slots. ([#49612](https://github.com/expo/expo/pull/49612) by [@felipe-software](https://github.com/felipe-software))
34
+
35
+ ### 🐛 Bug fixes
36
+
37
+ - [Android] Fixed a `Text` or an `Icon` with no explicit color rendering black inside `Host`, which made it unreadable in the dark color scheme. `Host` now provides `LocalContentColor` from the color scheme. ([#49697](https://github.com/expo/expo/pull/49697) by [@expo-bot](https://github.com/expo-bot))
38
+
39
+ ### 💡 Others
40
+
41
+ - [Android] Marked `InnerTextFieldProps` and `PlaceholderProps` as optimized, so they no longer fall back to reflection-based props parsing and no longer log a warning on startup. ([#49632](https://github.com/expo/expo/issues/49632) by [@sfourdrinier](https://github.com/sfourdrinier)) ([#46919](https://github.com/expo/expo/pull/46919) by [@lukmccall](https://github.com/lukmccall))
42
+
13
43
  ## 57.0.15 — 2026-09-01
14
44
 
15
45
  ### 🎉 New features
@@ -12,13 +12,13 @@ apply plugin: 'expo-module-gradle-plugin'
12
12
  apply plugin: 'org.jetbrains.kotlin.plugin.compose'
13
13
 
14
14
  group = 'expo.modules.ui'
15
- version = '57.0.15'
15
+ version = '57.0.17'
16
16
 
17
17
  android {
18
18
  namespace "expo.modules.ui"
19
19
  defaultConfig {
20
20
  versionCode 1
21
- versionName "57.0.15"
21
+ versionName "57.0.17"
22
22
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
23
23
  }
24
24
  buildFeatures {
@@ -242,10 +242,14 @@ fun rememberSelectableDates(selectableDatesRecord: SelectableDatesRecord?): Sele
242
242
  // https://github.com/expo/expo/issues/47206
243
243
  @OptIn(ExperimentalMaterial3Api::class)
244
244
  @Composable
245
- fun rememberDatePickerYearRange(selectableDatesRecord: SelectableDatesRecord?, initialDateMillis: Long): IntRange {
245
+ fun rememberDatePickerYearRange(
246
+ selectableDatesRecord: SelectableDatesRecord?,
247
+ initialDateMillis: Long,
248
+ initialEndDateMillis: Long? = null
249
+ ): IntRange {
246
250
  val start = selectableDatesRecord?.start
247
251
  val end = selectableDatesRecord?.end
248
- return remember(start, end, initialDateMillis) {
252
+ return remember(start, end, initialDateMillis, initialEndDateMillis) {
249
253
  val defaults = DatePickerDefaults.YearRange
250
254
  val yearOf = { millis: Long ->
251
255
  Calendar.getInstance().apply { timeInMillis = millis }.get(Calendar.YEAR)
@@ -254,8 +258,9 @@ fun rememberDatePickerYearRange(selectableDatesRecord: SelectableDatesRecord?, i
254
258
  val endYear = end?.let(yearOf) ?: defaults.last
255
259
  // Keep the initially selected/displayed year inside the range so Material3 doesn't discard the
256
260
  // selection
257
- val initialYear = yearOf(initialDateMillis)
258
- minOf(startYear, initialYear)..maxOf(endYear, initialYear)
261
+ val initialStartYear = yearOf(initialDateMillis)
262
+ val initialEndYear = initialEndDateMillis?.let(yearOf) ?: initialStartYear
263
+ minOf(startYear, initialStartYear, initialEndYear)..maxOf(endYear, initialStartYear, initialEndYear)
259
264
  }
260
265
  }
261
266
 
@@ -319,6 +324,25 @@ fun buildTimePickerColors(
319
324
  )
320
325
  }
321
326
 
327
+ @OptIn(ExperimentalMaterial3Api::class)
328
+ @Composable
329
+ internal fun ApplyDatePickerDialogKeyboardBehavior(displayMode: DisplayMode) {
330
+ val view = LocalView.current
331
+ val keyboardController = LocalSoftwareKeyboardController.current
332
+ val originalSoftInputMode = remember(view) {
333
+ (view.parent as? DialogWindowProvider)?.window?.attributes?.softInputMode
334
+ }
335
+ SideEffect {
336
+ val window = (view.parent as? DialogWindowProvider)?.window ?: return@SideEffect
337
+ if (displayMode == DisplayMode.Picker) {
338
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
339
+ keyboardController?.hide()
340
+ } else if (originalSoftInputMode != null) {
341
+ window.setSoftInputMode(originalSoftInputMode)
342
+ }
343
+ }
344
+ }
345
+
322
346
  @OptIn(ExperimentalMaterial3Api::class)
323
347
  @Composable
324
348
  fun ExpoDatePickerDialogContent(props: DatePickerDialogProps, onDateSelected: (DatePickerResult) -> Unit, onDismissRequest: () -> Unit) {
@@ -358,21 +382,8 @@ fun ExpoDatePickerDialogContent(props: DatePickerDialogProps, onDateSelected: (D
358
382
  },
359
383
  colors = colors
360
384
  ) {
361
- val view = LocalView.current
362
- val keyboardController = LocalSoftwareKeyboardController.current
363
385
  val displayMode = state.displayMode
364
- val originalSoftInputMode = remember(view) {
365
- (view.parent as? DialogWindowProvider)?.window?.attributes?.softInputMode
366
- }
367
- SideEffect {
368
- val window = (view.parent as? DialogWindowProvider)?.window ?: return@SideEffect
369
- if (displayMode == DisplayMode.Picker) {
370
- window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
371
- keyboardController?.hide()
372
- } else if (originalSoftInputMode != null) {
373
- window.setSoftInputMode(originalSoftInputMode)
374
- }
375
- }
386
+ ApplyDatePickerDialogKeyboardBehavior(displayMode)
376
387
 
377
388
  // Material3's year-selector chevron tints from the ambient LocalContentColor (which defaults to
378
389
  // black), not `navigationContentColor`; bind the local so the chevron honors the navigation color.
@@ -0,0 +1,191 @@
1
+ package expo.modules.ui
2
+
3
+ import androidx.compose.material3.ButtonDefaults
4
+ import androidx.compose.material3.DatePickerDefaults
5
+ import androidx.compose.material3.DatePickerDialog
6
+ import androidx.compose.material3.DateRangePicker
7
+ import androidx.compose.material3.DateRangePickerState
8
+ import androidx.compose.material3.ExperimentalMaterial3Api
9
+ import androidx.compose.material3.LocalContentColor
10
+ import androidx.compose.material3.Text
11
+ import androidx.compose.material3.TextButton
12
+ import androidx.compose.runtime.Composable
13
+ import androidx.compose.runtime.CompositionLocalProvider
14
+ import androidx.compose.runtime.LaunchedEffect
15
+ import androidx.compose.runtime.remember
16
+ import androidx.compose.ui.platform.LocalConfiguration
17
+ import androidx.compose.ui.res.stringResource
18
+ import expo.modules.kotlin.records.Field
19
+ import expo.modules.kotlin.records.Record
20
+ import expo.modules.kotlin.types.OptimizedRecord
21
+ import expo.modules.kotlin.views.ComposeProps
22
+ import expo.modules.kotlin.views.FunctionalComposableScope
23
+ import expo.modules.kotlin.views.OptimizedComposeProps
24
+ import java.util.Date
25
+ import android.graphics.Color as AndroidColor
26
+
27
+ @OptimizedRecord
28
+ data class DateRangePickerResult(
29
+ @Field
30
+ val start: Long?,
31
+ @Field
32
+ val end: Long?
33
+ ) : Record
34
+
35
+ @OptimizedComposeProps
36
+ data class DateRangePickerProps(
37
+ val initialStartDate: Long? = null,
38
+ val initialEndDate: Long? = null,
39
+ val variant: Variant = Variant.PICKER,
40
+ val showVariantToggle: Boolean = true,
41
+ val color: AndroidColor? = null,
42
+ val elementColors: DateTimePickerColorOverrides = DateTimePickerColorOverrides(),
43
+ val selectableDates: SelectableDatesRecord? = null,
44
+ val modifiers: ModifierList = emptyList()
45
+ ) : ComposeProps
46
+
47
+ @OptimizedComposeProps
48
+ data class DateRangePickerDialogProps(
49
+ val initialStartDate: Long? = null,
50
+ val initialEndDate: Long? = null,
51
+ val variant: Variant = Variant.PICKER,
52
+ val showVariantToggle: Boolean = true,
53
+ val confirmButtonLabel: String? = null,
54
+ val dismissButtonLabel: String? = null,
55
+ val color: AndroidColor? = null,
56
+ val elementColors: DateTimePickerColorOverrides = DateTimePickerColorOverrides(),
57
+ val selectableDates: SelectableDatesRecord? = null
58
+ ) : ComposeProps
59
+
60
+ @OptIn(ExperimentalMaterial3Api::class)
61
+ @Composable
62
+ private fun rememberDateRangePickerState(
63
+ initialStartDate: Long?,
64
+ initialEndDate: Long?,
65
+ variant: Variant,
66
+ selectableDatesRecord: SelectableDatesRecord?
67
+ ): DateRangePickerState {
68
+ val locale = LocalConfiguration.current.locales[0]
69
+ val displayMode = variant.toDisplayMode()
70
+ val selectableDates = rememberSelectableDates(selectableDatesRecord)
71
+ val fallbackDate = remember { Date().time }
72
+ val initialDisplayedMonth = initialStartDate ?: fallbackDate
73
+ val yearRange = rememberDatePickerYearRange(
74
+ selectableDatesRecord,
75
+ initialDisplayedMonth,
76
+ initialEndDate
77
+ )
78
+
79
+ val state = remember(locale, displayMode, selectableDates, yearRange) {
80
+ DateRangePickerState(
81
+ locale = locale,
82
+ initialSelectedStartDateMillis = initialStartDate,
83
+ initialSelectedEndDateMillis = initialEndDate,
84
+ initialDisplayedMonthMillis = initialDisplayedMonth,
85
+ yearRange = yearRange,
86
+ initialDisplayMode = displayMode,
87
+ selectableDates = selectableDates
88
+ )
89
+ }
90
+
91
+ LaunchedEffect(state, initialStartDate, initialEndDate) {
92
+ if (state.selectedStartDateMillis != initialStartDate || state.selectedEndDateMillis != initialEndDate) {
93
+ try {
94
+ state.setSelection(initialStartDate, initialEndDate)
95
+ } catch (e: IllegalArgumentException) {
96
+ // Material 3 rejects a range outside the year range or an end date before the start date.
97
+ }
98
+ }
99
+ }
100
+
101
+ return state
102
+ }
103
+
104
+ @OptIn(ExperimentalMaterial3Api::class)
105
+ @Composable
106
+ fun ExpoDateRangePickerDialogContent(
107
+ props: DateRangePickerDialogProps,
108
+ onDateRangeSelected: (DateRangePickerResult) -> Unit,
109
+ onDismissRequest: () -> Unit
110
+ ) {
111
+ val state = rememberDateRangePickerState(
112
+ props.initialStartDate,
113
+ props.initialEndDate,
114
+ props.variant,
115
+ props.selectableDates
116
+ )
117
+ val colors = buildDatePickerColors(props.elementColors, props.color.composeOrNull, DatePickerDefaults.colors())
118
+ val buttonColors = props.color.composeOrNull
119
+ ?.let { ButtonDefaults.textButtonColors(contentColor = it) } ?: ButtonDefaults.textButtonColors()
120
+
121
+ DatePickerDialog(
122
+ onDismissRequest = { onDismissRequest() },
123
+ confirmButton = {
124
+ TextButton(
125
+ onClick = {
126
+ onDateRangeSelected(
127
+ DateRangePickerResult(
128
+ start = state.selectedStartDateMillis,
129
+ end = state.selectedEndDateMillis
130
+ )
131
+ )
132
+ },
133
+ enabled = state.selectedStartDateMillis != null && state.selectedEndDateMillis != null,
134
+ colors = buttonColors
135
+ ) {
136
+ Text(props.confirmButtonLabel ?: stringResource(android.R.string.ok))
137
+ }
138
+ },
139
+ dismissButton = {
140
+ TextButton(onClick = { onDismissRequest() }, colors = buttonColors) {
141
+ Text(props.dismissButtonLabel ?: stringResource(android.R.string.cancel))
142
+ }
143
+ },
144
+ colors = colors
145
+ ) {
146
+ val displayMode = state.displayMode
147
+ ApplyDatePickerDialogKeyboardBehavior(displayMode)
148
+
149
+ CompositionLocalProvider(LocalContentColor provides colors.navigationContentColor) {
150
+ DateRangePicker(
151
+ state = state,
152
+ showModeToggle = props.showVariantToggle,
153
+ colors = colors
154
+ )
155
+ }
156
+ }
157
+ }
158
+
159
+ @OptIn(ExperimentalMaterial3Api::class)
160
+ @Composable
161
+ fun FunctionalComposableScope.DateRangePickerContent(
162
+ props: DateRangePickerProps,
163
+ onDateRangeSelected: (DateRangePickerResult) -> Unit
164
+ ) {
165
+ val modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher)
166
+ val state = rememberDateRangePickerState(
167
+ props.initialStartDate,
168
+ props.initialEndDate,
169
+ props.variant,
170
+ props.selectableDates
171
+ )
172
+
173
+ LaunchedEffect(state.selectedStartDateMillis, state.selectedEndDateMillis) {
174
+ onDateRangeSelected(
175
+ DateRangePickerResult(
176
+ start = state.selectedStartDateMillis,
177
+ end = state.selectedEndDateMillis
178
+ )
179
+ )
180
+ }
181
+
182
+ val colors = buildDatePickerColors(props.elementColors, props.color.composeOrNull)
183
+ CompositionLocalProvider(LocalContentColor provides colors.navigationContentColor) {
184
+ DateRangePicker(
185
+ modifier = modifier,
186
+ state = state,
187
+ showModeToggle = props.showVariantToggle,
188
+ colors = colors
189
+ )
190
+ }
191
+ }
@@ -350,6 +350,14 @@ class ExpoUIModule : Module() {
350
350
  }
351
351
  }
352
352
 
353
+ ExpoUIView<VerticalSliderProps>("VerticalSliderView") {
354
+ Events("onValueChange", "onValueChangeFinished")
355
+
356
+ Content { props ->
357
+ VerticalSliderContent(props)
358
+ }
359
+ }
360
+
353
361
  ExpoUIView<ShapeProps>("ShapeView") {
354
362
  Content { props ->
355
363
  ShapeContent(props)
@@ -376,6 +384,14 @@ class ExpoUIModule : Module() {
376
384
  }
377
385
  }
378
386
 
387
+ ExpoUIView<DateRangePickerProps>("DateRangePickerView") {
388
+ val onDateRangeSelected by Event<DateRangePickerResult>()
389
+
390
+ Content { props ->
391
+ DateRangePickerContent(props) { onDateRangeSelected(it) }
392
+ }
393
+ }
394
+
379
395
  ExpoUIView<DatePickerDialogProps>("DatePickerDialogView") {
380
396
  val onDateSelected by Event<DatePickerResult>()
381
397
  val onDismissRequest by Event<Unit>()
@@ -385,6 +401,15 @@ class ExpoUIModule : Module() {
385
401
  }
386
402
  }
387
403
 
404
+ ExpoUIView<DateRangePickerDialogProps>("DateRangePickerDialogView") {
405
+ val onDateRangeSelected by Event<DateRangePickerResult>()
406
+ val onDismissRequest by Event<Unit>()
407
+
408
+ Content { props ->
409
+ ExpoDateRangePickerDialogContent(props, { onDateRangeSelected(it) }, { onDismissRequest(Unit) })
410
+ }
411
+ }
412
+
388
413
  ExpoUIView<TimePickerDialogProps>("TimePickerDialogView") {
389
414
  val onDateSelected by Event<DatePickerResult>()
390
415
  val onDismissRequest by Event<Unit>()
@@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.wrapContentHeight
14
14
  import androidx.compose.foundation.layout.wrapContentWidth
15
15
  import androidx.compose.material3.ColorScheme
16
16
  import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
17
+ import androidx.compose.material3.LocalContentColor
17
18
  import androidx.compose.material3.MaterialExpressiveTheme
18
19
  import androidx.compose.material3.darkColorScheme
19
20
  import androidx.compose.material3.dynamicDarkColorScheme
@@ -118,7 +119,13 @@ internal class HostView(context: Context, appContext: AppContext) :
118
119
  }
119
120
  val layoutDirection = props.layoutDirection.value.toLayoutDirection()
120
121
 
121
- CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
122
+ // Material3's `MaterialTheme` does not provide `LocalContentColor` — only `Surface` does — so
123
+ // without this a `Text` or `Icon` with no explicit color falls back to the `Color.Black`
124
+ // default of `LocalContentColor` and is unreadable in the dark color scheme.
125
+ CompositionLocalProvider(
126
+ LocalLayoutDirection provides layoutDirection,
127
+ LocalContentColor provides colorScheme.onSurface
128
+ ) {
122
129
  MaterialExpressiveTheme(colorScheme = colorScheme) {
123
130
  MaybeMatchContentsLayout {
124
131
  Children(this@Content)
@@ -1,15 +1,30 @@
1
1
  package expo.modules.ui
2
2
 
3
+ import android.graphics.Color
3
4
  import androidx.compose.material3.RadioButton
5
+ import androidx.compose.material3.RadioButtonDefaults
4
6
  import androidx.compose.runtime.Composable
7
+ import expo.modules.kotlin.records.Field
8
+ import expo.modules.kotlin.records.Record
9
+ import expo.modules.kotlin.types.OptimizedRecord
5
10
  import expo.modules.kotlin.views.ComposeProps
6
11
  import expo.modules.kotlin.views.FunctionalComposableScope
7
12
  import expo.modules.kotlin.views.OptimizedComposeProps
8
13
 
14
+ @OptimizedRecord
15
+ data class RadioButtonColors(
16
+ @Field val selectedColor: Color? = null,
17
+ @Field val unselectedColor: Color? = null,
18
+ @Field val disabledSelectedColor: Color? = null,
19
+ @Field val disabledUnselectedColor: Color? = null
20
+ ) : Record
21
+
9
22
  @OptimizedComposeProps
10
23
  data class RadioButtonProps(
11
24
  val selected: Boolean = false,
25
+ val enabled: Boolean = true,
12
26
  val clickable: Boolean = true,
27
+ val colors: RadioButtonColors = RadioButtonColors(),
13
28
  val modifiers: ModifierList = emptyList()
14
29
  ) : ComposeProps
15
30
 
@@ -21,6 +36,13 @@ fun FunctionalComposableScope.RadioButtonContent(
21
36
  RadioButton(
22
37
  selected = props.selected,
23
38
  onClick = onClick,
24
- modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher)
39
+ modifier = ModifierRegistry.applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher),
40
+ enabled = props.enabled,
41
+ colors = RadioButtonDefaults.colors(
42
+ selectedColor = props.colors.selectedColor.compose,
43
+ unselectedColor = props.colors.unselectedColor.compose,
44
+ disabledSelectedColor = props.colors.disabledSelectedColor.compose,
45
+ disabledUnselectedColor = props.colors.disabledUnselectedColor.compose
46
+ )
25
47
  )
26
48
  }
@@ -0,0 +1,145 @@
1
+ package expo.modules.ui
2
+
3
+ import androidx.compose.foundation.interaction.MutableInteractionSource
4
+ import androidx.compose.material3.ExperimentalMaterial3Api
5
+ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
6
+ import androidx.compose.material3.SliderDefaults
7
+ import androidx.compose.material3.SliderState
8
+ import androidx.compose.material3.VerticalSlider
9
+ import androidx.compose.runtime.Composable
10
+ import androidx.compose.runtime.SideEffect
11
+ import androidx.compose.runtime.getValue
12
+ import androidx.compose.runtime.mutableFloatStateOf
13
+ import androidx.compose.runtime.remember
14
+ import androidx.compose.runtime.setValue
15
+ import expo.modules.kotlin.viewevent.getValue
16
+ import expo.modules.kotlin.views.ComposeProps
17
+ import expo.modules.kotlin.views.FunctionalComposableScope
18
+ import expo.modules.kotlin.views.OptimizedComposeProps
19
+
20
+ @OptimizedComposeProps
21
+ data class VerticalSliderProps(
22
+ val value: Float = 0.0f,
23
+ val min: Float = 0.0f,
24
+ val max: Float = 1.0f,
25
+ val lowerLimit: Float? = null,
26
+ val upperLimit: Float? = null,
27
+ val steps: Int = 0,
28
+ val enabled: Boolean = true,
29
+ val reverseDirection: Boolean = false,
30
+ val colors: SliderColors = SliderColors(),
31
+ val modifiers: ModifierList = emptyList()
32
+ ) : ComposeProps
33
+
34
+ @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
35
+ @Composable
36
+ fun FunctionalComposableScope.VerticalSliderContent(props: VerticalSliderProps) {
37
+ val onValueChange by remember { this@VerticalSliderContent.EventDispatcher<SliderValueChangedEvent>() }
38
+ val onValueChangeFinished by remember { this@VerticalSliderContent.EventDispatcher<Unit>() }
39
+ val interactionSource = remember { MutableInteractionSource() }
40
+
41
+ val effectiveLower = maxOf(props.min, props.lowerLimit ?: Float.NEGATIVE_INFINITY)
42
+ val effectiveUpper = minOf(props.max, props.upperLimit ?: Float.POSITIVE_INFINITY)
43
+
44
+ var localValue by remember { mutableFloatStateOf(props.value.coerceIn(effectiveLower, effectiveUpper)) }
45
+ val clampedPropsValue = props.value.coerceIn(effectiveLower, effectiveUpper)
46
+ var prevPropsValue by remember { mutableFloatStateOf(clampedPropsValue) }
47
+
48
+ val sliderState = remember(props.min, props.max, props.steps) {
49
+ SliderState(
50
+ value = localValue.coerceIn(effectiveLower, effectiveUpper),
51
+ steps = props.steps,
52
+ valueRange = props.min..props.max
53
+ )
54
+ }
55
+
56
+ if (clampedPropsValue != prevPropsValue) {
57
+ prevPropsValue = clampedPropsValue
58
+ if (!sliderState.isDragging) {
59
+ localValue = clampedPropsValue
60
+ }
61
+ }
62
+
63
+ val thumbSlotView = findChildSlotView(view, "thumb")
64
+ val trackSlotView = findChildSlotView(view, "track")
65
+
66
+ val sliderColors = SliderDefaults.colors(
67
+ thumbColor = props.colors.thumbColor.compose,
68
+ activeTrackColor = props.colors.activeTrackColor.compose,
69
+ inactiveTrackColor = props.colors.inactiveTrackColor.compose,
70
+ activeTickColor = props.colors.activeTickColor.compose,
71
+ inactiveTickColor = props.colors.inactiveTickColor.compose
72
+ )
73
+ val modifier = ModifierRegistry.applyModifiers(
74
+ props.modifiers,
75
+ appContext,
76
+ composableScope,
77
+ globalEventDispatcher
78
+ )
79
+
80
+ SideEffect {
81
+ sliderState.onValueChange = {
82
+ val clamped = it.coerceIn(effectiveLower, effectiveUpper)
83
+ localValue = clamped
84
+ sliderState.value = clamped
85
+ onValueChange(SliderValueChangedEvent(clamped))
86
+ }
87
+ sliderState.onValueChangeFinished = {
88
+ onValueChangeFinished(Unit)
89
+ }
90
+ val target = localValue.coerceIn(effectiveLower, effectiveUpper)
91
+ if (sliderState.value != target) {
92
+ sliderState.value = target
93
+ }
94
+ }
95
+
96
+ when {
97
+ thumbSlotView != null && trackSlotView != null -> VerticalSlider(
98
+ state = sliderState,
99
+ enabled = props.enabled,
100
+ reverseDirection = props.reverseDirection,
101
+ colors = sliderColors,
102
+ interactionSource = interactionSource,
103
+ thumb = {
104
+ with(UIComposableScope()) { with(thumbSlotView) { Content() } }
105
+ },
106
+ track = {
107
+ with(UIComposableScope()) { with(trackSlotView) { Content() } }
108
+ },
109
+ modifier = modifier
110
+ )
111
+
112
+ thumbSlotView != null -> VerticalSlider(
113
+ state = sliderState,
114
+ enabled = props.enabled,
115
+ reverseDirection = props.reverseDirection,
116
+ colors = sliderColors,
117
+ interactionSource = interactionSource,
118
+ thumb = {
119
+ with(UIComposableScope()) { with(thumbSlotView) { Content() } }
120
+ },
121
+ modifier = modifier
122
+ )
123
+
124
+ trackSlotView != null -> VerticalSlider(
125
+ state = sliderState,
126
+ enabled = props.enabled,
127
+ reverseDirection = props.reverseDirection,
128
+ colors = sliderColors,
129
+ interactionSource = interactionSource,
130
+ track = {
131
+ with(UIComposableScope()) { with(trackSlotView) { Content() } }
132
+ },
133
+ modifier = modifier
134
+ )
135
+
136
+ else -> VerticalSlider(
137
+ state = sliderState,
138
+ enabled = props.enabled,
139
+ reverseDirection = props.reverseDirection,
140
+ colors = sliderColors,
141
+ interactionSource = interactionSource,
142
+ modifier = modifier
143
+ )
144
+ }
145
+ }
@@ -38,6 +38,7 @@ import expo.modules.ui.state.WorkletCallback
38
38
  */
39
39
  val LocalInnerTextField = compositionLocalOf<(@Composable () -> Unit)?> { null }
40
40
 
41
+ @OptimizedComposeProps
41
42
  class InnerTextFieldProps : ComposeProps
42
43
 
43
44
  /**
@@ -63,6 +64,7 @@ class InnerTextFieldView(context: Context, appContext: AppContext) :
63
64
  */
64
65
  val LocalTextFieldIsEmpty = compositionLocalOf { true }
65
66
 
67
+ @OptimizedComposeProps
66
68
  class PlaceholderProps : ComposeProps
67
69
 
68
70
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheet.android.d.ts","sourceRoot":"","sources":["../../../src/community/bottom-sheet/BottomSheet.android.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAMpE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAqC3C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CA+JlD"}
1
+ {"version":3,"file":"BottomSheet.android.d.ts","sourceRoot":"","sources":["../../../src/community/bottom-sheet/BottomSheet.android.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAMpE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAqC3C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CAiKlD"}
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheet.ios.d.ts","sourceRoot":"","sources":["../../../src/community/bottom-sheet/BottomSheet.ios.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAepE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAwD3C;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CA2KlD"}
1
+ {"version":3,"file":"BottomSheet.ios.d.ts","sourceRoot":"","sources":["../../../src/community/bottom-sheet/BottomSheet.ios.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAepE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAwD3C;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CA4KlD"}