react-native-platform-components 0.4.0 → 0.5.0
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/README.md +38 -1
- package/android/src/main/java/com/platformcomponents/PCDatePickerView.kt +39 -21
- package/android/src/main/java/com/platformcomponents/PCDatePickerViewManager.kt +6 -4
- package/ios/PCConstants.swift +9 -2
- package/ios/PCDatePicker.mm +6 -5
- package/ios/PCDatePickerView.swift +37 -10
- package/ios/PCSelectionMenu.swift +97 -41
- package/lib/module/DatePicker.js +8 -5
- package/lib/module/DatePicker.js.map +1 -1
- package/lib/typescript/src/DatePicker.d.ts.map +1 -1
- package/lib/typescript/src/SelectionMenu.d.ts +1 -1
- package/lib/typescript/src/SelectionMenu.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/DatePicker.tsx +9 -5
- package/src/SelectionMenu.tsx +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# react-native-platform-components
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/react-native-platform-components)
|
|
4
|
+
[](https://www.npmjs.com/package/react-native-platform-components)
|
|
5
|
+
|
|
6
|
+
<table>
|
|
7
|
+
<tr>
|
|
8
|
+
<td align="center"><strong>iOS DatePicker</strong></td>
|
|
9
|
+
<td align="center"><strong>Android DatePicker</strong></td>
|
|
10
|
+
</tr>
|
|
11
|
+
<tr>
|
|
12
|
+
<td><video src="https://github.com/user-attachments/assets/a9fb6237-6078-496b-8a58-1f2fae4f1af5" height="400"></video></td>
|
|
13
|
+
<td><video src="https://github.com/user-attachments/assets/70e42d98-7ea6-40fe-90c5-c1efc2227f25" height="400"></video></td>
|
|
14
|
+
</tr>
|
|
15
|
+
<tr>
|
|
16
|
+
<td align="center"><strong>iOS SelectionMenu</strong></td>
|
|
17
|
+
<td align="center"><strong>Android SelectionMenu</strong></td>
|
|
18
|
+
</tr>
|
|
19
|
+
<tr>
|
|
20
|
+
<td><video src="https://github.com/user-attachments/assets/f1af8e91-4e98-4dbe-a2a6-57f91770678e" height="400"></video></td>
|
|
21
|
+
<td><video src="https://github.com/user-attachments/assets/dc61da04-45e5-43c3-b766-6048367775bb" height="400"></video></td>
|
|
22
|
+
</tr>
|
|
23
|
+
</table>
|
|
24
|
+
|
|
25
|
+
> 🚧 In development — not ready for public use.
|
|
26
|
+
|
|
3
27
|
High-quality **native UI components for React Native**, implemented with platform-first APIs and exposed through clean, typed JavaScript interfaces.
|
|
4
28
|
|
|
5
29
|
This library focuses on **true native behavior**, not JavaScript re-implementations — providing:
|
|
@@ -181,6 +205,8 @@ Native selection menu with **inline** and **headless** modes.
|
|
|
181
205
|
- **Headless mode** (default): Menu visibility controlled by `visible` prop. Use for custom trigger UI.
|
|
182
206
|
- **Inline mode** (`inlineMode={true}`): Native picker UI rendered inline. Menu managed internally.
|
|
183
207
|
|
|
208
|
+
> **Note:** On iOS, headless mode uses a custom popover to enable programmatic presentation. For the full native menu experience (system animations, scroll physics), use inline mode. This is an intentional trade-off: headless gives you control over the trigger UI, inline gives you the complete system menu behavior.
|
|
209
|
+
|
|
184
210
|
---
|
|
185
211
|
|
|
186
212
|
## DatePicker
|
|
@@ -216,7 +242,7 @@ Native date & time picker using **platform system pickers**.
|
|
|
216
242
|
| Prop | Type | Description |
|
|
217
243
|
|------|------|-------------|
|
|
218
244
|
| `firstDayOfWeek` | `number` | First day of week (1-7, Sunday=1) |
|
|
219
|
-
| `material` | `'system' \| 'm3'` | Material Design style |
|
|
245
|
+
| `material` | `'system' \| 'm3'` | Material Design style (modal only; embedded always uses system picker) |
|
|
220
246
|
| `dialogTitle` | `string` | Custom dialog title |
|
|
221
247
|
| `positiveButtonTitle` | `string` | Custom confirm button text |
|
|
222
248
|
| `negativeButtonTitle` | `string` | Custom cancel button text |
|
|
@@ -233,6 +259,17 @@ Native date & time picker using **platform system pickers**.
|
|
|
233
259
|
|
|
234
260
|
---
|
|
235
261
|
|
|
262
|
+
## Theming
|
|
263
|
+
|
|
264
|
+
This library does not expose theming props. Components inherit their appearance from your app's native platform theme.
|
|
265
|
+
|
|
266
|
+
- **iOS**: Components follow system appearance (light/dark mode) and use system-defined styles (e.g., `UIBlurEffect` for menu backgrounds). These are not customizable per-component.
|
|
267
|
+
- **Android**: Components respect your app's Material Theme. Customize via your `styles.xml` or Material 3 theme configuration.
|
|
268
|
+
|
|
269
|
+
This is intentional. The goal is native fidelity, not pixel-level customization. If you need custom styling beyond what the platform theme provides, this library may not be the right fit.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
236
273
|
## Contributing
|
|
237
274
|
|
|
238
275
|
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
@@ -136,14 +136,16 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
|
|
|
136
136
|
minDateMs = value
|
|
137
137
|
// clamp if needed
|
|
138
138
|
dateMs = clamp(dateMs ?: System.currentTimeMillis())
|
|
139
|
-
|
|
139
|
+
// Rebuild inline picker to apply new min date (avoids CalendarView bugs)
|
|
140
|
+
if (isInline()) rebuildUI() else syncInlineFromState()
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
fun applyMaxDateMs(value: Long?) {
|
|
143
144
|
maxDateMs = value
|
|
144
145
|
// clamp if needed
|
|
145
146
|
dateMs = clamp(dateMs ?: System.currentTimeMillis())
|
|
146
|
-
|
|
147
|
+
// Rebuild inline picker to apply new max date (avoids CalendarView bugs)
|
|
148
|
+
if (isInline()) rebuildUI() else syncInlineFromState()
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
/**
|
|
@@ -205,8 +207,11 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
|
|
|
205
207
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
206
208
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
207
209
|
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
// Use spinner mode to avoid CalendarView rendering bugs when scrolling months
|
|
211
|
+
calendarViewShown = false
|
|
212
|
+
spinnersShown = true
|
|
213
|
+
minDateMs?.let { minDate = it }
|
|
214
|
+
maxDateMs?.let { maxDate = it }
|
|
210
215
|
}
|
|
211
216
|
container.addView(dp)
|
|
212
217
|
inlineDatePicker = dp
|
|
@@ -239,7 +244,14 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
|
|
|
239
244
|
inlineContainer = container
|
|
240
245
|
|
|
241
246
|
syncInlineFromState()
|
|
242
|
-
|
|
247
|
+
|
|
248
|
+
// Force layout refresh - post to ensure React Native's layout system picks it up
|
|
249
|
+
post {
|
|
250
|
+
requestLayout()
|
|
251
|
+
invalidate()
|
|
252
|
+
// Also request layout from parent to notify React Native
|
|
253
|
+
(parent as? ViewGroup)?.requestLayout()
|
|
254
|
+
}
|
|
243
255
|
}
|
|
244
256
|
|
|
245
257
|
private fun syncInlineFromState() {
|
|
@@ -253,10 +265,8 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
|
|
|
253
265
|
suppressInlineCallbacks = true
|
|
254
266
|
try {
|
|
255
267
|
inlineDatePicker?.let { dp ->
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
maxDateMs?.let { dp.maxDate = it }
|
|
259
|
-
|
|
268
|
+
// Note: min/max dates are set during picker creation in rebuildUI()
|
|
269
|
+
// to avoid CalendarView rendering bugs from repeated updates
|
|
260
270
|
val y = cal.get(Calendar.YEAR)
|
|
261
271
|
val m = cal.get(Calendar.MONTH)
|
|
262
272
|
val d = cal.get(Calendar.DAY_OF_MONTH)
|
|
@@ -319,20 +329,28 @@ class PCDatePickerView(context: Context) : FrameLayout(context) {
|
|
|
319
329
|
|
|
320
330
|
private fun presentIfNeeded() {
|
|
321
331
|
if (showingModal) return
|
|
322
|
-
val act = findFragmentActivity() ?: run {
|
|
323
|
-
Log.w(TAG, "presentIfNeeded: no FragmentActivity found")
|
|
324
|
-
onCancel?.invoke()
|
|
325
|
-
showingModal = false
|
|
326
|
-
return
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
Log.d(TAG, "presentIfNeeded mode=$mode material=$androidMaterialMode")
|
|
330
332
|
showingModal = true
|
|
331
333
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
// Defer presentation to the next frame to ensure all props from the current
|
|
335
|
+
// React Native batch are applied first. This guarantees dateMs reflects the
|
|
336
|
+
// latest value from React Native before we create the dialog.
|
|
337
|
+
post {
|
|
338
|
+
if (!showingModal) return@post
|
|
339
|
+
|
|
340
|
+
val act = findFragmentActivity() ?: run {
|
|
341
|
+
Log.w(TAG, "presentIfNeeded: no FragmentActivity found")
|
|
342
|
+
onCancel?.invoke()
|
|
343
|
+
showingModal = false
|
|
344
|
+
return@post
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
Log.d(TAG, "presentIfNeeded mode=$mode material=$androidMaterialMode")
|
|
348
|
+
|
|
349
|
+
when (mode) {
|
|
350
|
+
"time" -> presentTime(act)
|
|
351
|
+
"dateAndTime" -> presentDateThenTime(act)
|
|
352
|
+
else -> presentDate(act)
|
|
353
|
+
}
|
|
336
354
|
}
|
|
337
355
|
}
|
|
338
356
|
|
|
@@ -60,17 +60,19 @@ class PCDatePickerViewManager :
|
|
|
60
60
|
view.applyTimeZoneName(value)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// Sentinel is MIN_SAFE_INTEGER to allow negative timestamps for pre-1970 dates
|
|
64
|
+
private val noDateSentinel = -9007199254740991.0
|
|
65
|
+
|
|
64
66
|
override fun setDateMs(view: PCDatePickerView, value: Double) {
|
|
65
|
-
view.applyDateMs(if (value
|
|
67
|
+
view.applyDateMs(if (value > noDateSentinel) value.toLong() else null)
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
override fun setMinDateMs(view: PCDatePickerView, value: Double) {
|
|
69
|
-
view.applyMinDateMs(if (value
|
|
71
|
+
view.applyMinDateMs(if (value > noDateSentinel) value.toLong() else null)
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
override fun setMaxDateMs(view: PCDatePickerView, value: Double) {
|
|
73
|
-
view.applyMaxDateMs(if (value
|
|
75
|
+
view.applyMaxDateMs(if (value > noDateSentinel) value.toLong() else null)
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
// --- platform objects ---
|
package/ios/PCConstants.swift
CHANGED
|
@@ -16,8 +16,15 @@ enum PCConstants {
|
|
|
16
16
|
/// Maximum popover height before scrolling
|
|
17
17
|
static let popoverMaxHeight: CGFloat = 400
|
|
18
18
|
|
|
19
|
-
///
|
|
20
|
-
static let
|
|
19
|
+
/// Minimum row height in selection menu popover (used as baseline)
|
|
20
|
+
static let popoverRowHeightMin: CGFloat = 44
|
|
21
|
+
|
|
22
|
+
/// Dynamic row height that respects user's preferred content size
|
|
23
|
+
static var popoverRowHeight: CGFloat {
|
|
24
|
+
let bodyFont = UIFont.preferredFont(forTextStyle: .body)
|
|
25
|
+
// Row height = font line height + vertical padding (16pt total)
|
|
26
|
+
return max(popoverRowHeightMin, ceil(bodyFont.lineHeight) + 16)
|
|
27
|
+
}
|
|
21
28
|
|
|
22
29
|
/// Vertical padding in selection menu popover (top + bottom)
|
|
23
30
|
static let popoverVerticalPadding: CGFloat = 16
|
package/ios/PCDatePicker.mm
CHANGED
|
@@ -117,20 +117,21 @@ using namespace facebook::react;
|
|
|
117
117
|
_datePickerView.open = newOpen;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
// dateMs (sentinel -
|
|
120
|
+
// dateMs (sentinel is MIN_SAFE_INTEGER to allow negative timestamps for pre-1970 dates)
|
|
121
|
+
static const double kNoDateSentinel = -9007199254740991.0;
|
|
121
122
|
if (oldViewProps.dateMs != newViewProps.dateMs) {
|
|
122
123
|
_datePickerView.dateMs =
|
|
123
|
-
(newViewProps.dateMs
|
|
124
|
+
(newViewProps.dateMs > kNoDateSentinel) ? @(newViewProps.dateMs) : nil;
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
// min/max (sentinel
|
|
127
|
+
// min/max (same sentinel)
|
|
127
128
|
if (oldViewProps.minDateMs != newViewProps.minDateMs) {
|
|
128
129
|
_datePickerView.minDateMs =
|
|
129
|
-
(newViewProps.minDateMs
|
|
130
|
+
(newViewProps.minDateMs > kNoDateSentinel) ? @(newViewProps.minDateMs) : nil;
|
|
130
131
|
}
|
|
131
132
|
if (oldViewProps.maxDateMs != newViewProps.maxDateMs) {
|
|
132
133
|
_datePickerView.maxDateMs =
|
|
133
|
-
(newViewProps.maxDateMs
|
|
134
|
+
(newViewProps.maxDateMs > kNoDateSentinel) ? @(newViewProps.maxDateMs) : nil;
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
// locale
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import os.log
|
|
2
1
|
import UIKit
|
|
2
|
+
import os.log
|
|
3
3
|
|
|
4
4
|
private let logger = Logger(subsystem: "com.platformcomponents", category: "DatePicker")
|
|
5
5
|
|
|
@@ -134,7 +134,9 @@ public final class PCDatePickerView: UIControl,
|
|
|
134
134
|
picker.setNeedsLayout()
|
|
135
135
|
picker.layoutIfNeeded()
|
|
136
136
|
let fitted = picker.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
|
137
|
-
return CGSize(
|
|
137
|
+
return CGSize(
|
|
138
|
+
width: UIView.noIntrinsicMetric,
|
|
139
|
+
height: max(PCConstants.minTouchTargetHeight, fitted.height))
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
/// ✅ Called by your measuring pipeline.
|
|
@@ -153,7 +155,9 @@ public final class PCDatePickerView: UIControl,
|
|
|
153
155
|
withHorizontalFittingPriority: .required,
|
|
154
156
|
verticalFittingPriority: .fittingSizeLevel
|
|
155
157
|
)
|
|
156
|
-
return CGSize(
|
|
158
|
+
return CGSize(
|
|
159
|
+
width: constrainedSize.width,
|
|
160
|
+
height: max(PCConstants.minTouchTargetHeight, fitted.height))
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
/// Separate sizing for popover content.
|
|
@@ -213,12 +217,16 @@ public final class PCDatePickerView: UIControl,
|
|
|
213
217
|
}
|
|
214
218
|
logger.debug("presentIfNeeded: presenting modal picker")
|
|
215
219
|
|
|
216
|
-
// Prevent “settle” events right as we present.
|
|
217
|
-
suppressNextChangesBriefly()
|
|
218
|
-
|
|
219
220
|
// Ensure picker is not inline.
|
|
220
221
|
detachInlinePickerIfNeeded()
|
|
221
222
|
|
|
223
|
+
// Sync the picker's date to the current prop value before presenting.
|
|
224
|
+
// This ensures React Native's date is always respected as the source of truth.
|
|
225
|
+
applyDateMs(animated: false)
|
|
226
|
+
|
|
227
|
+
// Prevent "settle" events right as we present.
|
|
228
|
+
suppressNextChangesBriefly()
|
|
229
|
+
|
|
222
230
|
let vc = UIViewController()
|
|
223
231
|
vc.view.backgroundColor = .clear
|
|
224
232
|
vc.view.isOpaque = false
|
|
@@ -226,8 +234,14 @@ public final class PCDatePickerView: UIControl,
|
|
|
226
234
|
picker.translatesAutoresizingMaskIntoConstraints = false
|
|
227
235
|
vc.view.addSubview(picker)
|
|
228
236
|
|
|
237
|
+
// Position picker at top-leading (constraints required to avoid freeze with inline style)
|
|
238
|
+
NSLayoutConstraint.activate([
|
|
239
|
+
picker.topAnchor.constraint(equalTo: vc.view.topAnchor),
|
|
240
|
+
picker.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor),
|
|
241
|
+
])
|
|
242
|
+
|
|
229
243
|
let size = popoverContentSize()
|
|
230
|
-
vc.preferredContentSize = size
|
|
244
|
+
vc.preferredContentSize = size
|
|
231
245
|
|
|
232
246
|
// ✅ Anchored popover-style (not a full sheet)
|
|
233
247
|
vc.modalPresentationStyle = .popover
|
|
@@ -236,7 +250,15 @@ public final class PCDatePickerView: UIControl,
|
|
|
236
250
|
if let pop = vc.popoverPresentationController {
|
|
237
251
|
pop.delegate = self
|
|
238
252
|
pop.sourceView = self
|
|
239
|
-
|
|
253
|
+
// Use a minimum height for sourceRect to help popover positioning
|
|
254
|
+
// (modal presentation views have zero intrinsic height)
|
|
255
|
+
let sourceRect = CGRect(
|
|
256
|
+
x: bounds.minX,
|
|
257
|
+
y: bounds.minY,
|
|
258
|
+
width: max(bounds.width, 44),
|
|
259
|
+
height: max(bounds.height, 44)
|
|
260
|
+
)
|
|
261
|
+
pop.sourceRect = sourceRect
|
|
240
262
|
pop.permittedArrowDirections = [.up, .down]
|
|
241
263
|
}
|
|
242
264
|
|
|
@@ -288,9 +310,14 @@ public final class PCDatePickerView: UIControl,
|
|
|
288
310
|
// MARK: - Apply props (avoid firing valueChanged)
|
|
289
311
|
|
|
290
312
|
private func applyDateMs(animated: Bool) {
|
|
291
|
-
guard let ms = dateMs?.doubleValue else { return }
|
|
292
313
|
suppressNextChangesBriefly()
|
|
293
|
-
|
|
314
|
+
if let ms = dateMs?.doubleValue {
|
|
315
|
+
picker.setDate(Date(timeIntervalSince1970: ms / 1000.0), animated: animated)
|
|
316
|
+
} else {
|
|
317
|
+
// When no date is provided, default to now to avoid layout issues
|
|
318
|
+
// (especially with inline style in modal presentation)
|
|
319
|
+
picker.setDate(Date(), animated: animated)
|
|
320
|
+
}
|
|
294
321
|
}
|
|
295
322
|
|
|
296
323
|
private func applyMinMax() {
|
|
@@ -41,6 +41,7 @@ private struct PCSelectionMenuInlinePickerView: View {
|
|
|
41
41
|
} label: {
|
|
42
42
|
HStack(spacing: 8) {
|
|
43
43
|
Text(model.displayTitle)
|
|
44
|
+
.font(.body)
|
|
44
45
|
.lineLimit(1)
|
|
45
46
|
.truncationMode(.tail)
|
|
46
47
|
|
|
@@ -279,22 +280,42 @@ public final class PCSelectionMenuView: UIControl {
|
|
|
279
280
|
}
|
|
280
281
|
)
|
|
281
282
|
|
|
282
|
-
|
|
283
|
+
// Calculate menu position relative to source view
|
|
284
|
+
let sourceFrame = self.convert(self.bounds, to: vc.view)
|
|
285
|
+
let screenBounds = vc.view.bounds
|
|
283
286
|
let popoverHeight = min(
|
|
284
287
|
CGFloat(opts.count) * PCConstants.popoverRowHeight + PCConstants.popoverVerticalPadding,
|
|
285
288
|
PCConstants.popoverMaxHeight
|
|
286
289
|
)
|
|
287
|
-
|
|
290
|
+
let spacing: CGFloat = 8
|
|
291
|
+
|
|
292
|
+
// Check if menu fits below the source view
|
|
293
|
+
let rowHeight = PCConstants.popoverRowHeight
|
|
294
|
+
let wouldExtendBeyondBottom = sourceFrame.maxY + spacing + popoverHeight > screenBounds.maxY - 20
|
|
295
|
+
|
|
296
|
+
let menuY: CGFloat
|
|
297
|
+
if wouldExtendBeyondBottom {
|
|
298
|
+
// Position above the source view (no overlap offset)
|
|
299
|
+
menuY = sourceFrame.minY - spacing - popoverHeight
|
|
300
|
+
} else {
|
|
301
|
+
// Position below, but shift up by one row to overlap trigger (like system menu)
|
|
302
|
+
menuY = sourceFrame.maxY + spacing - rowHeight
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Center horizontally, but keep within screen bounds
|
|
306
|
+
var menuX = sourceFrame.midX - PCConstants.popoverWidth / 2
|
|
307
|
+
menuX = max(16, min(menuX, screenBounds.maxX - PCConstants.popoverWidth - 16))
|
|
308
|
+
|
|
309
|
+
let menuFrame = CGRect(
|
|
310
|
+
x: menuX,
|
|
311
|
+
y: menuY,
|
|
288
312
|
width: PCConstants.popoverWidth,
|
|
289
313
|
height: popoverHeight
|
|
290
314
|
)
|
|
291
315
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
popover.permittedArrowDirections = [] // Remove arrow to match inline
|
|
296
|
-
popover.delegate = menuVC
|
|
297
|
-
}
|
|
316
|
+
menuVC.modalPresentationStyle = .overCurrentContext
|
|
317
|
+
menuVC.modalTransitionStyle = .crossDissolve
|
|
318
|
+
menuVC.menuFrame = menuFrame
|
|
298
319
|
|
|
299
320
|
vc.present(menuVC, animated: true)
|
|
300
321
|
}
|
|
@@ -326,13 +347,40 @@ public final class PCSelectionMenuView: UIControl {
|
|
|
326
347
|
}
|
|
327
348
|
}
|
|
328
349
|
|
|
350
|
+
// MARK: - Glass Menu Cell
|
|
351
|
+
|
|
352
|
+
private class PCGlassMenuCell: UITableViewCell {
|
|
353
|
+
static let reuseIdentifier = "PCGlassMenuCell"
|
|
354
|
+
|
|
355
|
+
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
|
356
|
+
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
357
|
+
setupCell()
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
required init?(coder: NSCoder) {
|
|
361
|
+
super.init(coder: coder)
|
|
362
|
+
setupCell()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private func setupCell() {
|
|
366
|
+
backgroundColor = .clear
|
|
367
|
+
contentView.backgroundColor = .clear
|
|
368
|
+
selectionStyle = .none
|
|
369
|
+
textLabel?.font = .preferredFont(forTextStyle: .body)
|
|
370
|
+
textLabel?.adjustsFontForContentSizeCategory = true
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
329
374
|
// MARK: - Custom Menu View Controller (matches SwiftUI Menu appearance)
|
|
330
375
|
|
|
331
|
-
private class PCMenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
|
|
376
|
+
private class PCMenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
|
332
377
|
private let options: [PCSelectionMenuOption]
|
|
333
378
|
private let onSelect: (Int) -> Void
|
|
334
379
|
private let onCancel: () -> Void
|
|
335
380
|
private var tableView: UITableView!
|
|
381
|
+
private var menuContainer: UIView!
|
|
382
|
+
|
|
383
|
+
var menuFrame: CGRect = .zero
|
|
336
384
|
|
|
337
385
|
init(options: [PCSelectionMenuOption], onSelect: @escaping (Int) -> Void, onCancel: @escaping () -> Void) {
|
|
338
386
|
self.options = options
|
|
@@ -348,37 +396,56 @@ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITab
|
|
|
348
396
|
override func viewDidLoad() {
|
|
349
397
|
super.viewDidLoad()
|
|
350
398
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
399
|
+
view.backgroundColor = .clear
|
|
400
|
+
|
|
401
|
+
// Tap outside to dismiss
|
|
402
|
+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackgroundTap))
|
|
403
|
+
tapGesture.cancelsTouchesInView = false
|
|
404
|
+
view.addGestureRecognizer(tapGesture)
|
|
405
|
+
|
|
406
|
+
// Menu container positioned at menuFrame
|
|
407
|
+
menuContainer = UIView(frame: menuFrame)
|
|
408
|
+
menuContainer.backgroundColor = .clear
|
|
409
|
+
menuContainer.layer.cornerRadius = 12
|
|
410
|
+
menuContainer.clipsToBounds = true
|
|
411
|
+
view.addSubview(menuContainer)
|
|
412
|
+
|
|
413
|
+
// Use liquid glass on iOS 26+, fall back to system material blur on older versions
|
|
414
|
+
let effectView: UIVisualEffectView
|
|
415
|
+
if #available(iOS 26, *) {
|
|
416
|
+
var glassEffect = UIGlassEffect()
|
|
417
|
+
glassEffect.isInteractive = true
|
|
418
|
+
effectView = UIVisualEffectView(effect: glassEffect)
|
|
419
|
+
} else {
|
|
420
|
+
let blurEffect = UIBlurEffect(style: .systemMaterial)
|
|
421
|
+
effectView = UIVisualEffectView(effect: blurEffect)
|
|
422
|
+
}
|
|
423
|
+
effectView.frame = menuContainer.bounds
|
|
424
|
+
effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
425
|
+
menuContainer.addSubview(effectView)
|
|
356
426
|
|
|
357
|
-
tableView = UITableView(frame: .
|
|
427
|
+
tableView = UITableView(frame: menuContainer.bounds, style: .plain)
|
|
358
428
|
tableView.delegate = self
|
|
359
429
|
tableView.dataSource = self
|
|
360
|
-
tableView.register(
|
|
430
|
+
tableView.register(PCGlassMenuCell.self, forCellReuseIdentifier: PCGlassMenuCell.reuseIdentifier)
|
|
361
431
|
tableView.backgroundColor = .clear
|
|
362
432
|
tableView.separatorStyle = .none
|
|
363
433
|
tableView.isScrollEnabled = true
|
|
364
434
|
tableView.rowHeight = PCConstants.popoverRowHeight
|
|
435
|
+
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
365
436
|
let verticalPad = PCConstants.popoverVerticalPadding / 2
|
|
366
437
|
tableView.contentInset = UIEdgeInsets(top: verticalPad, left: 0, bottom: verticalPad, right: 0)
|
|
367
|
-
tableView.translatesAutoresizingMaskIntoConstraints = false
|
|
368
438
|
|
|
369
|
-
|
|
439
|
+
effectView.contentView.addSubview(tableView)
|
|
440
|
+
}
|
|
370
441
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
tableView.bottomAnchor.constraint(equalTo: blurView.contentView.bottomAnchor),
|
|
379
|
-
tableView.leadingAnchor.constraint(equalTo: blurView.contentView.leadingAnchor),
|
|
380
|
-
tableView.trailingAnchor.constraint(equalTo: blurView.contentView.trailingAnchor),
|
|
381
|
-
])
|
|
442
|
+
@objc private func handleBackgroundTap(_ gesture: UITapGestureRecognizer) {
|
|
443
|
+
let location = gesture.location(in: view)
|
|
444
|
+
if !menuContainer.frame.contains(location) {
|
|
445
|
+
dismiss(animated: true) { [weak self] in
|
|
446
|
+
self?.onCancel()
|
|
447
|
+
}
|
|
448
|
+
}
|
|
382
449
|
}
|
|
383
450
|
|
|
384
451
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
@@ -386,11 +453,8 @@ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITab
|
|
|
386
453
|
}
|
|
387
454
|
|
|
388
455
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
389
|
-
let cell = tableView.dequeueReusableCell(withIdentifier:
|
|
456
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: PCGlassMenuCell.reuseIdentifier, for: indexPath)
|
|
390
457
|
cell.textLabel?.text = options[indexPath.row].label
|
|
391
|
-
cell.textLabel?.font = .systemFont(ofSize: 17)
|
|
392
|
-
cell.backgroundColor = .clear
|
|
393
|
-
cell.selectionStyle = .default
|
|
394
458
|
return cell
|
|
395
459
|
}
|
|
396
460
|
|
|
@@ -400,13 +464,5 @@ private class PCMenuViewController: UIViewController, UITableViewDelegate, UITab
|
|
|
400
464
|
self?.onSelect(indexPath.row)
|
|
401
465
|
}
|
|
402
466
|
}
|
|
403
|
-
|
|
404
|
-
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
|
405
|
-
onCancel()
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
|
|
409
|
-
return .none
|
|
410
|
-
}
|
|
411
467
|
}
|
|
412
468
|
|
package/lib/module/DatePicker.js
CHANGED
|
@@ -5,8 +5,11 @@ import React, { useCallback, useMemo } from 'react';
|
|
|
5
5
|
import { StyleSheet } from 'react-native';
|
|
6
6
|
import NativeDatePicker from './DatePickerNativeComponent';
|
|
7
7
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Sentinel value for "no date". Using MIN_SAFE_INTEGER ensures we don't
|
|
9
|
+
// conflict with valid negative timestamps (dates before 1970).
|
|
10
|
+
const NO_DATE_SENTINEL = Number.MIN_SAFE_INTEGER;
|
|
11
|
+
function dateToMsOrSentinel(d) {
|
|
12
|
+
return d ? d.getTime() : NO_DATE_SENTINEL;
|
|
10
13
|
}
|
|
11
14
|
function normalizeVisible(presentation, visible) {
|
|
12
15
|
// Only meaningful in modal presentation. Keep undefined for inline to avoid noise.
|
|
@@ -44,9 +47,9 @@ export function DatePicker(props) {
|
|
|
44
47
|
timeZoneName,
|
|
45
48
|
presentation,
|
|
46
49
|
visible: normalizeVisible(presentation, visible),
|
|
47
|
-
dateMs:
|
|
48
|
-
minDateMs:
|
|
49
|
-
maxDateMs:
|
|
50
|
+
dateMs: dateToMsOrSentinel(date),
|
|
51
|
+
minDateMs: dateToMsOrSentinel(minDate ?? null),
|
|
52
|
+
maxDateMs: dateToMsOrSentinel(maxDate ?? null),
|
|
50
53
|
onConfirm: onConfirm ? handleConfirm : undefined,
|
|
51
54
|
onClosed: onClosed ? handleClosed : undefined,
|
|
52
55
|
ios: ios ? {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useCallback","useMemo","StyleSheet","NativeDatePicker","jsx","_jsx","
|
|
1
|
+
{"version":3,"names":["React","useCallback","useMemo","StyleSheet","NativeDatePicker","jsx","_jsx","NO_DATE_SENTINEL","Number","MIN_SAFE_INTEGER","dateToMsOrSentinel","d","getTime","normalizeVisible","presentation","visible","undefined","DatePicker","props","style","date","minDate","maxDate","locale","timeZoneName","mode","onConfirm","onClosed","ios","android","testID","handleConfirm","e","Date","nativeEvent","timestampMs","handleClosed","styles","createStyles","nativeProps","picker","dateMs","minDateMs","maxDateMs","preferredStyle","countDownDurationSeconds","minuteInterval","roundsToMinuteInterval","firstDayOfWeek","material","dialogTitle","positiveButtonTitle","negativeButtonTitle","create"],"sourceRoot":"../../src","sources":["DatePicker.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/DatePicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,KAAK,EAAwB,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG/E,OAAyB,EAGvB,KAAK,QAAQ,IAAI,cAAc,EAC/B,KAAK,YAAY,IAAI,kBAAkB,EACvC,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,mBAAmB,EAAW,MAAM,eAAe,CAAC;AAElE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAE7B,2DAA2D;IAC3D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,mDAAmD;IACnD,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAEtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,YAAY,CAAC,EAAE,sBAAsB,CAAC;IAEtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,GAAG,CAAC,EAAE;QACJ,cAAc,CAAC,EAAE,kBAAkB,CAAC;QACpC,wBAAwB,CAAC,EAAE,cAAc,CAAC,0BAA0B,CAAC,CAAC;QACtE,cAAc,CAAC,EAAE,cAAc,CAAC,gBAAgB,CAAC,CAAC;QAClD,sBAAsB,CAAC,EAAE,yBAAyB,CAAC;KACpD,CAAC;IAEF,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;QACtD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;QAC/B,WAAW,CAAC,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;QAChD,mBAAmB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;QAChE,mBAAmB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;KACjE,CAAC;CACH,CAAC;
|
|
1
|
+
{"version":3,"file":"DatePicker.d.ts","sourceRoot":"","sources":["../../../src/DatePicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,KAAK,EAAwB,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG/E,OAAyB,EAGvB,KAAK,QAAQ,IAAI,cAAc,EAC/B,KAAK,YAAY,IAAI,kBAAkB,EACvC,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,mBAAmB,EAAW,MAAM,eAAe,CAAC;AAElE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAE7B,2DAA2D;IAC3D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,mDAAmD;IACnD,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAEtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,YAAY,CAAC,EAAE,sBAAsB,CAAC;IAEtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,GAAG,CAAC,EAAE;QACJ,cAAc,CAAC,EAAE,kBAAkB,CAAC;QACpC,wBAAwB,CAAC,EAAE,cAAc,CAAC,0BAA0B,CAAC,CAAC;QACtE,cAAc,CAAC,EAAE,cAAc,CAAC,gBAAgB,CAAC,CAAC;QAClD,sBAAsB,CAAC,EAAE,yBAAyB,CAAC;KACpD,CAAC;IAEF,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;QACtD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;QAC/B,WAAW,CAAC,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;QAChD,mBAAmB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;QAChE,mBAAmB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;KACjE,CAAC;CACH,CAAC;AAmBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CAqErE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectionMenu.d.ts","sourceRoot":"","sources":["../../../src/SelectionMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpE,OAA4B,EAC1B,KAAK,mBAAmB,EAEzB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACnD,yCAAyC;IACzC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAExC;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhE;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,CAAC;IAET,OAAO,CAAC,EAAE;QACR,
|
|
1
|
+
{"version":3,"file":"SelectionMenu.d.ts","sourceRoot":"","sources":["../../../src/SelectionMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAA+B,MAAM,OAAO,CAAC;AACpD,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpE,OAA4B,EAC1B,KAAK,mBAAmB,EAEzB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACnD,yCAAyC;IACzC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAExC;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhE;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,CAAC;IAET,OAAO,CAAC,EAAE;QACR,6CAA6C;QAC7C,QAAQ,CAAC,EAAE,mBAAmB,CAAC;KAChC,CAAC;IAEF,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,KAAK,CAAC,YAAY,CAkE3E"}
|
package/package.json
CHANGED
package/src/DatePicker.tsx
CHANGED
|
@@ -59,8 +59,12 @@ export type DatePickerProps = {
|
|
|
59
59
|
};
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
// Sentinel value for "no date". Using MIN_SAFE_INTEGER ensures we don't
|
|
63
|
+
// conflict with valid negative timestamps (dates before 1970).
|
|
64
|
+
const NO_DATE_SENTINEL = Number.MIN_SAFE_INTEGER;
|
|
65
|
+
|
|
66
|
+
function dateToMsOrSentinel(d: Date | null | undefined): number {
|
|
67
|
+
return d ? d.getTime() : NO_DATE_SENTINEL;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
function normalizeVisible(
|
|
@@ -113,9 +117,9 @@ export function DatePicker(props: DatePickerProps): React.ReactElement {
|
|
|
113
117
|
presentation,
|
|
114
118
|
visible: normalizeVisible(presentation, visible) as any,
|
|
115
119
|
|
|
116
|
-
dateMs:
|
|
117
|
-
minDateMs:
|
|
118
|
-
maxDateMs:
|
|
120
|
+
dateMs: dateToMsOrSentinel(date) as any,
|
|
121
|
+
minDateMs: dateToMsOrSentinel(minDate ?? null) as any,
|
|
122
|
+
maxDateMs: dateToMsOrSentinel(maxDate ?? null) as any,
|
|
119
123
|
|
|
120
124
|
onConfirm: onConfirm ? handleConfirm : undefined,
|
|
121
125
|
onClosed: onClosed ? handleClosed : undefined,
|
package/src/SelectionMenu.tsx
CHANGED