clox-picker 0.1.1
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.
- package/.eslintrc.js +2 -0
- package/README.md +35 -0
- package/android/build.gradle +42 -0
- package/android/src/main/java/expo/modules/cloxpicker/CloxPickerModule.kt +30 -0
- package/android/src/main/java/expo/modules/cloxpicker/CloxPickerView.kt +173 -0
- package/build/CloxPicker.types.d.ts +35 -0
- package/build/CloxPicker.types.d.ts.map +1 -0
- package/build/CloxPicker.types.js +2 -0
- package/build/CloxPicker.types.js.map +1 -0
- package/build/CloxPickerModule.d.ts +9 -0
- package/build/CloxPickerModule.d.ts.map +1 -0
- package/build/CloxPickerModule.js +3 -0
- package/build/CloxPickerModule.js.map +1 -0
- package/build/CloxPickerView.d.ts +5 -0
- package/build/CloxPickerView.d.ts.map +1 -0
- package/build/CloxPickerView.js +8 -0
- package/build/CloxPickerView.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +11 -0
- package/build/index.js.map +1 -0
- package/example/App.tsx +158 -0
- package/example/android/app/build.gradle +182 -0
- package/example/android/app/debug.keystore +0 -0
- package/example/android/app/proguard-rules.pro +14 -0
- package/example/android/app/src/debug/AndroidManifest.xml +7 -0
- package/example/android/app/src/debugOptimized/AndroidManifest.xml +7 -0
- package/example/android/app/src/main/AndroidManifest.xml +31 -0
- package/example/android/app/src/main/java/expo/modules/cloxpicker/example/MainActivity.kt +61 -0
- package/example/android/app/src/main/java/expo/modules/cloxpicker/example/MainApplication.kt +56 -0
- package/example/android/app/src/main/res/drawable/ic_launcher_background.xml +6 -0
- package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/example/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png +0 -0
- package/example/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png +0 -0
- package/example/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png +0 -0
- package/example/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png +0 -0
- package/example/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png +0 -0
- package/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp +0 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- package/example/android/app/src/main/res/values/colors.xml +6 -0
- package/example/android/app/src/main/res/values/strings.xml +5 -0
- package/example/android/app/src/main/res/values/styles.xml +11 -0
- package/example/android/app/src/main/res/values-night/colors.xml +1 -0
- package/example/android/build.gradle +24 -0
- package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/example/android/gradle.properties +65 -0
- package/example/android/gradlew +251 -0
- package/example/android/gradlew.bat +94 -0
- package/example/android/settings.gradle +39 -0
- package/example/app.json +30 -0
- package/example/assets/adaptive-icon.png +0 -0
- package/example/assets/favicon.png +0 -0
- package/example/assets/icon.png +0 -0
- package/example/assets/splash-icon.png +0 -0
- package/example/index.ts +5 -0
- package/example/ios/.xcode.env +11 -0
- package/example/ios/Podfile +60 -0
- package/example/ios/Podfile.lock +2211 -0
- package/example/ios/Podfile.properties.json +5 -0
- package/example/ios/cloxpickerexample/AppDelegate.swift +70 -0
- package/example/ios/cloxpickerexample/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
- package/example/ios/cloxpickerexample/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
- package/example/ios/cloxpickerexample/Images.xcassets/Contents.json +6 -0
- package/example/ios/cloxpickerexample/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
- package/example/ios/cloxpickerexample/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +23 -0
- package/example/ios/cloxpickerexample/Images.xcassets/SplashScreenLegacy.imageset/image.png +0 -0
- package/example/ios/cloxpickerexample/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png +0 -0
- package/example/ios/cloxpickerexample/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png +0 -0
- package/example/ios/cloxpickerexample/Info.plist +82 -0
- package/example/ios/cloxpickerexample/PrivacyInfo.xcprivacy +48 -0
- package/example/ios/cloxpickerexample/SplashScreen.storyboard +48 -0
- package/example/ios/cloxpickerexample/Supporting/Expo.plist +12 -0
- package/example/ios/cloxpickerexample/cloxpickerexample-Bridging-Header.h +3 -0
- package/example/ios/cloxpickerexample/cloxpickerexample.entitlements +5 -0
- package/example/ios/cloxpickerexample.xcodeproj/project.pbxproj +552 -0
- package/example/ios/cloxpickerexample.xcodeproj/xcshareddata/xcschemes/cloxpickerexample.xcscheme +88 -0
- package/example/ios/cloxpickerexample.xcworkspace/contents.xcworkspacedata +10 -0
- package/example/metro.config.js +30 -0
- package/example/package.json +37 -0
- package/example/tsconfig.json +11 -0
- package/example/yarn.lock +5942 -0
- package/expo-module.config.json +9 -0
- package/ios/CloxPicker.podspec +29 -0
- package/ios/CloxPickerModule.swift +48 -0
- package/ios/CloxPickerView.swift +372 -0
- package/package.json +56 -0
- package/src/CloxPicker.types.ts +33 -0
- package/src/CloxPickerModule.ts +10 -0
- package/src/CloxPickerView.tsx +12 -0
- package/src/index.ts +13 -0
- package/tsconfig.json +8 -0
package/.eslintrc.js
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# clox-picker
|
|
2
|
+
|
|
3
|
+
Clox Picker native module
|
|
4
|
+
|
|
5
|
+
# API documentation
|
|
6
|
+
|
|
7
|
+
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/clox-picker/)
|
|
8
|
+
- [Documentation for the main branch](https://docs.expo.dev/versions/unversioned/sdk/clox-picker/)
|
|
9
|
+
|
|
10
|
+
# Installation in managed Expo projects
|
|
11
|
+
|
|
12
|
+
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no documentation available then this library is not yet usable within managed projects — it is likely to be included in an upcoming Expo SDK release.
|
|
13
|
+
|
|
14
|
+
# Installation in bare React Native projects
|
|
15
|
+
|
|
16
|
+
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
|
|
17
|
+
|
|
18
|
+
### Add the package to your npm dependencies
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
npm install clox-picker
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Configure for Android
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Configure for iOS
|
|
30
|
+
|
|
31
|
+
Run `npx pod-install` after installing the npm package.
|
|
32
|
+
|
|
33
|
+
# Contributing
|
|
34
|
+
|
|
35
|
+
Contributions are very welcome! Please refer to guidelines described in the [contributing guide]( https://github.com/expo/expo#contributing).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
apply plugin: 'kotlin-android'
|
|
3
|
+
|
|
4
|
+
group = 'expo.modules.cloxpicker'
|
|
5
|
+
version = '1.0.0'
|
|
6
|
+
|
|
7
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
8
|
+
apply from: expoModulesCorePlugin
|
|
9
|
+
applyKotlinExpoModulesCorePlugin()
|
|
10
|
+
useExpoPublishing()
|
|
11
|
+
|
|
12
|
+
android {
|
|
13
|
+
namespace "expo.modules.cloxpicker"
|
|
14
|
+
compileSdkVersion 36
|
|
15
|
+
|
|
16
|
+
defaultConfig {
|
|
17
|
+
minSdkVersion 24
|
|
18
|
+
targetSdkVersion 36
|
|
19
|
+
versionCode 1
|
|
20
|
+
versionName "1.0.0"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
buildTypes {
|
|
24
|
+
release {
|
|
25
|
+
minifyEnabled false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
compileOptions {
|
|
30
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
31
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
kotlinOptions {
|
|
35
|
+
jvmTarget = "17"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dependencies {
|
|
40
|
+
implementation project(':expo-modules-core')
|
|
41
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.23"
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package expo.modules.cloxpicker
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.modules.Module
|
|
4
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
5
|
+
|
|
6
|
+
class CloxPickerModule : Module() {
|
|
7
|
+
override fun definition() = ModuleDefinition {
|
|
8
|
+
Name("CloxPicker")
|
|
9
|
+
|
|
10
|
+
Function("getHello") {
|
|
11
|
+
"Hello world"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
View(CloxPickerView::class) {
|
|
15
|
+
Prop("tabs") { view: CloxPickerView, tabs: List<Map<String, Any>> ->
|
|
16
|
+
view.setTabs(tabs)
|
|
17
|
+
}
|
|
18
|
+
Prop("height") { view: CloxPickerView, height: Double ->
|
|
19
|
+
view.setHeight(height)
|
|
20
|
+
}
|
|
21
|
+
Prop("value") { view: CloxPickerView, value: Int ->
|
|
22
|
+
view.setValue(value)
|
|
23
|
+
}
|
|
24
|
+
Prop("useLiquidGlass") { view: CloxPickerView, _: Boolean ->
|
|
25
|
+
// Android: no-op (always non–liquid-glass look)
|
|
26
|
+
}
|
|
27
|
+
Events("onTabChange")
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
package expo.modules.cloxpicker
|
|
2
|
+
|
|
3
|
+
import android.animation.ValueAnimator
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.drawable.GradientDrawable
|
|
6
|
+
import android.util.TypedValue
|
|
7
|
+
import android.view.Gravity
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.widget.FrameLayout
|
|
10
|
+
import android.widget.LinearLayout
|
|
11
|
+
import android.widget.TextView
|
|
12
|
+
import expo.modules.kotlin.AppContext
|
|
13
|
+
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
14
|
+
import expo.modules.kotlin.views.ExpoView
|
|
15
|
+
|
|
16
|
+
data class TabItem(val id: Int, val name: String, val iconUri: String?)
|
|
17
|
+
|
|
18
|
+
class CloxPickerView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
|
|
19
|
+
|
|
20
|
+
private val onTabChange by EventDispatcher()
|
|
21
|
+
|
|
22
|
+
private var tabs: List<TabItem> = listOf(TabItem(0, "Tab", null))
|
|
23
|
+
private var pickerHeight: Int = dp(44)
|
|
24
|
+
private var selectedIndex: Int = 0
|
|
25
|
+
private var thumbWidth: Int = 0
|
|
26
|
+
private var thumbHeight: Int = 0
|
|
27
|
+
private var thumbTop: Int = 0
|
|
28
|
+
|
|
29
|
+
private val trackBackground: View
|
|
30
|
+
private val thumb: View
|
|
31
|
+
private val segmentsContainer: LinearLayout
|
|
32
|
+
|
|
33
|
+
init {
|
|
34
|
+
setBackgroundColor(0)
|
|
35
|
+
|
|
36
|
+
trackBackground = View(context).apply {
|
|
37
|
+
setBackgroundColor(0xFFEBEBEB.toInt())
|
|
38
|
+
}
|
|
39
|
+
addView(trackBackground)
|
|
40
|
+
|
|
41
|
+
thumb = View(context).apply {
|
|
42
|
+
setBackgroundColor(0xFFFFFFFF.toInt() and 0x00FFFFFF or (0.65 * 255).toInt().shl(24))
|
|
43
|
+
}
|
|
44
|
+
addView(thumb)
|
|
45
|
+
|
|
46
|
+
segmentsContainer = LinearLayout(context).apply {
|
|
47
|
+
orientation = LinearLayout.HORIZONTAL
|
|
48
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
49
|
+
}
|
|
50
|
+
addView(segmentsContainer)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private fun dp(value: Int): Int {
|
|
54
|
+
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value.toFloat(), resources.displayMetrics).toInt()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fun setTabs(tabsList: List<Map<String, Any>>) {
|
|
58
|
+
tabs = tabsList.mapNotNull { dict ->
|
|
59
|
+
val name = dict["name"] as? String ?: return@mapNotNull null
|
|
60
|
+
val id = (dict["id"] as? Number)?.toInt() ?: return@mapNotNull null
|
|
61
|
+
val icon = (dict["icon"] as? Map<*, *>)?.get("uri") as? String
|
|
62
|
+
TabItem(id, name, icon)
|
|
63
|
+
}
|
|
64
|
+
if (tabs.isEmpty()) tabs = listOf(TabItem(0, "Tab", null))
|
|
65
|
+
buildSegments()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fun setHeight(height: Double) {
|
|
69
|
+
pickerHeight = dp(height.toInt().coerceIn(24, 96))
|
|
70
|
+
requestLayout()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fun setValue(value: Int) {
|
|
74
|
+
val newIndex = value.coerceIn(0, (tabs.size - 1).coerceAtLeast(0))
|
|
75
|
+
if (newIndex != selectedIndex) {
|
|
76
|
+
selectedIndex = newIndex
|
|
77
|
+
animateThumbTo(selectedIndex)
|
|
78
|
+
buildSegments()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private fun buildSegments() {
|
|
83
|
+
segmentsContainer.removeAllViews()
|
|
84
|
+
val count = tabs.size.coerceAtLeast(1)
|
|
85
|
+
for ((index, tab) in tabs.withIndex()) {
|
|
86
|
+
val textView = TextView(context).apply {
|
|
87
|
+
text = tab.name
|
|
88
|
+
setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f)
|
|
89
|
+
setTextColor(if (index == selectedIndex) 0xFF000000.toInt() else 0xFF8E8E93.toInt())
|
|
90
|
+
setTypeface(null, if (index == selectedIndex) android.graphics.Typeface.BOLD else android.graphics.Typeface.NORMAL)
|
|
91
|
+
gravity = Gravity.CENTER
|
|
92
|
+
layoutParams = LinearLayout.LayoutParams(0, pickerHeight, 1f)
|
|
93
|
+
setOnClickListener {
|
|
94
|
+
if (index != selectedIndex) {
|
|
95
|
+
selectedIndex = index
|
|
96
|
+
animateThumbTo(selectedIndex)
|
|
97
|
+
onTabChange(mapOf("tabIndex" to index))
|
|
98
|
+
buildSegments()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
segmentsContainer.addView(textView)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private var thumbAnimator: ValueAnimator? = null
|
|
107
|
+
|
|
108
|
+
private fun animateThumbTo(index: Int) {
|
|
109
|
+
thumbAnimator?.cancel()
|
|
110
|
+
if (width == 0 || thumbWidth == 0) return
|
|
111
|
+
val count = tabs.size.coerceAtLeast(1)
|
|
112
|
+
val segmentWidth = width / count
|
|
113
|
+
val padding = dp(4)
|
|
114
|
+
val tw = segmentWidth - padding * 2
|
|
115
|
+
val targetLeft = padding + index * segmentWidth + (segmentWidth - tw) / 2
|
|
116
|
+
|
|
117
|
+
val startLeft = thumb.left.toFloat()
|
|
118
|
+
thumbAnimator = ValueAnimator.ofFloat(startLeft, targetLeft.toFloat()).apply {
|
|
119
|
+
duration = 250
|
|
120
|
+
addUpdateListener { anim ->
|
|
121
|
+
val x = (anim.animatedValue as Float).toInt()
|
|
122
|
+
thumb.layout(x, thumbTop, x + thumbWidth, thumbTop + thumbHeight)
|
|
123
|
+
}
|
|
124
|
+
start()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
129
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
130
|
+
val w = right - left
|
|
131
|
+
val h = bottom - top
|
|
132
|
+
if (w == 0 || h == 0) return
|
|
133
|
+
|
|
134
|
+
val count = tabs.size.coerceAtLeast(1)
|
|
135
|
+
val segmentWidth = w / count
|
|
136
|
+
val padding = dp(4)
|
|
137
|
+
val thumbWidth = segmentWidth - padding * 2
|
|
138
|
+
val thumbHeight = pickerHeight - padding * 2
|
|
139
|
+
val topOffset = (h - pickerHeight) / 2
|
|
140
|
+
|
|
141
|
+
// Track (pill background)
|
|
142
|
+
(trackBackground.background as? GradientDrawable)?.cornerRadius = (pickerHeight / 2).toFloat()
|
|
143
|
+
if (trackBackground.background !is GradientDrawable) {
|
|
144
|
+
trackBackground.background = GradientDrawable().apply {
|
|
145
|
+
setColor(0xFFEBEBEB.toInt())
|
|
146
|
+
cornerRadius = (pickerHeight / 2).toFloat()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
trackBackground.layout(0, topOffset, w, topOffset + pickerHeight)
|
|
150
|
+
|
|
151
|
+
// Thumb (rounded rect)
|
|
152
|
+
this.thumbWidth = thumbWidth
|
|
153
|
+
this.thumbHeight = thumbHeight
|
|
154
|
+
this.thumbTop = topOffset + padding
|
|
155
|
+
val thumbLeft = padding + selectedIndex * segmentWidth + (segmentWidth - thumbWidth) / 2
|
|
156
|
+
if (thumb.background !is GradientDrawable) {
|
|
157
|
+
thumb.background = GradientDrawable().apply {
|
|
158
|
+
setColor(0xFFFFFFFF.toInt() and 0x00FFFFFF or (0.65 * 255).toInt().shl(24))
|
|
159
|
+
cornerRadius = (thumbHeight / 2).toFloat()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
thumb.layout(thumbLeft, thumbTop, thumbLeft + thumbWidth, thumbTop + thumbHeight)
|
|
163
|
+
|
|
164
|
+
// Segments container
|
|
165
|
+
segmentsContainer.layout(0, topOffset, w, topOffset + pickerHeight)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
169
|
+
val h = View.resolveSize(pickerHeight, heightMeasureSpec)
|
|
170
|
+
val w = View.getDefaultSize(suggestedMinimumWidth, widthMeasureSpec)
|
|
171
|
+
setMeasuredDimension(w, h)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
export type CloxPickerModuleEvents = {};
|
|
3
|
+
/** Single tab item for the segmented picker */
|
|
4
|
+
export interface CloxPickerTab {
|
|
5
|
+
/** Optional icon (local file or remote URL) */
|
|
6
|
+
icon?: {
|
|
7
|
+
uri: string;
|
|
8
|
+
};
|
|
9
|
+
/** Display label */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Stable id (used as key; can match index) */
|
|
12
|
+
id: number;
|
|
13
|
+
}
|
|
14
|
+
export interface CloxPickerViewProps {
|
|
15
|
+
/** Tab items (icon optional, name, id) */
|
|
16
|
+
tabs: CloxPickerTab[];
|
|
17
|
+
/** Height of the picker in points/dp */
|
|
18
|
+
height: number;
|
|
19
|
+
/** Current selected tab index */
|
|
20
|
+
value: number;
|
|
21
|
+
/** Called when user selects a different tab (passes tab index) */
|
|
22
|
+
onTabChange?: (event: {
|
|
23
|
+
tabIndex: number;
|
|
24
|
+
}) => void;
|
|
25
|
+
/**
|
|
26
|
+
* iOS only: use Liquid Glass effect when available (iOS 26+).
|
|
27
|
+
* When false, uses the same non–liquid-glass look on all iOS versions.
|
|
28
|
+
* Default: true.
|
|
29
|
+
*/
|
|
30
|
+
useLiquidGlass?: boolean;
|
|
31
|
+
/** Color for selected tab (hex string, e.g., "#007AFF" or "blue") */
|
|
32
|
+
selectedColor?: string;
|
|
33
|
+
style?: StyleProp<ViewStyle>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=CloxPicker.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloxPicker.types.d.ts","sourceRoot":"","sources":["../src/CloxPicker.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAExC,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,mBAAmB;IAClC,0CAA0C;IAC1C,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACpD;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloxPicker.types.js","sourceRoot":"","sources":["../src/CloxPicker.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StyleProp, ViewStyle } from 'react-native';\n\nexport type CloxPickerModuleEvents = {};\n\n/** Single tab item for the segmented picker */\nexport interface CloxPickerTab {\n /** Optional icon (local file or remote URL) */\n icon?: { uri: string };\n /** Display label */\n name: string;\n /** Stable id (used as key; can match index) */\n id: number;\n}\n\nexport interface CloxPickerViewProps {\n /** Tab items (icon optional, name, id) */\n tabs: CloxPickerTab[];\n /** Height of the picker in points/dp */\n height: number;\n /** Current selected tab index */\n value: number;\n /** Called when user selects a different tab (passes tab index) */\n onTabChange?: (event: { tabIndex: number }) => void;\n /**\n * iOS only: use Liquid Glass effect when available (iOS 26+).\n * When false, uses the same non–liquid-glass look on all iOS versions.\n * Default: true.\n */\n useLiquidGlass?: boolean;\n /** Color for selected tab (hex string, e.g., \"#007AFF\" or \"blue\") */\n selectedColor?: string;\n style?: StyleProp<ViewStyle>;\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NativeModule } from 'expo';
|
|
2
|
+
import { CloxPickerModuleEvents } from './CloxPicker.types';
|
|
3
|
+
declare class CloxPickerModule extends NativeModule<CloxPickerModuleEvents> {
|
|
4
|
+
getHello(): string;
|
|
5
|
+
setColorScheme(scheme: 'light' | 'dark' | 'auto'): void;
|
|
6
|
+
}
|
|
7
|
+
declare const _default: CloxPickerModule;
|
|
8
|
+
export default _default;
|
|
9
|
+
//# sourceMappingURL=CloxPickerModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloxPickerModule.d.ts","sourceRoot":"","sources":["../src/CloxPickerModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,OAAO,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;IACzE,QAAQ,IAAI,MAAM;IAClB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI;CACxD;;AAED,wBAAmE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloxPickerModule.js","sourceRoot":"","sources":["../src/CloxPickerModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AASzD,eAAe,mBAAmB,CAAmB,YAAY,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\nimport { CloxPickerModuleEvents } from './CloxPicker.types';\n\ndeclare class CloxPickerModule extends NativeModule<CloxPickerModuleEvents> {\n getHello(): string;\n setColorScheme(scheme: 'light' | 'dark' | 'auto'): void;\n}\n\nexport default requireNativeModule<CloxPickerModule>('CloxPicker');\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloxPickerView.d.ts","sourceRoot":"","sources":["../src/CloxPickerView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAI9D,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,qBAExD;AAED,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
const NativeCloxPickerView = requireNativeView('CloxPicker');
|
|
4
|
+
export function CloxPickerView(props) {
|
|
5
|
+
return <NativeCloxPickerView {...props}/>;
|
|
6
|
+
}
|
|
7
|
+
export default CloxPickerView;
|
|
8
|
+
//# sourceMappingURL=CloxPickerView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloxPickerView.js","sourceRoot":"","sources":["../src/CloxPickerView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,oBAAoB,GAAG,iBAAiB,CAAsB,YAAY,CAAC,CAAC;AAElF,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,OAAO,CAAC,oBAAoB,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AAC7C,CAAC;AAED,eAAe,cAAc,CAAC","sourcesContent":["import { requireNativeView } from 'expo';\nimport * as React from 'react';\n\nimport type { CloxPickerViewProps } from './CloxPicker.types';\n\nconst NativeCloxPickerView = requireNativeView<CloxPickerViewProps>('CloxPicker');\n\nexport function CloxPickerView(props: CloxPickerViewProps) {\n return <NativeCloxPickerView {...props} />;\n}\n\nexport default CloxPickerView;\n"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function getHello(): string;
|
|
2
|
+
export declare function setColorScheme(scheme: 'light' | 'dark' | 'auto'): void;
|
|
3
|
+
export { default as CloxPickerModule } from './CloxPickerModule';
|
|
4
|
+
export { CloxPickerView, default as CloxPickerViewDefault } from './CloxPickerView';
|
|
5
|
+
export * from './CloxPicker.types';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAEtE;AAED,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACpF,cAAc,oBAAoB,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import CloxPickerModule from './CloxPickerModule';
|
|
2
|
+
export function getHello() {
|
|
3
|
+
return CloxPickerModule.getHello();
|
|
4
|
+
}
|
|
5
|
+
export function setColorScheme(scheme) {
|
|
6
|
+
CloxPickerModule.setColorScheme(scheme);
|
|
7
|
+
}
|
|
8
|
+
export { default as CloxPickerModule } from './CloxPickerModule';
|
|
9
|
+
export { CloxPickerView, default as CloxPickerViewDefault } from './CloxPickerView';
|
|
10
|
+
export * from './CloxPicker.types';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,MAAM,oBAAoB,CAAC;AAElD,MAAM,UAAU,QAAQ;IACtB,OAAO,gBAAgB,CAAC,QAAQ,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAiC;IAC9D,gBAAgB,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC;AAED,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACpF,cAAc,oBAAoB,CAAC","sourcesContent":["import CloxPickerModule from './CloxPickerModule';\n\nexport function getHello(): string {\n return CloxPickerModule.getHello();\n}\n\nexport function setColorScheme(scheme: 'light' | 'dark' | 'auto'): void {\n CloxPickerModule.setColorScheme(scheme);\n}\n\nexport { default as CloxPickerModule } from './CloxPickerModule';\nexport { CloxPickerView, default as CloxPickerViewDefault } from './CloxPickerView';\nexport * from './CloxPicker.types';\n"]}
|
package/example/App.tsx
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CloxPickerView,
|
|
3
|
+
setColorScheme,
|
|
4
|
+
type CloxPickerTab,
|
|
5
|
+
} from 'clox-picker';
|
|
6
|
+
import React, { useState, useEffect } from 'react';
|
|
7
|
+
import {
|
|
8
|
+
Appearance,
|
|
9
|
+
Button,
|
|
10
|
+
Image,
|
|
11
|
+
Platform,
|
|
12
|
+
ScrollView,
|
|
13
|
+
StyleSheet,
|
|
14
|
+
Text,
|
|
15
|
+
useColorScheme,
|
|
16
|
+
useWindowDimensions,
|
|
17
|
+
View,
|
|
18
|
+
} from 'react-native';
|
|
19
|
+
|
|
20
|
+
const DEFAULT_TABS: CloxPickerTab[] = [
|
|
21
|
+
{ id: 0, name: 'All', icon: { uri: 'https://cronoz-assets.s3.us-west-2.amazonaws.com/icons/play.png' } },
|
|
22
|
+
{ id: 1, name: 'Calendar', icon: { uri: 'https://cronoz-assets.s3.us-west-2.amazonaws.com/icons/upload.png' } },
|
|
23
|
+
{ id: 2, name: 'Planning', icon: { uri: 'https://cronoz-assets.s3.us-west-2.amazonaws.com/icons/male.png' } },
|
|
24
|
+
{ id: 3, name: 'Explore', icon: { uri: 'https://cronoz-assets.s3.us-west-2.amazonaws.com/icons/female.png' } },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const PICKER_HEIGHT = 90;
|
|
28
|
+
|
|
29
|
+
export default function App() {
|
|
30
|
+
const {width, height} = useWindowDimensions();
|
|
31
|
+
const colorScheme = useColorScheme();
|
|
32
|
+
const [liquidIndex, setLiquidIndex] = useState(0);
|
|
33
|
+
const [standardIndex, setStandardIndex] = useState(0);
|
|
34
|
+
const [androidIndex, setAndroidIndex] = useState(0);
|
|
35
|
+
const [currentScheme, setCurrentScheme] = useState<'light' | 'dark' | null>(null);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setCurrentScheme(colorScheme || 'light');
|
|
39
|
+
}, [colorScheme]);
|
|
40
|
+
|
|
41
|
+
const toggleColorScheme = () => {
|
|
42
|
+
const newScheme = currentScheme === 'light' ? 'dark' : 'light';
|
|
43
|
+
setCurrentScheme(newScheme);
|
|
44
|
+
|
|
45
|
+
if (Platform.OS === 'ios') {
|
|
46
|
+
// Use native module to change color scheme
|
|
47
|
+
setColorScheme(newScheme);
|
|
48
|
+
} else {
|
|
49
|
+
// On Android, use Appearance API
|
|
50
|
+
Appearance.setColorScheme(newScheme);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<View style={styles.container}>
|
|
56
|
+
<Image
|
|
57
|
+
source={{ uri: 'https://picsum.photos/800/1600?random=' + Date.now() }}
|
|
58
|
+
style={{
|
|
59
|
+
position: 'absolute',
|
|
60
|
+
top: 0,
|
|
61
|
+
left: 0,
|
|
62
|
+
width: width,
|
|
63
|
+
height: height,
|
|
64
|
+
}}
|
|
65
|
+
resizeMode="cover"
|
|
66
|
+
/>
|
|
67
|
+
<View style={styles.toggleContainer}>
|
|
68
|
+
<Button
|
|
69
|
+
title={`Toggle Theme (Current: ${currentScheme === 'dark' ? 'Dark' : 'Light'})`}
|
|
70
|
+
onPress={toggleColorScheme}
|
|
71
|
+
/>
|
|
72
|
+
</View>
|
|
73
|
+
<ScrollView
|
|
74
|
+
contentContainerStyle={styles.container}
|
|
75
|
+
style={styles.scroll}
|
|
76
|
+
>
|
|
77
|
+
{Platform.OS === 'ios' && (
|
|
78
|
+
<View style={{ width: 400 }}>
|
|
79
|
+
<Text style={styles.title}>Liquid Glass (iOS)</Text>
|
|
80
|
+
<View style={styles.pickerWrap}>
|
|
81
|
+
<CloxPickerView
|
|
82
|
+
tabs={DEFAULT_TABS}
|
|
83
|
+
height={PICKER_HEIGHT}
|
|
84
|
+
value={liquidIndex}
|
|
85
|
+
useLiquidGlass={true}
|
|
86
|
+
selectedColor="#FF6B6B"
|
|
87
|
+
onTabChange={({ tabIndex }) => setLiquidIndex(tabIndex)}
|
|
88
|
+
style={styles.picker}
|
|
89
|
+
/>
|
|
90
|
+
</View>
|
|
91
|
+
|
|
92
|
+
<Text style={styles.title}>Standard (no liquid glass)</Text>
|
|
93
|
+
<View style={styles.pickerWrap}>
|
|
94
|
+
<CloxPickerView
|
|
95
|
+
tabs={DEFAULT_TABS}
|
|
96
|
+
height={PICKER_HEIGHT}
|
|
97
|
+
value={standardIndex}
|
|
98
|
+
useLiquidGlass={false}
|
|
99
|
+
selectedColor="#4ECDC4"
|
|
100
|
+
onTabChange={({ tabIndex }) => setStandardIndex(tabIndex)}
|
|
101
|
+
style={styles.picker}
|
|
102
|
+
/>
|
|
103
|
+
</View>
|
|
104
|
+
</View>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{Platform.OS === 'android' && (
|
|
108
|
+
<>
|
|
109
|
+
<Text style={styles.title}>Segmented Picker</Text>
|
|
110
|
+
<View style={styles.pickerWrap}>
|
|
111
|
+
<CloxPickerView
|
|
112
|
+
tabs={DEFAULT_TABS}
|
|
113
|
+
height={PICKER_HEIGHT}
|
|
114
|
+
value={androidIndex}
|
|
115
|
+
onTabChange={({ tabIndex }) => setAndroidIndex(tabIndex)}
|
|
116
|
+
style={styles.picker}
|
|
117
|
+
/>
|
|
118
|
+
</View>
|
|
119
|
+
</>
|
|
120
|
+
)}
|
|
121
|
+
</ScrollView>
|
|
122
|
+
|
|
123
|
+
</View>
|
|
124
|
+
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const styles = StyleSheet.create({
|
|
129
|
+
scroll: { flex: 1 },
|
|
130
|
+
container: {
|
|
131
|
+
flexGrow: 1,
|
|
132
|
+
paddingVertical: 48,
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
},
|
|
135
|
+
toggleContainer: {
|
|
136
|
+
position: 'absolute',
|
|
137
|
+
top: 60,
|
|
138
|
+
right: 20,
|
|
139
|
+
zIndex: 1000,
|
|
140
|
+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
141
|
+
borderRadius: 8,
|
|
142
|
+
padding: 8,
|
|
143
|
+
},
|
|
144
|
+
title: {
|
|
145
|
+
fontSize: 16,
|
|
146
|
+
fontWeight: '600',
|
|
147
|
+
marginBottom: 12,
|
|
148
|
+
marginTop: 24,
|
|
149
|
+
},
|
|
150
|
+
pickerWrap: {
|
|
151
|
+
width: '100%',
|
|
152
|
+
height: PICKER_HEIGHT, // Give native view explicit height so `height` prop is respected
|
|
153
|
+
},
|
|
154
|
+
picker: {
|
|
155
|
+
width: '100%',
|
|
156
|
+
height: PICKER_HEIGHT,
|
|
157
|
+
},
|
|
158
|
+
});
|