@retray-dev/ui-kit 10.2.0 → 12.1.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 +287 -37
- package/README.md +11 -2
- package/dist/Accordion.mjs +2 -2
- package/dist/AlertBanner.mjs +2 -2
- package/dist/AppHeader.mjs +3 -3
- package/dist/Avatar.mjs +2 -2
- package/dist/Badge.mjs +2 -2
- package/dist/Button.mjs +2 -2
- package/dist/Card.mjs +2 -2
- package/dist/CategoryStrip.mjs +2 -2
- package/dist/Checkbox.mjs +2 -2
- package/dist/Chip.mjs +2 -2
- package/dist/ConfirmDialog.d.mts +1 -6
- package/dist/ConfirmDialog.d.ts +1 -6
- package/dist/ConfirmDialog.js +29 -23
- package/dist/ConfirmDialog.mjs +3 -3
- package/dist/CurrencyDisplay.mjs +2 -2
- package/dist/CurrencyInput.d.mts +3 -8
- package/dist/CurrencyInput.d.ts +3 -8
- package/dist/CurrencyInput.js +3 -1
- package/dist/CurrencyInput.mjs +3 -3
- package/dist/DetailRow.mjs +2 -2
- package/dist/EmptyState.mjs +3 -3
- package/dist/ErrorBoundary.mjs +2 -2
- package/dist/Form.mjs +2 -2
- package/dist/IconButton.mjs +2 -2
- package/dist/IconPicker.js +675 -248
- package/dist/IconPicker.mjs +3 -2
- package/dist/ImageUpload.mjs +3 -3
- package/dist/ImageViewer.mjs +4 -4
- package/dist/Input.mjs +2 -2
- package/dist/LabelValue.mjs +2 -2
- package/dist/ListGroup.mjs +2 -2
- package/dist/ListItem.d.mts +7 -7
- package/dist/ListItem.d.ts +7 -7
- package/dist/ListItem.js +12 -7
- package/dist/ListItem.mjs +2 -2
- package/dist/MediaCard.mjs +2 -2
- package/dist/MenuGroup.mjs +2 -2
- package/dist/MenuItem.mjs +2 -2
- package/dist/MonthPicker.mjs +2 -2
- package/dist/NumberStepper.mjs +2 -2
- package/dist/PagerDots.mjs +2 -2
- package/dist/Pressable.d.mts +15 -7
- package/dist/Pressable.d.ts +15 -7
- package/dist/Pressable.js +7 -3
- package/dist/Pressable.mjs +1 -1
- package/dist/PricingCard.mjs +4 -4
- package/dist/Progress.mjs +2 -2
- package/dist/RadioGroup.mjs +2 -2
- package/dist/RetrayProvider.mjs +3 -3
- package/dist/Select.mjs +2 -2
- package/dist/SelectableGrid.mjs +2 -2
- package/dist/Separator.mjs +2 -2
- package/dist/Sheet.d.mts +4 -46
- package/dist/Sheet.d.ts +4 -46
- package/dist/Sheet.js +46 -114
- package/dist/Sheet.mjs +2 -3
- package/dist/SheetSelect.mjs +2 -2
- package/dist/Skeleton.mjs +2 -2
- package/dist/Slider.mjs +2 -2
- package/dist/Spinner.mjs +2 -2
- package/dist/Stats.d.mts +30 -0
- package/dist/Stats.d.ts +30 -0
- package/dist/Stats.js +429 -0
- package/dist/Stats.mjs +9 -0
- package/dist/Switch.mjs +2 -2
- package/dist/TabBar.mjs +2 -2
- package/dist/Tabs.mjs +2 -2
- package/dist/Text.d.mts +3 -1
- package/dist/Text.d.ts +3 -1
- package/dist/Text.js +3 -3
- package/dist/Text.mjs +2 -2
- package/dist/Textarea.mjs +2 -2
- package/dist/Toast.mjs +2 -2
- package/dist/Toggle.mjs +2 -2
- package/dist/{chunk-YJ7I257J.mjs → chunk-265G6A46.mjs} +1 -1
- package/dist/{chunk-ELXBDILQ.mjs → chunk-2A2LEFZG.mjs} +2 -2
- package/dist/{chunk-ID72TK46.mjs → chunk-2CBQKU7H.mjs} +1 -1
- package/dist/{chunk-OB4JUQ3O.mjs → chunk-2I2AYECM.mjs} +1 -1
- package/dist/{chunk-WJLKJMKR.mjs → chunk-357YO24D.mjs} +4 -4
- package/dist/{chunk-GQYFLP3D.mjs → chunk-3GEYJ7I5.mjs} +1 -1
- package/dist/{chunk-AV4EMIRH.mjs → chunk-3N2M3WZL.mjs} +1 -1
- package/dist/{chunk-VF2ATYN3.mjs → chunk-3UYAZ7I4.mjs} +1 -1
- package/dist/{chunk-JMOZEC77.mjs → chunk-4WFMPFZB.mjs} +1 -1
- package/dist/chunk-5OLNXP3S.mjs +144 -0
- package/dist/{chunk-6SECQ2ZF.mjs → chunk-7HSILTC4.mjs} +2 -2
- package/dist/{chunk-IRRY3CRZ.mjs → chunk-AKM4EPOT.mjs} +1 -1
- package/dist/{chunk-IX3NYLYQ.mjs → chunk-AQEVCEXV.mjs} +1 -1
- package/dist/{chunk-WBOOUHSS.mjs → chunk-BCWEHE34.mjs} +1 -1
- package/dist/{chunk-AJ7ZDNBT.mjs → chunk-BOVUP27T.mjs} +1 -1
- package/dist/{chunk-BRKYVJVV.mjs → chunk-BQZE3HAW.mjs} +1 -1
- package/dist/{chunk-Z6SFHN6T.mjs → chunk-D3Y2T42P.mjs} +1 -1
- package/dist/{chunk-T2KCAHOS.mjs → chunk-DF6DU42P.mjs} +1 -1
- package/dist/{chunk-TB6SD2FT.mjs → chunk-DI7CBDL6.mjs} +1 -1
- package/dist/{chunk-HTHGSXFG.mjs → chunk-DOGIPOF5.mjs} +1 -1
- package/dist/{chunk-MBMXYJJV.mjs → chunk-E7NEHHXV.mjs} +7 -3
- package/dist/{chunk-MX6HRKMI.mjs → chunk-EFLFRAHD.mjs} +1 -1
- package/dist/{chunk-SOYNZDVY.mjs → chunk-EMUWGDWC.mjs} +6 -1
- package/dist/{chunk-AJRVDP2H.mjs → chunk-F4V6XLP4.mjs} +3 -3
- package/dist/{chunk-DYT7BG5I.mjs → chunk-FA2KMTH5.mjs} +1 -1
- package/dist/{chunk-Y2NS74WS.mjs → chunk-FFTYLPSB.mjs} +46 -98
- package/dist/{chunk-VKID2D2I.mjs → chunk-FUVYSVGR.mjs} +13 -8
- package/dist/{chunk-7LWRKMF5.mjs → chunk-FVTVCJAH.mjs} +1 -1
- package/dist/{chunk-TZDGAP5N.mjs → chunk-GK4VRMNE.mjs} +2 -2
- package/dist/{chunk-6Q64UFIA.mjs → chunk-HJ46DTJE.mjs} +1 -1
- package/dist/{chunk-WF2XDFRK.mjs → chunk-HLMPMUK2.mjs} +1 -1
- package/dist/{chunk-GD6KXMG5.mjs → chunk-I4V5XZPS.mjs} +1 -1
- package/dist/{chunk-TBNZHU6C.mjs → chunk-ISY26JQJ.mjs} +2 -2
- package/dist/{chunk-X4G6APW6.mjs → chunk-J6Q2YJEV.mjs} +1 -1
- package/dist/{chunk-WYEUNUTP.mjs → chunk-JCZQOY4O.mjs} +31 -24
- package/dist/{chunk-U2XJFYED.mjs → chunk-JNVAIDLK.mjs} +1 -1
- package/dist/{chunk-SOA2Z4RB.mjs → chunk-JULSIZDM.mjs} +1 -1
- package/dist/chunk-KHYX4IOM.mjs +1114 -0
- package/dist/{chunk-RYZC432S.mjs → chunk-LRM4AVYY.mjs} +1 -1
- package/dist/{chunk-6L4G6PBT.mjs → chunk-MYZ2EDYU.mjs} +1 -1
- package/dist/{chunk-BUMAMSTZ.mjs → chunk-N4ZPVCJH.mjs} +1 -1
- package/dist/{chunk-Z4VHZ7B5.mjs → chunk-NXI4YDZ2.mjs} +1 -1
- package/dist/{chunk-ZZ2R6KZ3.mjs → chunk-OULVKTWL.mjs} +1 -1
- package/dist/{chunk-FCSSQK3L.mjs → chunk-P64WHW4A.mjs} +1 -1
- package/dist/{chunk-KOO4WITD.mjs → chunk-P73V2EKS.mjs} +1 -1
- package/dist/{chunk-SXLKNTA4.mjs → chunk-PGERH3P7.mjs} +1 -1
- package/dist/{chunk-2UYENBLV.mjs → chunk-QSFV2P7O.mjs} +1 -1
- package/dist/{chunk-JT7HKXRB.mjs → chunk-S3KJCPEJ.mjs} +1 -1
- package/dist/{chunk-BEMIQXXU.mjs → chunk-V6NFJXKO.mjs} +1 -1
- package/dist/{chunk-A3A6KNQN.mjs → chunk-WOEWGSTU.mjs} +1 -1
- package/dist/{chunk-NMU5FMQJ.mjs → chunk-X26S5EVZ.mjs} +4 -2
- package/dist/{chunk-YFZ3ELX5.mjs → chunk-XBAGGKLW.mjs} +2 -2
- package/dist/{chunk-S2R7UVOE.mjs → chunk-ZHMSAYLT.mjs} +1 -1
- package/dist/fonts.d.mts +1 -7
- package/dist/fonts.d.ts +1 -7
- package/dist/fonts.js +0 -2
- package/dist/fonts.mjs +1 -2
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1184 -708
- package/dist/index.mjs +53 -52
- package/package.json +3 -3
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +39 -30
- package/src/components/CurrencyInput/CurrencyInput.tsx +4 -7
- package/src/components/IconPicker/IconPicker.tsx +124 -112
- package/src/components/ListItem/ListItem.tsx +43 -28
- package/src/components/Pressable/Pressable.tsx +20 -8
- package/src/components/Sheet/Sheet.tsx +64 -172
- package/src/components/Stats/Stats.tsx +226 -0
- package/src/components/Stats/index.ts +2 -0
- package/src/components/Text/Text.tsx +4 -2
- package/src/fonts.ts +0 -7
- package/src/index.ts +4 -0
- package/src/theme/colorUtils.ts +9 -0
- package/src/utils/curatedIcons.ts +698 -135
- package/src/utils/fontGuard.ts +2 -1
- package/dist/chunk-53Z3NYGE.mjs +0 -742
|
@@ -31,13 +31,13 @@ export interface ListItemProps {
|
|
|
31
31
|
leftRender?: React.ReactNode
|
|
32
32
|
/**
|
|
33
33
|
* Arbitrary content rendered on the right (badge, price, chevron, switch, etc.).
|
|
34
|
-
* Replaces the old `trailing` prop (still accepted as an alias).
|
|
35
34
|
*/
|
|
36
35
|
rightRender?: React.ReactNode | string
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Multiple action buttons rendered on the right with automatic gap.
|
|
38
|
+
* Takes precedence over `rightRender` and `showChevron`.
|
|
39
|
+
*/
|
|
40
|
+
rightActions?: React.ReactNode[]
|
|
41
41
|
/**
|
|
42
42
|
* Icon name from `@expo/vector-icons` rendered in the left slot.
|
|
43
43
|
* See https://icons.expo.fyi. Takes precedence over `leftRender`.
|
|
@@ -65,7 +65,7 @@ export interface ListItemProps {
|
|
|
65
65
|
*/
|
|
66
66
|
variant?: ListItemVariant
|
|
67
67
|
|
|
68
|
-
/** Show a right-pointing chevron on the far right. Ignored when `rightRender` / `
|
|
68
|
+
/** Show a right-pointing chevron on the far right. Ignored when `rightRender` / `rightActions` / `rightIcon` is set. */
|
|
69
69
|
showChevron?: boolean
|
|
70
70
|
|
|
71
71
|
/** Visual separator line at the bottom of the item. Useful when rendering multiple plain items in a list. */
|
|
@@ -91,8 +91,7 @@ function ListItemBase({
|
|
|
91
91
|
imageSource,
|
|
92
92
|
leftRender,
|
|
93
93
|
rightRender,
|
|
94
|
-
|
|
95
|
-
icon,
|
|
94
|
+
rightActions,
|
|
96
95
|
leftIcon,
|
|
97
96
|
rightIcon,
|
|
98
97
|
leftIconColor,
|
|
@@ -119,16 +118,14 @@ function ListItemBase({
|
|
|
119
118
|
onPress?.()
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
// imageSource takes precedence, then leftIcon, then leftRender
|
|
121
|
+
// imageSource takes precedence, then leftIcon, then leftRender
|
|
123
122
|
const effectiveLeft: React.ReactNode = imageSource
|
|
124
123
|
? <Image source={imageSource} style={styles.image} />
|
|
125
124
|
: leftIcon
|
|
126
125
|
? renderIcon(leftIcon, 24, leftIconColor ?? colors.foreground)
|
|
127
|
-
: leftRender
|
|
126
|
+
: leftRender
|
|
128
127
|
|
|
129
|
-
const
|
|
130
|
-
? renderIcon(rightIcon, 24, rightIconColor ?? colors.foregroundMuted)
|
|
131
|
-
: rightRender ?? trailing
|
|
128
|
+
const hasRightContent = !!(rightIcon || (rightActions && rightActions.length > 0) || rightRender !== undefined || showChevron)
|
|
132
129
|
|
|
133
130
|
const cardStyle: ViewStyle =
|
|
134
131
|
variant === 'card'
|
|
@@ -181,21 +178,33 @@ function ListItemBase({
|
|
|
181
178
|
) : null}
|
|
182
179
|
</View>
|
|
183
180
|
|
|
184
|
-
{
|
|
185
|
-
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
181
|
+
{hasRightContent ? (
|
|
182
|
+
rightIcon ? (
|
|
183
|
+
<View style={styles.rightContainer}>
|
|
184
|
+
{renderIcon(rightIcon, 24, rightIconColor ?? colors.foregroundMuted)}
|
|
185
|
+
</View>
|
|
186
|
+
) : rightActions && rightActions.length > 0 ? (
|
|
187
|
+
<View style={styles.rightActionsContainer}>
|
|
188
|
+
{rightActions.map((action, i) => (
|
|
189
|
+
<React.Fragment key={i}>{action}</React.Fragment>
|
|
190
|
+
))}
|
|
191
|
+
</View>
|
|
192
|
+
) : rightRender !== undefined ? (
|
|
193
|
+
<View style={styles.rightContainer}>
|
|
194
|
+
{typeof rightRender === 'string' ? (
|
|
195
|
+
<Text
|
|
196
|
+
style={[styles.rightText, { color: colors.foregroundMuted }]}
|
|
197
|
+
allowFontScaling={true}
|
|
198
|
+
>
|
|
199
|
+
{rightRender}
|
|
200
|
+
</Text>
|
|
201
|
+
) : (
|
|
202
|
+
rightRender
|
|
203
|
+
)}
|
|
204
|
+
</View>
|
|
205
|
+
) : showChevron ? (
|
|
206
|
+
<Entypo name="chevron-with-circle-right" size={20} color={colors.foregroundMuted} />
|
|
207
|
+
) : null
|
|
199
208
|
) : null}
|
|
200
209
|
</>
|
|
201
210
|
)
|
|
@@ -283,6 +292,12 @@ const styles = StyleSheet.create({
|
|
|
283
292
|
flexShrink: 0,
|
|
284
293
|
maxWidth: s(160),
|
|
285
294
|
},
|
|
295
|
+
rightActionsContainer: {
|
|
296
|
+
flexDirection: 'row',
|
|
297
|
+
alignItems: 'center',
|
|
298
|
+
gap: s(8),
|
|
299
|
+
flexShrink: 0,
|
|
300
|
+
},
|
|
286
301
|
rightText: {
|
|
287
302
|
fontFamily: 'Sohne-Regular',
|
|
288
303
|
fontSize: ms(14),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { ViewStyle } from 'react-native'
|
|
2
|
+
import { ViewStyle, type AccessibilityRole } from 'react-native'
|
|
3
3
|
import { impactLight } from '../../utils/haptics'
|
|
4
4
|
import { PressableCard } from '../../utils/pressable'
|
|
5
5
|
import { PRESS_SCALE } from '../../utils/animations'
|
|
@@ -11,11 +11,6 @@ export interface PressableProps {
|
|
|
11
11
|
onPress?: () => void
|
|
12
12
|
/** Scale value on press. Defaults to `0.98` (MediaCard-style). */
|
|
13
13
|
pressScale?: number
|
|
14
|
-
/**
|
|
15
|
-
* @deprecated Use Reanimated spring config via `pressOutSpring` instead. Ignored.
|
|
16
|
-
* Kept for backwards compatibility with v6.x consumers.
|
|
17
|
-
*/
|
|
18
|
-
bounciness?: number
|
|
19
14
|
/** Enable haptic feedback on press. Defaults to `true`. */
|
|
20
15
|
haptics?: boolean
|
|
21
16
|
/** Additional style for the wrapper. */
|
|
@@ -24,6 +19,19 @@ export interface PressableProps {
|
|
|
24
19
|
disabled?: boolean
|
|
25
20
|
/** Hover scale (web only). Defaults to `1.02`. Set to `1` to disable. */
|
|
26
21
|
hoverScale?: number
|
|
22
|
+
/**
|
|
23
|
+
* Accessibility role for the pressable element.
|
|
24
|
+
* Defaults to `"button"`.
|
|
25
|
+
*/
|
|
26
|
+
accessibilityRole?: AccessibilityRole
|
|
27
|
+
/**
|
|
28
|
+
* Accessibility state for screen readers.
|
|
29
|
+
* Used to communicate states like `selected`, `expanded`, `checked`, etc.
|
|
30
|
+
* Defaults to `{ disabled: !!disabled }`.
|
|
31
|
+
*/
|
|
32
|
+
accessibilityState?: Record<string, unknown>
|
|
33
|
+
/** Accessibility label for screen readers. */
|
|
34
|
+
accessibilityLabel?: string
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -41,6 +49,9 @@ export function Pressable({
|
|
|
41
49
|
style,
|
|
42
50
|
disabled,
|
|
43
51
|
hoverScale: _hoverScale = 1.02,
|
|
52
|
+
accessibilityRole,
|
|
53
|
+
accessibilityState,
|
|
54
|
+
accessibilityLabel,
|
|
44
55
|
}: PressableProps) {
|
|
45
56
|
const handlePress = () => {
|
|
46
57
|
if (disabled || !onPress) return
|
|
@@ -56,8 +67,9 @@ export function Pressable({
|
|
|
56
67
|
rippleColor="transparent"
|
|
57
68
|
touchSoundDisabled
|
|
58
69
|
activateOnHover
|
|
59
|
-
accessibilityRole=
|
|
60
|
-
accessibilityState={{ disabled: !!disabled }}
|
|
70
|
+
accessibilityRole={accessibilityRole ?? 'button'}
|
|
71
|
+
accessibilityState={accessibilityState ?? { disabled: !!disabled }}
|
|
72
|
+
accessibilityLabel={accessibilityLabel}
|
|
61
73
|
>
|
|
62
74
|
{children}
|
|
63
75
|
</PressableCard>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef } from 'react'
|
|
2
|
-
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle
|
|
3
|
-
import
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useId } from 'react'
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
+
import {
|
|
4
|
+
BottomSheetModal,
|
|
4
5
|
BottomSheetView,
|
|
5
6
|
BottomSheetScrollView,
|
|
6
7
|
BottomSheetBackdrop,
|
|
@@ -15,14 +16,8 @@ import { AntDesign } from '@expo/vector-icons'
|
|
|
15
16
|
import { impactMedium } from '../../utils/haptics'
|
|
16
17
|
import { useTheme } from '../../theme'
|
|
17
18
|
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
18
|
-
import { BREAKPOINTS, RADIUS, SHADOWS } from '../../tokens'
|
|
19
|
-
|
|
20
|
-
const SCREEN_HEIGHT = Dimensions.get('window').height
|
|
21
|
-
const DEFAULT_MAX_HEIGHT = SCREEN_HEIGHT * 0.85
|
|
22
|
-
const isAndroid = Platform.OS === 'android'
|
|
23
19
|
|
|
24
20
|
export { BottomSheetModalProvider }
|
|
25
|
-
// Re-export BottomSheetTextInput as SheetTextInput for consumer convenience
|
|
26
21
|
export { BottomSheetTextInput as SheetTextInput }
|
|
27
22
|
|
|
28
23
|
export interface SheetHeaderProps {
|
|
@@ -44,67 +39,25 @@ export interface SheetProps {
|
|
|
44
39
|
open: boolean
|
|
45
40
|
onClose: () => void
|
|
46
41
|
title?: string
|
|
47
|
-
/** Secondary text below title. */
|
|
48
42
|
subtitle?: string
|
|
49
|
-
/** @deprecated Use `subtitle` instead. */
|
|
50
|
-
description?: string
|
|
51
|
-
/** Show an X close button in the header. */
|
|
52
43
|
showCloseButton?: boolean
|
|
53
44
|
children?: React.ReactNode
|
|
54
|
-
/** Style for the inner content container. */
|
|
55
45
|
style?: ViewStyle
|
|
56
|
-
/** Style for the content wrapper (outside the scroll container). */
|
|
57
46
|
contentStyle?: ViewStyle
|
|
58
|
-
/** Render children inside BottomSheetScrollView. */
|
|
47
|
+
/** Render children inside BottomSheetScrollView instead of BottomSheetView. */
|
|
59
48
|
scrollable?: boolean
|
|
60
|
-
/** Cap sheet height (dp).
|
|
49
|
+
/** Cap sheet height (dp). Content scrolls when exceeding this value. Requires `scrollable`. */
|
|
61
50
|
maxHeight?: number
|
|
62
|
-
/**
|
|
63
|
-
* Keyboard behavior — how the sheet responds to keyboard appearance.
|
|
64
|
-
* - 'interactive': offset sheet by keyboard size (default, works on both platforms)
|
|
65
|
-
* - 'fillParent': extend sheet to fill parent view (can cause restore issues with dynamic sizing)
|
|
66
|
-
* - 'extend': extend sheet to maximum snap point
|
|
67
|
-
*
|
|
68
|
-
* Default: 'interactive' on both platforms.
|
|
69
|
-
*/
|
|
70
51
|
keyboardBehavior?: 'extend' | 'fillParent' | 'interactive'
|
|
71
|
-
/**
|
|
72
|
-
* Keyboard blur behavior — what happens when keyboard dismisses.
|
|
73
|
-
* - 'none': do nothing
|
|
74
|
-
* - 'restore': restore sheet to previous position (default)
|
|
75
|
-
*/
|
|
76
52
|
keyboardBlurBehavior?: 'none' | 'restore'
|
|
77
|
-
/**
|
|
78
|
-
* Blur keyboard when user starts dragging the sheet down.
|
|
79
|
-
* Default: true (recommended for better UX)
|
|
80
|
-
*/
|
|
81
53
|
enableBlurKeyboardOnGesture?: boolean
|
|
82
|
-
/**
|
|
83
|
-
* Android-only: defines keyboard input mode.
|
|
84
|
-
* - 'adjustPan': pan the sheet content (default, fixes restore issues with dynamic sizing)
|
|
85
|
-
* - 'adjustResize': resize the sheet container (can cause transparent gap on dismiss)
|
|
86
|
-
*/
|
|
87
54
|
android_keyboardInputMode?: 'adjustPan' | 'adjustResize'
|
|
88
|
-
/** Sticky footer rendered below the scroll area. */
|
|
89
55
|
footer?: React.ReactNode
|
|
90
56
|
/**
|
|
91
|
-
* Array of snap points
|
|
57
|
+
* Array of snap points (e.g., ['50%', '85%'] or [200, 500]).
|
|
92
58
|
* When provided, disables enableDynamicSizing.
|
|
93
|
-
* When omitted, sheet uses dynamic sizing (auto-fits content).
|
|
94
59
|
*/
|
|
95
60
|
snapPoints?: (string | number)[]
|
|
96
|
-
/**
|
|
97
|
-
* When true, render as a centered modal dialog on wide screens (width ≥
|
|
98
|
-
* `BREAKPOINTS.wide`) instead of a bottom sheet. On narrow screens it stays a
|
|
99
|
-
* bottom sheet. Use for store/category/picker dialogs that should feel native
|
|
100
|
-
* on tablets and web.
|
|
101
|
-
*
|
|
102
|
-
* Note: the centered-dialog path uses a plain RN `Modal`, so `SheetTextInput`
|
|
103
|
-
* is not required there — use a regular `TextInput`.
|
|
104
|
-
*/
|
|
105
|
-
responsive?: boolean
|
|
106
|
-
/** Max width of the centered dialog (dp). Only applies when `responsive`. Defaults to 480. */
|
|
107
|
-
dialogMaxWidth?: number
|
|
108
61
|
}
|
|
109
62
|
|
|
110
63
|
export function SheetHeader({ children, style }: SheetHeaderProps) {
|
|
@@ -129,76 +82,73 @@ export function Sheet({
|
|
|
129
82
|
onClose,
|
|
130
83
|
title,
|
|
131
84
|
subtitle,
|
|
132
|
-
description,
|
|
133
85
|
showCloseButton = false,
|
|
134
86
|
children,
|
|
135
87
|
style,
|
|
136
88
|
contentStyle,
|
|
137
|
-
scrollable,
|
|
89
|
+
scrollable = false,
|
|
138
90
|
maxHeight,
|
|
139
|
-
keyboardBehavior,
|
|
91
|
+
keyboardBehavior = 'interactive',
|
|
140
92
|
keyboardBlurBehavior = 'restore',
|
|
141
93
|
enableBlurKeyboardOnGesture = true,
|
|
142
94
|
android_keyboardInputMode = 'adjustPan',
|
|
143
95
|
footer,
|
|
144
96
|
snapPoints,
|
|
145
|
-
responsive = false,
|
|
146
|
-
dialogMaxWidth = 480,
|
|
147
97
|
}: SheetProps) {
|
|
148
98
|
const { colors } = useTheme()
|
|
149
99
|
const insets = useSafeAreaInsets()
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
// 'interactive' + 'adjustPan' works properly with enableDynamicSizing on both platforms
|
|
155
|
-
// 'fillParent' + 'adjustResize' causes restore issues (transparent gap when keyboard dismisses)
|
|
156
|
-
const effectiveKeyboardBehavior = keyboardBehavior ?? 'interactive'
|
|
100
|
+
const ref = useRef<BottomSheetModal>(null)
|
|
101
|
+
const wasOpened = useRef(false)
|
|
102
|
+
const name = useId()
|
|
157
103
|
|
|
158
104
|
useEffect(() => {
|
|
159
105
|
if (open) {
|
|
160
106
|
impactMedium()
|
|
161
|
-
ref.current?.
|
|
162
|
-
|
|
163
|
-
|
|
107
|
+
ref.current?.present()
|
|
108
|
+
wasOpened.current = true
|
|
109
|
+
} else if (wasOpened.current) {
|
|
110
|
+
ref.current?.dismiss()
|
|
164
111
|
}
|
|
165
112
|
}, [open])
|
|
166
113
|
|
|
167
|
-
const renderBackdrop = useCallback(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
114
|
+
const renderBackdrop = useCallback(
|
|
115
|
+
(props: BottomSheetBackdropProps) => (
|
|
116
|
+
<BottomSheetBackdrop
|
|
117
|
+
{...props}
|
|
118
|
+
disappearsOnIndex={-1}
|
|
119
|
+
appearsOnIndex={0}
|
|
120
|
+
pressBehavior="close"
|
|
121
|
+
/>
|
|
122
|
+
),
|
|
123
|
+
[]
|
|
124
|
+
)
|
|
175
125
|
|
|
176
|
-
// Detect compound components in children
|
|
177
126
|
const childArray = React.Children.toArray(children)
|
|
178
127
|
const customHeader = childArray.find((child) => React.isValidElement(child) && child.type === SheetHeader)
|
|
179
128
|
const customContent = childArray.find((child) => React.isValidElement(child) && child.type === SheetContent)
|
|
180
129
|
const customFooter = childArray.find((child) => React.isValidElement(child) && child.type === SheetFooter)
|
|
181
|
-
|
|
182
|
-
// If using compound components, filter them out from main children
|
|
183
|
-
const filteredChildren = customHeader || customContent || customFooter
|
|
184
|
-
? childArray.filter(
|
|
185
|
-
(child) =>
|
|
186
|
-
!React.isValidElement(child) ||
|
|
187
|
-
(child.type !== SheetHeader && child.type !== SheetContent && child.type !== SheetFooter)
|
|
188
|
-
)
|
|
189
|
-
: children
|
|
190
130
|
|
|
191
|
-
const
|
|
192
|
-
|
|
131
|
+
const filteredChildren =
|
|
132
|
+
customHeader || customContent || customFooter
|
|
133
|
+
? childArray.filter(
|
|
134
|
+
(child) =>
|
|
135
|
+
!React.isValidElement(child) ||
|
|
136
|
+
(child.type !== SheetHeader && child.type !== SheetContent && child.type !== SheetFooter)
|
|
137
|
+
)
|
|
138
|
+
: children
|
|
193
139
|
|
|
194
|
-
const
|
|
140
|
+
const showHeader = !!(title || subtitle || showCloseButton) && !customHeader
|
|
141
|
+
|
|
142
|
+
const headerNode = customHeader ? customHeader : showHeader ? (
|
|
195
143
|
<View style={[styles.header, { backgroundColor: colors.card }]} accessibilityRole="header">
|
|
196
144
|
<View style={styles.headerRow}>
|
|
197
145
|
{title ? (
|
|
198
146
|
<Text style={[styles.title, { color: colors.foreground }]} allowFontScaling={true}>
|
|
199
147
|
{title}
|
|
200
148
|
</Text>
|
|
201
|
-
) :
|
|
149
|
+
) : (
|
|
150
|
+
<View style={{ flex: 1 }} />
|
|
151
|
+
)}
|
|
202
152
|
{showCloseButton ? (
|
|
203
153
|
<TouchableOpacity
|
|
204
154
|
onPress={onClose}
|
|
@@ -213,90 +163,52 @@ export function Sheet({
|
|
|
213
163
|
</TouchableOpacity>
|
|
214
164
|
) : null}
|
|
215
165
|
</View>
|
|
216
|
-
{
|
|
166
|
+
{subtitle ? (
|
|
217
167
|
<Text style={[styles.subtitle, { color: colors.foregroundMuted }]} allowFontScaling={true}>
|
|
218
|
-
{
|
|
168
|
+
{subtitle}
|
|
219
169
|
</Text>
|
|
220
170
|
) : null}
|
|
221
171
|
</View>
|
|
222
|
-
) : null
|
|
172
|
+
) : null
|
|
223
173
|
|
|
224
174
|
const contentNode = customContent ? customContent : filteredChildren
|
|
225
175
|
const effectiveFooter = customFooter ? customFooter : footer
|
|
226
176
|
|
|
227
|
-
const renderFooter = useCallback(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
<BottomSheetFooter {...props}>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}, [effectiveFooter])
|
|
235
|
-
|
|
236
|
-
// Centered dialog path for wide screens — plain RN Modal, same header/content/footer.
|
|
237
|
-
if (asDialog) {
|
|
238
|
-
return (
|
|
239
|
-
<Modal visible={open} transparent animationType="fade" onRequestClose={onClose}>
|
|
240
|
-
<Pressable style={styles.dialogBackdrop} onPress={onClose} accessibilityRole="button" accessibilityLabel="Close">
|
|
241
|
-
{/* Inner Pressable swallows presses so taps inside the card don't close it. */}
|
|
242
|
-
<Pressable
|
|
243
|
-
style={[
|
|
244
|
-
styles.dialogCard,
|
|
245
|
-
{ backgroundColor: colors.card, maxWidth: dialogMaxWidth, maxHeight: SCREEN_HEIGHT * 0.85 },
|
|
246
|
-
]}
|
|
247
|
-
onPress={() => {}}
|
|
248
|
-
>
|
|
249
|
-
{headerNode}
|
|
250
|
-
<ScrollView
|
|
251
|
-
contentContainerStyle={[styles.dialogContent, style]}
|
|
252
|
-
style={contentStyle}
|
|
253
|
-
showsVerticalScrollIndicator={true}
|
|
254
|
-
bounces={false}
|
|
255
|
-
>
|
|
256
|
-
{contentNode}
|
|
257
|
-
</ScrollView>
|
|
258
|
-
{effectiveFooter}
|
|
259
|
-
</Pressable>
|
|
260
|
-
</Pressable>
|
|
261
|
-
</Modal>
|
|
262
|
-
)
|
|
263
|
-
}
|
|
177
|
+
const renderFooter = useCallback(
|
|
178
|
+
(props: BottomSheetFooterProps) => {
|
|
179
|
+
if (!effectiveFooter) return null
|
|
180
|
+
return <BottomSheetFooter {...props}>{effectiveFooter}</BottomSheetFooter>
|
|
181
|
+
},
|
|
182
|
+
[effectiveFooter]
|
|
183
|
+
)
|
|
264
184
|
|
|
265
|
-
const useScroll = scrollable || !!maxHeight
|
|
266
|
-
const effectiveMaxHeight = maxHeight ?? DEFAULT_MAX_HEIGHT
|
|
267
|
-
|
|
268
|
-
// If snapPoints provided, disable dynamic sizing. Otherwise use dynamic sizing.
|
|
269
185
|
const useDynamicSizing = !snapPoints
|
|
270
|
-
|
|
186
|
+
|
|
271
187
|
return (
|
|
272
|
-
|
|
188
|
+
<BottomSheetModal
|
|
273
189
|
ref={ref}
|
|
274
|
-
|
|
275
|
-
|
|
190
|
+
name={name}
|
|
191
|
+
onDismiss={onClose}
|
|
276
192
|
enableDynamicSizing={useDynamicSizing}
|
|
277
193
|
snapPoints={snapPoints}
|
|
278
|
-
maxDynamicContentSize={useDynamicSizing ?
|
|
194
|
+
maxDynamicContentSize={useDynamicSizing && maxHeight ? maxHeight : undefined}
|
|
279
195
|
backdropComponent={renderBackdrop}
|
|
280
196
|
footerComponent={effectiveFooter ? renderFooter : undefined}
|
|
281
|
-
backgroundStyle={
|
|
282
|
-
handleIndicatorStyle={
|
|
197
|
+
backgroundStyle={{ ...styles.background, backgroundColor: colors.card }}
|
|
198
|
+
handleIndicatorStyle={{ ...styles.handle, backgroundColor: colors.border }}
|
|
283
199
|
enablePanDownToClose
|
|
284
200
|
topInset={insets.top}
|
|
285
|
-
keyboardBehavior={
|
|
201
|
+
keyboardBehavior={keyboardBehavior}
|
|
286
202
|
keyboardBlurBehavior={keyboardBlurBehavior}
|
|
287
203
|
android_keyboardInputMode={android_keyboardInputMode}
|
|
288
204
|
enableBlurKeyboardOnGesture={enableBlurKeyboardOnGesture}
|
|
289
205
|
>
|
|
290
|
-
{
|
|
206
|
+
{scrollable ? (
|
|
291
207
|
<BottomSheetScrollView
|
|
292
|
-
contentContainerStyle={[
|
|
293
|
-
styles.scrollContent,
|
|
294
|
-
style,
|
|
295
|
-
]}
|
|
208
|
+
contentContainerStyle={[styles.scrollContent, style]}
|
|
296
209
|
style={contentStyle}
|
|
297
|
-
showsVerticalScrollIndicator
|
|
298
|
-
|
|
299
|
-
persistentScrollbar={isAndroid}
|
|
210
|
+
showsVerticalScrollIndicator
|
|
211
|
+
bounces={false}
|
|
300
212
|
stickyHeaderIndices={headerNode ? [0] : undefined}
|
|
301
213
|
>
|
|
302
214
|
{headerNode}
|
|
@@ -308,7 +220,7 @@ export function Sheet({
|
|
|
308
220
|
{contentNode}
|
|
309
221
|
</BottomSheetView>
|
|
310
222
|
)}
|
|
311
|
-
</
|
|
223
|
+
</BottomSheetModal>
|
|
312
224
|
)
|
|
313
225
|
}
|
|
314
226
|
|
|
@@ -358,7 +270,6 @@ const styles = StyleSheet.create({
|
|
|
358
270
|
scrollContent: {
|
|
359
271
|
paddingHorizontal: s(16),
|
|
360
272
|
paddingBottom: vs(32),
|
|
361
|
-
paddingRight: s(16),
|
|
362
273
|
},
|
|
363
274
|
sheetContent: {
|
|
364
275
|
gap: vs(16),
|
|
@@ -370,23 +281,4 @@ const styles = StyleSheet.create({
|
|
|
370
281
|
flexDirection: 'row',
|
|
371
282
|
gap: s(12),
|
|
372
283
|
},
|
|
373
|
-
dialogBackdrop: {
|
|
374
|
-
flex: 1,
|
|
375
|
-
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
376
|
-
alignItems: 'center',
|
|
377
|
-
justifyContent: 'center',
|
|
378
|
-
padding: s(24),
|
|
379
|
-
},
|
|
380
|
-
dialogCard: {
|
|
381
|
-
width: '100%',
|
|
382
|
-
borderRadius: RADIUS.lg,
|
|
383
|
-
paddingTop: vs(16),
|
|
384
|
-
overflow: 'hidden',
|
|
385
|
-
...SHADOWS.xl,
|
|
386
|
-
},
|
|
387
|
-
dialogContent: {
|
|
388
|
-
paddingHorizontal: s(16),
|
|
389
|
-
paddingBottom: vs(16),
|
|
390
|
-
},
|
|
391
284
|
})
|
|
392
|
-
|