@retray-dev/ui-kit 7.0.1 → 9.0.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/COMPONENTS.md +554 -11
- package/EXAMPLES.md +2 -2
- package/README.md +14 -8
- package/dist/Accordion.js +57 -5
- package/dist/Accordion.mjs +4 -3
- package/dist/AlertBanner.js +4 -1
- package/dist/AlertBanner.mjs +3 -2
- package/dist/AppHeader.d.mts +40 -0
- package/dist/AppHeader.d.ts +40 -0
- package/dist/AppHeader.js +515 -0
- package/dist/AppHeader.mjs +10 -0
- package/dist/Avatar.js +39 -29
- package/dist/Avatar.mjs +2 -1
- package/dist/Badge.js +11 -1
- package/dist/Badge.mjs +2 -1
- package/dist/Button.d.mts +8 -3
- package/dist/Button.d.ts +8 -3
- package/dist/Button.js +126 -108
- package/dist/Button.mjs +6 -5
- package/dist/ButtonGroup.mjs +1 -0
- package/dist/Card.js +90 -70
- package/dist/Card.mjs +5 -4
- package/dist/CategoryStrip.js +79 -22
- package/dist/CategoryStrip.mjs +6 -6
- package/dist/Checkbox.js +118 -86
- package/dist/Checkbox.mjs +5 -5
- package/dist/Chip.js +113 -80
- package/dist/Chip.mjs +5 -5
- package/dist/ConfirmDialog.js +140 -110
- package/dist/ConfirmDialog.mjs +7 -6
- package/dist/CurrencyDisplay.mjs +1 -0
- package/dist/CurrencyInput.d.mts +1 -1
- package/dist/CurrencyInput.d.ts +1 -1
- package/dist/CurrencyInput.js +9 -5
- package/dist/CurrencyInput.mjs +5 -4
- package/dist/DetailRow.mjs +1 -0
- package/dist/EmptyState.js +131 -111
- package/dist/EmptyState.mjs +7 -6
- package/dist/ErrorBoundary.d.mts +42 -0
- package/dist/ErrorBoundary.d.ts +42 -0
- package/dist/ErrorBoundary.js +351 -0
- package/dist/ErrorBoundary.mjs +7 -0
- package/dist/Form.mjs +1 -0
- package/dist/HolographicCard.d.mts +55 -0
- package/dist/HolographicCard.d.ts +55 -0
- package/dist/HolographicCard.js +316 -0
- package/dist/HolographicCard.mjs +191 -0
- package/dist/IconButton.d.mts +8 -3
- package/dist/IconButton.d.ts +8 -3
- package/dist/IconButton.js +115 -98
- package/dist/IconButton.mjs +5 -4
- package/dist/ImageViewer.d.mts +23 -0
- package/dist/ImageViewer.d.ts +23 -0
- package/dist/ImageViewer.js +582 -0
- package/dist/ImageViewer.mjs +8 -0
- package/dist/Input.mjs +4 -3
- package/dist/LabelValue.mjs +1 -0
- package/dist/ListGroup.mjs +1 -0
- package/dist/ListItem.js +131 -117
- package/dist/ListItem.mjs +6 -5
- package/dist/MediaCard.js +54 -6
- package/dist/MediaCard.mjs +6 -5
- package/dist/MenuGroup.mjs +1 -0
- package/dist/MenuItem.js +91 -79
- package/dist/MenuItem.mjs +6 -5
- package/dist/MonthPicker.d.mts +10 -2
- package/dist/MonthPicker.d.ts +10 -2
- package/dist/MonthPicker.js +80 -17
- package/dist/MonthPicker.mjs +3 -2
- package/dist/PagerDots.d.mts +35 -0
- package/dist/PagerDots.d.ts +35 -0
- package/dist/PagerDots.js +392 -0
- package/dist/PagerDots.mjs +7 -0
- package/dist/Pressable.d.mts +5 -5
- package/dist/Pressable.d.ts +5 -5
- package/dist/Pressable.js +97 -86
- package/dist/Pressable.mjs +5 -4
- package/dist/PricingCard.d.mts +50 -0
- package/dist/PricingCard.d.ts +50 -0
- package/dist/PricingCard.js +636 -0
- package/dist/PricingCard.mjs +11 -0
- package/dist/Progress.mjs +3 -2
- package/dist/RadioGroup.js +81 -30
- package/dist/RadioGroup.mjs +5 -5
- package/dist/RetrayProvider.d.mts +2 -0
- package/dist/RetrayProvider.d.ts +2 -0
- package/dist/RetrayProvider.js +214 -0
- package/dist/RetrayProvider.mjs +5 -0
- package/dist/Select.js +51 -4
- package/dist/Select.mjs +5 -4
- package/dist/SelectableGrid.d.mts +44 -0
- package/dist/SelectableGrid.d.ts +44 -0
- package/dist/SelectableGrid.js +448 -0
- package/dist/SelectableGrid.mjs +9 -0
- package/dist/Separator.mjs +1 -0
- package/dist/Sheet.d.mts +13 -1
- package/dist/Sheet.d.ts +13 -1
- package/dist/Sheet.js +115 -5
- package/dist/Sheet.mjs +4 -2
- package/dist/Skeleton.d.mts +50 -0
- package/dist/Skeleton.d.ts +50 -0
- package/dist/Skeleton.js +61 -0
- package/dist/Skeleton.mjs +4 -2
- package/dist/Slider.js +51 -4
- package/dist/Slider.mjs +3 -2
- package/dist/Spinner.js +28 -7
- package/dist/Spinner.mjs +2 -1
- package/dist/Switch.js +98 -48
- package/dist/Switch.mjs +4 -3
- package/dist/TabBar.d.mts +42 -0
- package/dist/TabBar.d.ts +42 -0
- package/dist/TabBar.js +361 -0
- package/dist/TabBar.mjs +6 -0
- package/dist/Tabs.js +92 -62
- package/dist/Tabs.mjs +5 -4
- package/dist/Text.js +16 -0
- package/dist/Text.mjs +2 -1
- package/dist/Textarea.mjs +4 -3
- package/dist/Toast.d.mts +7 -7
- package/dist/Toast.d.ts +7 -7
- package/dist/Toast.mjs +1 -0
- package/dist/Toggle.d.mts +6 -3
- package/dist/Toggle.d.ts +6 -3
- package/dist/Toggle.js +135 -120
- package/dist/Toggle.mjs +5 -5
- package/dist/VirtualList.mjs +1 -0
- package/dist/{chunk-7H2OR44A.mjs → chunk-26BCI223.mjs} +1 -1
- package/dist/{chunk-CRYBX2CM.mjs → chunk-2TFTAWVJ.mjs} +44 -59
- package/dist/chunk-3DKJ2GIC.mjs +30 -0
- package/dist/{chunk-KWCPOM6W.mjs → chunk-3U4SSNWP.mjs} +32 -48
- package/dist/chunk-4I7D47FH.mjs +139 -0
- package/dist/chunk-4K625MVM.mjs +142 -0
- package/dist/{chunk-MN7OG7IY.mjs → chunk-6OAZJ577.mjs} +6 -4
- package/dist/{chunk-L7E7TVEZ.mjs → chunk-756RAKE4.mjs} +2 -2
- package/dist/{chunk-HSPSMN6U.mjs → chunk-7QHVVCB3.mjs} +2 -2
- package/dist/{chunk-URLL5JBR.mjs → chunk-A3A6KNQN.mjs} +3 -3
- package/dist/chunk-AJ7ZDNBT.mjs +120 -0
- package/dist/{chunk-FTLJOUOQ.mjs → chunk-AV4EMIRH.mjs} +25 -28
- package/dist/chunk-AZJF2BLK.mjs +115 -0
- package/dist/chunk-BNP626TY.mjs +159 -0
- package/dist/{chunk-5IKW3VNC.mjs → chunk-DVK4G2GT.mjs} +17 -1
- package/dist/{chunk-6LQYY7HC.mjs → chunk-EH745HE5.mjs} +2 -2
- package/dist/chunk-EJ7ZPXOH.mjs +163 -0
- package/dist/{chunk-RKLHUDZS.mjs → chunk-GD6KXMG5.mjs} +29 -15
- package/dist/{chunk-RR2VQLKE.mjs → chunk-GQYFLP3D.mjs} +14 -17
- package/dist/{chunk-Y6MXOREN.mjs → chunk-ID72TK46.mjs} +8 -17
- package/dist/{chunk-NQGVLMWG.mjs → chunk-JMOZEC77.mjs} +1 -1
- package/dist/{chunk-GCWOGZYL.mjs → chunk-JT7HKXRB.mjs} +39 -29
- package/dist/{chunk-LWG526VX.mjs → chunk-KIHCWCWL.mjs} +47 -62
- package/dist/chunk-LXJIIOYQ.mjs +104 -0
- package/dist/{chunk-SBZYEV4S.mjs → chunk-M6ZXVBTK.mjs} +5 -2
- package/dist/{chunk-XDMN67KV.mjs → chunk-MAC465BB.mjs} +10 -8
- package/dist/chunk-MBMXYJJV.mjs +36 -0
- package/dist/chunk-MLF3EZFW.mjs +119 -0
- package/dist/chunk-NA7PARID.mjs +147 -0
- package/dist/{chunk-QXGYKWI7.mjs → chunk-O3HA6TYM.mjs} +9 -4
- package/dist/{chunk-63357L2X.mjs → chunk-OB4JUQ3O.mjs} +1 -1
- package/dist/{chunk-AU2VDY4P.mjs → chunk-PFZTM6D5.mjs} +52 -4
- package/dist/chunk-QKH5ZOD5.mjs +97 -0
- package/dist/{chunk-KZJRQOIU.mjs → chunk-TERDKCLE.mjs} +11 -1
- package/dist/{chunk-U4N7WF4Z.mjs → chunk-UREA2GYY.mjs} +28 -23
- package/dist/{chunk-TAJ2PQ2O.mjs → chunk-VGTDN7SW.mjs} +7 -6
- package/dist/{chunk-URDE3EUU.mjs → chunk-VQ57HWPL.mjs} +27 -15
- package/dist/chunk-WBOOUHSS.mjs +62 -0
- package/dist/{chunk-GNGLDL6Z.mjs → chunk-WJLKJMKR.mjs} +18 -0
- package/dist/{chunk-YZJAFS4P.mjs → chunk-X4G6APW6.mjs} +22 -19
- package/dist/chunk-Y6FXYEAI.mjs +8 -0
- package/dist/chunk-YFZ3ELX5.mjs +16 -0
- package/dist/{chunk-QCNARS3X.mjs → chunk-YNROWHQJ.mjs} +1 -1
- package/dist/chunk-Z4BVUWW6.mjs +196 -0
- package/dist/{chunk-GPOUINK5.mjs → chunk-ZJKGQMYH.mjs} +10 -27
- package/dist/index-wt-orHUi.d.mts +85 -0
- package/dist/index-wt-orHUi.d.ts +85 -0
- package/dist/index.d.mts +59 -51
- package/dist/index.d.ts +59 -51
- package/dist/index.js +1940 -744
- package/dist/index.mjs +49 -39
- package/package.json +35 -5
- package/src/components/Accordion/Accordion.tsx +12 -1
- package/src/components/AlertBanner/AlertBanner.tsx +5 -0
- package/src/components/AppHeader/AppHeader.tsx +172 -0
- package/src/components/AppHeader/index.ts +1 -0
- package/src/components/Avatar/Avatar.tsx +10 -2
- package/src/components/Badge/Badge.tsx +8 -1
- package/src/components/Button/Button.tsx +20 -27
- package/src/components/Card/Card.tsx +12 -23
- package/src/components/CategoryStrip/CategoryStrip.tsx +17 -21
- package/src/components/Checkbox/Checkbox.tsx +26 -40
- package/src/components/Chip/Chip.tsx +24 -33
- package/src/components/CurrencyInput/CurrencyInput.tsx +10 -8
- package/src/components/EmptyState/EmptyState.tsx +2 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +153 -0
- package/src/components/ErrorBoundary/index.ts +1 -0
- package/src/components/HolographicCard/HolographicCard.tsx +315 -0
- package/src/components/HolographicCard/index.ts +1 -0
- package/src/components/IconButton/IconButton.tsx +19 -27
- package/src/components/ImageViewer/ImageViewer.tsx +290 -0
- package/src/components/ImageViewer/index.ts +1 -0
- package/src/components/ListItem/ListItem.tsx +70 -67
- package/src/components/MediaCard/MediaCard.tsx +8 -2
- package/src/components/MenuItem/MenuItem.tsx +10 -25
- package/src/components/MonthPicker/MonthPicker.tsx +39 -13
- package/src/components/MonthPicker/index.ts +1 -1
- package/src/components/PagerDots/PagerDots.tsx +200 -0
- package/src/components/PagerDots/index.ts +1 -0
- package/src/components/Pressable/Pressable.tsx +19 -35
- package/src/components/PricingCard/PricingCard.tsx +220 -0
- package/src/components/PricingCard/index.ts +1 -0
- package/src/components/RadioGroup/RadioGroup.tsx +14 -27
- package/src/components/RetrayProvider/RetrayProvider.tsx +59 -0
- package/src/components/RetrayProvider/index.ts +1 -0
- package/src/components/SelectableGrid/SelectableGrid.tsx +205 -0
- package/src/components/SelectableGrid/index.ts +1 -0
- package/src/components/Sheet/Sheet.tsx +65 -1
- package/src/components/Skeleton/Skeleton.tsx +142 -1
- package/src/components/Spinner/Spinner.tsx +17 -2
- package/src/components/Switch/Switch.tsx +30 -58
- package/src/components/TabBar/TabBar.tsx +169 -0
- package/src/components/TabBar/index.ts +1 -0
- package/src/components/Tabs/Tabs.tsx +23 -26
- package/src/components/Text/Text.tsx +2 -0
- package/src/components/Toggle/Toggle.tsx +35 -51
- package/src/fonts.ts +4 -1
- package/src/index.ts +23 -2
- package/src/utils/animations.ts +29 -1
- package/src/utils/fontGuard.ts +34 -0
- package/src/utils/haptics.ts +211 -9
- package/src/utils/pressable.ts +66 -0
- package/dist/chunk-76PFOSM2.mjs +0 -41
- package/dist/chunk-DITNP6PL.mjs +0 -106
- package/dist/chunk-JBLL7U3U.mjs +0 -64
- package/dist/chunk-LG4DO3DK.mjs +0 -174
- package/dist/chunk-RMMK64W5.mjs +0 -54
- package/dist/chunk-RTC3CFXF.mjs +0 -29
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import React, { useState, useCallback } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
View,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
useWindowDimensions,
|
|
8
|
+
ImageSourcePropType,
|
|
9
|
+
ScrollView,
|
|
10
|
+
NativeSyntheticEvent,
|
|
11
|
+
NativeScrollEvent,
|
|
12
|
+
} from 'react-native'
|
|
13
|
+
import { GestureHandlerRootView, GestureDetector, Gesture } from 'react-native-gesture-handler'
|
|
14
|
+
import Animated, {
|
|
15
|
+
useSharedValue,
|
|
16
|
+
useAnimatedStyle,
|
|
17
|
+
withTiming,
|
|
18
|
+
runOnJS,
|
|
19
|
+
} from 'react-native-reanimated'
|
|
20
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
21
|
+
import { renderIcon } from '../../utils/icons'
|
|
22
|
+
import { PagerDots } from '../PagerDots'
|
|
23
|
+
import { s, vs } from '../../utils/scaling'
|
|
24
|
+
|
|
25
|
+
const MAX_SCALE = 3
|
|
26
|
+
const DOUBLE_TAP_SCALE = 2.5
|
|
27
|
+
|
|
28
|
+
interface ZoomableImageProps {
|
|
29
|
+
source: ImageSourcePropType
|
|
30
|
+
width: number
|
|
31
|
+
height: number
|
|
32
|
+
/** Reports whether this page is currently zoomed in, so the pager can lock paging. */
|
|
33
|
+
onZoomChange: (zoomed: boolean) => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ZoomableImage({ source, width, height, onZoomChange }: ZoomableImageProps) {
|
|
37
|
+
const scale = useSharedValue(1)
|
|
38
|
+
const savedScale = useSharedValue(1)
|
|
39
|
+
const translateX = useSharedValue(0)
|
|
40
|
+
const translateY = useSharedValue(0)
|
|
41
|
+
const savedX = useSharedValue(0)
|
|
42
|
+
const savedY = useSharedValue(0)
|
|
43
|
+
|
|
44
|
+
const reportZoom = useCallback((zoomed: boolean) => onZoomChange(zoomed), [onZoomChange])
|
|
45
|
+
|
|
46
|
+
const reset = () => {
|
|
47
|
+
'worklet'
|
|
48
|
+
scale.value = withTiming(1)
|
|
49
|
+
savedScale.value = 1
|
|
50
|
+
translateX.value = withTiming(0)
|
|
51
|
+
translateY.value = withTiming(0)
|
|
52
|
+
savedX.value = 0
|
|
53
|
+
savedY.value = 0
|
|
54
|
+
runOnJS(reportZoom)(false)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const pinch = Gesture.Pinch()
|
|
58
|
+
.onUpdate((e) => {
|
|
59
|
+
scale.value = Math.max(1, Math.min(savedScale.value * e.scale, MAX_SCALE))
|
|
60
|
+
})
|
|
61
|
+
.onEnd(() => {
|
|
62
|
+
savedScale.value = scale.value
|
|
63
|
+
if (scale.value <= 1) {
|
|
64
|
+
reset()
|
|
65
|
+
} else {
|
|
66
|
+
runOnJS(reportZoom)(true)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const pan = Gesture.Pan()
|
|
71
|
+
.onUpdate((e) => {
|
|
72
|
+
if (scale.value <= 1) return
|
|
73
|
+
translateX.value = savedX.value + e.translationX
|
|
74
|
+
translateY.value = savedY.value + e.translationY
|
|
75
|
+
})
|
|
76
|
+
.onEnd(() => {
|
|
77
|
+
savedX.value = translateX.value
|
|
78
|
+
savedY.value = translateY.value
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const doubleTap = Gesture.Tap()
|
|
82
|
+
.numberOfTaps(2)
|
|
83
|
+
.onEnd(() => {
|
|
84
|
+
if (scale.value > 1) {
|
|
85
|
+
reset()
|
|
86
|
+
} else {
|
|
87
|
+
scale.value = withTiming(DOUBLE_TAP_SCALE)
|
|
88
|
+
savedScale.value = DOUBLE_TAP_SCALE
|
|
89
|
+
runOnJS(reportZoom)(true)
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const composed = Gesture.Exclusive(doubleTap, Gesture.Simultaneous(pinch, pan))
|
|
94
|
+
|
|
95
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
96
|
+
transform: [
|
|
97
|
+
{ translateX: translateX.value },
|
|
98
|
+
{ translateY: translateY.value },
|
|
99
|
+
{ scale: scale.value },
|
|
100
|
+
],
|
|
101
|
+
}))
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<GestureDetector gesture={composed}>
|
|
105
|
+
<Animated.View style={[{ width, height }, styles.imageWrap]}>
|
|
106
|
+
<Animated.Image
|
|
107
|
+
source={source}
|
|
108
|
+
style={[{ width, height }, animatedStyle]}
|
|
109
|
+
resizeMode="contain"
|
|
110
|
+
/>
|
|
111
|
+
</Animated.View>
|
|
112
|
+
</GestureDetector>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface ImageViewerProps {
|
|
117
|
+
/** Images to show — URI strings via `{ uri }` or `require()` sources. */
|
|
118
|
+
images: ImageSourcePropType[]
|
|
119
|
+
visible: boolean
|
|
120
|
+
onClose: () => void
|
|
121
|
+
/** Page to open on. Defaults to 0. */
|
|
122
|
+
initialIndex?: number
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Full-screen zoomable image gallery. Horizontal paging + pinch / double-tap
|
|
127
|
+
* zoom + pan. Page dots and a close button overlay the images.
|
|
128
|
+
*
|
|
129
|
+
* Requires `react-native-gesture-handler` (already a peer dependency).
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* <ImageViewer images={pages} visible={open} initialIndex={page} onClose={() => setOpen(false)} />
|
|
133
|
+
*/
|
|
134
|
+
export function ImageViewer({ images, visible, onClose, initialIndex = 0 }: ImageViewerProps) {
|
|
135
|
+
const { width, height } = useWindowDimensions()
|
|
136
|
+
const insets = useSafeAreaInsets()
|
|
137
|
+
const [index, setIndex] = useState(initialIndex)
|
|
138
|
+
const [pagingEnabled, setPagingEnabled] = useState(true)
|
|
139
|
+
const scrollRef = React.useRef<ScrollView>(null)
|
|
140
|
+
|
|
141
|
+
// Reset to the requested page each time the viewer is opened. State updates are
|
|
142
|
+
// deferred to the next frame (also when contentOffset must land on the page),
|
|
143
|
+
// so this never sets state synchronously during the effect.
|
|
144
|
+
React.useEffect(() => {
|
|
145
|
+
if (!visible) return
|
|
146
|
+
const handle = requestAnimationFrame(() => {
|
|
147
|
+
setIndex(initialIndex)
|
|
148
|
+
setPagingEnabled(true)
|
|
149
|
+
scrollRef.current?.scrollTo({ x: initialIndex * width, animated: false })
|
|
150
|
+
})
|
|
151
|
+
return () => cancelAnimationFrame(handle)
|
|
152
|
+
}, [visible, initialIndex, width])
|
|
153
|
+
|
|
154
|
+
// Swipe-down-to-dismiss. Only active when no image is zoomed (pagingEnabled).
|
|
155
|
+
// Drags the whole gallery down + fades the black backdrop; releases past
|
|
156
|
+
// threshold → close, otherwise springs back.
|
|
157
|
+
const dragY = useSharedValue(0)
|
|
158
|
+
const DISMISS_THRESHOLD = height * 0.18
|
|
159
|
+
|
|
160
|
+
const closeViewer = useCallback(() => onClose(), [onClose])
|
|
161
|
+
|
|
162
|
+
const swipeDown = Gesture.Pan()
|
|
163
|
+
.enabled(pagingEnabled)
|
|
164
|
+
.activeOffsetY(12)
|
|
165
|
+
.failOffsetX([-16, 16])
|
|
166
|
+
.onUpdate((e) => {
|
|
167
|
+
dragY.value = Math.max(0, e.translationY)
|
|
168
|
+
})
|
|
169
|
+
.onEnd((e) => {
|
|
170
|
+
if (e.translationY > DISMISS_THRESHOLD || e.velocityY > 800) {
|
|
171
|
+
runOnJS(closeViewer)()
|
|
172
|
+
} else {
|
|
173
|
+
dragY.value = withTiming(0)
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Reset drag offset whenever the viewer opens.
|
|
178
|
+
React.useEffect(() => {
|
|
179
|
+
if (visible) dragY.value = 0
|
|
180
|
+
}, [visible, dragY])
|
|
181
|
+
|
|
182
|
+
const dismissStyle = useAnimatedStyle(() => ({
|
|
183
|
+
transform: [{ translateY: dragY.value }],
|
|
184
|
+
}))
|
|
185
|
+
|
|
186
|
+
const backdropStyle = useAnimatedStyle(() => ({
|
|
187
|
+
opacity: 1 - Math.min(dragY.value / (height * 0.5), 0.85),
|
|
188
|
+
}))
|
|
189
|
+
|
|
190
|
+
const onMomentumEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
191
|
+
const page = Math.round(e.nativeEvent.contentOffset.x / width)
|
|
192
|
+
setIndex(page)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const goTo = (page: number) => {
|
|
196
|
+
scrollRef.current?.scrollTo({ x: page * width, animated: true })
|
|
197
|
+
setIndex(page)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<Modal visible={visible} transparent={false} animationType="fade" onRequestClose={onClose} statusBarTranslucent>
|
|
202
|
+
<GestureHandlerRootView style={styles.root}>
|
|
203
|
+
<Animated.View style={[styles.backdrop, backdropStyle]} pointerEvents="none" />
|
|
204
|
+
<Animated.View style={[styles.container, dismissStyle]}>
|
|
205
|
+
<GestureDetector gesture={swipeDown}>
|
|
206
|
+
<Animated.View style={styles.root}>
|
|
207
|
+
<ScrollView
|
|
208
|
+
ref={scrollRef}
|
|
209
|
+
horizontal
|
|
210
|
+
pagingEnabled
|
|
211
|
+
scrollEnabled={pagingEnabled}
|
|
212
|
+
showsHorizontalScrollIndicator={false}
|
|
213
|
+
onMomentumScrollEnd={onMomentumEnd}
|
|
214
|
+
bounces={false}
|
|
215
|
+
>
|
|
216
|
+
{images.map((source, i) => (
|
|
217
|
+
<ZoomableImage
|
|
218
|
+
key={i}
|
|
219
|
+
source={source}
|
|
220
|
+
width={width}
|
|
221
|
+
height={height}
|
|
222
|
+
onZoomChange={(zoomed) => setPagingEnabled(!zoomed)}
|
|
223
|
+
/>
|
|
224
|
+
))}
|
|
225
|
+
</ScrollView>
|
|
226
|
+
</Animated.View>
|
|
227
|
+
</GestureDetector>
|
|
228
|
+
|
|
229
|
+
<TouchableOpacity
|
|
230
|
+
style={[styles.closeButton, { top: insets.top + vs(8) }]}
|
|
231
|
+
onPress={onClose}
|
|
232
|
+
activeOpacity={0.7}
|
|
233
|
+
touchSoundDisabled={true}
|
|
234
|
+
accessibilityRole="button"
|
|
235
|
+
accessibilityLabel="Close"
|
|
236
|
+
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
|
|
237
|
+
>
|
|
238
|
+
{renderIcon('x', 26, '#fff')}
|
|
239
|
+
</TouchableOpacity>
|
|
240
|
+
|
|
241
|
+
{images.length > 1 ? (
|
|
242
|
+
<View style={[styles.dots, { bottom: insets.bottom + vs(16) }]} pointerEvents="box-none">
|
|
243
|
+
<PagerDots
|
|
244
|
+
count={images.length}
|
|
245
|
+
activeIndex={index}
|
|
246
|
+
onDotPress={goTo}
|
|
247
|
+
activeColor="#fff"
|
|
248
|
+
inactiveColor="rgba(255,255,255,0.4)"
|
|
249
|
+
/>
|
|
250
|
+
</View>
|
|
251
|
+
) : null}
|
|
252
|
+
</Animated.View>
|
|
253
|
+
</GestureHandlerRootView>
|
|
254
|
+
</Modal>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const styles = StyleSheet.create({
|
|
259
|
+
root: {
|
|
260
|
+
flex: 1,
|
|
261
|
+
},
|
|
262
|
+
container: {
|
|
263
|
+
flex: 1,
|
|
264
|
+
},
|
|
265
|
+
backdrop: {
|
|
266
|
+
...StyleSheet.absoluteFillObject,
|
|
267
|
+
backgroundColor: '#000',
|
|
268
|
+
},
|
|
269
|
+
imageWrap: {
|
|
270
|
+
alignItems: 'center',
|
|
271
|
+
justifyContent: 'center',
|
|
272
|
+
overflow: 'hidden',
|
|
273
|
+
},
|
|
274
|
+
closeButton: {
|
|
275
|
+
position: 'absolute',
|
|
276
|
+
right: s(12),
|
|
277
|
+
width: s(40),
|
|
278
|
+
height: s(40),
|
|
279
|
+
borderRadius: s(20),
|
|
280
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
281
|
+
alignItems: 'center',
|
|
282
|
+
justifyContent: 'center',
|
|
283
|
+
},
|
|
284
|
+
dots: {
|
|
285
|
+
position: 'absolute',
|
|
286
|
+
left: 0,
|
|
287
|
+
right: 0,
|
|
288
|
+
alignItems: 'center',
|
|
289
|
+
},
|
|
290
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ImageViewer'
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import {
|
|
3
|
-
TouchableOpacity,
|
|
4
3
|
View,
|
|
5
4
|
Text,
|
|
6
5
|
StyleSheet,
|
|
7
6
|
ViewStyle,
|
|
8
7
|
TextStyle,
|
|
9
8
|
} from 'react-native'
|
|
10
|
-
import Animated from 'react-native-reanimated'
|
|
11
9
|
import { Entypo } from '@expo/vector-icons'
|
|
12
10
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
13
11
|
import { useTheme } from '../../theme'
|
|
14
12
|
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
15
13
|
import { renderIcon } from '../../utils/icons'
|
|
16
14
|
import { RADIUS } from '../../tokens'
|
|
17
|
-
import {
|
|
18
|
-
import { SPRINGS, PRESS_SCALE } from '../../utils/animations'
|
|
15
|
+
import { PressableRow } from '../../utils/pressable'
|
|
19
16
|
|
|
20
17
|
export type ListItemVariant = 'plain' | 'card'
|
|
21
18
|
|
|
@@ -105,12 +102,6 @@ function ListItemBase({
|
|
|
105
102
|
accessibilityLabel,
|
|
106
103
|
}: ListItemProps) {
|
|
107
104
|
const { colors } = useTheme()
|
|
108
|
-
const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
|
|
109
|
-
pressScale: PRESS_SCALE.row,
|
|
110
|
-
pressInSpring: SPRINGS.surfacePressIn,
|
|
111
|
-
pressOutSpring: SPRINGS.surfacePressOut,
|
|
112
|
-
disabled: !onPress || disabled,
|
|
113
|
-
})
|
|
114
105
|
|
|
115
106
|
const handlePress = () => {
|
|
116
107
|
hapticSelection()
|
|
@@ -142,79 +133,91 @@ function ListItemBase({
|
|
|
142
133
|
|
|
143
134
|
const a11yLabel = accessibilityLabel ?? [title, subtitle, caption].filter(Boolean).join('. ')
|
|
144
135
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
style={
|
|
149
|
-
|
|
150
|
-
onPressIn={onPressIn}
|
|
151
|
-
onPressOut={onPressOut}
|
|
152
|
-
disabled={disabled}
|
|
153
|
-
activeOpacity={1}
|
|
154
|
-
touchSoundDisabled={true}
|
|
155
|
-
accessibilityRole={onPress ? 'button' : undefined}
|
|
156
|
-
accessibilityLabel={onPress ? a11yLabel : undefined}
|
|
157
|
-
accessibilityState={onPress ? { disabled: !!disabled } : undefined}
|
|
158
|
-
>
|
|
159
|
-
{effectiveLeft ? (
|
|
160
|
-
<View style={styles.leftContainer}>{effectiveLeft}</View>
|
|
161
|
-
) : null}
|
|
136
|
+
const content = (
|
|
137
|
+
<>
|
|
138
|
+
{effectiveLeft ? (
|
|
139
|
+
<View style={styles.leftContainer}>{effectiveLeft}</View>
|
|
140
|
+
) : null}
|
|
162
141
|
|
|
163
|
-
|
|
142
|
+
<View style={styles.content}>
|
|
143
|
+
<Text
|
|
144
|
+
style={[styles.title, { color: colors.foreground }, titleStyle]}
|
|
145
|
+
numberOfLines={2}
|
|
146
|
+
allowFontScaling={true}
|
|
147
|
+
>
|
|
148
|
+
{title}
|
|
149
|
+
</Text>
|
|
150
|
+
{subtitle ? (
|
|
164
151
|
<Text
|
|
165
|
-
style={[styles.
|
|
152
|
+
style={[styles.subtitle, { color: colors.foregroundMuted }, subtitleStyle]}
|
|
166
153
|
numberOfLines={2}
|
|
167
154
|
allowFontScaling={true}
|
|
168
155
|
>
|
|
169
|
-
{
|
|
156
|
+
{subtitle}
|
|
170
157
|
</Text>
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
158
|
+
) : null}
|
|
159
|
+
{caption ? (
|
|
160
|
+
<Text
|
|
161
|
+
style={[styles.caption, { color: colors.foregroundMuted }, captionStyle]}
|
|
162
|
+
numberOfLines={1}
|
|
163
|
+
allowFontScaling={true}
|
|
164
|
+
>
|
|
165
|
+
{caption}
|
|
166
|
+
</Text>
|
|
167
|
+
) : null}
|
|
168
|
+
</View>
|
|
169
|
+
|
|
170
|
+
{effectiveRight !== undefined ? (
|
|
171
|
+
<View style={styles.rightContainer}>
|
|
172
|
+
{typeof effectiveRight === 'string' ? (
|
|
181
173
|
<Text
|
|
182
|
-
style={[styles.
|
|
183
|
-
numberOfLines={1}
|
|
174
|
+
style={[styles.rightText, { color: colors.foregroundMuted }]}
|
|
184
175
|
allowFontScaling={true}
|
|
185
176
|
>
|
|
186
|
-
{
|
|
177
|
+
{effectiveRight}
|
|
187
178
|
</Text>
|
|
188
|
-
) :
|
|
179
|
+
) : (
|
|
180
|
+
effectiveRight
|
|
181
|
+
)}
|
|
189
182
|
</View>
|
|
183
|
+
) : showChevron ? (
|
|
184
|
+
<Entypo name="chevron-with-circle-right" size={20} color={colors.foregroundMuted} />
|
|
185
|
+
) : null}
|
|
186
|
+
</>
|
|
187
|
+
)
|
|
190
188
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
189
|
+
if (onPress) {
|
|
190
|
+
return (
|
|
191
|
+
<View style={disabled && styles.disabled}>
|
|
192
|
+
<PressableRow
|
|
193
|
+
style={[styles.container, cardStyle, style]}
|
|
194
|
+
onPress={handlePress}
|
|
195
|
+
enabled={!disabled}
|
|
196
|
+
rippleColor="transparent"
|
|
197
|
+
touchSoundDisabled
|
|
198
|
+
activateOnHover
|
|
199
|
+
accessibilityRole="button"
|
|
200
|
+
accessibilityLabel={a11yLabel}
|
|
201
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
202
|
+
>
|
|
203
|
+
{content}
|
|
204
|
+
</PressableRow>
|
|
205
|
+
{showSeparator ? (
|
|
206
|
+
<View style={[styles.separator, { backgroundColor: colors.separator }]} />
|
|
206
207
|
) : null}
|
|
207
|
-
</
|
|
208
|
+
</View>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
208
211
|
|
|
212
|
+
return (
|
|
213
|
+
<View style={[disabled && styles.disabled]}>
|
|
214
|
+
<View style={[styles.container, cardStyle, style]}>
|
|
215
|
+
{content}
|
|
216
|
+
</View>
|
|
209
217
|
{showSeparator ? (
|
|
210
|
-
<View
|
|
211
|
-
style={[
|
|
212
|
-
styles.separator,
|
|
213
|
-
{ backgroundColor: colors.separator },
|
|
214
|
-
]}
|
|
215
|
-
/>
|
|
218
|
+
<View style={[styles.separator, { backgroundColor: colors.separator }]} />
|
|
216
219
|
) : null}
|
|
217
|
-
</
|
|
220
|
+
</View>
|
|
218
221
|
)
|
|
219
222
|
}
|
|
220
223
|
|
|
@@ -135,10 +135,16 @@ function MediaCardBase({
|
|
|
135
135
|
{(onActionPress || actionIcon || actionIconName) && (
|
|
136
136
|
<TouchableOpacity
|
|
137
137
|
style={[styles.actionButton, { backgroundColor: 'rgba(0,0,0,0.24)' }]}
|
|
138
|
-
onPress={() => {
|
|
138
|
+
onPress={(e) => {
|
|
139
|
+
// Stop propagation to prevent triggering parent onPress
|
|
140
|
+
e?.stopPropagation?.()
|
|
141
|
+
impactLight()
|
|
142
|
+
onActionPress?.()
|
|
143
|
+
}}
|
|
139
144
|
activeOpacity={0.8}
|
|
140
145
|
touchSoundDisabled={true}
|
|
141
|
-
|
|
146
|
+
// On web, avoid nested <button> by using a non-button role when parent is pressable
|
|
147
|
+
accessibilityRole={Platform.OS === 'web' && onPress ? undefined : 'button'}
|
|
142
148
|
accessibilityLabel={actionIconName ?? 'action'}
|
|
143
149
|
accessibilityState={{ selected: actionActive }}
|
|
144
150
|
>
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import {
|
|
3
|
-
TouchableOpacity,
|
|
4
3
|
View,
|
|
5
4
|
Text,
|
|
6
5
|
StyleSheet,
|
|
7
6
|
ViewStyle,
|
|
8
7
|
TextStyle,
|
|
9
8
|
} from 'react-native'
|
|
10
|
-
import Animated from 'react-native-reanimated'
|
|
11
9
|
import { Entypo } from '@expo/vector-icons'
|
|
12
10
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
13
11
|
import { useTheme } from '../../theme'
|
|
14
12
|
import { s, vs, ms } from '../../utils/scaling'
|
|
15
13
|
import { renderIcon } from '../../utils/icons'
|
|
16
14
|
import { RADIUS } from '../../tokens'
|
|
17
|
-
import {
|
|
18
|
-
import { SPRINGS, PRESS_SCALE } from '../../utils/animations'
|
|
15
|
+
import { PressableRow } from '../../utils/pressable'
|
|
19
16
|
|
|
20
17
|
export type MenuItemVariant = 'plain' | 'card'
|
|
21
18
|
|
|
@@ -77,12 +74,6 @@ function MenuItemBase({
|
|
|
77
74
|
accessibilityLabel,
|
|
78
75
|
}: MenuItemProps) {
|
|
79
76
|
const { colors } = useTheme()
|
|
80
|
-
const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
|
|
81
|
-
pressScale: PRESS_SCALE.row,
|
|
82
|
-
pressInSpring: SPRINGS.surfacePressIn,
|
|
83
|
-
pressOutSpring: SPRINGS.surfacePressOut,
|
|
84
|
-
disabled,
|
|
85
|
-
})
|
|
86
77
|
|
|
87
78
|
const handlePress = () => {
|
|
88
79
|
hapticSelection()
|
|
@@ -111,15 +102,14 @@ function MenuItemBase({
|
|
|
111
102
|
const a11yLabel = accessibilityLabel ?? (subtitle ? `${label}. ${subtitle}` : label)
|
|
112
103
|
|
|
113
104
|
return (
|
|
114
|
-
<
|
|
115
|
-
<
|
|
105
|
+
<View style={disabled && styles.disabled}>
|
|
106
|
+
<PressableRow
|
|
116
107
|
style={[styles.container, cardStyle, style]}
|
|
117
108
|
onPress={handlePress}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
touchSoundDisabled={true}
|
|
109
|
+
enabled={!disabled}
|
|
110
|
+
rippleColor="transparent"
|
|
111
|
+
touchSoundDisabled
|
|
112
|
+
activateOnHover
|
|
123
113
|
accessibilityRole="button"
|
|
124
114
|
accessibilityLabel={a11yLabel}
|
|
125
115
|
accessibilityState={{ disabled }}
|
|
@@ -158,17 +148,12 @@ function MenuItemBase({
|
|
|
158
148
|
) : showChevron ? (
|
|
159
149
|
<Entypo name="chevron-right" size={18} color={colors.foregroundMuted} />
|
|
160
150
|
) : null}
|
|
161
|
-
</
|
|
151
|
+
</PressableRow>
|
|
162
152
|
|
|
163
153
|
{showSeparator ? (
|
|
164
|
-
<View
|
|
165
|
-
style={[
|
|
166
|
-
styles.separator,
|
|
167
|
-
{ backgroundColor: colors.separator },
|
|
168
|
-
]}
|
|
169
|
-
/>
|
|
154
|
+
<View style={[styles.separator, { backgroundColor: colors.separator }]} />
|
|
170
155
|
) : null}
|
|
171
|
-
</
|
|
156
|
+
</View>
|
|
172
157
|
)
|
|
173
158
|
}
|
|
174
159
|
|
|
@@ -18,9 +18,27 @@ export interface MonthPickerValue {
|
|
|
18
18
|
year: number
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Convert a JS `Date` to a `MonthPickerValue` (uses local time). */
|
|
22
|
+
export function dateToMonthPickerValue(date: Date): MonthPickerValue {
|
|
23
|
+
return { month: date.getMonth() + 1, year: date.getFullYear() }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Convert a `MonthPickerValue` to a `Date` at the first day of that month (local time). */
|
|
27
|
+
export function monthPickerValueToDate(value: MonthPickerValue): Date {
|
|
28
|
+
return new Date(value.year, value.month - 1, 1)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Absolute month index — lets us compare/clamp values with simple arithmetic.
|
|
32
|
+
const toIndex = (v: MonthPickerValue) => v.year * 12 + (v.month - 1)
|
|
33
|
+
const fromIndex = (i: number): MonthPickerValue => ({ year: Math.floor(i / 12), month: (i % 12) + 1 })
|
|
34
|
+
|
|
21
35
|
export interface MonthPickerProps {
|
|
22
36
|
value: MonthPickerValue
|
|
23
37
|
onChange: (value: MonthPickerValue) => void
|
|
38
|
+
/** Earliest selectable month (inclusive). Prev arrow disables at this bound. */
|
|
39
|
+
minValue?: MonthPickerValue
|
|
40
|
+
/** Latest selectable month (inclusive). Next arrow disables at this bound — e.g. cap at the current month. */
|
|
41
|
+
maxValue?: MonthPickerValue
|
|
24
42
|
/** BCP 47 locale tag. Built-in: 'en' | 'es' | 'pt' | 'fr'. For other locales supply formatLabel. */
|
|
25
43
|
locale?: string
|
|
26
44
|
/** Custom label formatter. Takes precedence over locale. */
|
|
@@ -28,9 +46,16 @@ export interface MonthPickerProps {
|
|
|
28
46
|
style?: ViewStyle
|
|
29
47
|
}
|
|
30
48
|
|
|
31
|
-
export function MonthPicker({ value, onChange, locale = 'en', formatLabel, style }: MonthPickerProps) {
|
|
49
|
+
export function MonthPicker({ value, onChange, minValue, maxValue, locale = 'en', formatLabel, style }: MonthPickerProps) {
|
|
32
50
|
const { colors } = useTheme()
|
|
33
51
|
|
|
52
|
+
const index = toIndex(value)
|
|
53
|
+
const minIndex = minValue ? toIndex(minValue) : -Infinity
|
|
54
|
+
const maxIndex = maxValue ? toIndex(maxValue) : Infinity
|
|
55
|
+
|
|
56
|
+
const prevDisabled = index - 1 < minIndex
|
|
57
|
+
const nextDisabled = index + 1 > maxIndex
|
|
58
|
+
|
|
34
59
|
const getLabel = (): string => {
|
|
35
60
|
if (formatLabel) return formatLabel(value)
|
|
36
61
|
const names = MONTH_NAMES[locale] ?? MONTH_NAMES.en
|
|
@@ -38,32 +63,28 @@ export function MonthPicker({ value, onChange, locale = 'en', formatLabel, style
|
|
|
38
63
|
}
|
|
39
64
|
|
|
40
65
|
const handlePrev = () => {
|
|
66
|
+
if (prevDisabled) return
|
|
41
67
|
hapticSelection()
|
|
42
|
-
|
|
43
|
-
onChange({ month: 12, year: value.year - 1 })
|
|
44
|
-
} else {
|
|
45
|
-
onChange({ month: value.month - 1, year: value.year })
|
|
46
|
-
}
|
|
68
|
+
onChange(fromIndex(index - 1))
|
|
47
69
|
}
|
|
48
70
|
|
|
49
71
|
const handleNext = () => {
|
|
72
|
+
if (nextDisabled) return
|
|
50
73
|
hapticSelection()
|
|
51
|
-
|
|
52
|
-
onChange({ month: 1, year: value.year + 1 })
|
|
53
|
-
} else {
|
|
54
|
-
onChange({ month: value.month + 1, year: value.year })
|
|
55
|
-
}
|
|
74
|
+
onChange(fromIndex(index + 1))
|
|
56
75
|
}
|
|
57
76
|
|
|
58
77
|
return (
|
|
59
78
|
<View style={[styles.container, style]} accessibilityRole="adjustable" accessibilityLabel={getLabel()}>
|
|
60
79
|
<TouchableOpacity
|
|
61
|
-
style={styles.arrow}
|
|
80
|
+
style={[styles.arrow, prevDisabled && styles.arrowDisabled]}
|
|
62
81
|
onPress={handlePrev}
|
|
82
|
+
disabled={prevDisabled}
|
|
63
83
|
activeOpacity={0.6}
|
|
64
84
|
touchSoundDisabled={true}
|
|
65
85
|
accessibilityRole="button"
|
|
66
86
|
accessibilityLabel="Previous month"
|
|
87
|
+
accessibilityState={{ disabled: prevDisabled }}
|
|
67
88
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
68
89
|
>
|
|
69
90
|
<Entypo name="chevron-left" size={22} color={colors.foreground} />
|
|
@@ -76,12 +97,14 @@ export function MonthPicker({ value, onChange, locale = 'en', formatLabel, style
|
|
|
76
97
|
{getLabel()}
|
|
77
98
|
</Text>
|
|
78
99
|
<TouchableOpacity
|
|
79
|
-
style={styles.arrow}
|
|
100
|
+
style={[styles.arrow, nextDisabled && styles.arrowDisabled]}
|
|
80
101
|
onPress={handleNext}
|
|
102
|
+
disabled={nextDisabled}
|
|
81
103
|
activeOpacity={0.6}
|
|
82
104
|
touchSoundDisabled={true}
|
|
83
105
|
accessibilityRole="button"
|
|
84
106
|
accessibilityLabel="Next month"
|
|
107
|
+
accessibilityState={{ disabled: nextDisabled }}
|
|
85
108
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
86
109
|
>
|
|
87
110
|
<Entypo name="chevron-right" size={22} color={colors.foreground} />
|
|
@@ -102,6 +125,9 @@ const styles = StyleSheet.create({
|
|
|
102
125
|
alignItems: 'center',
|
|
103
126
|
justifyContent: 'center',
|
|
104
127
|
},
|
|
128
|
+
arrowDisabled: {
|
|
129
|
+
opacity: 0.3,
|
|
130
|
+
},
|
|
105
131
|
label: {
|
|
106
132
|
fontFamily: 'Sohne-Medium',
|
|
107
133
|
fontSize: ms(17),
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { MonthPicker } from './MonthPicker'
|
|
1
|
+
export { MonthPicker, dateToMonthPickerValue, monthPickerValueToDate } from './MonthPicker'
|
|
2
2
|
export type { MonthPickerProps, MonthPickerValue } from './MonthPicker'
|