@expokit/cli 0.4.4 → 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 (239) hide show
  1. package/README.md +30 -3
  2. package/bin/expo-kit.js +749 -288
  3. package/bin/lib.mjs +310 -3
  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/glass-search-bar.json +1 -1
  12. package/registry/_docs/grain.json +6 -1
  13. package/registry/_docs/isometric-bar.json +6 -0
  14. package/registry/_docs/liquidglass-tab.json +1 -1
  15. package/registry/_docs/password-input.json +1 -1
  16. package/registry/_docs/pixel-bar.json +6 -0
  17. package/registry/_docs/polar-area.json +6 -0
  18. package/registry/_docs/qr-code-button.json +9 -3
  19. package/registry/_docs/scratch-card.json +7 -1
  20. package/registry/_docs/search-bar.json +1 -1
  21. package/registry/_docs/select.json +2 -2
  22. package/registry/_docs/slide-to-confirm.json +2 -2
  23. package/registry/_docs/spinner.json +1 -1
  24. package/registry/_docs/stateful-button.json +2 -2
  25. package/registry/_docs/swipe-deck.json +1 -1
  26. package/registry/_docs/time-picker.json +2 -2
  27. package/registry/_docs/timeline-chart.json +6 -0
  28. package/registry/_docs/toast.json +15 -3
  29. package/registry/_docs/treemap.json +6 -0
  30. package/registry/_docs/waterfall.json +6 -0
  31. package/registry/accordion/files/src/components/ui/accordion.tsx +6 -4
  32. package/registry/accordion/files/src/lib/kit.tsx +363 -11
  33. package/registry/accordion.json +1 -0
  34. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +43 -48
  35. package/registry/action-sheet/files/src/lib/kit.tsx +363 -11
  36. package/registry/action-sheet.json +1 -0
  37. package/registry/alert/files/src/components/ui/alert.tsx +19 -12
  38. package/registry/alert/files/src/lib/kit.tsx +363 -11
  39. package/registry/alert.json +3 -4
  40. package/registry/assistant-app.json +1 -0
  41. package/registry/avatar/files/src/components/ui/avatar.tsx +84 -66
  42. package/registry/avatar/files/src/lib/kit.tsx +363 -11
  43. package/registry/avatar.json +1 -0
  44. package/registry/badge/files/src/components/ui/badge.tsx +34 -34
  45. package/registry/badge/files/src/lib/kit.tsx +363 -11
  46. package/registry/badge.json +1 -0
  47. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +28 -27
  48. package/registry/bottom-nav/files/src/lib/kit.tsx +363 -11
  49. package/registry/bottom-nav.json +5 -0
  50. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +353 -289
  51. package/registry/bottom-sheet/files/src/lib/kit.tsx +363 -11
  52. package/registry/bottom-sheet.json +5 -1
  53. package/registry/bullet.json +1 -0
  54. package/registry/button/files/src/components/ui/button.tsx +18 -16
  55. package/registry/button/files/src/lib/kit.tsx +363 -11
  56. package/registry/button-3d/files/src/components/ui/button-3d.tsx +2 -2
  57. package/registry/button-3d/files/src/lib/kit.tsx +363 -11
  58. package/registry/button-3d.json +1 -0
  59. package/registry/button.json +1 -0
  60. package/registry/calendar/files/src/components/ui/calendar.tsx +74 -76
  61. package/registry/calendar/files/src/lib/kit.tsx +363 -11
  62. package/registry/calendar.json +1 -0
  63. package/registry/card/files/src/components/ui/card.tsx +3 -3
  64. package/registry/card/files/src/lib/kit.tsx +363 -11
  65. package/registry/card-stack.json +1 -0
  66. package/registry/card.json +1 -0
  67. package/registry/carousel.json +1 -0
  68. package/registry/chat.json +7 -0
  69. package/registry/checkbox/files/src/components/ui/checkbox.tsx +4 -4
  70. package/registry/checkbox/files/src/lib/kit.tsx +363 -11
  71. package/registry/checkbox.json +1 -0
  72. package/registry/checkout.json +1 -0
  73. package/registry/chip-group/files/src/components/ui/chip-group.tsx +22 -22
  74. package/registry/chip-group/files/src/lib/kit.tsx +363 -11
  75. package/registry/chip-group.json +1 -0
  76. package/registry/confetti/files/src/components/ui/confetti.tsx +15 -14
  77. package/registry/confetti.json +3 -6
  78. package/registry/coupon/files/src/components/ui/coupon.tsx +42 -42
  79. package/registry/coupon/files/src/components/ui/flip-card.tsx +6 -5
  80. package/registry/coupon/files/src/components/ui/tilt-card.tsx +76 -91
  81. package/registry/coupon/files/src/lib/kit.tsx +363 -11
  82. package/registry/coupon/files/src/lib/ticket-shape.tsx +54 -44
  83. package/registry/coupon.json +1 -0
  84. package/registry/cube-pager.json +1 -0
  85. package/registry/dashboard.json +1 -0
  86. package/registry/date-picker/files/src/components/ui/calendar.tsx +74 -76
  87. package/registry/date-picker/files/src/components/ui/date-picker.tsx +55 -56
  88. package/registry/date-picker/files/src/lib/kit.tsx +363 -11
  89. package/registry/date-picker.json +1 -0
  90. package/registry/dialog/files/src/components/ui/dialog.tsx +60 -56
  91. package/registry/dialog/files/src/lib/kit.tsx +363 -11
  92. package/registry/dialog.json +1 -0
  93. package/registry/donut-shop.json +1 -0
  94. package/registry/drawer/files/src/components/ui/drawer.tsx +73 -69
  95. package/registry/drawer/files/src/lib/kit.tsx +363 -11
  96. package/registry/drawer.json +1 -0
  97. package/registry/fab.json +1 -0
  98. package/registry/fintech-app.json +1 -0
  99. package/registry/flip-calendar/files/src/components/ui/flip-calendar.tsx +92 -86
  100. package/registry/flip-calendar/files/src/lib/kit.tsx +363 -11
  101. package/registry/flip-calendar.json +7 -0
  102. package/registry/flip-card/files/src/components/ui/flip-card.tsx +6 -5
  103. package/registry/flip-card/files/src/lib/kit.tsx +363 -11
  104. package/registry/flip-card.json +1 -0
  105. package/registry/floating-input/files/src/components/ui/floating-input.tsx +33 -33
  106. package/registry/floating-input/files/src/lib/kit.tsx +363 -11
  107. package/registry/floating-input.json +1 -0
  108. package/registry/food-app.json +1 -0
  109. package/registry/gift-card.json +1 -0
  110. package/registry/glass-button.json +5 -0
  111. package/registry/glass-nav-bar.json +1 -0
  112. package/registry/glass-search-bar.json +1 -0
  113. package/registry/glass-segmented.json +1 -0
  114. package/registry/glass-sheet.json +1 -0
  115. package/registry/glass-tab-bar.json +5 -0
  116. package/registry/glass.json +1 -0
  117. package/registry/grain/files/src/components/ui/grain.tsx +16 -14
  118. package/registry/grain.json +8 -6
  119. package/registry/inbox.json +1 -0
  120. package/registry/isometric-bar.json +1 -0
  121. package/registry/kit/files/src/lib/kit.tsx +363 -11
  122. package/registry/kit.json +1 -0
  123. package/registry/liquidglass-tab/files/src/app/_layout.tsx +0 -1
  124. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +23 -9
  125. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +2 -2
  126. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +1 -1
  127. package/registry/liquidglass-tab.json +1 -0
  128. package/registry/list-item/files/src/components/ui/list-item.tsx +42 -42
  129. package/registry/list-item/files/src/lib/kit.tsx +363 -11
  130. package/registry/list-item.json +1 -0
  131. package/registry/loyalty-card.json +1 -0
  132. package/registry/morphing-button-group.json +1 -0
  133. package/registry/number-ticker.json +1 -0
  134. package/registry/offers.json +1 -0
  135. package/registry/onboarding.json +1 -0
  136. package/registry/orb-reveal.json +1 -0
  137. package/registry/otp-input/files/src/components/ui/otp-input.tsx +48 -29
  138. package/registry/otp-input/files/src/lib/kit.tsx +363 -11
  139. package/registry/otp-input.json +1 -0
  140. package/registry/parallax-layers.json +1 -0
  141. package/registry/parallax-scroll.json +1 -0
  142. package/registry/password-input/files/src/components/ui/floating-input.tsx +33 -33
  143. package/registry/password-input/files/src/components/ui/password-input.tsx +29 -25
  144. package/registry/password-input/files/src/lib/kit.tsx +363 -11
  145. package/registry/password-input.json +1 -0
  146. package/registry/payment-card/files/src/components/ui/flip-card.tsx +6 -5
  147. package/registry/payment-card/files/src/components/ui/payment-card.tsx +17 -17
  148. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +76 -91
  149. package/registry/payment-card/files/src/lib/kit.tsx +363 -11
  150. package/registry/payment-card.json +1 -0
  151. package/registry/paywall.json +1 -0
  152. package/registry/pixel-bar.json +1 -0
  153. package/registry/polar-area.json +1 -0
  154. package/registry/profile.json +1 -0
  155. package/registry/progress/files/src/components/ui/progress.tsx +29 -29
  156. package/registry/progress/files/src/lib/kit.tsx +363 -11
  157. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +20 -20
  158. package/registry/progress-steps/files/src/lib/kit.tsx +363 -11
  159. package/registry/progress-steps.json +1 -0
  160. package/registry/progress.json +1 -0
  161. package/registry/qr-code-button.json +1 -0
  162. package/registry/radio-group/files/src/components/ui/radio-group.tsx +19 -19
  163. package/registry/radio-group/files/src/lib/kit.tsx +363 -11
  164. package/registry/radio-group.json +1 -0
  165. package/registry/rating.json +1 -0
  166. package/registry/ring-carousel.json +1 -0
  167. package/registry/ruler-time-picker/files/src/components/ui/ruler-time-picker.tsx +98 -82
  168. package/registry/ruler-time-picker/files/src/lib/kit.tsx +363 -11
  169. package/registry/ruler-time-picker.json +7 -0
  170. package/registry/scratch-card.json +1 -0
  171. package/registry/search-bar/files/src/components/ui/search-bar.tsx +25 -21
  172. package/registry/search-bar/files/src/lib/kit.tsx +363 -11
  173. package/registry/search-bar.json +1 -0
  174. package/registry/search-results.json +4 -0
  175. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +8 -7
  176. package/registry/segmented-control/files/src/lib/kit.tsx +363 -11
  177. package/registry/segmented-control.json +1 -0
  178. package/registry/select/files/src/components/ui/select.tsx +93 -88
  179. package/registry/select/files/src/lib/kit.tsx +363 -11
  180. package/registry/select.json +6 -0
  181. package/registry/settings.json +1 -0
  182. package/registry/sign-in.json +1 -0
  183. package/registry/skeleton/files/src/components/ui/skeleton.tsx +149 -80
  184. package/registry/skeleton/files/src/lib/kit.tsx +363 -11
  185. package/registry/skeleton.json +1 -0
  186. package/registry/slide-to-confirm.json +1 -0
  187. package/registry/slider/files/src/components/ui/slider.tsx +103 -96
  188. package/registry/slider/files/src/lib/kit.tsx +363 -11
  189. package/registry/slider.json +5 -0
  190. package/registry/spin-wheel.json +1 -0
  191. package/registry/spinner/files/src/components/ui/spinner.tsx +39 -29
  192. package/registry/spinner/files/src/lib/kit.tsx +363 -11
  193. package/registry/spinner.json +6 -0
  194. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +39 -32
  195. package/registry/stateful-button/files/src/lib/kit.tsx +363 -11
  196. package/registry/stateful-button.json +5 -0
  197. package/registry/stepper/files/src/components/ui/stepper.tsx +38 -36
  198. package/registry/stepper/files/src/lib/kit.tsx +363 -11
  199. package/registry/stepper.json +1 -0
  200. package/registry/swipe-deck.json +1 -0
  201. package/registry/swipeable-row.json +4 -0
  202. package/registry/switch/files/src/components/ui/switch.tsx +39 -42
  203. package/registry/switch/files/src/lib/kit.tsx +363 -11
  204. package/registry/switch.json +1 -0
  205. package/registry/tabs/files/src/components/ui/tabs.tsx +59 -62
  206. package/registry/tabs/files/src/lib/kit.tsx +363 -11
  207. package/registry/tabs.json +1 -0
  208. package/registry/text-animation/files/src/components/ui/text-animation.tsx +55 -42
  209. package/registry/text-animation/files/src/lib/kit.tsx +363 -11
  210. package/registry/text-animation.json +7 -0
  211. package/registry/textarea/files/src/components/ui/textarea.tsx +10 -10
  212. package/registry/textarea/files/src/lib/kit.tsx +363 -11
  213. package/registry/textarea.json +1 -0
  214. package/registry/ticket-card.json +1 -0
  215. package/registry/ticket-shape/files/src/lib/kit.tsx +363 -11
  216. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +54 -44
  217. package/registry/ticket-shape.json +1 -0
  218. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +76 -91
  219. package/registry/tilt-card/files/src/lib/kit.tsx +363 -11
  220. package/registry/tilt-card.json +1 -0
  221. package/registry/time-picker/files/src/components/ui/time-picker.tsx +122 -119
  222. package/registry/time-picker/files/src/lib/kit.tsx +363 -11
  223. package/registry/time-picker.json +1 -0
  224. package/registry/timeline/files/src/components/ui/avatar.tsx +84 -66
  225. package/registry/timeline/files/src/components/ui/timeline.tsx +44 -41
  226. package/registry/timeline/files/src/lib/kit.tsx +363 -11
  227. package/registry/timeline-chart.json +1 -0
  228. package/registry/timeline.json +1 -0
  229. package/registry/toast/files/src/components/ui/toast.tsx +541 -463
  230. package/registry/toast/files/src/lib/kit.tsx +363 -11
  231. package/registry/toast.json +7 -5
  232. package/registry/treemap.json +1 -0
  233. package/registry/waterfall.json +1 -0
  234. package/registry/wheel-list.json +1 -0
  235. package/schema/components.schema.json +1 -0
  236. package/registry/alert/files/src/components/ui/button.tsx +0 -290
  237. package/registry/confetti/files/src/lib/kit.tsx +0 -320
  238. package/registry/grain/files/src/lib/kit.tsx +0 -320
  239. package/registry/toast/files/src/components/ui/button.tsx +0 -290
@@ -4,7 +4,6 @@ import {
4
4
  AccessibilityInfo,
5
5
  Animated,
6
6
  Easing,
7
- PanResponder,
8
7
  Platform,
9
8
  StyleSheet,
10
9
  Text,
@@ -14,7 +13,7 @@ import {
14
13
  type ViewStyle,
15
14
  } from 'react-native';
16
15
 
17
- import { motion, readableOn, shadowFor, useKit } from '@/lib/kit';
16
+ import { motion, readableOn, shadowFor, useKit, usePanResponder, type Labels } from '@/lib/kit';
18
17
 
19
18
  export interface FlipCalendarProps {
20
19
  /** Controlled date (its time of day is kept on every change). */
@@ -171,28 +170,30 @@ export function FlipCalendar({
171
170
  accessibilityLabel,
172
171
  style,
173
172
  }: FlipCalendarProps) {
174
- const { colors, radius, dark } = useKit();
173
+ const { colors, radius, dark, labels } = useKit();
175
174
  const reduce = useReduceMotion();
176
175
  const tint = accent ?? colors.accent;
177
176
 
178
- const start = useRef(value ?? defaultValue ?? new Date()).current;
177
+ const [start] = useState(() => value ?? defaultValue ?? new Date());
179
178
  const clock = useRef({ h: start.getHours(), min: start.getMinutes() });
180
179
  const [day, setDay] = useState(() => dayOf(start));
181
180
 
182
181
  // Animated values per page, keyed by its date. A page keeps its angle when its role changes, so a
183
182
  // page turned over (180) is already yesterday's page, and the one under it is already lying flat.
184
- const pages = useRef(new Map<number, PageValues>());
183
+ const [pages] = useState(() => new Map<number, PageValues>());
185
184
  const valuesFor = (t: Day, flipped: boolean) => {
186
185
  const k = utc(t);
187
- let v = pages.current.get(k);
188
- if (!v) pages.current.set(k, (v = { flip: new Animated.Value(flipped ? 180 : 0), nudge: new Animated.Value(0) }));
186
+ let v = pages.get(k);
187
+ if (!v) pages.set(k, (v = { flip: new Animated.Value(flipped ? 180 : 0), nudge: new Animated.Value(0) }));
189
188
  return v;
190
189
  };
191
190
 
192
191
  const minKey = minDate ? utc(dayOf(minDate)) : -Infinity;
193
192
  const maxKey = maxDate ? utc(dayOf(maxDate)) : Infinity;
194
193
  const live = useRef({ day, onChange, haptics, reduce, minKey, maxKey });
195
- live.current = { day, onChange, haptics, reduce, minKey, maxKey };
194
+ useLayoutEffect(() => {
195
+ live.current = { day, onChange, haptics, reduce, minKey, maxKey };
196
+ });
196
197
  const canNext = () => utc(addDays(live.current.day, 1)) <= live.current.maxKey;
197
198
  const canPrev = () => utc(addDays(live.current.day, -1)) >= live.current.minKey;
198
199
 
@@ -239,14 +240,16 @@ export function FlipCalendar({
239
240
  });
240
241
  }
241
242
  const actions = useRef({ flipOver, flipBack });
242
- actions.current = { flipOver, flipBack };
243
+ useLayoutEffect(() => {
244
+ actions.current = { flipOver, flipBack };
245
+ });
243
246
 
244
247
  // After a page change: forget pages that left the pad, then keep turning if a fling asked.
245
248
  useLayoutEffect(() => {
246
249
  if (!pending.current) return;
247
250
  pending.current = false;
248
251
  const keep = new Set([-1, 0, 1].map((n) => utc(addDays(day, n))));
249
- for (const k of pages.current.keys()) if (!keep.has(k)) pages.current.delete(k);
252
+ for (const k of pages.keys()) if (!keep.has(k)) pages.delete(k);
250
253
  busy.current = false;
251
254
  if (chain.current !== 0) {
252
255
  const forward = chain.current > 0;
@@ -266,7 +269,7 @@ export function FlipCalendar({
266
269
  clock.current = { h: value.getHours(), min: value.getMinutes() };
267
270
  chain.current = 0;
268
271
  busy.current = false;
269
- pages.current.clear();
272
+ pages.clear();
270
273
  setDay(dayOf(value));
271
274
  // eslint-disable-next-line react-hooks/exhaustive-deps
272
275
  }, [external]);
@@ -287,76 +290,77 @@ export function FlipCalendar({
287
290
  r: 1,
288
291
  over: false,
289
292
  });
290
- const pan = useMemo(() => {
291
- const g0 = grip.current;
292
- const spring = (v: Animated.Value, to: number) => Animated.spring(v, { toValue: to, useNativeDriver: true, ...motion.snappy }).start();
293
- return PanResponder.create({
294
- onMoveShouldSetPanResponder: (_, g) => !busy.current && Math.abs(g.dy) > 6 && Math.abs(g.dy) > Math.abs(g.dx),
295
- onPanResponderTerminationRequest: () => false,
296
- onPanResponderGrant: (e, g) => {
297
- chain.current = 0;
298
- g0.started = false;
299
- g0.page = null;
300
- // Where the touch came down, in the pad.
301
- g0.y0 = e.nativeEvent.locationY - g.dy;
302
- },
303
- onPanResponderMove: (_, g) => {
304
- if (!g0.started) {
305
- if (Math.abs(g.dy) < 3) return;
306
- g0.started = true;
307
- // Lifting turns today's page; pulling down brings yesterday's back (which starts over the top).
308
- g0.back = g.dy > 0;
309
- g0.over = g0.back;
310
- g0.page = g0.back ? (canPrev() ? valuesFor(addDays(live.current.day, -1), true) : null) : valuesFor(live.current.day, false);
311
- // The point of the paper under the finger: where you grabbed today's page (at least halfway
312
- // down, so a grab near the rings still turns it), or near the free end of yesterday's.
313
- g0.r = g0.back ? H * 0.9 : clamp(g0.y0 - PAGE_TOP, H * 0.55, H);
314
- g0.y = curlPoint(g0.r, g0.back ? 1 : 0, H, curlRadius(H)).y;
315
- }
316
- const top = valuesFor(live.current.day, false);
317
- if (!g0.page) {
318
- // Nothing earlier to bring back: today's page only gives a little.
319
- top.nudge.setValue(Math.max(0, g.dy) * 0.18);
320
- return;
321
- }
322
- const angle = progressUnderFinger(g0.r, g0.y + g.dy, H, curlRadius(H)) * 180;
323
- // At the last day the page only lifts a little.
324
- g0.page.flip.setValue(!g0.back && !canNext() ? Math.min(angle * 0.25, 24) : angle);
325
- const over = angle > 90;
326
- if (over !== g0.over) {
327
- g0.over = over;
328
- if (live.current.haptics) Haptics.selectionAsync().catch(() => {});
329
- }
330
- },
331
- onPanResponderRelease: (_, g) => {
332
- const top = valuesFor(live.current.day, false);
333
- spring(top.nudge, 0);
334
- const page = g0.page;
335
- if (!page) return;
336
- if (g0.back) {
337
- // Past upright, or flicked down: it lands; a hard flick brings back a few more.
338
- if (!g0.over || g.vy > 0.5) {
339
- chain.current = -clamp(Math.floor((g.vy - 1.2) * 3), 0, MAX_CHAIN);
340
- actions.current.flipBack();
341
- } else spring(page.flip, 180);
342
- } else if (canNext() && (g0.over || g.vy < -0.5)) {
343
- chain.current = clamp(Math.floor((-g.vy - 1.2) * 3), 0, MAX_CHAIN);
344
- actions.current.flipOver();
345
- } else spring(page.flip, 0);
346
- },
347
- onPanResponderTerminate: () => {
348
- spring(valuesFor(live.current.day, false).nudge, 0);
349
- if (g0.page) spring(g0.page.flip, g0.back ? 180 : 0);
350
- },
351
- });
352
- }, [H]);
293
+ const spring = (v: Animated.Value, to: number) => Animated.spring(v, { toValue: to, useNativeDriver: true, ...motion.snappy }).start();
294
+ const pan = usePanResponder({
295
+ onMoveShouldSetPanResponder: (_, g) => !busy.current && Math.abs(g.dy) > 6 && Math.abs(g.dy) > Math.abs(g.dx),
296
+ onPanResponderTerminationRequest: () => false,
297
+ onPanResponderGrant: (e, g) => {
298
+ const g0 = grip.current;
299
+ chain.current = 0;
300
+ g0.started = false;
301
+ g0.page = null;
302
+ // Where the touch came down, in the pad.
303
+ g0.y0 = e.nativeEvent.locationY - g.dy;
304
+ },
305
+ onPanResponderMove: (_, g) => {
306
+ const g0 = grip.current;
307
+ if (!g0.started) {
308
+ if (Math.abs(g.dy) < 3) return;
309
+ g0.started = true;
310
+ // Lifting turns today's page; pulling down brings yesterday's back (which starts over the top).
311
+ g0.back = g.dy > 0;
312
+ g0.over = g0.back;
313
+ g0.page = g0.back ? (canPrev() ? valuesFor(addDays(live.current.day, -1), true) : null) : valuesFor(live.current.day, false);
314
+ // The point of the paper under the finger: where you grabbed today's page (at least halfway
315
+ // down, so a grab near the rings still turns it), or near the free end of yesterday's.
316
+ g0.r = g0.back ? H * 0.9 : clamp(g0.y0 - PAGE_TOP, H * 0.55, H);
317
+ g0.y = curlPoint(g0.r, g0.back ? 1 : 0, H, curlRadius(H)).y;
318
+ }
319
+ const top = valuesFor(live.current.day, false);
320
+ if (!g0.page) {
321
+ // Nothing earlier to bring back: today's page only gives a little.
322
+ top.nudge.setValue(Math.max(0, g.dy) * 0.18);
323
+ return;
324
+ }
325
+ const angle = progressUnderFinger(g0.r, g0.y + g.dy, H, curlRadius(H)) * 180;
326
+ // At the last day the page only lifts a little.
327
+ g0.page.flip.setValue(!g0.back && !canNext() ? Math.min(angle * 0.25, 24) : angle);
328
+ const over = angle > 90;
329
+ if (over !== g0.over) {
330
+ g0.over = over;
331
+ if (live.current.haptics) Haptics.selectionAsync().catch(() => {});
332
+ }
333
+ },
334
+ onPanResponderRelease: (_, g) => {
335
+ const top = valuesFor(live.current.day, false);
336
+ spring(top.nudge, 0);
337
+ const g0 = grip.current;
338
+ const page = g0.page;
339
+ if (!page) return;
340
+ if (g0.back) {
341
+ // Past upright, or flicked down: it lands; a hard flick brings back a few more.
342
+ if (!g0.over || g.vy > 0.5) {
343
+ chain.current = -clamp(Math.floor((g.vy - 1.2) * 3), 0, MAX_CHAIN);
344
+ actions.current.flipBack();
345
+ } else spring(page.flip, 180);
346
+ } else if (canNext() && (g0.over || g.vy < -0.5)) {
347
+ chain.current = clamp(Math.floor((-g.vy - 1.2) * 3), 0, MAX_CHAIN);
348
+ actions.current.flipOver();
349
+ } else spring(page.flip, 0);
350
+ },
351
+ onPanResponderTerminate: () => {
352
+ spring(valuesFor(live.current.day, false).nudge, 0);
353
+ const g0 = grip.current;
354
+ if (g0.page) spring(g0.page.flip, g0.back ? 180 : 0);
355
+ },
356
+ });
353
357
 
354
358
  const names = namesFor(locale);
355
359
  const spoken = (t: Day) => `${names.days[new Date(utc(t)).getUTCDay()]}, ${t.d} ${names.months[t.m]} ${t.y}`;
356
360
  const prevDay = addDays(day, -1);
357
361
  const nextDay = addDays(day, 1);
358
362
  const corner = radius.scale(14);
359
- const pageProps = { W, H, tint, names, colors, corner };
363
+ const pageProps = { W, H, tint, names, labels, colors, corner };
360
364
  const top = valuesFor(day, false);
361
365
 
362
366
  return (
@@ -364,15 +368,15 @@ export function FlipCalendar({
364
368
  onLayout={(e: LayoutChangeEvent) => setWidth(Math.round(e.nativeEvent.layout.width))}
365
369
  accessible
366
370
  accessibilityRole="adjustable"
367
- accessibilityLabel={accessibilityLabel ?? 'Date'}
371
+ accessibilityLabel={accessibilityLabel ?? labels.date}
368
372
  accessibilityValue={{ text: spoken(day) }}
369
- accessibilityHint="Swipe up to turn to the next day, down for the previous one."
373
+ accessibilityHint={labels.flipCalendarHint}
370
374
  accessibilityActions={[{ name: 'increment' }, { name: 'decrement' }]}
371
375
  onAccessibilityAction={(e) => (e.nativeEvent.actionName === 'increment' ? flipOver() : flipBack())}
372
376
  style={[styles.root, style]}
373
377
  >
374
378
  {W > 0 ? (
375
- <View style={[styles.pad, styles.grab, { width: W, height: H + PAGE_TOP + 14 }]} {...pan.panHandlers}>
379
+ <View style={[styles.pad, styles.grab, { width: W, height: H + PAGE_TOP + 14 }]} {...pan}>
376
380
  {/* The board the pages hang from, and page edges stacked under them for thickness. */}
377
381
  <View style={[styles.board, { backgroundColor: colors.foreground, borderRadius: radius.scale(8) }]} />
378
382
  {[2, 1].map((n) => (
@@ -391,7 +395,7 @@ export function FlipCalendar({
391
395
  <Animated.View style={{ transform: [{ translateY: top.nudge }] }}>
392
396
  <Page key={utc(day)} day={day} values={top} {...pageProps} />
393
397
  </Animated.View>
394
- {canPrev() ? <Page key={utc(prevDay)} day={prevDay} values={valuesFor(prevDay, true)} {...pageProps} /> : null}
398
+ {utc(prevDay) >= minKey ? <Page key={utc(prevDay)} day={prevDay} values={valuesFor(prevDay, true)} {...pageProps} /> : null}
395
399
  </View>
396
400
  <Rings width={W} color={colors.mutedForeground} hole={colors.foreground} />
397
401
  </View>
@@ -411,6 +415,7 @@ interface PageProps {
411
415
  H: number;
412
416
  tint: string;
413
417
  names: Names;
418
+ labels: Labels;
414
419
  colors: ReturnType<typeof useKit>['colors'];
415
420
  corner: number;
416
421
  }
@@ -458,7 +463,7 @@ function stripTracks(H: number): StripTrack[] {
458
463
  * near the fold roll toward you and darken or catch the light as they turn, and past the fold the
459
464
  * plain back comes up over the top.
460
465
  */
461
- const Page = React.memo(function Page({ day, values, under, shadowFrom, W, H, tint, names, colors, corner }: PageProps) {
466
+ const Page = React.memo(function Page({ day, values, under, shadowFrom, W, H, tint, names, labels, colors, corner }: PageProps) {
462
467
  const { flip } = values;
463
468
  const h = H / STRIPS;
464
469
  const strips = useMemo(() => {
@@ -487,7 +492,7 @@ const Page = React.memo(function Page({ day, values, under, shadowFrom, W, H, ti
487
492
  );
488
493
 
489
494
  const sheet = { width: W, height: H, backgroundColor: colors.card, borderColor: colors.border, borderRadius: corner };
490
- const face = <Face day={day} W={W} tint={tint} names={names} colors={colors} sheet={sheet} />;
495
+ const face = <Face day={day} W={W} tint={tint} names={names} labels={labels} colors={colors} sheet={sheet} />;
491
496
 
492
497
  if (under || !strips) {
493
498
  return (
@@ -537,12 +542,13 @@ interface FaceProps {
537
542
  W: number;
538
543
  tint: string;
539
544
  names: Names;
545
+ labels: Labels;
540
546
  colors: ReturnType<typeof useKit>['colors'];
541
547
  sheet: ViewStyle;
542
548
  }
543
549
 
544
550
  /** The printed side of a page: perforation, month band, the day, weekday and a footer. */
545
- const Face = React.memo(function Face({ day, W, tint, names, colors, sheet }: FaceProps) {
551
+ const Face = React.memo(function Face({ day, W, tint, names, labels, colors, sheet }: FaceProps) {
546
552
  const wd = new Date(utc(day)).getUTCDay();
547
553
  const ink = wd === 0 ? tint : colors.foreground;
548
554
  const band = readableOn(tint);
@@ -551,18 +557,18 @@ const Face = React.memo(function Face({ day, W, tint, names, colors, sheet }: Fa
551
557
  <View style={[styles.face, sheet]}>
552
558
  <View style={[styles.perforation, { borderBottomColor: colors.border }]} />
553
559
  <View style={[styles.band, { backgroundColor: tint }]}>
554
- <Text numberOfLines={1} style={[styles.month, { color: band }]}>{names.months[day.m].toUpperCase()}</Text>
560
+ <Text numberOfLines={1} style={[styles.month, { color: band }]}>{(names.months[day.m] ?? '').toUpperCase()}</Text>
555
561
  <Text style={[styles.year, { color: band }]}>{day.y}</Text>
556
562
  </View>
557
563
  <View style={styles.body}>
558
564
  <Text numberOfLines={1} adjustsFontSizeToFit style={[styles.day, { color: ink, fontSize: W * 0.5, lineHeight: W * 0.56 }]}>
559
565
  {day.d}
560
566
  </Text>
561
- <Text numberOfLines={1} style={[styles.weekday, { color: ink }]}>{names.days[wd].toUpperCase()}</Text>
567
+ <Text numberOfLines={1} style={[styles.weekday, { color: ink }]}>{(names.days[wd] ?? '').toUpperCase()}</Text>
562
568
  </View>
563
569
  <View style={[styles.footer, { borderTopColor: colors.border }]}>
564
- <Text style={[styles.meta, { color: colors.mutedForeground }]}>Day {dayOfYear(day)} · Week {isoWeek(day)}</Text>
565
- <Text style={[styles.meta, { color: colors.mutedForeground }]}>{left} left</Text>
570
+ <Text style={[styles.meta, { color: colors.mutedForeground }]}>{labels.dayAndWeek(dayOfYear(day), isoWeek(day))}</Text>
571
+ <Text style={[styles.meta, { color: colors.mutedForeground }]}>{labels.daysLeft(left)}</Text>
566
572
  </View>
567
573
  </View>
568
574
  );