@expokit/cli 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/README.md +30 -3
  2. package/bin/expo-kit.js +749 -288
  3. package/bin/lib.mjs +313 -4
  4. package/package.json +1 -1
  5. package/registry/_docs/action-sheet.json +1 -1
  6. package/registry/_docs/alert.json +6 -0
  7. package/registry/_docs/bottom-sheet.json +6 -0
  8. package/registry/_docs/bullet.json +6 -0
  9. package/registry/_docs/dialog.json +1 -1
  10. package/registry/_docs/drawer.json +3 -3
  11. package/registry/_docs/flip-calendar.json +106 -0
  12. package/registry/_docs/glass-search-bar.json +1 -1
  13. package/registry/_docs/grain.json +6 -1
  14. package/registry/_docs/isometric-bar.json +6 -0
  15. package/registry/_docs/liquidglass-tab.json +1 -1
  16. package/registry/_docs/password-input.json +1 -1
  17. package/registry/_docs/paywall.json +144 -0
  18. package/registry/_docs/pixel-bar.json +6 -0
  19. package/registry/_docs/polar-area.json +6 -0
  20. package/registry/_docs/qr-code-button.json +9 -3
  21. package/registry/_docs/ruler-time-picker.json +147 -0
  22. package/registry/_docs/scratch-card.json +7 -1
  23. package/registry/_docs/search-bar.json +1 -1
  24. package/registry/_docs/select.json +2 -2
  25. package/registry/_docs/slide-to-confirm.json +2 -2
  26. package/registry/_docs/spinner.json +1 -1
  27. package/registry/_docs/stateful-button.json +2 -2
  28. package/registry/_docs/swipe-deck.json +1 -1
  29. package/registry/_docs/time-picker.json +2 -2
  30. package/registry/_docs/timeline-chart.json +6 -0
  31. package/registry/_docs/toast.json +15 -3
  32. package/registry/_docs/treemap.json +6 -0
  33. package/registry/_docs/waterfall.json +6 -0
  34. package/registry/accordion/files/src/components/ui/accordion.tsx +6 -4
  35. package/registry/accordion/files/src/lib/kit.tsx +462 -12
  36. package/registry/accordion.json +1 -0
  37. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +50 -58
  38. package/registry/action-sheet/files/src/lib/kit.tsx +462 -12
  39. package/registry/action-sheet.json +1 -0
  40. package/registry/alert/files/src/components/ui/alert.tsx +20 -13
  41. package/registry/alert/files/src/lib/kit.tsx +462 -12
  42. package/registry/alert.json +3 -4
  43. package/registry/assistant-app.json +1 -0
  44. package/registry/avatar/files/src/components/ui/avatar.tsx +89 -74
  45. package/registry/avatar/files/src/lib/kit.tsx +462 -12
  46. package/registry/avatar.json +1 -0
  47. package/registry/badge/files/src/components/ui/badge.tsx +36 -36
  48. package/registry/badge/files/src/lib/kit.tsx +462 -12
  49. package/registry/badge.json +1 -0
  50. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +39 -44
  51. package/registry/bottom-nav/files/src/lib/kit.tsx +462 -12
  52. package/registry/bottom-nav.json +5 -0
  53. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +357 -300
  54. package/registry/bottom-sheet/files/src/lib/kit.tsx +462 -12
  55. package/registry/bottom-sheet.json +5 -1
  56. package/registry/bullet.json +1 -0
  57. package/registry/button/files/src/components/ui/button.tsx +23 -34
  58. package/registry/button/files/src/lib/kit.tsx +462 -12
  59. package/registry/button-3d/files/src/components/ui/button-3d.tsx +6 -9
  60. package/registry/button-3d/files/src/lib/kit.tsx +462 -12
  61. package/registry/button-3d.json +1 -0
  62. package/registry/button.json +1 -0
  63. package/registry/calendar/files/src/components/ui/calendar.tsx +77 -79
  64. package/registry/calendar/files/src/lib/kit.tsx +462 -12
  65. package/registry/calendar.json +1 -0
  66. package/registry/card/files/src/components/ui/card.tsx +7 -6
  67. package/registry/card/files/src/lib/kit.tsx +462 -12
  68. package/registry/card-stack.json +1 -0
  69. package/registry/card.json +1 -0
  70. package/registry/carousel.json +1 -0
  71. package/registry/chat.json +7 -0
  72. package/registry/checkbox/files/src/components/ui/checkbox.tsx +4 -4
  73. package/registry/checkbox/files/src/lib/kit.tsx +462 -12
  74. package/registry/checkbox.json +1 -0
  75. package/registry/checkout.json +1 -0
  76. package/registry/chip-group/files/src/components/ui/chip-group.tsx +24 -24
  77. package/registry/chip-group/files/src/lib/kit.tsx +462 -12
  78. package/registry/chip-group.json +1 -0
  79. package/registry/confetti/files/src/components/ui/confetti.tsx +15 -14
  80. package/registry/confetti.json +3 -6
  81. package/registry/coupon/files/src/components/ui/coupon.tsx +47 -52
  82. package/registry/coupon/files/src/components/ui/flip-card.tsx +9 -14
  83. package/registry/coupon/files/src/components/ui/tilt-card.tsx +80 -101
  84. package/registry/coupon/files/src/lib/kit.tsx +462 -12
  85. package/registry/coupon/files/src/lib/ticket-shape.tsx +54 -44
  86. package/registry/coupon.json +1 -0
  87. package/registry/cube-pager.json +1 -0
  88. package/registry/dashboard.json +1 -0
  89. package/registry/date-picker/files/src/components/ui/calendar.tsx +77 -79
  90. package/registry/date-picker/files/src/components/ui/date-picker.tsx +64 -71
  91. package/registry/date-picker/files/src/lib/kit.tsx +462 -12
  92. package/registry/date-picker.json +1 -0
  93. package/registry/dialog/files/src/components/ui/dialog.tsx +66 -68
  94. package/registry/dialog/files/src/lib/kit.tsx +462 -12
  95. package/registry/dialog.json +1 -0
  96. package/registry/donut-shop.json +1 -0
  97. package/registry/drawer/files/src/components/ui/drawer.tsx +76 -88
  98. package/registry/drawer/files/src/lib/kit.tsx +462 -12
  99. package/registry/drawer.json +1 -0
  100. package/registry/fab.json +1 -0
  101. package/registry/fintech-app.json +1 -0
  102. package/registry/flip-calendar/files/src/components/ui/flip-calendar.tsx +632 -0
  103. package/registry/flip-calendar/files/src/lib/kit.tsx +672 -0
  104. package/registry/flip-calendar.json +24 -0
  105. package/registry/flip-card/files/src/components/ui/flip-card.tsx +9 -14
  106. package/registry/flip-card/files/src/lib/kit.tsx +462 -12
  107. package/registry/flip-card.json +1 -0
  108. package/registry/floating-input/files/src/components/ui/floating-input.tsx +33 -33
  109. package/registry/floating-input/files/src/lib/kit.tsx +462 -12
  110. package/registry/floating-input.json +1 -0
  111. package/registry/food-app.json +1 -0
  112. package/registry/gift-card.json +1 -0
  113. package/registry/glass-button.json +5 -0
  114. package/registry/glass-nav-bar.json +1 -0
  115. package/registry/glass-search-bar.json +1 -0
  116. package/registry/glass-segmented.json +1 -0
  117. package/registry/glass-sheet.json +1 -0
  118. package/registry/glass-tab-bar.json +5 -0
  119. package/registry/glass.json +1 -0
  120. package/registry/grain/files/src/components/ui/grain.tsx +16 -14
  121. package/registry/grain.json +8 -6
  122. package/registry/inbox.json +1 -0
  123. package/registry/isometric-bar.json +1 -0
  124. package/registry/kit/files/src/lib/kit.tsx +462 -12
  125. package/registry/kit.json +1 -0
  126. package/registry/liquidglass-tab/files/src/app/_layout.tsx +0 -1
  127. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +29 -16
  128. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +33 -19
  129. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +1 -1
  130. package/registry/liquidglass-tab.json +1 -0
  131. package/registry/list-item/files/src/components/ui/list-item.tsx +43 -43
  132. package/registry/list-item/files/src/lib/kit.tsx +462 -12
  133. package/registry/list-item.json +1 -0
  134. package/registry/loyalty-card.json +1 -0
  135. package/registry/morphing-button-group.json +1 -0
  136. package/registry/number-ticker.json +1 -0
  137. package/registry/offers.json +1 -0
  138. package/registry/onboarding.json +1 -0
  139. package/registry/orb-reveal.json +1 -0
  140. package/registry/otp-input/files/src/components/ui/otp-input.tsx +49 -30
  141. package/registry/otp-input/files/src/lib/kit.tsx +462 -12
  142. package/registry/otp-input.json +1 -0
  143. package/registry/parallax-layers.json +1 -0
  144. package/registry/parallax-scroll.json +1 -0
  145. package/registry/password-input/files/src/components/ui/floating-input.tsx +33 -33
  146. package/registry/password-input/files/src/components/ui/password-input.tsx +29 -25
  147. package/registry/password-input/files/src/lib/kit.tsx +462 -12
  148. package/registry/password-input.json +1 -0
  149. package/registry/payment-card/files/src/components/ui/flip-card.tsx +9 -14
  150. package/registry/payment-card/files/src/components/ui/payment-card.tsx +26 -20
  151. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +80 -101
  152. package/registry/payment-card/files/src/lib/kit.tsx +462 -12
  153. package/registry/payment-card.json +1 -0
  154. package/registry/paywall.json +23 -0
  155. package/registry/pixel-bar.json +1 -0
  156. package/registry/polar-area.json +1 -0
  157. package/registry/profile.json +1 -0
  158. package/registry/progress/files/src/components/ui/progress.tsx +31 -29
  159. package/registry/progress/files/src/lib/kit.tsx +462 -12
  160. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +21 -21
  161. package/registry/progress-steps/files/src/lib/kit.tsx +462 -12
  162. package/registry/progress-steps.json +1 -0
  163. package/registry/progress.json +1 -0
  164. package/registry/qr-code-button.json +1 -0
  165. package/registry/radio-group/files/src/components/ui/radio-group.tsx +19 -19
  166. package/registry/radio-group/files/src/lib/kit.tsx +462 -12
  167. package/registry/radio-group.json +1 -0
  168. package/registry/rating.json +1 -0
  169. package/registry/ring-carousel.json +1 -0
  170. package/registry/ruler-time-picker/files/src/components/ui/ruler-time-picker.tsx +832 -0
  171. package/registry/ruler-time-picker/files/src/lib/kit.tsx +672 -0
  172. package/registry/ruler-time-picker.json +26 -0
  173. package/registry/scratch-card.json +1 -0
  174. package/registry/search-bar/files/src/components/ui/search-bar.tsx +28 -28
  175. package/registry/search-bar/files/src/lib/kit.tsx +462 -12
  176. package/registry/search-bar.json +1 -0
  177. package/registry/search-results.json +4 -0
  178. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +11 -17
  179. package/registry/segmented-control/files/src/lib/kit.tsx +462 -12
  180. package/registry/segmented-control.json +1 -0
  181. package/registry/select/files/src/components/ui/select.tsx +107 -104
  182. package/registry/select/files/src/lib/kit.tsx +462 -12
  183. package/registry/select.json +6 -0
  184. package/registry/settings.json +1 -0
  185. package/registry/sign-in.json +1 -0
  186. package/registry/skeleton/files/src/components/ui/skeleton.tsx +149 -80
  187. package/registry/skeleton/files/src/lib/kit.tsx +462 -12
  188. package/registry/skeleton.json +1 -0
  189. package/registry/slide-to-confirm.json +1 -0
  190. package/registry/slider/files/src/components/ui/slider.tsx +106 -108
  191. package/registry/slider/files/src/lib/kit.tsx +462 -12
  192. package/registry/slider.json +5 -0
  193. package/registry/spin-wheel.json +1 -0
  194. package/registry/spinner/files/src/components/ui/spinner.tsx +39 -29
  195. package/registry/spinner/files/src/lib/kit.tsx +462 -12
  196. package/registry/spinner.json +6 -0
  197. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +39 -32
  198. package/registry/stateful-button/files/src/lib/kit.tsx +462 -12
  199. package/registry/stateful-button.json +5 -0
  200. package/registry/stepper/files/src/components/ui/stepper.tsx +40 -38
  201. package/registry/stepper/files/src/lib/kit.tsx +462 -12
  202. package/registry/stepper.json +1 -0
  203. package/registry/swipe-deck.json +1 -0
  204. package/registry/swipeable-row.json +4 -0
  205. package/registry/switch/files/src/components/ui/switch.tsx +41 -47
  206. package/registry/switch/files/src/lib/kit.tsx +462 -12
  207. package/registry/switch.json +1 -0
  208. package/registry/tabs/files/src/components/ui/tabs.tsx +61 -64
  209. package/registry/tabs/files/src/lib/kit.tsx +462 -12
  210. package/registry/tabs.json +1 -0
  211. package/registry/text-animation/files/src/components/ui/text-animation.tsx +55 -42
  212. package/registry/text-animation/files/src/lib/kit.tsx +462 -12
  213. package/registry/text-animation.json +7 -0
  214. package/registry/textarea/files/src/components/ui/textarea.tsx +10 -10
  215. package/registry/textarea/files/src/lib/kit.tsx +462 -12
  216. package/registry/textarea.json +1 -0
  217. package/registry/ticket-card.json +1 -0
  218. package/registry/ticket-shape/files/src/lib/kit.tsx +462 -12
  219. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +54 -44
  220. package/registry/ticket-shape.json +1 -0
  221. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +80 -101
  222. package/registry/tilt-card/files/src/lib/kit.tsx +462 -12
  223. package/registry/tilt-card.json +1 -0
  224. package/registry/time-picker/files/src/components/ui/time-picker.tsx +136 -131
  225. package/registry/time-picker/files/src/lib/kit.tsx +462 -12
  226. package/registry/time-picker.json +1 -0
  227. package/registry/timeline/files/src/components/ui/avatar.tsx +89 -74
  228. package/registry/timeline/files/src/components/ui/timeline.tsx +49 -57
  229. package/registry/timeline/files/src/lib/kit.tsx +462 -12
  230. package/registry/timeline-chart.json +1 -0
  231. package/registry/timeline.json +1 -0
  232. package/registry/toast/files/src/components/ui/toast.tsx +543 -465
  233. package/registry/toast/files/src/lib/kit.tsx +462 -12
  234. package/registry/toast.json +7 -5
  235. package/registry/treemap.json +1 -0
  236. package/registry/waterfall.json +1 -0
  237. package/registry/wheel-list.json +1 -0
  238. package/schema/components.schema.json +1 -0
  239. package/registry/alert/files/src/components/ui/button.tsx +0 -303
  240. package/registry/confetti/files/src/lib/kit.tsx +0 -222
  241. package/registry/grain/files/src/lib/kit.tsx +0 -222
  242. package/registry/toast/files/src/components/ui/button.tsx +0 -303
@@ -1,5 +1,5 @@
1
1
  import * as Haptics from 'expo-haptics';
2
- import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
2
+ import React, { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
3
3
  import {
4
4
  AccessibilityInfo,
5
5
  Animated,
@@ -7,7 +7,6 @@ import {
7
7
  Easing,
8
8
  Image,
9
9
  Modal,
10
- PanResponder,
11
10
  Platform,
12
11
  Pressable,
13
12
  ScrollView,
@@ -23,7 +22,7 @@ import {
23
22
  } from 'react-native';
24
23
  import Svg, { Path } from 'react-native-svg';
25
24
 
26
- import { alpha, motion, useKit } from '@/lib/kit';
25
+ import { alpha, motion, useKit, usePanResponder } from '@/lib/kit';
27
26
 
28
27
  export type DrawerSide = 'left' | 'right';
29
28
  /**
@@ -135,11 +134,12 @@ export function Drawer({
135
134
  haptics = true,
136
135
  topInset,
137
136
  bottomInset,
138
- accessibilityLabel = 'Navigation drawer',
137
+ accessibilityLabel: accessibilityLabelProp,
139
138
  style,
140
139
  panelStyle,
141
140
  }: DrawerProps) {
142
- const { colors, dark, radius } = useKit();
141
+ const { colors, dark, radius, labels } = useKit();
142
+ const accessibilityLabel = accessibilityLabelProp ?? labels.navigationDrawer;
143
143
  const reduceMotion = useReduceMotion();
144
144
  const win = useWindowDimensions();
145
145
  const inline = children !== undefined;
@@ -159,13 +159,17 @@ export function Drawer({
159
159
  const [inner, setInner] = useState(defaultOpen);
160
160
  const isOpen = open ?? inner;
161
161
  const [mounted, setMounted] = useState(isOpen); // Modal mode only
162
+ // Mount as soon as it opens (state adjusted during render); unmounted after the exit animation.
163
+ if (isOpen && !inline && !mounted) setMounted(true);
162
164
  // Progress 0 (closed) → 1 (open), native-driven; every transform and opacity reads from it.
163
- const p = useRef(new Animated.Value(isOpen ? 1 : 0)).current;
165
+ const [p] = useState(() => new Animated.Value(isOpen ? 1 : 0));
164
166
  // Same progress on the JS driver, for the screen's corner radius and edge colour (separate view).
165
- const r = useRef(new Animated.Value(isOpen ? 1 : 0)).current;
167
+ const [r] = useState(() => new Animated.Value(isOpen ? 1 : 0));
166
168
 
167
169
  const live = useRef({ open: isOpen, w: W, box, reduce: reduceMotion, haptics, movesScreen, edgeSwipe, edgeWidth, dismissible });
168
- live.current = { open: isOpen, w: W, box, reduce: reduceMotion, haptics, movesScreen, edgeSwipe, edgeWidth, dismissible };
170
+ useLayoutEffect(() => {
171
+ live.current = { open: isOpen, w: W, box, reduce: reduceMotion, haptics, movesScreen, edgeSwipe, edgeWidth, dismissible };
172
+ });
169
173
 
170
174
  const setOpen = useCallback(
171
175
  (next: boolean) => {
@@ -175,7 +179,9 @@ export function Drawer({
175
179
  [open, onOpenChange],
176
180
  );
177
181
  const setOpenRef = useRef(setOpen);
178
- setOpenRef.current = setOpen;
182
+ useLayoutEffect(() => {
183
+ setOpenRef.current = setOpen;
184
+ });
179
185
 
180
186
  const target = useRef<0 | 1>(isOpen ? 1 : 0);
181
187
  const settle = useCallback(
@@ -203,7 +209,6 @@ export function Drawer({
203
209
  );
204
210
 
205
211
  useEffect(() => {
206
- if (isOpen && !inline) setMounted(true);
207
212
  // Skip when a drag release already started a velocity spring towards this state.
208
213
  if (target.current !== (isOpen ? 1 : 0)) settle(isOpen ? 1 : 0);
209
214
  }, [isOpen, settle, inline]);
@@ -242,50 +247,47 @@ export function Drawer({
242
247
  // zone (so taps there still reach the screen), `drag` captures horizontal drags on the open
243
248
  // panel / scrim (so vertical scrolling and taps inside the panel keep working).
244
249
  const start = useRef(0);
245
- const pans = useMemo(() => {
246
- const toward = (dx: number) => (side === 'left' ? dx : -dx); // + means "more open"
247
- const horizontal = (g: PanResponderGestureState) => Math.abs(g.dx) > 10 && Math.abs(g.dx) > Math.abs(g.dy) * 1.5;
248
- const base = {
249
- onPanResponderTerminationRequest: () => false,
250
- onPanResponderGrant: () => {
251
- p.stopAnimation((v) => (start.current = v));
252
- r.stopAnimation();
253
- if (!live.current.open && !inline) setMounted(true);
254
- },
255
- onPanResponderMove: (_: unknown, g: PanResponderGestureState) => {
256
- const w = live.current.w || 1;
257
- let next = start.current + toward(g.dx) / w;
258
- if (next > 1) next = 1 + rubber((next - 1) * w, EXTRA) / w;
259
- if (next < 0) next = 0;
260
- p.setValue(next);
261
- if (live.current.movesScreen) r.setValue(Math.min(1, next));
262
- },
263
- onPanResponderRelease: (_: unknown, g: PanResponderGestureState) => {
264
- const w = live.current.w || 1;
265
- const v = toward(g.vx); // px/ms, + means opening
266
- const projected = start.current + toward(g.dx) / w + (v * 200) / w;
267
- let to: 0 | 1 = projected > 0.5 ? 1 : 0;
268
- if (Math.abs(v) > 0.35) to = v > 0 ? 1 : 0; // a flick decides on its own
269
- settle(to, (v * 1000) / w);
270
- if ((to === 1) !== live.current.open) setOpenRef.current(to === 1);
271
- },
272
- onPanResponderTerminate: () => settle(live.current.open ? 1 : 0),
273
- };
274
- const edge = PanResponder.create({
275
- ...base,
276
- onMoveShouldSetPanResponderCapture: (_, g) => {
277
- const l = live.current;
278
- if (!l.edgeSwipe || l.open || !horizontal(g) || toward(g.dx) <= 0) return false;
279
- const x = g.x0 - rootX.current;
280
- return side === 'left' ? x <= l.edgeWidth : x >= l.box - l.edgeWidth;
281
- },
282
- });
283
- const drag = PanResponder.create({
284
- ...base,
285
- onMoveShouldSetPanResponderCapture: (_, g) => live.current.dismissible && live.current.open && horizontal(g),
286
- });
287
- return { edge, drag };
288
- }, [p, r, side, settle, inline]);
250
+ const toward = (dx: number) => (side === 'left' ? dx : -dx); // + means "more open"
251
+ const horizontal = (g: PanResponderGestureState) => Math.abs(g.dx) > 10 && Math.abs(g.dx) > Math.abs(g.dy) * 1.5;
252
+ const base = {
253
+ onPanResponderTerminationRequest: () => false,
254
+ onPanResponderGrant: () => {
255
+ p.stopAnimation((v) => (start.current = v));
256
+ r.stopAnimation();
257
+ if (!live.current.open && !inline) setMounted(true);
258
+ },
259
+ onPanResponderMove: (_: unknown, g: PanResponderGestureState) => {
260
+ const w = live.current.w || 1;
261
+ let next = start.current + toward(g.dx) / w;
262
+ if (next > 1) next = 1 + rubber((next - 1) * w, EXTRA) / w;
263
+ if (next < 0) next = 0;
264
+ p.setValue(next);
265
+ if (live.current.movesScreen) r.setValue(Math.min(1, next));
266
+ },
267
+ onPanResponderRelease: (_: unknown, g: PanResponderGestureState) => {
268
+ const w = live.current.w || 1;
269
+ const v = toward(g.vx); // px/ms, + means opening
270
+ const projected = start.current + toward(g.dx) / w + (v * 200) / w;
271
+ let to: 0 | 1 = projected > 0.5 ? 1 : 0;
272
+ if (Math.abs(v) > 0.35) to = v > 0 ? 1 : 0; // a flick decides on its own
273
+ settle(to, (v * 1000) / w);
274
+ if ((to === 1) !== live.current.open) setOpenRef.current(to === 1);
275
+ },
276
+ onPanResponderTerminate: () => settle(live.current.open ? 1 : 0),
277
+ };
278
+ const edgePan = usePanResponder({
279
+ ...base,
280
+ onMoveShouldSetPanResponderCapture: (_, g) => {
281
+ const l = live.current;
282
+ if (!l.edgeSwipe || l.open || !horizontal(g) || toward(g.dx) <= 0) return false;
283
+ const x = g.x0 - rootX.current;
284
+ return side === 'left' ? x <= l.edgeWidth : x >= l.box - l.edgeWidth;
285
+ },
286
+ });
287
+ const dragPan = usePanResponder({
288
+ ...base,
289
+ onMoveShouldSetPanResponderCapture: (_, g) => live.current.dismissible && live.current.open && horizontal(g),
290
+ });
289
291
 
290
292
  const ctx = useMemo(
291
293
  () => ({ open: isOpen, setOpen, toggle: () => setOpen(!isOpen), close: () => setOpen(false) }),
@@ -328,7 +330,7 @@ export function Drawer({
328
330
 
329
331
  const panel = (
330
332
  <Animated.View
331
- {...(dismissible ? pans.drag.panHandlers : null)}
333
+ {...(dismissible ? dragPan : null)}
332
334
  accessibilityViewIsModal={isOpen}
333
335
  accessibilityLabel={accessibilityLabel}
334
336
  importantForAccessibility={isOpen ? 'yes' : 'no-hide-descendants'}
@@ -337,13 +339,6 @@ export function Drawer({
337
339
  styles.panel,
338
340
  side === 'left' ? { left: -EXTRA } : { right: -EXTRA },
339
341
  { width: W + EXTRA, opacity: panelOpacity, transform: [{ translateX: panelX }] },
340
- !push && {
341
- shadowColor: '#000',
342
- shadowOpacity: dark ? 0.45 : 0.12,
343
- shadowRadius: 28,
344
- shadowOffset: { width: -dir * 8, height: 0 },
345
- elevation: 24,
346
- },
347
342
  panelStyle,
348
343
  ]}
349
344
  >
@@ -353,10 +348,8 @@ export function Drawer({
353
348
  round,
354
349
  !push && {
355
350
  backgroundColor: surface,
356
- shadowColor: '#000',
357
- shadowOpacity: dark ? 0.3 : 0.06,
358
- shadowRadius: 4,
359
- shadowOffset: { width: -dir * 1, height: 0 },
351
+ // Ambient + contact shadow as boxShadow: the same on iOS, Android and web.
352
+ boxShadow: `${-dir * 8}px 0px 56px rgba(0,0,0,${dark ? 0.45 : 0.12}), ${-dir}px 0px 8px rgba(0,0,0,${dark ? 0.3 : 0.06})`,
360
353
  },
361
354
  ]}
362
355
  >
@@ -402,7 +395,7 @@ export function Drawer({
402
395
 
403
396
  const scrim = (
404
397
  <Animated.View
405
- {...(dismissible ? pans.drag.panHandlers : null)}
398
+ {...(dismissible ? dragPan : null)}
406
399
  pointerEvents={isOpen ? 'auto' : 'none'}
407
400
  style={[StyleSheet.absoluteFill, { backgroundColor: colors.overlay, opacity: scrimOpacity }]}
408
401
  >
@@ -410,7 +403,7 @@ export function Drawer({
410
403
  style={StyleSheet.absoluteFill}
411
404
  onPress={() => dismissible && setOpen(false)}
412
405
  accessibilityRole="button"
413
- accessibilityLabel="Close drawer"
406
+ accessibilityLabel={labels.closeDrawer}
414
407
  />
415
408
  </Animated.View>
416
409
  );
@@ -430,7 +423,7 @@ export function Drawer({
430
423
  <DrawerContext.Provider value={ctx}>
431
424
  <View
432
425
  ref={rootRef}
433
- {...pans.edge.panHandlers}
426
+ {...edgePan}
434
427
  onTouchStart={measure}
435
428
  onLayout={(e: LayoutChangeEvent) => {
436
429
  setBox(e.nativeEvent.layout.width);
@@ -444,13 +437,6 @@ export function Drawer({
444
437
  style={[
445
438
  StyleSheet.absoluteFill,
446
439
  screenTransform && { transform: screenTransform },
447
- push &&
448
- Platform.OS === 'ios' && {
449
- shadowColor: '#000',
450
- shadowOpacity: dark ? 0.5 : 0.16,
451
- shadowRadius: 24,
452
- shadowOffset: { width: dir * 6, height: 8 },
453
- },
454
440
  ]}
455
441
  >
456
442
  <Animated.View
@@ -462,8 +448,8 @@ export function Drawer({
462
448
  borderWidth: StyleSheet.hairlineWidth,
463
449
  borderColor: screenEdge,
464
450
  overflow: 'hidden',
465
- elevation: push ? 16 : 0,
466
451
  },
452
+ push && { boxShadow: `${dir * 6}px 8px 48px rgba(0,0,0,${dark ? 0.5 : 0.16})` },
467
453
  ]}
468
454
  >
469
455
  {children}
@@ -490,13 +476,15 @@ export interface DrawerHeaderProps {
490
476
  /** Shows a close control on the trailing edge (default false). */
491
477
  showClose?: boolean;
492
478
  onClose?: () => void;
479
+ /** Screen-reader name of the close button (default: the kit's `labels.closeMenu`). */
480
+ closeLabel?: string;
493
481
  children?: React.ReactNode;
494
482
  style?: StyleProp<ViewStyle>;
495
483
  }
496
484
 
497
485
  /** Title block with optional leading node and close button — drop into `header`. */
498
- export function DrawerHeader({ title, subtitle, leading, showClose = false, onClose, children, style }: DrawerHeaderProps) {
499
- const { colors, radius, dark, font } = useKit();
486
+ export function DrawerHeader({ title, subtitle, leading, showClose = false, onClose, closeLabel, children, style }: DrawerHeaderProps) {
487
+ const { colors, radius, dark, font, labels } = useKit();
500
488
  const ff = font.family ? { fontFamily: font.family } : null;
501
489
  const drawerCtx = useContext(DrawerContext);
502
490
  const close = onClose ?? (() => drawerCtx?.close());
@@ -521,7 +509,7 @@ export function DrawerHeader({ title, subtitle, leading, showClose = false, onCl
521
509
  <Pressable
522
510
  onPress={close}
523
511
  accessibilityRole="button"
524
- accessibilityLabel="Close menu"
512
+ accessibilityLabel={closeLabel ?? labels.closeMenu}
525
513
  hitSlop={8}
526
514
  style={({ pressed }) => [
527
515
  styles.closeBtn,
@@ -574,9 +562,9 @@ export function DrawerProfile({
574
562
  accessibilityHint,
575
563
  style,
576
564
  }: DrawerProfileProps) {
577
- const { colors, radius, dark, font } = useKit();
565
+ const { colors, radius, dark, font, labels } = useKit();
578
566
  const ff = font.family ? { fontFamily: font.family } : null;
579
- const press = useRef(new Animated.Value(0)).current;
567
+ const [press] = useState(() => new Animated.Value(0));
580
568
  const stacked = layout === 'stacked';
581
569
  const trailing =
582
570
  accessory !== undefined ? (
@@ -613,7 +601,7 @@ export function DrawerProfile({
613
601
  onPressOut={() => spring(0)}
614
602
  accessibilityRole={onPress ? 'button' : undefined}
615
603
  accessibilityLabel={email ? `${name}, ${email}` : name}
616
- accessibilityHint={accessibilityHint ?? (onPress ? 'Switch account' : undefined)}
604
+ accessibilityHint={accessibilityHint ?? (onPress ? labels.switchAccount : undefined)}
617
605
  style={({ pressed }) => [
618
606
  stacked ? styles.profileStacked : styles.profileRow,
619
607
  {
@@ -642,7 +630,7 @@ export function DrawerProfile({
642
630
  );
643
631
  }
644
632
 
645
- function ProfileAvatar({ name, src, size, status }: { name: string; src?: string; size: number; status?: DrawerPresence }) {
633
+ function ProfileAvatar({ name, src, size, status }: { name: string; src?: string | undefined; size: number; status?: DrawerPresence | undefined }) {
646
634
  const { colors, radius, dark, font } = useKit();
647
635
  const ff = font.family ? { fontFamily: font.family } : null;
648
636
  const ring = useContext(SurfaceContext) ?? colors.card;
@@ -827,10 +815,10 @@ export function DrawerItem({
827
815
  const ink = dark ? tint : mix(tint, colors.foreground, 0.22);
828
816
 
829
817
  // Native-driven: pill presence, icon pop, badge pop, press scale — each on its own value.
830
- const on = useRef(new Animated.Value(active ? 1 : 0)).current;
831
- const pop = useRef(new Animated.Value(1)).current;
832
- const badgePop = useRef(new Animated.Value(1)).current;
833
- const press = useRef(new Animated.Value(0)).current;
818
+ const [on] = useState(() => new Animated.Value(active ? 1 : 0));
819
+ const [pop] = useState(() => new Animated.Value(1));
820
+ const [badgePop] = useState(() => new Animated.Value(1));
821
+ const [press] = useState(() => new Animated.Value(0));
834
822
  const first = useRef(true);
835
823
  const firstBadge = useRef(true);
836
824