@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.
Files changed (234) hide show
  1. package/COMPONENTS.md +554 -11
  2. package/EXAMPLES.md +2 -2
  3. package/README.md +14 -8
  4. package/dist/Accordion.js +57 -5
  5. package/dist/Accordion.mjs +4 -3
  6. package/dist/AlertBanner.js +4 -1
  7. package/dist/AlertBanner.mjs +3 -2
  8. package/dist/AppHeader.d.mts +40 -0
  9. package/dist/AppHeader.d.ts +40 -0
  10. package/dist/AppHeader.js +515 -0
  11. package/dist/AppHeader.mjs +10 -0
  12. package/dist/Avatar.js +39 -29
  13. package/dist/Avatar.mjs +2 -1
  14. package/dist/Badge.js +11 -1
  15. package/dist/Badge.mjs +2 -1
  16. package/dist/Button.d.mts +8 -3
  17. package/dist/Button.d.ts +8 -3
  18. package/dist/Button.js +126 -108
  19. package/dist/Button.mjs +6 -5
  20. package/dist/ButtonGroup.mjs +1 -0
  21. package/dist/Card.js +90 -70
  22. package/dist/Card.mjs +5 -4
  23. package/dist/CategoryStrip.js +79 -22
  24. package/dist/CategoryStrip.mjs +6 -6
  25. package/dist/Checkbox.js +118 -86
  26. package/dist/Checkbox.mjs +5 -5
  27. package/dist/Chip.js +113 -80
  28. package/dist/Chip.mjs +5 -5
  29. package/dist/ConfirmDialog.js +140 -110
  30. package/dist/ConfirmDialog.mjs +7 -6
  31. package/dist/CurrencyDisplay.mjs +1 -0
  32. package/dist/CurrencyInput.d.mts +1 -1
  33. package/dist/CurrencyInput.d.ts +1 -1
  34. package/dist/CurrencyInput.js +9 -5
  35. package/dist/CurrencyInput.mjs +5 -4
  36. package/dist/DetailRow.mjs +1 -0
  37. package/dist/EmptyState.js +131 -111
  38. package/dist/EmptyState.mjs +7 -6
  39. package/dist/ErrorBoundary.d.mts +42 -0
  40. package/dist/ErrorBoundary.d.ts +42 -0
  41. package/dist/ErrorBoundary.js +351 -0
  42. package/dist/ErrorBoundary.mjs +7 -0
  43. package/dist/Form.mjs +1 -0
  44. package/dist/HolographicCard.d.mts +55 -0
  45. package/dist/HolographicCard.d.ts +55 -0
  46. package/dist/HolographicCard.js +316 -0
  47. package/dist/HolographicCard.mjs +191 -0
  48. package/dist/IconButton.d.mts +8 -3
  49. package/dist/IconButton.d.ts +8 -3
  50. package/dist/IconButton.js +115 -98
  51. package/dist/IconButton.mjs +5 -4
  52. package/dist/ImageViewer.d.mts +23 -0
  53. package/dist/ImageViewer.d.ts +23 -0
  54. package/dist/ImageViewer.js +582 -0
  55. package/dist/ImageViewer.mjs +8 -0
  56. package/dist/Input.mjs +4 -3
  57. package/dist/LabelValue.mjs +1 -0
  58. package/dist/ListGroup.mjs +1 -0
  59. package/dist/ListItem.js +131 -117
  60. package/dist/ListItem.mjs +6 -5
  61. package/dist/MediaCard.js +54 -6
  62. package/dist/MediaCard.mjs +6 -5
  63. package/dist/MenuGroup.mjs +1 -0
  64. package/dist/MenuItem.js +91 -79
  65. package/dist/MenuItem.mjs +6 -5
  66. package/dist/MonthPicker.d.mts +10 -2
  67. package/dist/MonthPicker.d.ts +10 -2
  68. package/dist/MonthPicker.js +80 -17
  69. package/dist/MonthPicker.mjs +3 -2
  70. package/dist/PagerDots.d.mts +35 -0
  71. package/dist/PagerDots.d.ts +35 -0
  72. package/dist/PagerDots.js +392 -0
  73. package/dist/PagerDots.mjs +7 -0
  74. package/dist/Pressable.d.mts +5 -5
  75. package/dist/Pressable.d.ts +5 -5
  76. package/dist/Pressable.js +97 -86
  77. package/dist/Pressable.mjs +5 -4
  78. package/dist/PricingCard.d.mts +50 -0
  79. package/dist/PricingCard.d.ts +50 -0
  80. package/dist/PricingCard.js +636 -0
  81. package/dist/PricingCard.mjs +11 -0
  82. package/dist/Progress.mjs +3 -2
  83. package/dist/RadioGroup.js +81 -30
  84. package/dist/RadioGroup.mjs +5 -5
  85. package/dist/RetrayProvider.d.mts +2 -0
  86. package/dist/RetrayProvider.d.ts +2 -0
  87. package/dist/RetrayProvider.js +214 -0
  88. package/dist/RetrayProvider.mjs +5 -0
  89. package/dist/Select.js +51 -4
  90. package/dist/Select.mjs +5 -4
  91. package/dist/SelectableGrid.d.mts +44 -0
  92. package/dist/SelectableGrid.d.ts +44 -0
  93. package/dist/SelectableGrid.js +448 -0
  94. package/dist/SelectableGrid.mjs +9 -0
  95. package/dist/Separator.mjs +1 -0
  96. package/dist/Sheet.d.mts +13 -1
  97. package/dist/Sheet.d.ts +13 -1
  98. package/dist/Sheet.js +115 -5
  99. package/dist/Sheet.mjs +4 -2
  100. package/dist/Skeleton.d.mts +50 -0
  101. package/dist/Skeleton.d.ts +50 -0
  102. package/dist/Skeleton.js +61 -0
  103. package/dist/Skeleton.mjs +4 -2
  104. package/dist/Slider.js +51 -4
  105. package/dist/Slider.mjs +3 -2
  106. package/dist/Spinner.js +28 -7
  107. package/dist/Spinner.mjs +2 -1
  108. package/dist/Switch.js +98 -48
  109. package/dist/Switch.mjs +4 -3
  110. package/dist/TabBar.d.mts +42 -0
  111. package/dist/TabBar.d.ts +42 -0
  112. package/dist/TabBar.js +361 -0
  113. package/dist/TabBar.mjs +6 -0
  114. package/dist/Tabs.js +92 -62
  115. package/dist/Tabs.mjs +5 -4
  116. package/dist/Text.js +16 -0
  117. package/dist/Text.mjs +2 -1
  118. package/dist/Textarea.mjs +4 -3
  119. package/dist/Toast.d.mts +7 -7
  120. package/dist/Toast.d.ts +7 -7
  121. package/dist/Toast.mjs +1 -0
  122. package/dist/Toggle.d.mts +6 -3
  123. package/dist/Toggle.d.ts +6 -3
  124. package/dist/Toggle.js +135 -120
  125. package/dist/Toggle.mjs +5 -5
  126. package/dist/VirtualList.mjs +1 -0
  127. package/dist/{chunk-7H2OR44A.mjs → chunk-26BCI223.mjs} +1 -1
  128. package/dist/{chunk-CRYBX2CM.mjs → chunk-2TFTAWVJ.mjs} +44 -59
  129. package/dist/chunk-3DKJ2GIC.mjs +30 -0
  130. package/dist/{chunk-KWCPOM6W.mjs → chunk-3U4SSNWP.mjs} +32 -48
  131. package/dist/chunk-4I7D47FH.mjs +139 -0
  132. package/dist/chunk-4K625MVM.mjs +142 -0
  133. package/dist/{chunk-MN7OG7IY.mjs → chunk-6OAZJ577.mjs} +6 -4
  134. package/dist/{chunk-L7E7TVEZ.mjs → chunk-756RAKE4.mjs} +2 -2
  135. package/dist/{chunk-HSPSMN6U.mjs → chunk-7QHVVCB3.mjs} +2 -2
  136. package/dist/{chunk-URLL5JBR.mjs → chunk-A3A6KNQN.mjs} +3 -3
  137. package/dist/chunk-AJ7ZDNBT.mjs +120 -0
  138. package/dist/{chunk-FTLJOUOQ.mjs → chunk-AV4EMIRH.mjs} +25 -28
  139. package/dist/chunk-AZJF2BLK.mjs +115 -0
  140. package/dist/chunk-BNP626TY.mjs +159 -0
  141. package/dist/{chunk-5IKW3VNC.mjs → chunk-DVK4G2GT.mjs} +17 -1
  142. package/dist/{chunk-6LQYY7HC.mjs → chunk-EH745HE5.mjs} +2 -2
  143. package/dist/chunk-EJ7ZPXOH.mjs +163 -0
  144. package/dist/{chunk-RKLHUDZS.mjs → chunk-GD6KXMG5.mjs} +29 -15
  145. package/dist/{chunk-RR2VQLKE.mjs → chunk-GQYFLP3D.mjs} +14 -17
  146. package/dist/{chunk-Y6MXOREN.mjs → chunk-ID72TK46.mjs} +8 -17
  147. package/dist/{chunk-NQGVLMWG.mjs → chunk-JMOZEC77.mjs} +1 -1
  148. package/dist/{chunk-GCWOGZYL.mjs → chunk-JT7HKXRB.mjs} +39 -29
  149. package/dist/{chunk-LWG526VX.mjs → chunk-KIHCWCWL.mjs} +47 -62
  150. package/dist/chunk-LXJIIOYQ.mjs +104 -0
  151. package/dist/{chunk-SBZYEV4S.mjs → chunk-M6ZXVBTK.mjs} +5 -2
  152. package/dist/{chunk-XDMN67KV.mjs → chunk-MAC465BB.mjs} +10 -8
  153. package/dist/chunk-MBMXYJJV.mjs +36 -0
  154. package/dist/chunk-MLF3EZFW.mjs +119 -0
  155. package/dist/chunk-NA7PARID.mjs +147 -0
  156. package/dist/{chunk-QXGYKWI7.mjs → chunk-O3HA6TYM.mjs} +9 -4
  157. package/dist/{chunk-63357L2X.mjs → chunk-OB4JUQ3O.mjs} +1 -1
  158. package/dist/{chunk-AU2VDY4P.mjs → chunk-PFZTM6D5.mjs} +52 -4
  159. package/dist/chunk-QKH5ZOD5.mjs +97 -0
  160. package/dist/{chunk-KZJRQOIU.mjs → chunk-TERDKCLE.mjs} +11 -1
  161. package/dist/{chunk-U4N7WF4Z.mjs → chunk-UREA2GYY.mjs} +28 -23
  162. package/dist/{chunk-TAJ2PQ2O.mjs → chunk-VGTDN7SW.mjs} +7 -6
  163. package/dist/{chunk-URDE3EUU.mjs → chunk-VQ57HWPL.mjs} +27 -15
  164. package/dist/chunk-WBOOUHSS.mjs +62 -0
  165. package/dist/{chunk-GNGLDL6Z.mjs → chunk-WJLKJMKR.mjs} +18 -0
  166. package/dist/{chunk-YZJAFS4P.mjs → chunk-X4G6APW6.mjs} +22 -19
  167. package/dist/chunk-Y6FXYEAI.mjs +8 -0
  168. package/dist/chunk-YFZ3ELX5.mjs +16 -0
  169. package/dist/{chunk-QCNARS3X.mjs → chunk-YNROWHQJ.mjs} +1 -1
  170. package/dist/chunk-Z4BVUWW6.mjs +196 -0
  171. package/dist/{chunk-GPOUINK5.mjs → chunk-ZJKGQMYH.mjs} +10 -27
  172. package/dist/index-wt-orHUi.d.mts +85 -0
  173. package/dist/index-wt-orHUi.d.ts +85 -0
  174. package/dist/index.d.mts +59 -51
  175. package/dist/index.d.ts +59 -51
  176. package/dist/index.js +1940 -744
  177. package/dist/index.mjs +49 -39
  178. package/package.json +35 -5
  179. package/src/components/Accordion/Accordion.tsx +12 -1
  180. package/src/components/AlertBanner/AlertBanner.tsx +5 -0
  181. package/src/components/AppHeader/AppHeader.tsx +172 -0
  182. package/src/components/AppHeader/index.ts +1 -0
  183. package/src/components/Avatar/Avatar.tsx +10 -2
  184. package/src/components/Badge/Badge.tsx +8 -1
  185. package/src/components/Button/Button.tsx +20 -27
  186. package/src/components/Card/Card.tsx +12 -23
  187. package/src/components/CategoryStrip/CategoryStrip.tsx +17 -21
  188. package/src/components/Checkbox/Checkbox.tsx +26 -40
  189. package/src/components/Chip/Chip.tsx +24 -33
  190. package/src/components/CurrencyInput/CurrencyInput.tsx +10 -8
  191. package/src/components/EmptyState/EmptyState.tsx +2 -1
  192. package/src/components/ErrorBoundary/ErrorBoundary.tsx +153 -0
  193. package/src/components/ErrorBoundary/index.ts +1 -0
  194. package/src/components/HolographicCard/HolographicCard.tsx +315 -0
  195. package/src/components/HolographicCard/index.ts +1 -0
  196. package/src/components/IconButton/IconButton.tsx +19 -27
  197. package/src/components/ImageViewer/ImageViewer.tsx +290 -0
  198. package/src/components/ImageViewer/index.ts +1 -0
  199. package/src/components/ListItem/ListItem.tsx +70 -67
  200. package/src/components/MediaCard/MediaCard.tsx +8 -2
  201. package/src/components/MenuItem/MenuItem.tsx +10 -25
  202. package/src/components/MonthPicker/MonthPicker.tsx +39 -13
  203. package/src/components/MonthPicker/index.ts +1 -1
  204. package/src/components/PagerDots/PagerDots.tsx +200 -0
  205. package/src/components/PagerDots/index.ts +1 -0
  206. package/src/components/Pressable/Pressable.tsx +19 -35
  207. package/src/components/PricingCard/PricingCard.tsx +220 -0
  208. package/src/components/PricingCard/index.ts +1 -0
  209. package/src/components/RadioGroup/RadioGroup.tsx +14 -27
  210. package/src/components/RetrayProvider/RetrayProvider.tsx +59 -0
  211. package/src/components/RetrayProvider/index.ts +1 -0
  212. package/src/components/SelectableGrid/SelectableGrid.tsx +205 -0
  213. package/src/components/SelectableGrid/index.ts +1 -0
  214. package/src/components/Sheet/Sheet.tsx +65 -1
  215. package/src/components/Skeleton/Skeleton.tsx +142 -1
  216. package/src/components/Spinner/Spinner.tsx +17 -2
  217. package/src/components/Switch/Switch.tsx +30 -58
  218. package/src/components/TabBar/TabBar.tsx +169 -0
  219. package/src/components/TabBar/index.ts +1 -0
  220. package/src/components/Tabs/Tabs.tsx +23 -26
  221. package/src/components/Text/Text.tsx +2 -0
  222. package/src/components/Toggle/Toggle.tsx +35 -51
  223. package/src/fonts.ts +4 -1
  224. package/src/index.ts +23 -2
  225. package/src/utils/animations.ts +29 -1
  226. package/src/utils/fontGuard.ts +34 -0
  227. package/src/utils/haptics.ts +211 -9
  228. package/src/utils/pressable.ts +66 -0
  229. package/dist/chunk-76PFOSM2.mjs +0 -41
  230. package/dist/chunk-DITNP6PL.mjs +0 -106
  231. package/dist/chunk-JBLL7U3U.mjs +0 -64
  232. package/dist/chunk-LG4DO3DK.mjs +0 -174
  233. package/dist/chunk-RMMK64W5.mjs +0 -54
  234. package/dist/chunk-RTC3CFXF.mjs +0 -29
@@ -1,38 +1,240 @@
1
- import { Platform } from 'react-native'
1
+ /**
2
+ * Haptic feedback utilities using expo-haptics.
3
+ *
4
+ * Uses expo-haptics which works with both Expo Go and development builds.
5
+ * react-native-pulsar support is optional for dev builds with native modules.
6
+ *
7
+ * All functions are web-safe (no-op on web platform).
8
+ */
9
+ import { Platform, NativeModules } from 'react-native'
2
10
 
11
+ // ─── expo-haptics (primary) ───────────────────────────────────────────────────
3
12
  type HapticsModule = typeof import('expo-haptics')
4
-
5
13
  let _haptics: HapticsModule | null = null
14
+ let _hapticsLoaded = false
6
15
 
7
16
  async function getHaptics(): Promise<HapticsModule | null> {
8
17
  if (Platform.OS === 'web') return null
9
- if (!_haptics) {
10
- _haptics = await import('expo-haptics')
18
+ if (!_hapticsLoaded) {
19
+ _hapticsLoaded = true
20
+ try {
21
+ _haptics = await import('expo-haptics')
22
+ } catch {
23
+ _haptics = null
24
+ }
11
25
  }
12
26
  return _haptics
13
27
  }
14
28
 
29
+ // ─── Pulsar (optional for dev builds) ─────────────────────────────────────────
30
+ // Only available in development builds with native modules installed.
31
+ // Gracefully falls back to expo-haptics if not available.
32
+ type PulsarModule = typeof import('react-native-pulsar')
33
+ let _pulsar: PulsarModule | null = null
34
+ let _pulsarChecked = false
35
+ let _pulsarAvailable = false
36
+
37
+ function isPulsarNativeRegistered(): boolean {
38
+ // react-native-pulsar's NativeRNPulsar.ts calls
39
+ // TurboModuleRegistry.getEnforcing('RNPulsar') at module-eval time. When the
40
+ // native binary is absent (e.g. Expo Go), requiring the package throws during
41
+ // Metro module eval — and that throw is surfaced by RN's global error handler
42
+ // (red screen) even when wrapped in try/catch, because guardedLoadModule
43
+ // reports it before rethrowing. So we must confirm the native module is
44
+ // actually registered BEFORE requiring the JS wrapper.
45
+ //
46
+ // TurboModuleRegistry.get() returns a truthy proxy even for unregistered
47
+ // modules (false positive), so it can't be trusted. Instead we probe the low
48
+ // level: the new-arch turbo proxy returns null for unregistered names, and
49
+ // the old-arch NativeModules map omits them. A direct lookup throws nothing —
50
+ // unlike a top-level import — so any failure is caught safely here.
51
+ try {
52
+ const g = globalThis as {
53
+ __turboModuleProxy?: (name: string) => unknown
54
+ }
55
+ if (typeof g.__turboModuleProxy === 'function') {
56
+ return g.__turboModuleProxy('RNPulsar') != null
57
+ }
58
+ return NativeModules?.RNPulsar != null
59
+ } catch {
60
+ return false
61
+ }
62
+ }
63
+
64
+ function getPulsar(): PulsarModule | null {
65
+ if (Platform.OS === 'web') return null
66
+ if (!_pulsarChecked) {
67
+ _pulsarChecked = true
68
+ try {
69
+ // Only require the package when its native module is registered —
70
+ // otherwise the require would red-screen the app (see above).
71
+ if (isPulsarNativeRegistered()) {
72
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
73
+ _pulsar = require('react-native-pulsar') as PulsarModule
74
+ _pulsarAvailable = true
75
+ }
76
+ } catch {
77
+ _pulsar = null
78
+ _pulsarAvailable = false
79
+ }
80
+ }
81
+ return _pulsarAvailable ? _pulsar : null
82
+ }
83
+
84
+ // ─── Public API ───────────────────────────────────────────────────────────────
85
+
86
+ /**
87
+ * Light selection feedback — checkboxes, switches, toggles, pickers.
88
+ */
15
89
  export function selectionAsync(): void {
16
90
  if (Platform.OS === 'web') return
17
- getHaptics().then(h => h?.selectionAsync())
91
+ // Try expo-haptics first (always available in Expo Go)
92
+ getHaptics().then((h) => {
93
+ if (h) {
94
+ h.selectionAsync()
95
+ } else {
96
+ // Fallback to Pulsar if available
97
+ getPulsar()?.Presets.System.selection()
98
+ }
99
+ })
18
100
  }
19
101
 
102
+ /**
103
+ * Light impact — cards, surfaces, light interactions.
104
+ */
20
105
  export function impactLight(): void {
21
106
  if (Platform.OS === 'web') return
22
- getHaptics().then(h => h?.impactAsync(h.ImpactFeedbackStyle.Light))
107
+ getHaptics().then((h) => {
108
+ if (h) {
109
+ h.impactAsync(h.ImpactFeedbackStyle.Light)
110
+ } else {
111
+ getPulsar()?.Presets.System.impactLight()
112
+ }
113
+ })
23
114
  }
24
115
 
116
+ /**
117
+ * Medium impact — buttons, primary actions.
118
+ */
25
119
  export function impactMedium(): void {
26
120
  if (Platform.OS === 'web') return
27
- getHaptics().then(h => h?.impactAsync(h.ImpactFeedbackStyle.Medium))
121
+ getHaptics().then((h) => {
122
+ if (h) {
123
+ h.impactAsync(h.ImpactFeedbackStyle.Medium)
124
+ } else {
125
+ getPulsar()?.Presets.System.impactMedium()
126
+ }
127
+ })
128
+ }
129
+
130
+ /**
131
+ * Heavy impact — confirmations, important actions.
132
+ */
133
+ export function impactHeavy(): void {
134
+ if (Platform.OS === 'web') return
135
+ getHaptics().then((h) => {
136
+ if (h) {
137
+ h.impactAsync(h.ImpactFeedbackStyle.Heavy)
138
+ } else {
139
+ getPulsar()?.Presets.System.impactHeavy()
140
+ }
141
+ })
28
142
  }
29
143
 
144
+ /**
145
+ * Success notification — confirmations, completed actions.
146
+ */
30
147
  export function notificationSuccess(): void {
31
148
  if (Platform.OS === 'web') return
32
- getHaptics().then(h => h?.notificationAsync(h.NotificationFeedbackType.Success))
149
+ getHaptics().then((h) => {
150
+ if (h) {
151
+ h.notificationAsync(h.NotificationFeedbackType.Success)
152
+ } else {
153
+ getPulsar()?.Presets.System.notificationSuccess()
154
+ }
155
+ })
33
156
  }
34
157
 
158
+ /**
159
+ * Error notification — failed actions, errors.
160
+ */
35
161
  export function notificationError(): void {
36
162
  if (Platform.OS === 'web') return
37
- getHaptics().then(h => h?.notificationAsync(h.NotificationFeedbackType.Error))
163
+ getHaptics().then((h) => {
164
+ if (h) {
165
+ h.notificationAsync(h.NotificationFeedbackType.Error)
166
+ } else {
167
+ getPulsar()?.Presets.System.notificationError()
168
+ }
169
+ })
170
+ }
171
+
172
+ /**
173
+ * Warning notification — caution states.
174
+ */
175
+ export function notificationWarning(): void {
176
+ if (Platform.OS === 'web') return
177
+ getHaptics().then((h) => {
178
+ if (h) {
179
+ h.notificationAsync(h.NotificationFeedbackType.Warning)
180
+ } else {
181
+ getPulsar()?.Presets.System.notificationWarning()
182
+ }
183
+ })
184
+ }
185
+
186
+ // ─── Rich Pulsar Presets (enhanced feedback) ──────────────────────────────────
187
+
188
+ /**
189
+ * Rich haptic presets from Pulsar — enhanced feedback for special interactions.
190
+ * Falls back to basic expo-haptics on Expo Go or unsupported devices.
191
+ */
192
+ export const richHaptics = {
193
+ /** Hammer strike — strong confirmation feedback. */
194
+ hammer: (): void => {
195
+ if (Platform.OS === 'web') return
196
+ const p = getPulsar()
197
+ if (p) p.Presets.hammer()
198
+ else impactHeavy()
199
+ },
200
+
201
+ /** Pulse — rhythmic feedback for toggles or state changes. */
202
+ pulse: (): void => {
203
+ if (Platform.OS === 'web') return
204
+ const p = getPulsar()
205
+ if (p) p.Presets.pulse()
206
+ else selectionAsync()
207
+ },
208
+
209
+ /** Buzz — continuous vibration for attention. */
210
+ buzz: (): void => {
211
+ if (Platform.OS === 'web') return
212
+ const p = getPulsar()
213
+ if (p) p.Presets.buzz()
214
+ else impactMedium()
215
+ },
216
+
217
+ /** Flick — crisp click feedback. */
218
+ flick: (): void => {
219
+ if (Platform.OS === 'web') return
220
+ const p = getPulsar()
221
+ if (p) p.Presets.flick()
222
+ else selectionAsync()
223
+ },
224
+
225
+ /** Soft — gentle, subtle feedback. */
226
+ soft: (): void => {
227
+ if (Platform.OS === 'web') return
228
+ const p = getPulsar()
229
+ if (p) p.Presets.System.impactSoft()
230
+ else impactLight()
231
+ },
232
+
233
+ /** Rigid — firm, solid feedback. */
234
+ rigid: (): void => {
235
+ if (Platform.OS === 'web') return
236
+ const p = getPulsar()
237
+ if (p) p.Presets.System.impactRigid()
238
+ else impactMedium()
239
+ },
38
240
  }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pressto-based pressable primitives for the UI Kit.
3
+ *
4
+ * Pressto provides main-thread pressable animations built on react-native-gesture-handler
5
+ * and react-native-reanimated. Animations run at 60fps with zero JS overhead.
6
+ *
7
+ * These custom pressables match the UI Kit's design language:
8
+ * - Button: 0.95 scale (tight, premium press)
9
+ * - Card: 0.98 scale (subtle surface response)
10
+ * - Row: 0.97 scale (list items, menu items)
11
+ * - Chip: 0.94 scale (compact, snappy)
12
+ */
13
+ import { createAnimatedPressable } from 'pressto'
14
+ import { PRESS_SCALE } from './animations'
15
+
16
+ /**
17
+ * Button pressable — tight 0.95 scale for primary actions.
18
+ * Use for: Button, IconButton, Toggle, Checkbox triggers.
19
+ */
20
+ export const PressableButton = createAnimatedPressable((progress) => {
21
+ 'worklet'
22
+ const scale = 1 - (1 - PRESS_SCALE.button) * progress
23
+ return { transform: [{ scale }] }
24
+ })
25
+
26
+ /**
27
+ * Card pressable — subtle 0.98 scale for surfaces.
28
+ * Use for: Card (interactive), MediaCard, Pressable component.
29
+ */
30
+ export const PressableCard = createAnimatedPressable((progress) => {
31
+ 'worklet'
32
+ const scale = 1 - (1 - PRESS_SCALE.card) * progress
33
+ return { transform: [{ scale }] }
34
+ })
35
+
36
+ /**
37
+ * Row pressable — balanced 0.97 scale for list interactions.
38
+ * Use for: ListItem, MenuItem, Accordion triggers.
39
+ */
40
+ export const PressableRow = createAnimatedPressable((progress) => {
41
+ 'worklet'
42
+ const scale = 1 - (1 - PRESS_SCALE.row) * progress
43
+ return { transform: [{ scale }] }
44
+ })
45
+
46
+ /**
47
+ * Chip pressable — snappy 0.94 scale for compact elements.
48
+ * Use for: Chip, small toggleable items.
49
+ */
50
+ export const PressableChip = createAnimatedPressable((progress) => {
51
+ 'worklet'
52
+ const scale = 1 - (1 - PRESS_SCALE.chip) * progress
53
+ return { transform: [{ scale }] }
54
+ })
55
+
56
+ /**
57
+ * Tab pressable — same as button for tab triggers.
58
+ */
59
+ export const PressableTab = createAnimatedPressable((progress) => {
60
+ 'worklet'
61
+ const scale = 1 - (1 - PRESS_SCALE.button) * progress
62
+ return { transform: [{ scale }] }
63
+ })
64
+
65
+ // Re-export pressto components for direct use
66
+ export { PressableScale, PressableOpacity, PressablesConfig } from 'pressto'
@@ -1,41 +0,0 @@
1
- import { useTheme } from './chunk-SOYNZDVY.mjs';
2
- import { ms, mvs, vs } from './chunk-2CE3TQVY.mjs';
3
- import React from 'react';
4
- import { StyleSheet, View, ActivityIndicator, Text } from 'react-native';
5
-
6
- var sizeMap = {
7
- sm: "small",
8
- md: "small",
9
- lg: "large"
10
- };
11
- var labelFontSize = {
12
- sm: ms(11),
13
- md: ms(13),
14
- lg: ms(14)
15
- };
16
- function Spinner({ size = "md", color, label, ...props }) {
17
- const { colors } = useTheme();
18
- if (label) {
19
- return /* @__PURE__ */ React.createElement(View, { style: styles.wrapper }, /* @__PURE__ */ React.createElement(ActivityIndicator, { size: sizeMap[size], color: color ?? colors.primary, ...props }), /* @__PURE__ */ React.createElement(
20
- Text,
21
- {
22
- style: [styles.label, { color: colors.foregroundMuted, fontSize: labelFontSize[size] }],
23
- allowFontScaling: true
24
- },
25
- label
26
- ));
27
- }
28
- return /* @__PURE__ */ React.createElement(ActivityIndicator, { size: sizeMap[size], color: color ?? colors.primary, ...props });
29
- }
30
- var styles = StyleSheet.create({
31
- wrapper: {
32
- alignItems: "center",
33
- gap: vs(6)
34
- },
35
- label: {
36
- fontFamily: "Sohne-Regular",
37
- lineHeight: mvs(18)
38
- }
39
- });
40
-
41
- export { Spinner };
@@ -1,106 +0,0 @@
1
- import { selectionAsync } from './chunk-RTC3CFXF.mjs';
2
- import { SPRINGS, EASINGS, TIMINGS } from './chunk-5IKW3VNC.mjs';
3
- import { useTheme } from './chunk-SOYNZDVY.mjs';
4
- import { s } from './chunk-2CE3TQVY.mjs';
5
- import React, { useEffect } from 'react';
6
- import { StyleSheet, View, TouchableOpacity } from 'react-native';
7
- import Animated, { useSharedValue, withSpring, useAnimatedStyle, interpolateColor, withTiming } from 'react-native-reanimated';
8
- import { Feather } from '@expo/vector-icons';
9
-
10
- var TRACK_WIDTH = s(52);
11
- var TRACK_HEIGHT = s(30);
12
- var THUMB_SIZE = s(24);
13
- var THUMB_OFFSET = s(3);
14
- var THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2;
15
- var ICON_SIZE = s(13);
16
- function Switch({ checked = false, onCheckedChange, disabled, style, accessibilityLabel }) {
17
- const { colors } = useTheme();
18
- const progress = useSharedValue(checked ? 1 : 0);
19
- useEffect(() => {
20
- progress.value = withSpring(checked ? 1 : 0, SPRINGS.elastic);
21
- }, [checked, progress]);
22
- const thumbStyle = useAnimatedStyle(() => ({
23
- transform: [{ translateX: progress.value * THUMB_TRAVEL }]
24
- }));
25
- const trackStyle = useAnimatedStyle(() => ({
26
- backgroundColor: interpolateColor(
27
- progress.value,
28
- [0, 1],
29
- [colors.surfaceStrong, colors.primary]
30
- )
31
- }));
32
- const trackBorderStyle = useAnimatedStyle(() => ({
33
- borderWidth: 1.5,
34
- borderColor: interpolateColor(
35
- progress.value,
36
- [0, 1],
37
- [colors.border, "transparent"]
38
- )
39
- }));
40
- const checkIconStyle = useAnimatedStyle(() => ({
41
- opacity: withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard })
42
- }));
43
- const crossIconStyle = useAnimatedStyle(() => ({
44
- opacity: withTiming(checked ? 0 : 1, { duration: TIMINGS.state.duration, easing: EASINGS.standard })
45
- }));
46
- return /* @__PURE__ */ React.createElement(View, { style: [{ opacity: disabled ? 0.45 : 1, alignSelf: "flex-start" }, style] }, /* @__PURE__ */ React.createElement(
47
- TouchableOpacity,
48
- {
49
- onPress: () => {
50
- selectionAsync();
51
- onCheckedChange?.(!checked);
52
- },
53
- disabled,
54
- activeOpacity: 0.8,
55
- touchSoundDisabled: true,
56
- accessibilityRole: "switch",
57
- accessibilityLabel,
58
- accessibilityState: { checked, disabled: !!disabled },
59
- style: styles.touchable
60
- },
61
- /* @__PURE__ */ React.createElement(Animated.View, { style: [styles.track, trackStyle] }, /* @__PURE__ */ React.createElement(Animated.View, { style: [styles.trackBorder, trackBorderStyle], pointerEvents: "none" }), /* @__PURE__ */ React.createElement(
62
- Animated.View,
63
- {
64
- style: [styles.thumb, { backgroundColor: colors.primaryForeground }, thumbStyle]
65
- },
66
- /* @__PURE__ */ React.createElement(Animated.View, { style: [styles.iconWrapper, checkIconStyle] }, /* @__PURE__ */ React.createElement(Feather, { name: "check", size: ICON_SIZE, color: colors.primary })),
67
- /* @__PURE__ */ React.createElement(Animated.View, { style: [styles.iconWrapper, crossIconStyle] }, /* @__PURE__ */ React.createElement(Feather, { name: "x", size: ICON_SIZE, color: colors.foregroundMuted }))
68
- ))
69
- ));
70
- }
71
- var styles = StyleSheet.create({
72
- touchable: {
73
- alignSelf: "flex-start"
74
- },
75
- track: {
76
- width: TRACK_WIDTH,
77
- height: TRACK_HEIGHT,
78
- borderRadius: TRACK_HEIGHT / 2
79
- },
80
- trackBorder: {
81
- ...StyleSheet.absoluteFillObject,
82
- borderRadius: TRACK_HEIGHT / 2
83
- },
84
- thumb: {
85
- position: "absolute",
86
- top: THUMB_OFFSET,
87
- left: THUMB_OFFSET,
88
- width: THUMB_SIZE,
89
- height: THUMB_SIZE,
90
- borderRadius: THUMB_SIZE / 2,
91
- shadowColor: "#000",
92
- shadowOffset: { width: 0, height: 1 },
93
- shadowOpacity: 0.15,
94
- shadowRadius: 2,
95
- elevation: 2,
96
- alignItems: "center",
97
- justifyContent: "center"
98
- },
99
- iconWrapper: {
100
- position: "absolute",
101
- alignItems: "center",
102
- justifyContent: "center"
103
- }
104
- });
105
-
106
- export { Switch };
@@ -1,64 +0,0 @@
1
- import { TIMINGS } from './chunk-5IKW3VNC.mjs';
2
- import { useTheme } from './chunk-SOYNZDVY.mjs';
3
- import { s } from './chunk-2CE3TQVY.mjs';
4
- import React, { useState, useEffect } from 'react';
5
- import { StyleSheet, View } from 'react-native';
6
- import Animated, { useSharedValue, withRepeat, withTiming, Easing, useAnimatedStyle } from 'react-native-reanimated';
7
- import { LinearGradient } from 'expo-linear-gradient';
8
-
9
- function Skeleton({
10
- width = "100%",
11
- height = 16,
12
- borderRadius = 6,
13
- preset = "base",
14
- diameter = 40,
15
- style
16
- }) {
17
- const { colors, colorScheme } = useTheme();
18
- const shimmer = useSharedValue(0);
19
- const [containerWidth, setContainerWidth] = useState(300);
20
- const shimmerHighlight = colorScheme === "dark" ? "rgba(255,255,255,0.08)" : "rgba(255,255,255,0.7)";
21
- useEffect(() => {
22
- shimmer.value = withRepeat(
23
- withTiming(1, { duration: TIMINGS.shimmer.duration, easing: Easing.linear }),
24
- -1,
25
- false
26
- );
27
- }, [shimmer]);
28
- const shimmerStyle = useAnimatedStyle(() => ({
29
- transform: [{ translateX: -containerWidth + shimmer.value * (containerWidth * 2) }]
30
- }));
31
- const resolvedWidth = preset === "circle" ? s(diameter) : preset === "text" ? "60%" : width;
32
- const resolvedHeight = preset === "circle" ? s(diameter) : preset === "text" ? 14 : height;
33
- const resolvedRadius = preset === "circle" ? 9999 : preset === "text" ? 4 : borderRadius;
34
- return /* @__PURE__ */ React.createElement(
35
- View,
36
- {
37
- style: [
38
- styles.base,
39
- { width: resolvedWidth, height: resolvedHeight, borderRadius: resolvedRadius, backgroundColor: colors.surface },
40
- style
41
- ],
42
- onLayout: (e) => setContainerWidth(e.nativeEvent.layout.width),
43
- accessibilityRole: "progressbar",
44
- accessibilityLabel: "Loading",
45
- accessibilityState: { busy: true }
46
- },
47
- /* @__PURE__ */ React.createElement(Animated.View, { style: [StyleSheet.absoluteFill, shimmerStyle] }, /* @__PURE__ */ React.createElement(
48
- LinearGradient,
49
- {
50
- colors: ["transparent", shimmerHighlight, "transparent"],
51
- start: { x: 0, y: 0 },
52
- end: { x: 1, y: 0 },
53
- style: StyleSheet.absoluteFill
54
- }
55
- ))
56
- );
57
- }
58
- var styles = StyleSheet.create({
59
- base: {
60
- overflow: "hidden"
61
- }
62
- });
63
-
64
- export { Skeleton };
@@ -1,174 +0,0 @@
1
- import { renderIcon } from './chunk-T7XZ7H7Y.mjs';
2
- import { usePressScale } from './chunk-QCNARS3X.mjs';
3
- import { selectionAsync } from './chunk-RTC3CFXF.mjs';
4
- import { SPRINGS, PRESS_SCALE } from './chunk-5IKW3VNC.mjs';
5
- import { RADIUS } from './chunk-QY3X2UYR.mjs';
6
- import { useTheme } from './chunk-SOYNZDVY.mjs';
7
- import { ms, s, mvs, vs } from './chunk-2CE3TQVY.mjs';
8
- import React from 'react';
9
- import { StyleSheet, TouchableOpacity, View, Text } from 'react-native';
10
- import Animated from 'react-native-reanimated';
11
- import { Entypo } from '@expo/vector-icons';
12
-
13
- function ListItemBase({
14
- leftRender,
15
- rightRender,
16
- trailing,
17
- icon,
18
- leftIcon,
19
- rightIcon,
20
- leftIconColor,
21
- rightIconColor,
22
- title,
23
- subtitle,
24
- caption,
25
- variant = "plain",
26
- showChevron = false,
27
- showSeparator = false,
28
- onPress,
29
- disabled,
30
- style,
31
- titleStyle,
32
- subtitleStyle,
33
- captionStyle,
34
- accessibilityLabel
35
- }) {
36
- const { colors } = useTheme();
37
- const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
38
- pressScale: PRESS_SCALE.row,
39
- pressInSpring: SPRINGS.surfacePressIn,
40
- pressOutSpring: SPRINGS.surfacePressOut,
41
- disabled: !onPress || disabled
42
- });
43
- const handlePress = () => {
44
- selectionAsync();
45
- onPress?.();
46
- };
47
- const effectiveLeft = leftIcon ? renderIcon(leftIcon, 24, leftIconColor ?? colors.foreground) : leftRender ?? icon;
48
- const effectiveRight = rightIcon ? renderIcon(rightIcon, 24, rightIconColor ?? colors.foregroundMuted) : rightRender ?? trailing;
49
- const cardStyle = variant === "card" ? {
50
- backgroundColor: colors.card,
51
- borderRadius: RADIUS.md,
52
- borderWidth: 1,
53
- borderColor: colors.border,
54
- shadowColor: "#000",
55
- shadowOffset: { width: 0, height: 2 },
56
- shadowOpacity: 0.06,
57
- shadowRadius: 6,
58
- elevation: 2
59
- } : {};
60
- const a11yLabel = accessibilityLabel ?? [title, subtitle, caption].filter(Boolean).join(". ");
61
- return /* @__PURE__ */ React.createElement(Animated.View, { style: [animatedStyle, disabled && styles.disabled], ...hoverHandlers }, /* @__PURE__ */ React.createElement(
62
- TouchableOpacity,
63
- {
64
- style: [styles.container, cardStyle, style],
65
- onPress: onPress ? handlePress : void 0,
66
- onPressIn,
67
- onPressOut,
68
- disabled,
69
- activeOpacity: 1,
70
- touchSoundDisabled: true,
71
- accessibilityRole: onPress ? "button" : void 0,
72
- accessibilityLabel: onPress ? a11yLabel : void 0,
73
- accessibilityState: onPress ? { disabled: !!disabled } : void 0
74
- },
75
- effectiveLeft ? /* @__PURE__ */ React.createElement(View, { style: styles.leftContainer }, effectiveLeft) : null,
76
- /* @__PURE__ */ React.createElement(View, { style: styles.content }, /* @__PURE__ */ React.createElement(
77
- Text,
78
- {
79
- style: [styles.title, { color: colors.foreground }, titleStyle],
80
- numberOfLines: 2,
81
- allowFontScaling: true
82
- },
83
- title
84
- ), subtitle ? /* @__PURE__ */ React.createElement(
85
- Text,
86
- {
87
- style: [styles.subtitle, { color: colors.foregroundMuted }, subtitleStyle],
88
- numberOfLines: 2,
89
- allowFontScaling: true
90
- },
91
- subtitle
92
- ) : null, caption ? /* @__PURE__ */ React.createElement(
93
- Text,
94
- {
95
- style: [styles.caption, { color: colors.foregroundMuted }, captionStyle],
96
- numberOfLines: 1,
97
- allowFontScaling: true
98
- },
99
- caption
100
- ) : null),
101
- effectiveRight !== void 0 ? /* @__PURE__ */ React.createElement(View, { style: styles.rightContainer }, typeof effectiveRight === "string" ? /* @__PURE__ */ React.createElement(
102
- Text,
103
- {
104
- style: [styles.rightText, { color: colors.foregroundMuted }],
105
- allowFontScaling: true
106
- },
107
- effectiveRight
108
- ) : effectiveRight) : showChevron ? /* @__PURE__ */ React.createElement(Entypo, { name: "chevron-with-circle-right", size: 20, color: colors.foregroundMuted }) : null
109
- ), showSeparator ? /* @__PURE__ */ React.createElement(
110
- View,
111
- {
112
- style: [
113
- styles.separator,
114
- { backgroundColor: colors.separator }
115
- ]
116
- }
117
- ) : null);
118
- }
119
- var ListItem = React.memo(ListItemBase);
120
- var styles = StyleSheet.create({
121
- container: {
122
- flexDirection: "row",
123
- alignItems: "center",
124
- paddingHorizontal: s(16),
125
- paddingVertical: vs(10),
126
- gap: s(12)
127
- },
128
- leftContainer: {
129
- width: s(44),
130
- height: s(44),
131
- alignItems: "center",
132
- justifyContent: "center",
133
- flexShrink: 0
134
- },
135
- content: {
136
- flex: 1,
137
- gap: vs(4)
138
- },
139
- title: {
140
- fontFamily: "Sohne-Medium",
141
- fontSize: ms(15),
142
- lineHeight: mvs(22)
143
- },
144
- subtitle: {
145
- fontFamily: "Sohne-Regular",
146
- fontSize: ms(13),
147
- lineHeight: mvs(18)
148
- },
149
- caption: {
150
- fontFamily: "Sohne-Regular",
151
- fontSize: ms(12),
152
- lineHeight: mvs(16),
153
- opacity: 0.7
154
- },
155
- rightContainer: {
156
- alignItems: "flex-end",
157
- justifyContent: "center",
158
- flexShrink: 0,
159
- maxWidth: s(160)
160
- },
161
- rightText: {
162
- fontFamily: "Sohne-Regular",
163
- fontSize: ms(14)
164
- },
165
- separator: {
166
- height: StyleSheet.hairlineWidth,
167
- marginRight: 0
168
- },
169
- disabled: {
170
- opacity: 0.45
171
- }
172
- });
173
-
174
- export { ListItem };