@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
@@ -3,7 +3,6 @@ import {
3
3
  AccessibilityInfo,
4
4
  Animated,
5
5
  Easing,
6
- PanResponder,
7
6
  Pressable,
8
7
  StyleSheet,
9
8
  Text,
@@ -14,7 +13,7 @@ import {
14
13
  } from 'react-native';
15
14
  import Svg, { Path } from 'react-native-svg';
16
15
 
17
- import { alpha, motion, readableOn, useKit } from '@/lib/kit';
16
+ import { alpha, motion, readableOn, useKit, usePanResponder } from '@/lib/kit';
18
17
 
19
18
  export type CalendarMode = 'single' | 'range' | 'multiple';
20
19
 
@@ -30,65 +29,65 @@ export type CalendarMarks = Record<string, string | string[]>;
30
29
 
31
30
  export interface CalendarBaseProps {
32
31
  /** Controlled visible month (any date inside it). */
33
- month?: Date;
32
+ month?: Date | undefined;
34
33
  /** Month shown first (default: the selected date, else today). */
35
- defaultMonth?: Date;
36
- onMonthChange?: (month: Date) => void;
34
+ defaultMonth?: Date | undefined;
35
+ onMonthChange?: ((month: Date) => void) | undefined;
37
36
  /** 0 = Sunday, 1 = Monday. */
38
- weekStartsOn?: 0 | 1;
37
+ weekStartsOn?: 0 | 1 | undefined;
39
38
  /** BCP 47 locale for month and weekday names (default: device locale). */
40
- locale?: string;
39
+ locale?: string | undefined;
41
40
  /** Earliest selectable day. */
42
- minDate?: Date;
41
+ minDate?: Date | undefined;
43
42
  /** Latest selectable day. */
44
- maxDate?: Date;
43
+ maxDate?: Date | undefined;
45
44
  /** Return true to disable a day (booked, holidays…). */
46
- isDateDisabled?: (date: Date) => boolean;
45
+ isDateDisabled?: ((date: Date) => boolean) | undefined;
47
46
  /** Coloured dots under dates: `{ [toDateKey(date)]: '#22c55e' | ['#22c55e', '#f59e0b'] }`. */
48
- markedDates?: CalendarMarks;
47
+ markedDates?: CalendarMarks | undefined;
49
48
  /** Fill whole days with a colour (birthdays, shifts, people…): `{ [toDateKey(date)]: '#f472b6' }`. */
50
- highlightedDates?: Record<string, string>;
49
+ highlightedDates?: Record<string, string> | undefined;
51
50
  /** `tiles` puts every day on a soft tile; `minimal` shows bare numbers. */
52
- variant?: 'tiles' | 'minimal';
51
+ variant?: 'tiles' | 'minimal' | undefined;
53
52
  /** Weekday header: `narrow` (M T W) or `short` (Mon Tue Wed). */
54
- weekdayFormat?: 'narrow' | 'short';
53
+ weekdayFormat?: 'narrow' | 'short' | undefined;
55
54
  /** Show a "Today" pill in the header while another month is on screen. */
56
- showTodayButton?: boolean;
55
+ showTodayButton?: boolean | undefined;
57
56
  /** Show the faded trailing / leading days of the neighbouring months. */
58
- showOutsideDays?: boolean;
57
+ showOutsideDays?: boolean | undefined;
59
58
  /** Ring and tint today's date. */
60
- showToday?: boolean;
59
+ showToday?: boolean | undefined;
61
60
  /** Always render six week rows so the height never changes between months. */
62
- fixedWeeks?: boolean;
61
+ fixedWeeks?: boolean | undefined;
63
62
  /** Tap the month title to jump through a month / year grid. */
64
- monthPicker?: boolean;
63
+ monthPicker?: boolean | undefined;
65
64
  /** Most dates selectable in `multiple` mode. */
66
- max?: number;
65
+ max?: number | undefined;
67
66
  /** Selection and today colour (default: theme accent). */
68
- accent?: string;
69
- style?: StyleProp<ViewStyle>;
67
+ accent?: string | undefined;
68
+ style?: StyleProp<ViewStyle> | undefined;
70
69
  }
71
70
 
72
71
  export interface CalendarSingleProps {
73
- mode?: 'single';
74
- value?: Date | null;
75
- defaultValue?: Date | null;
76
- onChange?: (date: Date) => void;
72
+ mode?: 'single' | undefined;
73
+ value?: Date | null | undefined;
74
+ defaultValue?: Date | null | undefined;
75
+ onChange?: ((date: Date) => void) | undefined;
77
76
  }
78
77
 
79
78
  export interface CalendarRangeProps {
80
79
  mode: 'range';
81
- value?: DateRange | null;
82
- defaultValue?: DateRange | null;
80
+ value?: DateRange | null | undefined;
81
+ defaultValue?: DateRange | null | undefined;
83
82
  /** Fires on every tap: first with `{ start, end: null }`, then with both ends. */
84
- onChange?: (range: DateRange) => void;
83
+ onChange?: ((range: DateRange) => void) | undefined;
85
84
  }
86
85
 
87
86
  export interface CalendarMultipleProps {
88
87
  mode: 'multiple';
89
- value?: Date[];
90
- defaultValue?: Date[];
91
- onChange?: (dates: Date[]) => void;
88
+ value?: Date[] | undefined;
89
+ defaultValue?: Date[] | undefined;
90
+ onChange?: ((dates: Date[]) => void) | undefined;
92
91
  }
93
92
 
94
93
  export type CalendarProps = CalendarBaseProps & (CalendarSingleProps | CalendarRangeProps | CalendarMultipleProps);
@@ -161,11 +160,11 @@ function useFormatters(locale?: string) {
161
160
  }
162
161
  };
163
162
  return {
164
- title: make({ month: 'long', year: 'numeric' }, (d) => `${EN_MONTHS[d.getMonth()]} ${d.getFullYear()}`),
165
- month: make({ month: 'short' }, (d) => EN_MONTHS[d.getMonth()].slice(0, 3)),
166
- weekday: make({ weekday: 'short' }, (d) => EN_DAYS[d.getDay()]),
167
- weekdayNarrow: make({ weekday: 'narrow' }, (d) => EN_DAYS[d.getDay()][0]),
168
- weekdayLong: make({ weekday: 'long' }, (d) => EN_DAYS[d.getDay()]),
163
+ title: make({ month: 'long', year: 'numeric' }, (d) => `${EN_MONTHS[d.getMonth()] ?? ''} ${d.getFullYear()}`),
164
+ month: make({ month: 'short' }, (d) => (EN_MONTHS[d.getMonth()] ?? '').slice(0, 3)),
165
+ weekday: make({ weekday: 'short' }, (d) => EN_DAYS[d.getDay()] ?? ''),
166
+ weekdayNarrow: make({ weekday: 'narrow' }, (d) => (EN_DAYS[d.getDay()] ?? '').slice(0, 1)),
167
+ weekdayLong: make({ weekday: 'long' }, (d) => EN_DAYS[d.getDay()] ?? ''),
169
168
  full: make({ weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }, (d) => d.toDateString()),
170
169
  };
171
170
  }, [locale]);
@@ -206,7 +205,7 @@ export function Calendar(props: CalendarProps) {
206
205
  accent,
207
206
  style,
208
207
  } = props as unknown as FlatProps;
209
- const { colors, radius, font } = useKit();
208
+ const { colors, radius, font, labels } = useKit();
210
209
  const ff = font.family ? { fontFamily: font.family } : null;
211
210
  const tint = accent ?? colors.accent;
212
211
  const fmt = useFormatters(locale);
@@ -244,9 +243,10 @@ export function Calendar(props: CalendarProps) {
244
243
 
245
244
  /* month slide transition */
246
245
  const [width, setWidth] = useState(0);
247
- const slide = useRef(new Animated.Value(1)).current; // native
246
+ const [slide] = useState(() => new Animated.Value(1)); // native
248
247
  const [leaving, setLeaving] = useState<{ month: Date; dir: number } | null>(null);
249
- const dirRef = useRef(1);
248
+ // Which way the last month change went: the incoming grid slides in from that side.
249
+ const [slideDir, setSlideDir] = useState(1);
250
250
  const shownRef = useRef(visible);
251
251
  const visibleKey = monthIndex(visible);
252
252
  useLayoutEffect(() => {
@@ -254,7 +254,7 @@ export function Calendar(props: CalendarProps) {
254
254
  if (monthIndex(prev) === visibleKey) return;
255
255
  shownRef.current = visible;
256
256
  const dir = visibleKey > monthIndex(prev) ? 1 : -1;
257
- dirRef.current = dir;
257
+ setSlideDir(dir);
258
258
  setLeaving({ month: prev, dir });
259
259
  slide.stopAnimation();
260
260
  slide.setValue(0);
@@ -271,7 +271,7 @@ export function Calendar(props: CalendarProps) {
271
271
  /* days / months / years view */
272
272
  const [view, setView] = useState<'days' | 'months' | 'years'>('days');
273
273
  const [pickYear, setPickYear] = useState(visible.getFullYear());
274
- const viewAnim = useRef(new Animated.Value(1)).current; // native
274
+ const [viewAnim] = useState(() => new Animated.Value(1)); // native
275
275
  const [gridH, setGridH] = useState(0);
276
276
  const switchView = (next: 'days' | 'months' | 'years') => {
277
277
  if (next !== 'days' && view === 'days') setPickYear(visible.getFullYear());
@@ -283,7 +283,9 @@ export function Calendar(props: CalendarProps) {
283
283
 
284
284
  /* selection */
285
285
  const stateRef = useRef({ mode, current, value, onChange, max, visible, goTo });
286
- stateRef.current = { mode, current, value, onChange, max, visible, goTo };
286
+ useLayoutEffect(() => {
287
+ stateRef.current = { mode, current, value, onChange, max, visible, goTo };
288
+ });
287
289
  const onDayPress = useCallback((d: Date) => {
288
290
  const s = stateRef.current;
289
291
  let next: CalendarValue;
@@ -305,18 +307,14 @@ export function Calendar(props: CalendarProps) {
305
307
  if (monthIndex(d) !== monthIndex(s.visible)) s.goTo(d);
306
308
  }, []);
307
309
 
308
- const pan = useMemo(
309
- () =>
310
- PanResponder.create({
311
- onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 14 && Math.abs(g.dx) > Math.abs(g.dy) * 1.4,
312
- onPanResponderRelease: (_, g) => {
313
- const s = stateRef.current;
314
- if (g.dx < -40 || g.vx < -0.5) s.goTo(addMonths(s.visible, 1));
315
- else if (g.dx > 40 || g.vx > 0.5) s.goTo(addMonths(s.visible, -1));
316
- },
317
- }),
318
- []
319
- );
310
+ const pan = usePanResponder({
311
+ onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 14 && Math.abs(g.dx) > Math.abs(g.dy) * 1.4,
312
+ onPanResponderRelease: (_, g) => {
313
+ const s = stateRef.current;
314
+ if (g.dx < -40 || g.vx < -0.5) s.goTo(addMonths(s.visible, 1));
315
+ else if (g.dx > 40 || g.vx > 0.5) s.goTo(addMonths(s.visible, -1));
316
+ },
317
+ });
320
318
 
321
319
  const weekdays = useMemo(() => {
322
320
  const base = new Date(2023, 0, 1); // a Sunday
@@ -349,7 +347,7 @@ export function Calendar(props: CalendarProps) {
349
347
 
350
348
  const W = width || 320;
351
349
  // Reduce Motion: months swap with a fade only, no sideways slide.
352
- const dir = reduce ? 0 : dirRef.current;
350
+ const dir = reduce ? 0 : slideDir;
353
351
  const prevDisabled = view === 'days' ? monthIndex(visible) - 1 < minM : view === 'months' ? pickYear * 12 - 1 < minM : false;
354
352
  const nextDisabled = view === 'days' ? monthIndex(visible) + 1 > maxM : view === 'months' ? (pickYear + 1) * 12 > maxM : false;
355
353
  const decadeStart = Math.floor(pickYear / 12) * 12;
@@ -374,7 +372,7 @@ export function Calendar(props: CalendarProps) {
374
372
  onPress={monthPicker ? () => switchView(view === 'days' ? 'months' : view === 'months' ? 'years' : 'days') : undefined}
375
373
  disabled={!monthPicker}
376
374
  accessibilityRole={monthPicker ? 'button' : 'header'}
377
- accessibilityLabel={monthPicker ? `${title}, ${view === 'days' ? 'choose month' : view === 'months' ? 'choose year' : 'back to days'}` : title}
375
+ accessibilityLabel={monthPicker ? `${title}, ${view === 'days' ? labels.chooseMonth : view === 'months' ? labels.chooseYear : labels.backToDays}` : title}
378
376
  hitSlop={6}
379
377
  style={({ pressed }) => [styles.titleBtn, { borderRadius: radius.sm, backgroundColor: pressed ? colors.muted : 'transparent' }]}
380
378
  >
@@ -401,12 +399,12 @@ export function Calendar(props: CalendarProps) {
401
399
  <Pressable
402
400
  onPress={() => goTo(today)}
403
401
  accessibilityRole="button"
404
- accessibilityLabel="Go to today"
402
+ accessibilityLabel={labels.goToToday}
405
403
  hitSlop={4}
406
404
  style={({ pressed }) => [styles.todayBtn, { borderRadius: radius.capsule(32), backgroundColor: pressed ? colors.secondary : colors.muted }]}
407
405
  >
408
406
  <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.todayText, ff, { color: tint }]}>
409
- Today
407
+ {labels.today}
410
408
  </Text>
411
409
  </Pressable>
412
410
  ) : null}
@@ -429,7 +427,7 @@ export function Calendar(props: CalendarProps) {
429
427
  </Text>
430
428
  ))}
431
429
  </View>
432
- <View {...pan.panHandlers} style={styles.gridClip} onLayout={(e) => setGridH(e.nativeEvent.layout.height)}>
430
+ <View {...pan} style={styles.gridClip} onLayout={(e) => setGridH(e.nativeEvent.layout.height)}>
433
431
  {leaving ? (
434
432
  <Animated.View
435
433
  pointerEvents="none"
@@ -525,16 +523,16 @@ interface MonthGridProps {
525
523
  fixedWeeks: boolean;
526
524
  showOutsideDays: boolean;
527
525
  showToday: boolean;
528
- minDate?: Date;
529
- maxDate?: Date;
530
- isDateDisabled?: (date: Date) => boolean;
531
- markedDates?: CalendarMarks;
532
- highlightedDates?: Record<string, string>;
526
+ minDate?: Date | undefined;
527
+ maxDate?: Date | undefined;
528
+ isDateDisabled?: ((date: Date) => boolean) | undefined;
529
+ markedDates?: CalendarMarks | undefined;
530
+ highlightedDates?: Record<string, string> | undefined;
533
531
  tiles: boolean;
534
- singleKey?: string;
535
- rangeStart?: number;
536
- rangeEnd?: number;
537
- manyKeys?: string;
532
+ singleKey?: string | undefined;
533
+ rangeStart?: number | undefined;
534
+ rangeEnd?: number | undefined;
535
+ manyKeys?: string | undefined;
538
536
  tint: string;
539
537
  fullLabel: (d: Date) => string;
540
538
  reduce: boolean;
@@ -636,8 +634,8 @@ interface DayCellProps {
636
634
  band: 'none' | 'start' | 'mid' | 'end';
637
635
  roundLeft: boolean;
638
636
  roundRight: boolean;
639
- marks?: string | string[];
640
- highlight?: string;
637
+ marks?: string | string[] | undefined;
638
+ highlight?: string | undefined;
641
639
  tile: boolean;
642
640
  tint: string;
643
641
  reduce: boolean;
@@ -665,8 +663,8 @@ const DayCell = memo(function DayCell({
665
663
  const { colors, radius, font } = useKit();
666
664
  const ff = font.family ? { fontFamily: font.family } : null;
667
665
  const r = tileRadius(radius.md);
668
- const pop = useRef(new Animated.Value(selected ? 1 : 0)).current; // native: circle scale + opacity
669
- const bandIn = useRef(new Animated.Value(band === 'none' ? 0 : 1)).current; // native: band opacity
666
+ const [pop] = useState(() => new Animated.Value(selected ? 1 : 0)); // native: circle scale + opacity
667
+ const [bandIn] = useState(() => new Animated.Value(band === 'none' ? 0 : 1)); // native: band opacity
670
668
  useEffect(() => {
671
669
  // Reduce Motion: the selection fills in with a quick fade, no scale pop.
672
670
  if (reduce) Animated.timing(pop, { toValue: selected ? 1 : 0, duration: 150, useNativeDriver: true }).start();
@@ -773,14 +771,14 @@ const DayCell = memo(function DayCell({
773
771
  /* ───────────────────────────────────── bits ───────────────────────────────────── */
774
772
 
775
773
  function NavButton({ dir, disabled, onPress }: { dir: 1 | -1; disabled: boolean; onPress: () => void }) {
776
- const { colors } = useKit();
774
+ const { colors, labels } = useKit();
777
775
  return (
778
776
  <Pressable
779
777
  onPress={onPress}
780
778
  disabled={disabled}
781
779
  hitSlop={4}
782
780
  accessibilityRole="button"
783
- accessibilityLabel={dir < 0 ? 'Previous' : 'Next'}
781
+ accessibilityLabel={dir < 0 ? labels.previous : labels.next}
784
782
  accessibilityState={{ disabled }}
785
783
  style={({ pressed }) => [styles.nav, { backgroundColor: pressed ? colors.secondary : colors.muted, opacity: disabled ? 0.3 : 1 }]}
786
784
  >
@@ -798,7 +796,7 @@ function NavButton({ dir, disabled, onPress }: { dir: 1 | -1; disabled: boolean;
798
796
  }
799
797
 
800
798
  function Chevron({ turned, color, reduce }: { turned: boolean; color: string; reduce: boolean }) {
801
- const turn = useRef(new Animated.Value(turned ? 1 : 0)).current; // native
799
+ const [turn] = useState(() => new Animated.Value(turned ? 1 : 0)); // native
802
800
  useEffect(() => {
803
801
  if (reduce) {
804
802
  turn.setValue(turned ? 1 : 0);
@@ -1,10 +1,9 @@
1
- import React, { useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
1
+ import React, { useEffect, useLayoutEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
2
2
  import {
3
3
  AccessibilityInfo,
4
4
  Animated,
5
5
  Easing,
6
6
  Modal,
7
- PanResponder,
8
7
  Pressable,
9
8
  ScrollView,
10
9
  StyleSheet,
@@ -26,7 +25,7 @@ import {
26
25
  type CalendarProps,
27
26
  type DateRange,
28
27
  } from '@/components/ui/calendar';
29
- import { alpha, motion, readableOn, useKit, useLayout } from '@/lib/kit';
28
+ import { alpha, defaultLabels, motion, readableOn, useKit, useLayout, usePanResponder, type Labels } from '@/lib/kit';
30
29
 
31
30
  export type { DateRange } from '@/components/ui/calendar';
32
31
  export type DatePickerSize = 'sm' | 'md' | 'lg';
@@ -148,40 +147,40 @@ const EMPTY_RANGE: DateRange = { start: null, end: null };
148
147
 
149
148
  const isRange = (v: Picked): v is DateRange => !!v && !(v instanceof Date);
150
149
 
151
- /** Built-in single-date shortcuts, computed relative to now. */
152
- export function singleDatePresets(): DatePickerPreset<Date>[] {
150
+ /** Built-in single-date shortcuts, computed relative to now. Pass `useKit().labels` for translated names. */
151
+ export function singleDatePresets(labels: Labels = defaultLabels): DatePickerPreset<Date>[] {
153
152
  const today = () => startOfDay(new Date());
154
153
  return [
155
- { label: 'Today', value: today },
156
- { label: 'Tomorrow', value: () => addDays(today(), 1) },
157
- { label: 'Next Monday', value: () => addDays(today(), ((8 - today().getDay()) % 7) || 7) },
158
- { label: 'In a week', value: () => addDays(today(), 7) },
159
- { label: 'In a month', value: () => new Date(today().getFullYear(), today().getMonth() + 1, today().getDate()) },
154
+ { label: labels.today, value: today },
155
+ { label: labels.tomorrow, value: () => addDays(today(), 1) },
156
+ { label: labels.nextMonday, value: () => addDays(today(), ((8 - today().getDay()) % 7) || 7) },
157
+ { label: labels.inAWeek, value: () => addDays(today(), 7) },
158
+ { label: labels.inAMonth, value: () => new Date(today().getFullYear(), today().getMonth() + 1, today().getDate()) },
160
159
  ];
161
160
  }
162
161
 
163
- /** Built-in range shortcuts, computed relative to now. */
164
- export function rangeDatePresets(): DatePickerPreset<DateRange>[] {
162
+ /** Built-in range shortcuts, computed relative to now. Pass `useKit().labels` for translated names. */
163
+ export function rangeDatePresets(labels: Labels = defaultLabels): DatePickerPreset<DateRange>[] {
165
164
  const today = () => startOfDay(new Date());
166
165
  return [
167
- { label: 'Today', value: () => ({ start: today(), end: today() }) },
168
- { label: 'Next 7 days', value: () => ({ start: today(), end: addDays(today(), 6) }) },
166
+ { label: labels.today, value: () => ({ start: today(), end: today() }) },
167
+ { label: labels.next7Days, value: () => ({ start: today(), end: addDays(today(), 6) }) },
169
168
  {
170
- label: 'Next weekend',
169
+ label: labels.nextWeekend,
171
170
  value: () => {
172
171
  const sat = addDays(today(), ((6 - today().getDay() + 7) % 7) || 7);
173
172
  return { start: sat, end: addDays(sat, 1) };
174
173
  },
175
174
  },
176
175
  {
177
- label: 'This month',
176
+ label: labels.thisMonth,
178
177
  value: () => {
179
178
  const t = today();
180
179
  return { start: new Date(t.getFullYear(), t.getMonth(), 1), end: new Date(t.getFullYear(), t.getMonth() + 1, 0) };
181
180
  },
182
181
  },
183
- { label: 'Last 7 days', value: () => ({ start: addDays(today(), -6), end: today() }) },
184
- { label: 'Last 30 days', value: () => ({ start: addDays(today(), -29), end: today() }) },
182
+ { label: labels.last7Days, value: () => ({ start: addDays(today(), -6), end: today() }) },
183
+ { label: labels.last30Days, value: () => ({ start: addDays(today(), -29), end: today() }) },
185
184
  ];
186
185
  }
187
186
 
@@ -231,7 +230,7 @@ export function DatePicker(props: DatePickerProps) {
231
230
  iconColor,
232
231
  menuStyle,
233
232
  } = props as unknown as FlatProps;
234
- const { colors, radius, font } = useKit();
233
+ const { colors, radius, font, labels } = useKit();
235
234
  const ff = font.family ? { fontFamily: font.family } : null;
236
235
  const tint = accent ?? colors.accent;
237
236
  const bg = surface ?? colors.background;
@@ -249,7 +248,9 @@ export function DatePicker(props: DatePickerProps) {
249
248
  const reduce = useReduceMotion();
250
249
  // The pan responder is built once; it reads Reduce Motion through this ref.
251
250
  const live = useRef({ reduce });
252
- live.current = { reduce };
251
+ useLayoutEffect(() => {
252
+ live.current = { reduce };
253
+ });
253
254
 
254
255
  const [inner, setInner] = useState<Picked>(defaultValue);
255
256
  const current = value !== undefined ? value : inner;
@@ -265,13 +266,13 @@ export function DatePicker(props: DatePickerProps) {
265
266
 
266
267
  const ok = !error && !!success;
267
268
  const floating = open || hasValue;
268
- const float = useRef(new Animated.Value(floating ? 1 : 0)).current; // native
269
- const focus = useRef(new Animated.Value(0)).current; // JS: border + label colour
270
- const halo = useRef(new Animated.Value(0)).current; // native
271
- const show = useRef(new Animated.Value(0)).current; // native: backdrop + sheet
272
- const drag = useRef(new Animated.Value(0)).current; // native
273
- const shake = useRef(new Animated.Value(0)).current; // native
274
- const check = useRef(new Animated.Value(ok ? 1 : 0)).current; // native
269
+ const [float] = useState(() => new Animated.Value(floating ? 1 : 0)); // native
270
+ const [focus] = useState(() => new Animated.Value(0)); // JS: border + label colour
271
+ const [halo] = useState(() => new Animated.Value(0)); // native
272
+ const [show] = useState(() => new Animated.Value(0)); // native: backdrop + sheet
273
+ const [drag] = useState(() => new Animated.Value(0)); // native
274
+ const [shake] = useState(() => new Animated.Value(0)); // native
275
+ const [check] = useState(() => new Animated.Value(ok ? 1 : 0)); // native
275
276
 
276
277
  const asSheet = presentation !== 'popover';
277
278
  // Centred dialog instead of a bottom sheet: on foldables, tablets and landscape phones.
@@ -401,9 +402,9 @@ export function DatePicker(props: DatePickerProps) {
401
402
 
402
403
  const presetList = useMemo<DatePickerPreset<Picked>[]>(() => {
403
404
  if (!presets) return [];
404
- if (presets === true) return (ranged ? rangeDatePresets() : singleDatePresets()) as DatePickerPreset<Picked>[];
405
+ if (presets === true) return (ranged ? rangeDatePresets(labels) : singleDatePresets(labels)) as DatePickerPreset<Picked>[];
405
406
  return presets;
406
- }, [presets, ranged]);
407
+ }, [presets, ranged, labels]);
407
408
  const resolve = (p: DatePickerPreset<Picked>) => (typeof p.value === 'function' ? p.value() : p.value);
408
409
  const presetOk = (v: Picked) => {
409
410
  const ds = isRange(v) ? [v.start, v.end] : [v];
@@ -414,23 +415,21 @@ export function DatePicker(props: DatePickerProps) {
414
415
  const same = (a: Picked, b: Picked) =>
415
416
  isRange(a) && isRange(b) ? isSameDay(a.start, b.start) && isSameDay(a.end, b.end) : !isRange(a) && !isRange(b) && isSameDay(a, b);
416
417
 
417
- const pan = useMemo(() => {
418
- const settle = () =>
419
- live.current.reduce
420
- ? Animated.timing(drag, { toValue: 0, duration: 150, easing: Easing.bezier(0.32, 0.72, 0, 1), useNativeDriver: true }).start()
421
- : Animated.spring(drag, { toValue: 0, useNativeDriver: true, ...motion.snappy }).start();
422
- return PanResponder.create({
423
- onMoveShouldSetPanResponder: (_, g) => g.dy > 4 && Math.abs(g.dy) > Math.abs(g.dx),
424
- onPanResponderMove: (_, g) => drag.setValue(Math.max(0, g.dy)),
425
- onPanResponderRelease: (_, g) => {
426
- if (g.dy > 110 || g.vy > 1.1) {
427
- setOpen(false);
428
- onOpenChange?.(false);
429
- } else settle();
430
- },
431
- onPanResponderTerminate: settle,
432
- });
433
- }, [drag, onOpenChange]);
418
+ const settle = () =>
419
+ reduce
420
+ ? Animated.timing(drag, { toValue: 0, duration: 150, easing: Easing.bezier(0.32, 0.72, 0, 1), useNativeDriver: true }).start()
421
+ : Animated.spring(drag, { toValue: 0, useNativeDriver: true, ...motion.snappy }).start();
422
+ const pan = usePanResponder({
423
+ onMoveShouldSetPanResponder: (_, g) => g.dy > 4 && Math.abs(g.dy) > Math.abs(g.dx),
424
+ onPanResponderMove: (_, g) => drag.setValue(Math.max(0, g.dy)),
425
+ onPanResponderRelease: (_, g) => {
426
+ if (g.dy > 110 || g.vy > 1.1) {
427
+ setOpen(false);
428
+ onOpenChange?.(false);
429
+ } else settle();
430
+ },
431
+ onPanResponderTerminate: settle,
432
+ });
434
433
 
435
434
  const active = error ? colors.danger : tint;
436
435
  const rest = error ? colors.danger : ok ? colors.success : ((userBorder as string | undefined) ?? colors.input);
@@ -451,8 +450,8 @@ export function DatePicker(props: DatePickerProps) {
451
450
 
452
451
  const subtitle = isRange(draft)
453
452
  ? draft.start
454
- ? `${fmt.header(draft.start)} → ${draft.end ? fmt.header(draft.end) : 'pick an end date'}`
455
- : 'Pick a start date'
453
+ ? `${fmt.header(draft.start)} → ${draft.end ? fmt.header(draft.end) : labels.pickEndDate}`
454
+ : labels.pickStartDate
456
455
  : draft
457
456
  ? fmt.header(draft)
458
457
  : placeholder ?? null;
@@ -520,7 +519,7 @@ export function DatePicker(props: DatePickerProps) {
520
519
  style={({ pressed }) => [styles.footBtn, { borderRadius: radius.md, opacity: draft ? (pressed ? 0.6 : 1) : 0.4 }]}
521
520
  >
522
521
  <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.footText, ff, { color: colors.foreground }]}>
523
- Clear
522
+ {labels.clear}
524
523
  </Text>
525
524
  </Pressable>
526
525
  <Pressable
@@ -529,7 +528,7 @@ export function DatePicker(props: DatePickerProps) {
529
528
  style={({ pressed }) => [styles.footBtn, styles.footMain, { backgroundColor: tint, borderRadius: radius.md, opacity: pressed ? 0.85 : 1 }]}
530
529
  >
531
530
  <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.footText, ff, { color: readableOn(tint) }]}>
532
- {draftDays > 1 ? `Done · ${draftDays} days` : 'Done'}
531
+ {draftDays > 1 ? labels.doneDays(draftDays) : labels.done}
533
532
  </Text>
534
533
  </Pressable>
535
534
  </View>
@@ -569,7 +568,7 @@ export function DatePicker(props: DatePickerProps) {
569
568
  disabled={disabled}
570
569
  accessibilityRole="button"
571
570
  accessibilityLabel={label}
572
- accessibilityValue={{ text: text || 'Not set' }}
571
+ accessibilityValue={{ text: text || labels.notSet }}
573
572
  accessibilityState={{ expanded: open, disabled: !!disabled }}
574
573
  style={{ opacity: disabled ? 0.5 : 1 }}
575
574
  >
@@ -597,7 +596,7 @@ export function DatePicker(props: DatePickerProps) {
597
596
  !text && { color: placeholderTextColor ?? colors.mutedForeground },
598
597
  ]}
599
598
  >
600
- {text || (open || !floatingLabel ? placeholder ?? (ranged ? 'Select dates' : 'Select a date') : '')}
599
+ {text || (open || !floatingLabel ? placeholder ?? (ranged ? labels.selectDates : labels.selectDate) : '')}
601
600
  </Text>
602
601
  {floatingLabel ? (
603
602
  <Animated.View
@@ -637,7 +636,7 @@ export function DatePicker(props: DatePickerProps) {
637
636
  onPress={() => commit(null)}
638
637
  hitSlop={12}
639
638
  accessibilityRole="button"
640
- accessibilityLabel={`Clear ${label}`}
639
+ accessibilityLabel={labels.clearField(label)}
641
640
  style={[styles.clear, { backgroundColor: alpha(restIcon, 0.22) }]}
642
641
  >
643
642
  <Icon name="x" size={11} color={iconColor ?? colors.foreground} strokeWidth={2.4} />
@@ -651,7 +650,7 @@ export function DatePicker(props: DatePickerProps) {
651
650
 
652
651
  <Modal visible={mounted} transparent animationType="none" statusBarTranslucent onRequestClose={close}>
653
652
  <Animated.View style={[StyleSheet.absoluteFill, { backgroundColor: asSheet ? colors.overlay : 'transparent', opacity: show }]}>
654
- <Pressable style={StyleSheet.absoluteFill} onPress={close} accessibilityLabel="Close" accessibilityRole="button" />
653
+ <Pressable style={StyleSheet.absoluteFill} onPress={close} accessibilityLabel={labels.close} accessibilityRole="button" />
655
654
  </Animated.View>
656
655
  {asSheet ? (
657
656
  <View style={[styles.sheetHost, wide && styles.centered]} pointerEvents="box-none">
@@ -679,7 +678,7 @@ export function DatePicker(props: DatePickerProps) {
679
678
  },
680
679
  ]}
681
680
  >
682
- <View {...pan.panHandlers} style={styles.sheetHeader}>
681
+ <View {...pan} style={styles.sheetHeader}>
683
682
  {!wide ? <View style={[styles.grabber, { backgroundColor: colors.border }]} /> : null}
684
683
  <Text style={[styles.title, ff, { color: colors.foreground }]}>{title ?? label}</Text>
685
684
  {subtitle ? (
@@ -737,7 +736,7 @@ function FieldMessage({
737
736
  style?: StyleProp<TextStyle>;
738
737
  }) {
739
738
  const { colors, font } = useKit();
740
- const enter = useRef(new Animated.Value(tone === 'helper' ? 1 : 0)).current; // native
739
+ const [enter] = useState(() => new Animated.Value(tone === 'helper' ? 1 : 0)); // native
741
740
  useEffect(() => {
742
741
  if (reduce) Animated.timing(enter, { toValue: 1, duration: 150, useNativeDriver: true }).start();
743
742
  else Animated.spring(enter, { toValue: 1, useNativeDriver: true, ...motion.snappy }).start();