@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,16 +1,14 @@
1
- import React, { memo, useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
1
+ import React, { memo, 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,
11
10
  Text,
12
11
  View,
13
- useWindowDimensions,
14
12
  type NativeScrollEvent,
15
13
  type NativeSyntheticEvent,
16
14
  type StyleProp,
@@ -19,7 +17,7 @@ import {
19
17
  } from 'react-native';
20
18
  import Svg, { Circle, Line, Path } from 'react-native-svg';
21
19
 
22
- import { alpha, motion, readableOn, useKit } from '@/lib/kit';
20
+ import { alpha, motion, readableOn, useKit, useLayout, usePanResponder } from '@/lib/kit';
23
21
 
24
22
  /** A wall-clock time, 24-hour: `{ hours: 0–23, minutes: 0–59 }`. */
25
23
  export interface TimeValue {
@@ -144,11 +142,11 @@ export function uses12Hour(locale?: string) {
144
142
  }
145
143
  }
146
144
 
147
- /** '9:30 AM' or '09:30'. */
148
- export function formatTime(t: TimeValue, hour12 = true) {
145
+ /** '9:30 AM' or '09:30'. `periods` names AM / PM (default English; pass `useKit().labels` to localise). */
146
+ export function formatTime(t: TimeValue, hour12 = true, periods: { am: string; pm: string } = { am: 'AM', pm: 'PM' }) {
149
147
  if (!hour12) return `${pad2(t.hours)}:${pad2(t.minutes)}`;
150
148
  const h = t.hours % 12 || 12;
151
- return `${h}:${pad2(t.minutes)} ${t.hours < 12 ? 'AM' : 'PM'}`;
149
+ return `${h}:${pad2(t.minutes)} ${t.hours < 12 ? periods.am : periods.pm}`;
152
150
  }
153
151
 
154
152
  /* ───────────────────────────── TimePicker ───────────────────────────── */
@@ -195,18 +193,18 @@ interface WheelProps {
195
193
  step: number;
196
194
  value: TimeValue | null;
197
195
  onChange: (t: TimeValue) => void;
198
- accent?: string;
199
- style?: StyleProp<ViewStyle>;
196
+ accent?: string | undefined;
197
+ style?: StyleProp<ViewStyle> | undefined;
200
198
  reduce: boolean;
201
199
  }
202
200
 
203
201
  function Wheel({ hour12, step, value, onChange, accent, style, reduce }: WheelProps) {
204
- const { colors, radius, font, dark } = useKit();
202
+ const { colors, radius, font, dark, labels } = useKit();
205
203
  const tint = accent ?? colors.accent;
206
204
  const t = value ?? { hours: 9, minutes: 0 };
207
205
  const hours = useMemo(() => (hour12 ? ['12', ...Array.from({ length: 11 }, (_, i) => String(i + 1))] : Array.from({ length: 24 }, (_, i) => pad2(i))), [hour12]);
208
206
  const minutes = useMemo(() => Array.from({ length: Math.ceil(60 / step) }, (_, i) => pad2(i * step)), [step]);
209
- const periods = useMemo(() => ['AM', 'PM'], []);
207
+ const periods = useMemo(() => [labels.am, labels.pm], [labels.am, labels.pm]);
210
208
 
211
209
  const hIdx = hour12 ? t.hours % 12 : t.hours;
212
210
  const mIdx = Math.min(minutes.length - 1, Math.round(t.minutes / step));
@@ -214,7 +212,9 @@ function Wheel({ hour12, step, value, onChange, accent, style, reduce }: WheelPr
214
212
 
215
213
  // Keep the latest value for column callbacks that fire after a scroll settles.
216
214
  const latest = useRef(t);
217
- latest.current = t;
215
+ useLayoutEffect(() => {
216
+ latest.current = t;
217
+ });
218
218
  const set = (patch: Partial<{ h: number; m: number; p: number }>) => {
219
219
  const cur = latest.current;
220
220
  const curP = cur.hours >= 12 ? 1 : 0;
@@ -234,17 +234,17 @@ function Wheel({ hour12, step, value, onChange, accent, style, reduce }: WheelPr
234
234
  style={[styles.band, { top: PAD, height: ITEM, borderRadius: radius.scale(12), backgroundColor: alpha(tint, dark ? 0.16 : 0.11) }]}
235
235
  />
236
236
  <View style={styles.wheelGroup}>
237
- <WheelColumn items={hours} index={hIdx} onIndexChange={(h) => set({ h })} label="Hour" width={64} align="right" reduce={reduce} />
237
+ <WheelColumn items={hours} index={hIdx} onIndexChange={(h) => set({ h })} label={labels.hour} width={64} align="right" reduce={reduce} />
238
238
  <View pointerEvents="none" style={styles.colon}>
239
239
  <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.colonText, font.family ? { fontFamily: font.family } : null, { color: colors.mutedForeground }]}>:</Text>
240
240
  </View>
241
- <WheelColumn items={minutes} index={mIdx} onIndexChange={(m) => set({ m })} label="Minute" width={64} align="left" reduce={reduce} />
241
+ <WheelColumn items={minutes} index={mIdx} onIndexChange={(m) => set({ m })} label={labels.minute} width={64} align="left" reduce={reduce} />
242
242
  {hour12 ? (
243
243
  <WheelColumn
244
244
  items={periods}
245
245
  index={pIdx}
246
246
  onIndexChange={(p) => set({ p })}
247
- label="AM or PM"
247
+ label={labels.amPm}
248
248
  width={60}
249
249
  align="center"
250
250
  small
@@ -273,17 +273,21 @@ interface WheelColumnProps {
273
273
  const WheelColumn = memo(function WheelColumn({ items, index, onIndexChange, label, width, align, small, style, reduce }: WheelColumnProps) {
274
274
  const { colors, font } = useKit();
275
275
  const ff = font.family ? { fontFamily: font.family } : null;
276
- const scrollY = useRef(new Animated.Value(index * ITEM)).current; // native, driven by the scroll view
276
+ const [scrollY] = useState(() => new Animated.Value(index * ITEM)); // native, driven by the scroll view
277
277
  const ref = useRef<ComponentRef<typeof ScrollView>>(null);
278
278
  const settled = useRef(index);
279
279
  const dragging = useRef(false);
280
280
  const lastY = useRef(index * ITEM);
281
281
  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
282
282
  const cb = useRef(onIndexChange);
283
- cb.current = onIndexChange;
283
+ useLayoutEffect(() => {
284
+ cb.current = onIndexChange;
285
+ });
284
286
  // Read by settle(), which also runs from the scroll listener created once.
285
287
  const animated = useRef(!reduce);
286
- animated.current = !reduce;
288
+ useLayoutEffect(() => {
289
+ animated.current = !reduce;
290
+ });
287
291
 
288
292
  useEffect(() => {
289
293
  if (index !== settled.current && !dragging.current) {
@@ -317,6 +321,7 @@ const WheelColumn = memo(function WheelColumn({ items, index, onIndexChange, lab
317
321
 
318
322
  const onScroll = useMemo(
319
323
  () =>
324
+ // eslint-disable-next-line react-hooks/refs -- the listener reads refs on scroll events, never during render
320
325
  Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
321
326
  useNativeDriver: true,
322
327
  listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
@@ -403,8 +408,8 @@ interface ClockProps {
403
408
  step: number;
404
409
  value: TimeValue | null;
405
410
  onChange: (t: TimeValue) => void;
406
- accent?: string;
407
- style?: StyleProp<ViewStyle>;
411
+ accent?: string | undefined;
412
+ style?: StyleProp<ViewStyle> | undefined;
408
413
  reduce: boolean;
409
414
  }
410
415
 
@@ -424,7 +429,7 @@ const handAngle = (t: TimeValue, mode: ClockMode) => (mode === 'hour' ? (t.hours
424
429
  * native driver and snaps to hours, then auto-advances to minutes when you let go.
425
430
  */
426
431
  function Clock({ hour12, step, value, onChange, accent, style, reduce }: ClockProps) {
427
- const { colors, radius, font, dark } = useKit();
432
+ const { colors, radius, font, dark, labels } = useKit();
428
433
  const ff = font.family ? { fontFamily: font.family } : null;
429
434
  const tint = accent ?? colors.accent;
430
435
  const ink = readableOn(tint);
@@ -442,9 +447,9 @@ function Clock({ hour12, step, value, onChange, accent, style, reduce }: ClockPr
442
447
  const handR = mode === 'hour' && innerHour ? inner : outer;
443
448
 
444
449
  // Hand rotation in degrees (native). Kept continuous so 55 → 00 turns forward, not all the way back.
445
- const rot = useRef(new Animated.Value(handAngle(t, 'hour'))).current;
450
+ const [rot] = useState(() => new Animated.Value(handAngle(t, 'hour')));
446
451
  const angle = useRef(handAngle(t, 'hour'));
447
- const modeAnim = useRef(new Animated.Value(0)).current; // native: number crossfade
452
+ const [modeAnim] = useState(() => new Animated.Value(0)); // native: number crossfade
448
453
  useEffect(() => {
449
454
  const target = handAngle(t, mode);
450
455
  const d = ((((target - angle.current) % 360) + 540) % 360) - 180;
@@ -460,48 +465,48 @@ function Clock({ hour12, step, value, onChange, accent, style, reduce }: ClockPr
460
465
 
461
466
  // Latest values for the pan responder, which is created once.
462
467
  const live = useRef({ t, mode, hour12, step, onChange, c, outer, inner, touched: value !== null });
463
- live.current = { t, mode, hour12, step, onChange, c, outer, inner, touched: value !== null };
468
+ useLayoutEffect(() => {
469
+ live.current = { t, mode, hour12, step, onChange, c, outer, inner, touched: value !== null };
470
+ });
464
471
  const origin = useRef({ x: 0, y: 0 });
465
472
 
466
- const pan = useMemo(() => {
467
- const pick = (x: number, y: number) => {
468
- const L = live.current;
469
- const dx = x - L.c;
470
- const dy = y - L.c;
471
- let deg = (Math.atan2(dx, -dy) * 180) / Math.PI;
472
- if (deg < 0) deg += 360;
473
- const cur = L.t;
474
- let next: TimeValue;
475
- if (L.mode === 'hour') {
476
- const i = Math.round(deg / 30) % 12;
477
- if (L.hour12) next = { hours: i + (cur.hours >= 12 ? 12 : 0), minutes: cur.minutes };
478
- else {
479
- const innerRing = Math.hypot(dx, dy) < (L.outer + L.inner) / 2;
480
- next = { hours: innerRing ? (i === 0 ? 0 : i + 12) : i === 0 ? 12 : i, minutes: cur.minutes };
481
- }
482
- } else {
483
- next = { hours: cur.hours, minutes: (Math.round(deg / 6 / L.step) * L.step) % 60 };
484
- }
485
- if (next.hours !== cur.hours || next.minutes !== cur.minutes || !L.touched) {
486
- L.t = next;
487
- L.touched = true;
488
- L.onChange(next);
473
+ const pick = (x: number, y: number) => {
474
+ const L = live.current;
475
+ const dx = x - L.c;
476
+ const dy = y - L.c;
477
+ let deg = (Math.atan2(dx, -dy) * 180) / Math.PI;
478
+ if (deg < 0) deg += 360;
479
+ const cur = L.t;
480
+ let next: TimeValue;
481
+ if (L.mode === 'hour') {
482
+ const i = Math.round(deg / 30) % 12;
483
+ if (L.hour12) next = { hours: i + (cur.hours >= 12 ? 12 : 0), minutes: cur.minutes };
484
+ else {
485
+ const innerRing = Math.hypot(dx, dy) < (L.outer + L.inner) / 2;
486
+ next = { hours: innerRing ? (i === 0 ? 0 : i + 12) : i === 0 ? 12 : i, minutes: cur.minutes };
489
487
  }
490
- };
491
- return PanResponder.create({
492
- onStartShouldSetPanResponder: () => true,
493
- onMoveShouldSetPanResponder: () => true,
494
- onPanResponderTerminationRequest: () => false,
495
- onPanResponderGrant: (e) => {
496
- origin.current = { x: e.nativeEvent.locationX, y: e.nativeEvent.locationY };
497
- pick(origin.current.x, origin.current.y);
498
- },
499
- onPanResponderMove: (_, g) => pick(origin.current.x + g.dx, origin.current.y + g.dy),
500
- onPanResponderRelease: () => {
501
- if (live.current.mode === 'hour') setMode('minute');
502
- },
503
- });
504
- }, []);
488
+ } else {
489
+ next = { hours: cur.hours, minutes: (Math.round(deg / 6 / L.step) * L.step) % 60 };
490
+ }
491
+ if (next.hours !== cur.hours || next.minutes !== cur.minutes || !L.touched) {
492
+ L.t = next;
493
+ L.touched = true;
494
+ L.onChange(next);
495
+ }
496
+ };
497
+ const pan = usePanResponder({
498
+ onStartShouldSetPanResponder: () => true,
499
+ onMoveShouldSetPanResponder: () => true,
500
+ onPanResponderTerminationRequest: () => false,
501
+ onPanResponderGrant: (e) => {
502
+ origin.current = { x: e.nativeEvent.locationX, y: e.nativeEvent.locationY };
503
+ pick(origin.current.x, origin.current.y);
504
+ },
505
+ onPanResponderMove: (_, g) => pick(origin.current.x + g.dx, origin.current.y + g.dy),
506
+ onPanResponderRelease: () => {
507
+ if (live.current.mode === 'hour') setMode('minute');
508
+ },
509
+ });
505
510
 
506
511
  const adjust = (dir: 1 | -1) => {
507
512
  if (mode === 'hour') {
@@ -555,22 +560,22 @@ function Clock({ hour12, step, value, onChange, accent, style, reduce }: ClockPr
555
560
  return (
556
561
  <View style={[styles.clock, style]} onLayout={(e) => setWidth(e.nativeEvent.layout.width)}>
557
562
  <View style={styles.clockHead}>
558
- {segment('hour', hourText, 'Hour')}
563
+ {segment('hour', hourText, labels.hour)}
559
564
  <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.clockColon, ff, { fontSize: segFont, color: colors.mutedForeground }]}>:</Text>
560
- {segment('minute', pad2(t.minutes), 'Minute')}
565
+ {segment('minute', pad2(t.minutes), labels.minute)}
561
566
  {hour12 ? (
562
567
  <View
563
568
  accessibilityRole="radiogroup"
564
569
  style={[styles.period, { height: segH, borderRadius: radius.scale(10), borderColor: colors.border }]}
565
570
  >
566
- {(['AM', 'PM'] as const).map((p, i) => {
567
- const on = (p === 'PM') === pm;
571
+ {(['am', 'pm'] as const).map((p, i) => {
572
+ const on = (p === 'pm') === pm;
568
573
  return (
569
574
  <Pressable
570
575
  key={p}
571
- onPress={() => setPeriod(p === 'PM')}
576
+ onPress={() => setPeriod(p === 'pm')}
572
577
  accessibilityRole="radio"
573
- accessibilityLabel={p}
578
+ accessibilityLabel={labels[p]}
574
579
  accessibilityState={{ checked: on }}
575
580
  style={[
576
581
  styles.periodBtn,
@@ -578,7 +583,7 @@ function Clock({ hour12, step, value, onChange, accent, style, reduce }: ClockPr
578
583
  i === 1 && { borderTopWidth: StyleSheet.hairlineWidth * 2, borderTopColor: colors.border },
579
584
  ]}
580
585
  >
581
- <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.periodText, ff, { color: on ? tint : colors.mutedForeground }]}>{p}</Text>
586
+ <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.periodText, ff, { color: on ? tint : colors.mutedForeground }]}>{labels[p]}</Text>
582
587
  </Pressable>
583
588
  );
584
589
  })}
@@ -587,12 +592,12 @@ function Clock({ hour12, step, value, onChange, accent, style, reduce }: ClockPr
587
592
  </View>
588
593
 
589
594
  <View
590
- {...pan.panHandlers}
595
+ {...pan}
591
596
  accessible
592
597
  accessibilityRole="adjustable"
593
- accessibilityLabel={mode === 'hour' ? 'Hour' : 'Minute'}
594
- accessibilityHint="Drag around the dial, or swipe up or down to adjust."
595
- accessibilityValue={{ text: mode === 'hour' ? `${knobLabel || hourText} ${hour12 ? (pm ? 'PM' : 'AM') : 'hours'}` : `${t.minutes} minutes` }}
598
+ accessibilityLabel={mode === 'hour' ? labels.hour : labels.minute}
599
+ accessibilityHint={labels.timeDialHint}
600
+ accessibilityValue={{ text: mode === 'hour' ? (hour12 ? `${knobLabel || hourText} ${pm ? labels.pm : labels.am}` : labels.hours(knobLabel || hourText)) : labels.minutes(t.minutes) }}
596
601
  accessibilityActions={[{ name: 'increment' }, { name: 'decrement' }]}
597
602
  onAccessibilityAction={(e) => adjust(e.nativeEvent.actionName === 'increment' ? 1 : -1)}
598
603
  style={[styles.face, { width: size, height: size }]}
@@ -667,7 +672,7 @@ function SlotGrid({
667
672
  onPick,
668
673
  reduce,
669
674
  }: SlotGridProps) {
670
- const { colors, font } = useKit();
675
+ const { colors, font, labels } = useKit();
671
676
  const ff = font.family ? { fontFamily: font.family } : null;
672
677
  const tint = accent ?? colors.accent;
673
678
  const list = useMemo(() => {
@@ -683,12 +688,12 @@ function SlotGrid({
683
688
  const groups = useMemo(() => {
684
689
  if (!groupByPeriod) return [{ title: '', items: list }];
685
690
  const g = [
686
- { title: 'Morning', items: list.filter((n) => n < 720) },
687
- { title: 'Afternoon', items: list.filter((n) => n >= 720 && n < 1020) },
688
- { title: 'Evening', items: list.filter((n) => n >= 1020) },
691
+ { title: labels.morning, items: list.filter((n) => n < 720) },
692
+ { title: labels.afternoon, items: list.filter((n) => n >= 720 && n < 1020) },
693
+ { title: labels.evening, items: list.filter((n) => n >= 1020) },
689
694
  ];
690
695
  return g.filter((x) => x.items.length);
691
- }, [list, groupByPeriod]);
696
+ }, [list, groupByPeriod, labels]);
692
697
 
693
698
  return (
694
699
  <View style={[styles.slots, style]} accessibilityRole="radiogroup">
@@ -699,7 +704,7 @@ function SlotGrid({
699
704
  {g.title ? (
700
705
  <View style={styles.groupHead}>
701
706
  <Text style={[styles.groupTitle, ff, { color: colors.foreground }]}>{g.title}</Text>
702
- <Text style={[styles.groupCount, ff, { color: colors.mutedForeground }]}>{free ? `${free} open` : 'Full'}</Text>
707
+ <Text style={[styles.groupCount, ff, { color: colors.mutedForeground }]}>{free ? labels.slotsOpen(free) : labels.full}</Text>
703
708
  </View>
704
709
  ) : null}
705
710
  <View style={styles.grid}>
@@ -707,7 +712,7 @@ function SlotGrid({
707
712
  <View key={n} style={[styles.chipCell, { width: `${100 / columns}%` }]}>
708
713
  <Chip
709
714
  minutes={n}
710
- label={formatTime(fromMin(n), hour12)}
715
+ label={formatTime(fromMin(n), hour12, labels)}
711
716
  selected={n === selected}
712
717
  booked={booked.has(n)}
713
718
  disabled={off.has(n)}
@@ -738,9 +743,9 @@ interface ChipProps {
738
743
  }
739
744
 
740
745
  const Chip = memo(function Chip({ minutes, label, selected, booked, disabled, tint, reduce, onPick }: ChipProps) {
741
- const { colors, radius, font } = useKit();
742
- const pop = useRef(new Animated.Value(1)).current; // native: press + select spring
743
- const on = useRef(new Animated.Value(selected ? 1 : 0)).current; // native: fill opacity
746
+ const { colors, radius, font, labels } = useKit();
747
+ const [pop] = useState(() => new Animated.Value(1)); // native: press + select spring
748
+ const [on] = useState(() => new Animated.Value(selected ? 1 : 0)); // native: fill opacity
744
749
  useEffect(() => {
745
750
  Animated.timing(on, { toValue: selected ? 1 : 0, duration: motion.fast, useNativeDriver: true }).start();
746
751
  if (selected && !reduce) {
@@ -757,7 +762,7 @@ const Chip = memo(function Chip({ minutes, label, selected, booked, disabled, ti
757
762
  onPressOut={reduce ? undefined : () => Animated.spring(pop, { toValue: 1, useNativeDriver: true, ...motion.bouncy }).start()}
758
763
  disabled={blocked}
759
764
  accessibilityRole="radio"
760
- accessibilityLabel={`${label}${booked ? ', booked' : disabled ? ', unavailable' : ''}`}
765
+ accessibilityLabel={`${label}${booked ? `, ${labels.booked}` : disabled ? `, ${labels.unavailable}` : ''}`}
761
766
  accessibilityState={{ checked: selected, disabled: blocked }}
762
767
  >
763
768
  <Animated.View
@@ -807,10 +812,10 @@ function TimeField({
807
812
  current,
808
813
  commit,
809
814
  reduce,
810
- label = 'Time',
815
+ label: labelProp,
811
816
  floatingLabel = true,
812
817
  title,
813
- placeholder = 'Select a time',
818
+ placeholder: placeholderProp,
814
819
  size = 'md',
815
820
  confirm,
816
821
  error,
@@ -829,7 +834,9 @@ function TimeField({
829
834
  menuStyle,
830
835
  ...chipProps
831
836
  }: TimeFieldProps) {
832
- const { colors, radius, font } = useKit();
837
+ const { colors, radius, font, dark, shadow, labels } = useKit();
838
+ const label = labelProp ?? labels.time;
839
+ const placeholder = placeholderProp ?? labels.selectTime;
833
840
  const ff = font.family ? { fontFamily: font.family } : null;
834
841
  const tint = accent ?? colors.accent;
835
842
  const bg = surface ?? colors.background;
@@ -840,27 +847,26 @@ function TimeField({
840
847
  const sz = typeof userField.height === 'number' ? { ...base, h: userField.height } : base;
841
848
  const fieldRadius =
842
849
  typeof userField.borderRadius === 'number' ? userField.borderRadius : size === 'sm' ? radius.md - 2 : size === 'lg' ? radius.lg : radius.md;
843
- const { width: winW, height: winH } = useWindowDimensions();
844
- const wide = winW >= 640;
850
+ const layout = useLayout();
851
+ const { height: winH } = layout;
852
+ // A centred card whenever the kit caps the sheet (foldables, tablets, landscape phones).
853
+ const wide = layout.sheetWidth < layout.width;
845
854
  const staged = confirm ?? variant !== 'chips';
846
855
 
847
856
  const [open, setOpen] = useState(false);
848
857
  const [mounted, setMounted] = useState(false);
849
858
  const [draft, setDraft] = useState<TimeValue | null>(current);
850
859
  const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
851
- // Read by the sheet's pan responder, which is created once.
852
- const reduceRef = useRef(reduce);
853
- reduceRef.current = reduce;
854
860
 
855
861
  const ok = !error && !!success;
856
862
  const floating = open || !!current;
857
- const float = useRef(new Animated.Value(floating ? 1 : 0)).current; // native
858
- const focus = useRef(new Animated.Value(0)).current; // JS: colours
859
- const halo = useRef(new Animated.Value(0)).current; // native
860
- const show = useRef(new Animated.Value(0)).current; // native
861
- const drag = useRef(new Animated.Value(0)).current; // native
862
- const shake = useRef(new Animated.Value(0)).current; // native
863
- const check = useRef(new Animated.Value(ok ? 1 : 0)).current; // native
863
+ const [float] = useState(() => new Animated.Value(floating ? 1 : 0)); // native
864
+ const [focus] = useState(() => new Animated.Value(0)); // JS: colours
865
+ const [halo] = useState(() => new Animated.Value(0)); // native
866
+ const [show] = useState(() => new Animated.Value(0)); // native
867
+ const [drag] = useState(() => new Animated.Value(0)); // native
868
+ const [shake] = useState(() => new Animated.Value(0)); // native
869
+ const [check] = useState(() => new Animated.Value(ok ? 1 : 0)); // native
864
870
 
865
871
  useEffect(() => {
866
872
  if (reduce) float.setValue(floating ? 1 : 0);
@@ -917,21 +923,19 @@ function TimeField({
917
923
  }
918
924
  };
919
925
 
920
- const pan = useMemo(() => {
921
- const back = () =>
922
- reduceRef.current
923
- ? Animated.timing(drag, { toValue: 0, duration: 150, easing: Easing.bezier(0.32, 0.72, 0, 1), useNativeDriver: true }).start()
924
- : Animated.spring(drag, { toValue: 0, useNativeDriver: true, ...motion.snappy }).start();
925
- return PanResponder.create({
926
- onMoveShouldSetPanResponder: (_, g) => g.dy > 4 && Math.abs(g.dy) > Math.abs(g.dx),
927
- onPanResponderMove: (_, g) => drag.setValue(Math.max(0, g.dy)),
928
- onPanResponderRelease: (_, g) => {
929
- if (g.dy > 110 || g.vy > 1.1) setOpen(false);
930
- else back();
931
- },
932
- onPanResponderTerminate: back,
933
- });
934
- }, [drag]);
926
+ const back = () =>
927
+ reduce
928
+ ? Animated.timing(drag, { toValue: 0, duration: 150, easing: Easing.bezier(0.32, 0.72, 0, 1), useNativeDriver: true }).start()
929
+ : Animated.spring(drag, { toValue: 0, useNativeDriver: true, ...motion.snappy }).start();
930
+ const pan = usePanResponder({
931
+ onMoveShouldSetPanResponder: (_, g) => g.dy > 4 && Math.abs(g.dy) > Math.abs(g.dx),
932
+ onPanResponderMove: (_, g) => drag.setValue(Math.max(0, g.dy)),
933
+ onPanResponderRelease: (_, g) => {
934
+ if (g.dy > 110 || g.vy > 1.1) setOpen(false);
935
+ else back();
936
+ },
937
+ onPanResponderTerminate: back,
938
+ });
935
939
 
936
940
  const active = error ? colors.danger : tint;
937
941
  const rest = error ? colors.danger : ok ? colors.success : ((userBorder as string | undefined) ?? colors.input);
@@ -941,7 +945,7 @@ function TimeField({
941
945
  outputRange: [error ? colors.danger : ok ? colors.success : (userLabelColor ?? colors.mutedForeground), active],
942
946
  });
943
947
  const topLabelColor = focus.interpolate({ inputRange: [0, 1], outputRange: [error ? colors.danger : (userLabelColor ?? colors.foreground), active] });
944
- const text = current ? formatTime(current, hour12) : '';
948
+ const text = current ? formatTime(current, hour12, labels) : '';
945
949
  const successText = typeof success === 'string' && ok ? success : undefined;
946
950
  const message = error ?? successText ?? helper;
947
951
  const tone = error ? 'error' : successText ? 'success' : 'helper';
@@ -965,7 +969,7 @@ function TimeField({
965
969
  disabled={disabled}
966
970
  accessibilityRole="button"
967
971
  accessibilityLabel={label}
968
- accessibilityValue={{ text: text || 'Not set' }}
972
+ accessibilityValue={{ text: text || labels.notSet }}
969
973
  accessibilityState={{ expanded: open, disabled: !!disabled }}
970
974
  style={{ opacity: disabled ? 0.5 : 1 }}
971
975
  >
@@ -1031,7 +1035,7 @@ function TimeField({
1031
1035
 
1032
1036
  <Modal visible={mounted} transparent animationType="none" statusBarTranslucent onRequestClose={close}>
1033
1037
  <Animated.View style={[StyleSheet.absoluteFill, { backgroundColor: colors.overlay, opacity: show }]}>
1034
- <Pressable style={StyleSheet.absoluteFill} onPress={close} accessibilityLabel="Close" accessibilityRole="button" />
1038
+ <Pressable style={StyleSheet.absoluteFill} onPress={close} accessibilityLabel={labels.close} accessibilityRole="button" />
1035
1039
  </Animated.View>
1036
1040
  <View style={[styles.sheetHost, wide && styles.centered]} pointerEvents="box-none">
1037
1041
  <Animated.View
@@ -1040,8 +1044,14 @@ function TimeField({
1040
1044
  styles.sheet,
1041
1045
  { backgroundColor: colors.popover, borderColor: colors.border, maxHeight: winH * 0.9 },
1042
1046
  wide
1043
- ? { width: Math.min(420, winW - 48), borderRadius: radius.xl, paddingBottom: 14 }
1044
- : { alignSelf: 'stretch', borderTopLeftRadius: radius.xl + 4, borderTopRightRadius: radius.xl + 4, paddingBottom: 30 },
1047
+ ? { width: Math.min(420, layout.sheetWidth), borderRadius: radius.xl, paddingBottom: 14, ...shadow('xl') }
1048
+ : {
1049
+ alignSelf: 'stretch',
1050
+ borderTopLeftRadius: radius.xl + 4,
1051
+ borderTopRightRadius: radius.xl + 4,
1052
+ paddingBottom: 30,
1053
+ boxShadow: `0px -4px 32px rgba(0,0,0,${dark ? 0.45 : 0.18})`,
1054
+ },
1045
1055
  menuStyle,
1046
1056
  wide
1047
1057
  ? {
@@ -1058,11 +1068,11 @@ function TimeField({
1058
1068
  },
1059
1069
  ]}
1060
1070
  >
1061
- <View {...pan.panHandlers} style={styles.sheetHeader}>
1071
+ <View {...pan} style={styles.sheetHeader}>
1062
1072
  {!wide ? <View style={[styles.grabber, { backgroundColor: colors.border }]} /> : null}
1063
1073
  <Text style={[styles.title, ff, { color: colors.foreground }]}>{title ?? label}</Text>
1064
1074
  {variant === 'clock' ? null : (
1065
- <Text style={[styles.preview, ff, { color: draft ? tint : colors.mutedForeground }]}>{draft ? formatTime(draft, hour12) : placeholder}</Text>
1075
+ <Text style={[styles.preview, ff, { color: draft ? tint : colors.mutedForeground }]}>{draft ? formatTime(draft, hour12, labels) : placeholder}</Text>
1066
1076
  )}
1067
1077
  </View>
1068
1078
  <ScrollView style={styles.sheetBody} contentContainerStyle={styles.sheetContent} bounces={false} scrollEnabled={variant === 'chips'}>
@@ -1077,7 +1087,7 @@ function TimeField({
1077
1087
  {staged ? (
1078
1088
  <View style={[styles.footer, { borderTopColor: colors.border }]}>
1079
1089
  <Pressable onPress={close} accessibilityRole="button" style={({ pressed }) => [styles.footBtn, { borderRadius: radius.md, opacity: pressed ? 0.6 : 1 }]}>
1080
- <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.footText, ff, { color: colors.foreground }]}>Cancel</Text>
1090
+ <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.footText, ff, { color: colors.foreground }]}>{labels.cancel}</Text>
1081
1091
  </Pressable>
1082
1092
  <Pressable
1083
1093
  onPress={() => {
@@ -1092,7 +1102,7 @@ function TimeField({
1092
1102
  { backgroundColor: tint, borderRadius: radius.md, opacity: !draft ? 0.4 : pressed ? 0.85 : 1 },
1093
1103
  ]}
1094
1104
  >
1095
- <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.footText, ff, { color: readableOn(tint) }]}>{draft ? `Set ${formatTime(draft, hour12)}` : 'Done'}</Text>
1105
+ <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.footText, ff, { color: readableOn(tint) }]}>{draft ? labels.setTime(formatTime(draft, hour12, labels)) : labels.done}</Text>
1096
1106
  </Pressable>
1097
1107
  </View>
1098
1108
  ) : null}
@@ -1116,7 +1126,7 @@ function FieldMessage({
1116
1126
  style?: StyleProp<TextStyle>;
1117
1127
  }) {
1118
1128
  const { colors, font } = useKit();
1119
- const enter = useRef(new Animated.Value(tone === 'helper' ? 1 : 0)).current; // native
1129
+ const [enter] = useState(() => new Animated.Value(tone === 'helper' ? 1 : 0)); // native
1120
1130
  useEffect(() => {
1121
1131
  if (reduce) Animated.timing(enter, { toValue: 1, duration: 150, useNativeDriver: true }).start();
1122
1132
  else Animated.spring(enter, { toValue: 1, useNativeDriver: true, ...motion.snappy }).start();
@@ -1193,7 +1203,7 @@ const styles = StyleSheet.create({
1193
1203
  clockColon: { width: 22, textAlign: 'center', fontWeight: '400', includeFontPadding: false, transform: [{ translateY: -3 }] },
1194
1204
  period: { width: 52, marginLeft: 12, borderWidth: StyleSheet.hairlineWidth * 2, overflow: 'hidden' },
1195
1205
  periodBtn: { flex: 1, alignItems: 'center', justifyContent: 'center' },
1196
- periodText: { fontSize: 14, fontWeight: '400', letterSpacing: 0.3 },
1206
+ periodText: { fontSize: 14, fontWeight: '400', letterSpacing: 0.3, includeFontPadding: false, textAlignVertical: 'center' },
1197
1207
  face: { alignSelf: 'center' },
1198
1208
  num: { position: 'absolute', width: KNOB * 2, height: KNOB * 2, alignItems: 'center', justifyContent: 'center' },
1199
1209
  numText: { fontSize: 16, fontWeight: '400', fontVariant: ['tabular-nums'], includeFontPadding: false },
@@ -1206,7 +1216,7 @@ const styles = StyleSheet.create({
1206
1216
  grid: { flexDirection: 'row', flexWrap: 'wrap', marginHorizontal: -4 },
1207
1217
  chipCell: { padding: 4 },
1208
1218
  chip: { minHeight: 44, borderWidth: 1, alignItems: 'center', justifyContent: 'center', overflow: 'hidden' },
1209
- chipText: { fontSize: 14.5, fontWeight: '400', fontVariant: ['tabular-nums'] },
1219
+ chipText: { fontSize: 14.5, fontWeight: '400', fontVariant: ['tabular-nums'], includeFontPadding: false, textAlignVertical: 'center' },
1210
1220
  struck: { textDecorationLine: 'line-through' },
1211
1221
  field: { borderWidth: 1.5, justifyContent: 'center' },
1212
1222
  halo: { position: 'absolute', top: -4, left: -4, right: -4, bottom: -4, borderWidth: 4 },
@@ -1221,11 +1231,6 @@ const styles = StyleSheet.create({
1221
1231
  centered: { justifyContent: 'center', alignItems: 'center' },
1222
1232
  sheet: {
1223
1233
  borderWidth: StyleSheet.hairlineWidth,
1224
- shadowColor: '#000',
1225
- shadowOpacity: 0.18,
1226
- shadowRadius: 24,
1227
- shadowOffset: { width: 0, height: -4 },
1228
- elevation: 16,
1229
1234
  },
1230
1235
  sheetHeader: { alignItems: 'center', paddingTop: 10, paddingBottom: 10, paddingHorizontal: 16 },
1231
1236
  grabber: { width: 40, height: 5, borderRadius: 3, marginBottom: 12 },