@retray-dev/ui-kit 7.0.1 → 9.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 +567 -14
- package/EXAMPLES.md +21 -14
- 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,169 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
4
|
+
import { useTheme } from '../../theme'
|
|
5
|
+
import { renderIcon } from '../../utils/icons'
|
|
6
|
+
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
7
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
8
|
+
|
|
9
|
+
export interface TabBarItem {
|
|
10
|
+
/** Unique key for the tab. */
|
|
11
|
+
key: string
|
|
12
|
+
/** Label under the icon. Omit for an icon-only tab. */
|
|
13
|
+
label?: string
|
|
14
|
+
/** Icon name (active + inactive share the name; color signals state). */
|
|
15
|
+
iconName?: string
|
|
16
|
+
/** Custom icon node — receives no state, overrides `iconName`. */
|
|
17
|
+
icon?: React.ReactNode
|
|
18
|
+
/** Badge overlay — `true` shows a dot, a number shows a count (capped at 99). */
|
|
19
|
+
badge?: boolean | number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TabBarProps {
|
|
23
|
+
items: TabBarItem[]
|
|
24
|
+
/** Key of the active tab. */
|
|
25
|
+
activeKey: string
|
|
26
|
+
onTabPress: (key: string) => void
|
|
27
|
+
/** Active tint. Defaults to theme `primary`. */
|
|
28
|
+
activeColor?: string
|
|
29
|
+
/** Inactive tint. Defaults to theme `foregroundMuted`. */
|
|
30
|
+
inactiveColor?: string
|
|
31
|
+
/** Apply the bottom safe-area inset as padding. Defaults to true. */
|
|
32
|
+
withSafeArea?: boolean
|
|
33
|
+
style?: ViewStyle
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Bottom tab bar — icon + label tabs with active tint and badge support. Pair
|
|
38
|
+
* with your navigator, or drive it with local state for a single-screen app.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* <TabBar
|
|
42
|
+
* items={[{ key: 'home', label: 'Home', iconName: 'home' }, { key: 'profile', label: 'Profile', iconName: 'user', badge: 3 }]}
|
|
43
|
+
* activeKey={tab}
|
|
44
|
+
* onTabPress={setTab}
|
|
45
|
+
* />
|
|
46
|
+
*/
|
|
47
|
+
export function TabBar({
|
|
48
|
+
items,
|
|
49
|
+
activeKey,
|
|
50
|
+
onTabPress,
|
|
51
|
+
activeColor,
|
|
52
|
+
inactiveColor,
|
|
53
|
+
withSafeArea = true,
|
|
54
|
+
style,
|
|
55
|
+
}: TabBarProps) {
|
|
56
|
+
const { colors } = useTheme()
|
|
57
|
+
const insets = useSafeAreaInsets()
|
|
58
|
+
const resolvedActive = activeColor ?? colors.primary
|
|
59
|
+
const resolvedInactive = inactiveColor ?? colors.foregroundMuted
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View
|
|
63
|
+
style={[
|
|
64
|
+
styles.container,
|
|
65
|
+
{
|
|
66
|
+
backgroundColor: colors.card,
|
|
67
|
+
borderTopColor: colors.border,
|
|
68
|
+
paddingBottom: withSafeArea ? insets.bottom : 0,
|
|
69
|
+
},
|
|
70
|
+
style,
|
|
71
|
+
]}
|
|
72
|
+
accessibilityRole="tablist"
|
|
73
|
+
>
|
|
74
|
+
{items.map((item) => {
|
|
75
|
+
const active = item.key === activeKey
|
|
76
|
+
const tint = active ? resolvedActive : resolvedInactive
|
|
77
|
+
const iconNode = item.icon ?? (item.iconName ? renderIcon(item.iconName, ms(24), tint) : null)
|
|
78
|
+
const showBadge = item.badge !== undefined && item.badge !== false
|
|
79
|
+
const badgeCount = typeof item.badge === 'number' ? item.badge : undefined
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<TouchableOpacity
|
|
83
|
+
key={item.key}
|
|
84
|
+
style={styles.tab}
|
|
85
|
+
onPress={() => {
|
|
86
|
+
if (!active) hapticSelection()
|
|
87
|
+
onTabPress(item.key)
|
|
88
|
+
}}
|
|
89
|
+
activeOpacity={0.7}
|
|
90
|
+
touchSoundDisabled={true}
|
|
91
|
+
accessibilityRole="tab"
|
|
92
|
+
accessibilityState={{ selected: active }}
|
|
93
|
+
accessibilityLabel={item.label ?? item.key}
|
|
94
|
+
>
|
|
95
|
+
<View>
|
|
96
|
+
{iconNode}
|
|
97
|
+
{showBadge ? (
|
|
98
|
+
<View
|
|
99
|
+
style={[
|
|
100
|
+
styles.badge,
|
|
101
|
+
{ backgroundColor: colors.destructive, borderColor: colors.card },
|
|
102
|
+
badgeCount === undefined && styles.badgeDot,
|
|
103
|
+
]}
|
|
104
|
+
>
|
|
105
|
+
{badgeCount !== undefined ? (
|
|
106
|
+
<Text style={[styles.badgeText, { color: colors.destructiveForeground }]} allowFontScaling={false}>
|
|
107
|
+
{badgeCount > 99 ? '99+' : badgeCount}
|
|
108
|
+
</Text>
|
|
109
|
+
) : null}
|
|
110
|
+
</View>
|
|
111
|
+
) : null}
|
|
112
|
+
</View>
|
|
113
|
+
{item.label ? (
|
|
114
|
+
<Text style={[styles.label, { color: tint }]} numberOfLines={1} allowFontScaling={true}>
|
|
115
|
+
{item.label}
|
|
116
|
+
</Text>
|
|
117
|
+
) : null}
|
|
118
|
+
</TouchableOpacity>
|
|
119
|
+
)
|
|
120
|
+
})}
|
|
121
|
+
</View>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const styles = StyleSheet.create({
|
|
126
|
+
container: {
|
|
127
|
+
flexDirection: 'row',
|
|
128
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
129
|
+
},
|
|
130
|
+
tab: {
|
|
131
|
+
flex: 1,
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
justifyContent: 'center',
|
|
134
|
+
paddingTop: vs(8),
|
|
135
|
+
paddingBottom: vs(6),
|
|
136
|
+
gap: vs(2),
|
|
137
|
+
minHeight: vs(48),
|
|
138
|
+
},
|
|
139
|
+
label: {
|
|
140
|
+
fontFamily: 'Sohne-Medium',
|
|
141
|
+
fontSize: ms(11),
|
|
142
|
+
lineHeight: mvs(14),
|
|
143
|
+
},
|
|
144
|
+
badge: {
|
|
145
|
+
position: 'absolute',
|
|
146
|
+
top: -vs(4),
|
|
147
|
+
right: -s(10),
|
|
148
|
+
minWidth: s(16),
|
|
149
|
+
height: s(16),
|
|
150
|
+
borderRadius: s(8),
|
|
151
|
+
borderWidth: 1.5,
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
justifyContent: 'center',
|
|
154
|
+
paddingHorizontal: s(3),
|
|
155
|
+
},
|
|
156
|
+
badgeDot: {
|
|
157
|
+
minWidth: s(10),
|
|
158
|
+
height: s(10),
|
|
159
|
+
borderRadius: s(5),
|
|
160
|
+
top: -vs(2),
|
|
161
|
+
right: -s(6),
|
|
162
|
+
paddingHorizontal: 0,
|
|
163
|
+
},
|
|
164
|
+
badgeText: {
|
|
165
|
+
fontFamily: 'Sohne-SemiBold',
|
|
166
|
+
fontSize: ms(9),
|
|
167
|
+
lineHeight: ms(11),
|
|
168
|
+
},
|
|
169
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TabBar'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect, useCallback } from 'react'
|
|
2
|
-
import { View,
|
|
2
|
+
import { View, Text, StyleSheet, ViewStyle, LayoutChangeEvent } from 'react-native'
|
|
3
3
|
import Animated, {
|
|
4
4
|
useSharedValue,
|
|
5
5
|
useAnimatedStyle,
|
|
@@ -8,8 +8,8 @@ import Animated, {
|
|
|
8
8
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
9
9
|
import { useTheme } from '../../theme'
|
|
10
10
|
import { s, vs, ms } from '../../utils/scaling'
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { SPRINGS } from '../../utils/animations'
|
|
12
|
+
import { PressableTab } from '../../utils/pressable'
|
|
13
13
|
|
|
14
14
|
export interface TabItem {
|
|
15
15
|
label: string
|
|
@@ -49,29 +49,23 @@ function TabTrigger({
|
|
|
49
49
|
variant: TabsVariant
|
|
50
50
|
}) {
|
|
51
51
|
const { colors } = useTheme()
|
|
52
|
-
const { animatedStyle, onPressIn, onPressOut } = usePressScale({
|
|
53
|
-
pressScale: PRESS_SCALE.button,
|
|
54
|
-
})
|
|
55
52
|
const isUnderline = variant === 'underline'
|
|
56
53
|
|
|
57
54
|
return (
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
accessibilityLabel={tab.label}
|
|
73
|
-
>
|
|
74
|
-
<Animated.View style={animatedStyle}>
|
|
55
|
+
<View onLayout={onLayout} style={styles.triggerWrap}>
|
|
56
|
+
<PressableTab
|
|
57
|
+
style={[
|
|
58
|
+
styles.trigger,
|
|
59
|
+
isUnderline && styles.triggerUnderline,
|
|
60
|
+
isUnderline && isActive && { borderBottomColor: colors.primary },
|
|
61
|
+
]}
|
|
62
|
+
onPress={onPress}
|
|
63
|
+
rippleColor="transparent"
|
|
64
|
+
touchSoundDisabled
|
|
65
|
+
accessibilityRole="tab"
|
|
66
|
+
accessibilityState={{ selected: isActive }}
|
|
67
|
+
accessibilityLabel={tab.label}
|
|
68
|
+
>
|
|
75
69
|
<View style={styles.triggerInner}>
|
|
76
70
|
{tab.icon ? (
|
|
77
71
|
typeof tab.icon === 'function' ? tab.icon(isActive) : tab.icon
|
|
@@ -90,8 +84,8 @@ function TabTrigger({
|
|
|
90
84
|
{tab.label}
|
|
91
85
|
</Text>
|
|
92
86
|
</View>
|
|
93
|
-
</
|
|
94
|
-
</
|
|
87
|
+
</PressableTab>
|
|
88
|
+
</View>
|
|
95
89
|
)
|
|
96
90
|
}
|
|
97
91
|
|
|
@@ -207,6 +201,9 @@ const styles = StyleSheet.create({
|
|
|
207
201
|
borderBottomWidth: 1,
|
|
208
202
|
},
|
|
209
203
|
pill: {},
|
|
204
|
+
triggerWrap: {
|
|
205
|
+
flex: 1,
|
|
206
|
+
},
|
|
210
207
|
trigger: {
|
|
211
208
|
flex: 1,
|
|
212
209
|
paddingVertical: vs(7),
|
|
@@ -217,7 +214,7 @@ const styles = StyleSheet.create({
|
|
|
217
214
|
zIndex: 1,
|
|
218
215
|
},
|
|
219
216
|
triggerUnderline: {
|
|
220
|
-
flex:
|
|
217
|
+
flex: 1,
|
|
221
218
|
paddingVertical: vs(12),
|
|
222
219
|
paddingHorizontal: s(16),
|
|
223
220
|
borderRadius: 0,
|
|
@@ -3,6 +3,7 @@ import { Text as RNText, TextProps as RNTextProps, TextStyle } from 'react-nativ
|
|
|
3
3
|
import { useTheme } from '../../theme'
|
|
4
4
|
import { TYPOGRAPHY } from '../../tokens'
|
|
5
5
|
import { ms, mvs } from '../../utils/scaling'
|
|
6
|
+
import { warnIfFontsMissing } from '../../utils/fontGuard'
|
|
6
7
|
|
|
7
8
|
export type TextVariant =
|
|
8
9
|
| 'display-hero'
|
|
@@ -68,6 +69,7 @@ const defaultColorVariant: Partial<Record<TextVariant, 'foreground' | 'foregroun
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
function TextBase({ variant = 'body-md', color, style, children, ...props }: TextProps) {
|
|
72
|
+
warnIfFontsMissing()
|
|
71
73
|
const { colors } = useTheme()
|
|
72
74
|
|
|
73
75
|
const colorKey = defaultColorVariant[variant] ?? 'foreground'
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
useAnimatedStyle,
|
|
5
|
-
interpolateColor,
|
|
6
|
-
} from 'react-native-reanimated'
|
|
2
|
+
import { StyleSheet, ViewStyle, View, Text } from 'react-native'
|
|
3
|
+
import { EaseView } from 'react-native-ease'
|
|
7
4
|
import { FontAwesome5 } from '@expo/vector-icons'
|
|
8
5
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
9
6
|
import { useTheme } from '../../theme'
|
|
10
7
|
import { s, vs, ms } from '../../utils/scaling'
|
|
11
8
|
import { renderIcon } from '../../utils/icons'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { PRESS_SCALE } from '../../utils/animations'
|
|
9
|
+
import { COLOR_TRANSITION } from '../../utils/animations'
|
|
10
|
+
import { PressableButton } from '../../utils/pressable'
|
|
15
11
|
|
|
16
12
|
interface ToggleIconProps {
|
|
17
13
|
pressed: boolean
|
|
@@ -50,7 +46,7 @@ function ToggleIcon({ pressed, iconName, activeIconName, icon, activeIcon, iconC
|
|
|
50
46
|
export type ToggleVariant = 'default' | 'outline'
|
|
51
47
|
export type ToggleSize = 'sm' | 'md' | 'lg'
|
|
52
48
|
|
|
53
|
-
export interface ToggleProps
|
|
49
|
+
export interface ToggleProps {
|
|
54
50
|
pressed?: boolean
|
|
55
51
|
onPressedChange?: (pressed: boolean) => void
|
|
56
52
|
variant?: ToggleVariant
|
|
@@ -74,6 +70,9 @@ export interface ToggleProps extends TouchableOpacityProps {
|
|
|
74
70
|
iconColor?: string
|
|
75
71
|
/** Override the resolved active icon color. Defaults to `primary`. */
|
|
76
72
|
activeIconColor?: string
|
|
73
|
+
disabled?: boolean
|
|
74
|
+
style?: ViewStyle
|
|
75
|
+
accessibilityLabel?: string
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
const sizeStyles: Record<ToggleSize, ViewStyle> = {
|
|
@@ -99,55 +98,37 @@ export function Toggle({
|
|
|
99
98
|
disabled,
|
|
100
99
|
style,
|
|
101
100
|
accessibilityLabel,
|
|
102
|
-
...props
|
|
103
101
|
}: ToggleProps) {
|
|
104
102
|
const { colors } = useTheme()
|
|
105
|
-
const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
|
|
106
|
-
pressScale: PRESS_SCALE.button,
|
|
107
|
-
disabled,
|
|
108
|
-
})
|
|
109
|
-
const progress = useColorTransition(pressed)
|
|
110
103
|
|
|
111
104
|
const inactiveBorder = variant === 'outline' ? colors.border : 'transparent'
|
|
112
105
|
|
|
113
|
-
const surfaceStyle = useAnimatedStyle(() => ({
|
|
114
|
-
borderColor: interpolateColor(progress.value, [0, 1], [inactiveBorder, colors.primary]),
|
|
115
|
-
backgroundColor: interpolateColor(progress.value, [0, 1], ['transparent', colors.surfaceStrong]),
|
|
116
|
-
}))
|
|
117
|
-
|
|
118
|
-
const textStyle = useAnimatedStyle(() => ({
|
|
119
|
-
color: interpolateColor(progress.value, [0, 1], [colors.foreground, colors.primary]),
|
|
120
|
-
}))
|
|
121
|
-
|
|
122
106
|
const iconSize = iconSizeMap[size]
|
|
123
107
|
|
|
108
|
+
const handlePress = () => {
|
|
109
|
+
hapticSelection()
|
|
110
|
+
onPressedChange?.(!pressed)
|
|
111
|
+
}
|
|
112
|
+
|
|
124
113
|
return (
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
onPressedChange?.(!pressed)
|
|
133
|
-
}}
|
|
134
|
-
onPressIn={onPressIn}
|
|
135
|
-
onPressOut={onPressOut}
|
|
136
|
-
disabled={disabled}
|
|
137
|
-
activeOpacity={1}
|
|
138
|
-
touchSoundDisabled={true}
|
|
114
|
+
<View style={[disabled && styles.disabled, style]}>
|
|
115
|
+
<PressableButton
|
|
116
|
+
onPress={handlePress}
|
|
117
|
+
enabled={!disabled}
|
|
118
|
+
rippleColor="transparent"
|
|
119
|
+
touchSoundDisabled
|
|
120
|
+
activateOnHover
|
|
139
121
|
accessibilityRole="button"
|
|
140
122
|
accessibilityLabel={accessibilityLabel ?? label}
|
|
141
123
|
accessibilityState={{ selected: pressed, disabled: !!disabled }}
|
|
142
|
-
{...props}
|
|
143
124
|
>
|
|
144
|
-
<
|
|
145
|
-
style={[
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
125
|
+
<EaseView
|
|
126
|
+
style={[styles.base, sizeStyles[size], { borderWidth: 2 }]}
|
|
127
|
+
animate={{
|
|
128
|
+
borderColor: pressed ? colors.primary : inactiveBorder,
|
|
129
|
+
backgroundColor: pressed ? colors.surfaceStrong : 'transparent',
|
|
130
|
+
}}
|
|
131
|
+
transition={COLOR_TRANSITION}
|
|
151
132
|
>
|
|
152
133
|
<View style={styles.inner}>
|
|
153
134
|
<ToggleIcon
|
|
@@ -163,14 +144,17 @@ export function Toggle({
|
|
|
163
144
|
mutedColor={colors.foregroundMuted}
|
|
164
145
|
/>
|
|
165
146
|
{label ? (
|
|
166
|
-
<
|
|
147
|
+
<Text
|
|
148
|
+
style={[styles.label, { color: pressed ? colors.primary : colors.foreground }]}
|
|
149
|
+
allowFontScaling={true}
|
|
150
|
+
>
|
|
167
151
|
{label}
|
|
168
|
-
</
|
|
152
|
+
</Text>
|
|
169
153
|
) : null}
|
|
170
154
|
</View>
|
|
171
|
-
</
|
|
172
|
-
</
|
|
173
|
-
</
|
|
155
|
+
</EaseView>
|
|
156
|
+
</PressableButton>
|
|
157
|
+
</View>
|
|
174
158
|
)
|
|
175
159
|
}
|
|
176
160
|
|
package/src/fonts.ts
CHANGED
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
* // render app
|
|
14
14
|
* }
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
// `.otf` assets resolve to a Metro asset module id (number) via require() at the
|
|
17
|
+
// consumer's build time. Declared locally so the dts build does not depend on @types/node.
|
|
18
|
+
declare const require: (path: string) => number
|
|
19
|
+
|
|
17
20
|
export const SohneFonts = {
|
|
18
21
|
// Sohne base
|
|
19
22
|
'Sohne-ExtraLight': require('./assets/fonts/Sohne-ExtraLight.otf'),
|
package/src/index.ts
CHANGED
|
@@ -31,8 +31,6 @@ export * from './components/Select'
|
|
|
31
31
|
export * from './components/Toast'
|
|
32
32
|
export * from './components/CurrencyInput'
|
|
33
33
|
export * from './components/CurrencyDisplay'
|
|
34
|
-
// CurrencyInputLarge is deprecated — use <CurrencyInput size="large" /> instead
|
|
35
|
-
export { CurrencyInput as CurrencyInputLarge } from './components/CurrencyInput'
|
|
36
34
|
export * from './components/ListItem'
|
|
37
35
|
export * from './components/ListGroup'
|
|
38
36
|
export * from './components/MenuItem'
|
|
@@ -47,6 +45,17 @@ export * from './components/Pressable'
|
|
|
47
45
|
export * from './components/DetailRow'
|
|
48
46
|
export * from './components/Form'
|
|
49
47
|
export * from './components/VirtualList'
|
|
48
|
+
export * from './components/RetrayProvider'
|
|
49
|
+
export * from './components/ErrorBoundary'
|
|
50
|
+
export * from './components/PagerDots'
|
|
51
|
+
export * from './components/AppHeader'
|
|
52
|
+
export * from './components/SelectableGrid'
|
|
53
|
+
export * from './components/PricingCard'
|
|
54
|
+
export * from './components/TabBar'
|
|
55
|
+
export * from './components/ImageViewer'
|
|
56
|
+
// HolographicCard is intentionally NOT re-exported here — it depends on the
|
|
57
|
+
// optional peer @shopify/react-native-skia, so it must stay out of the main
|
|
58
|
+
// barrel's module graph. Deep-import it: '@retray-dev/ui-kit/HolographicCard'.
|
|
50
59
|
|
|
51
60
|
// Icon utility
|
|
52
61
|
export { Icon, renderIcon, configureIconFamilies } from './utils/icons'
|
|
@@ -55,6 +64,18 @@ export { Icon, renderIcon, configureIconFamilies } from './utils/icons'
|
|
|
55
64
|
export { getResponsiveFontSize } from './utils/typography'
|
|
56
65
|
export type { IconProps, IconFamily } from './utils/icons'
|
|
57
66
|
|
|
67
|
+
// Haptic feedback utilities
|
|
68
|
+
export {
|
|
69
|
+
selectionAsync,
|
|
70
|
+
impactLight,
|
|
71
|
+
impactMedium,
|
|
72
|
+
impactHeavy,
|
|
73
|
+
notificationSuccess,
|
|
74
|
+
notificationError,
|
|
75
|
+
notificationWarning,
|
|
76
|
+
richHaptics,
|
|
77
|
+
} from './utils/haptics'
|
|
78
|
+
|
|
58
79
|
// Design tokens
|
|
59
80
|
export {
|
|
60
81
|
SPACING,
|
package/src/utils/animations.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Easing } from 'react-native-reanimated'
|
|
2
|
+
import type { SingleTransition } from 'react-native-ease'
|
|
2
3
|
|
|
3
4
|
// ─── Spring presets ──────────────────────────────────────────────────────────
|
|
4
5
|
// Tuned for the "Apple HIG / Airbnb" press-feel: snap inward fast, settle out elastically.
|
|
@@ -17,7 +18,7 @@ export const SPRINGS = {
|
|
|
17
18
|
surfacePressOut: { stiffness: 220, damping: 20, mass: 0.95 },
|
|
18
19
|
|
|
19
20
|
/** Settled transitions for moving indicators — Tabs pill, Switch thumb. */
|
|
20
|
-
glide: { stiffness: 380, damping: 38, mass: 1
|
|
21
|
+
glide: { stiffness: 380, damping: 38, mass: 1 },
|
|
21
22
|
|
|
22
23
|
/** Elastic indicator — Switch thumb, RadioGroup dot. */
|
|
23
24
|
elastic: { stiffness: 320, damping: 22, mass: 0.7 },
|
|
@@ -48,6 +49,33 @@ export const EASINGS = {
|
|
|
48
49
|
collapse: Easing.in(Easing.ease),
|
|
49
50
|
} as const
|
|
50
51
|
|
|
52
|
+
// ─── EaseView transition presets ─────────────────────────────────────────────
|
|
53
|
+
// Equivalents of the reanimated presets above, in `react-native-ease` units.
|
|
54
|
+
// EaseView spring takes raw damping/stiffness/mass (same physical model). EaseView
|
|
55
|
+
// timing takes duration + an easing curve as a cubic-bezier tuple.
|
|
56
|
+
|
|
57
|
+
/** Color/border state transition for Toggle, Checkbox, Chip, CategoryStrip, Switch track. Mirrors TIMINGS.state + EASINGS.standard. */
|
|
58
|
+
export const COLOR_TRANSITION: SingleTransition = {
|
|
59
|
+
type: 'timing',
|
|
60
|
+
duration: TIMINGS.state.duration,
|
|
61
|
+
easing: [0.2, 0, 0, 1],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Icon/opacity crossfade. Mirrors TIMINGS.state + EASINGS.standard. */
|
|
65
|
+
export const OPACITY_TRANSITION: SingleTransition = {
|
|
66
|
+
type: 'timing',
|
|
67
|
+
duration: TIMINGS.state.duration,
|
|
68
|
+
easing: [0.2, 0, 0, 1],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Elastic indicator spring — Switch thumb, RadioGroup dot. Mirrors SPRINGS.elastic. */
|
|
72
|
+
export const SPRING_ELASTIC: SingleTransition = {
|
|
73
|
+
type: 'spring',
|
|
74
|
+
stiffness: 320,
|
|
75
|
+
damping: 22,
|
|
76
|
+
mass: 0.7,
|
|
77
|
+
}
|
|
78
|
+
|
|
51
79
|
// ─── Press scale tokens ──────────────────────────────────────────────────────
|
|
52
80
|
// Per-component press intensities — taken from DESIGN.md.
|
|
53
81
|
export const PRESS_SCALE = {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isLoaded } from 'expo-font'
|
|
2
|
+
|
|
3
|
+
declare const __DEV__: boolean
|
|
4
|
+
|
|
5
|
+
let warned = false
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Dev-only guard: warns once if the Sohne font family is not loaded.
|
|
9
|
+
*
|
|
10
|
+
* Called lazily from `Text` on first render. Without this, a missing
|
|
11
|
+
* `useFonts(SohneFonts)` call (or a broken font path) produces invisible /
|
|
12
|
+
* system-font text with no signal — a silent failure consumers reported losing
|
|
13
|
+
* hours to. In production (`__DEV__ === false`) this is a no-op.
|
|
14
|
+
*/
|
|
15
|
+
export function warnIfFontsMissing(): void {
|
|
16
|
+
if (warned) return
|
|
17
|
+
if (typeof __DEV__ !== 'undefined' && !__DEV__) return
|
|
18
|
+
warned = true
|
|
19
|
+
try {
|
|
20
|
+
if (!isLoaded('Sohne-Regular')) {
|
|
21
|
+
console.warn(
|
|
22
|
+
'[retray-ui-kit] Sohne fonts are not loaded — text will fall back to the ' +
|
|
23
|
+
'system font. Load them at your app root before rendering any UI kit ' +
|
|
24
|
+
'component:\n\n' +
|
|
25
|
+
" import { useFonts } from 'expo-font'\n" +
|
|
26
|
+
" import { SohneFonts } from '@retray-dev/ui-kit/fonts'\n\n" +
|
|
27
|
+
' const [fontsLoaded] = useFonts(SohneFonts)\n' +
|
|
28
|
+
' if (!fontsLoaded) return null\n',
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// `isLoaded` not available in this expo-font version — skip the guard silently.
|
|
33
|
+
}
|
|
34
|
+
}
|