@expokit/cli 0.3.0 → 0.4.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 (165) hide show
  1. package/README.md +8 -4
  2. package/bin/expo-kit.js +137 -24
  3. package/bin/lib.mjs +110 -1
  4. package/bin/mcp.mjs +40 -18
  5. package/package.json +2 -2
  6. package/registry/_docs/assistant-app.json +47 -0
  7. package/registry/_docs/avatar.json +1 -1
  8. package/registry/_docs/badge.json +1 -1
  9. package/registry/_docs/bottom-nav.json +6 -6
  10. package/registry/_docs/button-3d.json +2 -2
  11. package/registry/_docs/calendar.json +5 -5
  12. package/registry/_docs/checkout.json +7 -1
  13. package/registry/_docs/confetti.json +3 -3
  14. package/registry/_docs/date-picker.json +4 -4
  15. package/registry/_docs/donut-shop.json +88 -0
  16. package/registry/_docs/drawer.json +4 -4
  17. package/registry/_docs/fintech-app.json +49 -0
  18. package/registry/_docs/floating-input.json +5 -5
  19. package/registry/_docs/food-app.json +47 -0
  20. package/registry/_docs/glass.json +1 -1
  21. package/registry/_docs/grain.json +80 -0
  22. package/registry/_docs/morphing-button-group.json +96 -0
  23. package/registry/_docs/orb-reveal.json +132 -0
  24. package/registry/_docs/otp-input.json +4 -4
  25. package/registry/_docs/password-input.json +3 -3
  26. package/registry/_docs/qr-code-button.json +122 -0
  27. package/registry/_docs/search-bar.json +3 -3
  28. package/registry/_docs/select.json +7 -7
  29. package/registry/_docs/settings.json +6 -0
  30. package/registry/_docs/skeleton.json +2 -2
  31. package/registry/_docs/slider.json +6 -6
  32. package/registry/_docs/spinner.json +1 -1
  33. package/registry/_docs/stepper.json +7 -7
  34. package/registry/_docs/textarea.json +2 -2
  35. package/registry/_docs/ticket-shape.json +1 -1
  36. package/registry/_docs/tilt-card.json +4 -4
  37. package/registry/_docs/time-picker.json +6 -6
  38. package/registry/_docs/timeline.json +2 -2
  39. package/registry/_docs/toast.json +8 -8
  40. package/registry/accordion/files/src/components/ui/accordion.tsx +5 -4
  41. package/registry/accordion/files/src/lib/kit.tsx +8 -5
  42. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +9 -8
  43. package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
  44. package/registry/alert/files/src/components/ui/alert.tsx +7 -4
  45. package/registry/alert/files/src/components/ui/button.tsx +4 -3
  46. package/registry/alert/files/src/lib/kit.tsx +8 -5
  47. package/registry/assistant-app.json +49 -0
  48. package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
  49. package/registry/avatar/files/src/lib/kit.tsx +8 -5
  50. package/registry/badge/files/src/components/ui/badge.tsx +21 -7
  51. package/registry/badge/files/src/lib/kit.tsx +8 -5
  52. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +14 -7
  53. package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
  54. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
  55. package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
  56. package/registry/button/files/src/components/ui/button.tsx +4 -3
  57. package/registry/button/files/src/lib/kit.tsx +8 -5
  58. package/registry/button-3d/files/src/components/ui/button-3d.tsx +3 -2
  59. package/registry/button-3d/files/src/lib/kit.tsx +8 -5
  60. package/registry/calendar/files/src/components/ui/calendar.tsx +9 -5
  61. package/registry/calendar/files/src/lib/kit.tsx +8 -5
  62. package/registry/card/files/src/components/ui/card.tsx +8 -6
  63. package/registry/card/files/src/lib/kit.tsx +8 -5
  64. package/registry/checkbox/files/src/components/ui/checkbox.tsx +6 -5
  65. package/registry/checkbox/files/src/lib/kit.tsx +8 -5
  66. package/registry/chip-group/files/src/components/ui/chip-group.tsx +4 -2
  67. package/registry/chip-group/files/src/lib/kit.tsx +8 -5
  68. package/registry/confetti/files/src/lib/kit.tsx +8 -5
  69. package/registry/coupon/files/src/components/ui/coupon.tsx +23 -20
  70. package/registry/coupon/files/src/components/ui/flip-card.tsx +1 -1
  71. package/registry/coupon/files/src/components/ui/tilt-card.tsx +2 -2
  72. package/registry/coupon/files/src/lib/kit.tsx +8 -5
  73. package/registry/date-picker/files/src/components/ui/calendar.tsx +9 -5
  74. package/registry/date-picker/files/src/components/ui/date-picker.tsx +2 -2
  75. package/registry/date-picker/files/src/lib/kit.tsx +8 -5
  76. package/registry/dialog/files/src/components/ui/dialog.tsx +12 -6
  77. package/registry/dialog/files/src/lib/kit.tsx +8 -5
  78. package/registry/donut-shop.json +18 -0
  79. package/registry/drawer/files/src/components/ui/drawer.tsx +25 -19
  80. package/registry/drawer/files/src/lib/kit.tsx +8 -5
  81. package/registry/fintech-app.json +57 -0
  82. package/registry/flip-card/files/src/components/ui/flip-card.tsx +1 -1
  83. package/registry/flip-card/files/src/lib/kit.tsx +8 -5
  84. package/registry/floating-input/files/src/components/ui/floating-input.tsx +16 -6
  85. package/registry/floating-input/files/src/lib/kit.tsx +8 -5
  86. package/registry/food-app.json +55 -0
  87. package/registry/grain/files/src/components/ui/grain.tsx +113 -0
  88. package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
  89. package/registry/grain.json +17 -0
  90. package/registry/kit/files/src/lib/kit.tsx +8 -5
  91. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +1 -1
  92. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
  93. package/registry/list-item/files/src/components/ui/list-item.tsx +20 -16
  94. package/registry/list-item/files/src/lib/kit.tsx +8 -5
  95. package/registry/morphing-button-group.json +18 -0
  96. package/registry/orb-reveal.json +20 -0
  97. package/registry/otp-input/files/src/components/ui/otp-input.tsx +8 -6
  98. package/registry/otp-input/files/src/lib/kit.tsx +8 -5
  99. package/registry/password-input/files/src/components/ui/floating-input.tsx +16 -6
  100. package/registry/password-input/files/src/components/ui/password-input.tsx +2 -2
  101. package/registry/password-input/files/src/lib/kit.tsx +8 -5
  102. package/registry/payment-card/files/src/components/ui/flip-card.tsx +1 -1
  103. package/registry/payment-card/files/src/components/ui/payment-card.tsx +27 -14
  104. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +2 -2
  105. package/registry/payment-card/files/src/lib/kit.tsx +8 -5
  106. package/registry/progress/files/src/components/ui/progress.tsx +8 -5
  107. package/registry/progress/files/src/lib/kit.tsx +8 -5
  108. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +15 -11
  109. package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
  110. package/registry/qr-code-button.json +18 -0
  111. package/registry/radio-group/files/src/components/ui/radio-group.tsx +10 -9
  112. package/registry/radio-group/files/src/lib/kit.tsx +8 -5
  113. package/registry/search-bar/files/src/components/ui/search-bar.tsx +13 -11
  114. package/registry/search-bar/files/src/lib/kit.tsx +8 -5
  115. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +4 -2
  116. package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
  117. package/registry/select/files/src/components/ui/select.tsx +3 -3
  118. package/registry/select/files/src/lib/kit.tsx +8 -5
  119. package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
  120. package/registry/skeleton/files/src/lib/kit.tsx +8 -5
  121. package/registry/slider/files/src/components/ui/slider.tsx +5 -4
  122. package/registry/slider/files/src/lib/kit.tsx +8 -5
  123. package/registry/spinner/files/src/components/ui/spinner.tsx +17 -12
  124. package/registry/spinner/files/src/lib/kit.tsx +8 -5
  125. package/registry/stateful-button/files/src/lib/kit.tsx +8 -5
  126. package/registry/stepper/files/src/components/ui/stepper.tsx +8 -4
  127. package/registry/stepper/files/src/lib/kit.tsx +8 -5
  128. package/registry/switch/files/src/components/ui/switch.tsx +4 -3
  129. package/registry/switch/files/src/lib/kit.tsx +8 -5
  130. package/registry/tabs/files/src/components/ui/tabs.tsx +10 -8
  131. package/registry/tabs/files/src/lib/kit.tsx +8 -5
  132. package/registry/text-animation/files/src/components/ui/text-animation.tsx +4 -4
  133. package/registry/text-animation/files/src/lib/kit.tsx +8 -5
  134. package/registry/textarea/files/src/components/ui/textarea.tsx +11 -9
  135. package/registry/textarea/files/src/lib/kit.tsx +8 -5
  136. package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
  137. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +2 -2
  138. package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
  139. package/registry/time-picker/files/src/components/ui/time-picker.tsx +2 -2
  140. package/registry/time-picker/files/src/lib/kit.tsx +8 -5
  141. package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
  142. package/registry/timeline/files/src/components/ui/timeline.tsx +12 -10
  143. package/registry/timeline/files/src/lib/kit.tsx +8 -5
  144. package/registry/toast/files/src/components/ui/button.tsx +4 -3
  145. package/registry/toast/files/src/components/ui/toast.tsx +13 -11
  146. package/registry/toast/files/src/lib/kit.tsx +8 -5
  147. package/schema/components.schema.json +1 -1
  148. package/registry/_docs/color-swatch.json +0 -82
  149. package/registry/_docs/kpi-card.json +0 -14
  150. package/registry/_docs/like-button.json +0 -92
  151. package/registry/_docs/morph-button.json +0 -128
  152. package/registry/_docs/pagination-dots.json +0 -108
  153. package/registry/_docs/shimmer-button.json +0 -98
  154. package/registry/_docs/tag-input.json +0 -148
  155. package/registry/color-swatch.json +0 -17
  156. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
  157. package/registry/kpi-card.json +0 -14
  158. package/registry/like-button.json +0 -18
  159. package/registry/morph-button.json +0 -18
  160. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
  161. package/registry/pagination-dots/files/src/lib/kit.tsx +0 -219
  162. package/registry/pagination-dots.json +0 -15
  163. package/registry/shimmer-button.json +0 -17
  164. package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
  165. package/registry/tag-input.json +0 -17
@@ -137,7 +137,8 @@ export function Dialog({
137
137
  accent,
138
138
  style,
139
139
  }: DialogProps) {
140
- const { colors, radius, dark } = useKit();
140
+ const { colors, radius, dark, font } = useKit();
141
+ const ff = font.family ? { fontFamily: font.family } : null;
141
142
  const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
142
143
  const reduceMotion = useReduceMotion();
143
144
  const destructive = variant === 'destructive';
@@ -331,6 +332,7 @@ export function Dialog({
331
332
  selectionColor={accent ?? colors.accent}
332
333
  style={[
333
334
  ios ? styles.inputIos : styles.input,
335
+ ff,
334
336
  {
335
337
  color: colors.foreground,
336
338
  backgroundColor: alpha(colors.foreground, dark ? 0.07 : 0.04),
@@ -351,19 +353,19 @@ export function Dialog({
351
353
  {title ? (
352
354
  <Text
353
355
  accessibilityRole="header"
354
- style={[ios ? styles.titleIos : styles.title, { color: colors.foreground }, centred && styles.textCenter]}
356
+ style={[ios ? styles.titleIos : styles.title, ff, { color: colors.foreground }, centred && styles.textCenter]}
355
357
  >
356
358
  {title}
357
359
  </Text>
358
360
  ) : null}
359
361
  {description ? (
360
- <Text style={[ios ? styles.descIos : styles.desc, { color: colors.mutedForeground }, centred && styles.textCenter]}>
362
+ <Text style={[ios ? styles.descIos : styles.desc, ff, { color: colors.mutedForeground }, centred && styles.textCenter]}>
361
363
  {description}
362
364
  </Text>
363
365
  ) : null}
364
366
  {field ? <View style={ios ? styles.fieldIos : styles.field}>{field}</View> : null}
365
367
  {error ? (
366
- <Text accessibilityLiveRegion="polite" style={[styles.error, { color: colors.danger }, centred && styles.textCenter]}>
368
+ <Text accessibilityLiveRegion="polite" style={[styles.error, ff, { color: colors.danger }, centred && styles.textCenter]}>
367
369
  {error}
368
370
  </Text>
369
371
  ) : null}
@@ -514,6 +516,8 @@ function PillButton({
514
516
  onPress: () => void;
515
517
  style?: StyleProp<ViewStyle>;
516
518
  }) {
519
+ const { font } = useKit();
520
+ const ff = font.family ? { fontFamily: font.family } : null;
517
521
  const press = useRef(new Animated.Value(0)).current;
518
522
  const to = (v: number) => Animated.spring(press, { toValue: v, useNativeDriver: true, ...motion.snappy }).start();
519
523
  return (
@@ -538,7 +542,7 @@ function PillButton({
538
542
  },
539
543
  ]}
540
544
  >
541
- <Text numberOfLines={1} style={[styles.pillText, { color: fg, opacity: loading ? 0 : 1 }]}>
545
+ <Text numberOfLines={1} style={[styles.pillText, ff, { color: fg, opacity: loading ? 0 : 1 }]}>
542
546
  {label}
543
547
  </Text>
544
548
  {loading ? <ActivityIndicator style={StyleSheet.absoluteFill} size="small" color={fg} /> : null}
@@ -565,6 +569,8 @@ function IosButton({
565
569
  onPress: () => void;
566
570
  style?: StyleProp<ViewStyle>;
567
571
  }) {
572
+ const { font } = useKit();
573
+ const ff = font.family ? { fontFamily: font.family } : null;
568
574
  const hl = useRef(new Animated.Value(0)).current;
569
575
  return (
570
576
  <Pressable
@@ -581,7 +587,7 @@ function IosButton({
581
587
  style={[styles.iosButton, style]}
582
588
  >
583
589
  <Animated.View pointerEvents="none" style={[StyleSheet.absoluteFill, { backgroundColor: highlight, opacity: hl }]} />
584
- <Text numberOfLines={1} style={[styles.iosText, { color, opacity: loading ? 0 : disabled ? 0.4 : 1 }]}>
590
+ <Text numberOfLines={1} style={[styles.iosText, ff, { color, opacity: loading ? 0 : disabled ? 0.4 : 1 }]}>
585
591
  {label}
586
592
  </Text>
587
593
  {loading ? <ActivityIndicator style={StyleSheet.absoluteFill} size="small" color={color} /> : null}
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "donut-shop",
3
+ "type": "block",
4
+ "title": "Donut Shop",
5
+ "description": "A dessert shop screen where the products roll. Swipe and the donuts roll across the carousel like wheels; \"+\" grows into a stepper and an add-to-cart bar rises; tap the donut and it zooms out to the edge of the screen, still turning, beside its nutrition. The donuts are drawn — icing, sprinkles, drizzle — with light that stays put while they spin, or pass your own round product art.",
6
+ "tier": "pro",
7
+ "dependencies": [
8
+ "react-native-svg",
9
+ "expo-haptics"
10
+ ],
11
+ "registryDependencies": [
12
+ "kit"
13
+ ],
14
+ "sharedFiles": [
15
+ "lib/kit.tsx"
16
+ ],
17
+ "filesDir": null
18
+ }
@@ -1,5 +1,5 @@
1
1
  import * as Haptics from 'expo-haptics';
2
- import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
3
3
  import {
4
4
  AccessibilityInfo,
5
5
  Animated,
@@ -45,7 +45,7 @@ const SHADOW_PAD = 28;
45
45
  const PUSH_SCALE = 0.92;
46
46
  const PUSH_GAP = 14;
47
47
  /** Kit snappy with a touch more damping: the panel lands with a hint of overshoot, not a wobble. */
48
- const OPEN_SPRING = { ...motion.snappy, damping: 22 };
48
+ const OPEN_SPRING = motion.snappy;
49
49
  /** Closing is clamped, so it ends on the first crossing — exits feel faster than entrances. */
50
50
  const CLOSE_SPRING = { ...motion.snappy, overshootClamping: true };
51
51
 
@@ -226,7 +226,7 @@ export function Drawer({
226
226
  }, [isOpen, inline]);
227
227
 
228
228
  // Root position on the page, for the edge-swipe zone (gesture coordinates are page-based).
229
- const rootRef = useRef<View>(null);
229
+ const rootRef = useRef<ComponentRef<typeof View>>(null);
230
230
  const rootX = useRef(0);
231
231
  const measure = useCallback(() => {
232
232
  rootRef.current?.measure((_x, _y, _w, _h, pageX) => {
@@ -492,7 +492,8 @@ export interface DrawerHeaderProps {
492
492
 
493
493
  /** Title block with optional leading node and close button — drop into `header`. */
494
494
  export function DrawerHeader({ title, subtitle, leading, showClose = false, onClose, children, style }: DrawerHeaderProps) {
495
- const { colors, radius, dark } = useKit();
495
+ const { colors, radius, dark, font } = useKit();
496
+ const ff = font.family ? { fontFamily: font.family } : null;
496
497
  const drawerCtx = useContext(DrawerContext);
497
498
  const close = onClose ?? (() => drawerCtx?.close());
498
499
  const canClose = showClose || onClose !== undefined;
@@ -501,12 +502,12 @@ export function DrawerHeader({ title, subtitle, leading, showClose = false, onCl
501
502
  {leading ? <View>{leading}</View> : null}
502
503
  <View style={styles.headerText}>
503
504
  {title ? (
504
- <Text accessibilityRole="header" style={[styles.headerTitle, { color: colors.foreground }]} numberOfLines={1}>
505
+ <Text accessibilityRole="header" style={[styles.headerTitle, ff, { color: colors.foreground }]} numberOfLines={1}>
505
506
  {title}
506
507
  </Text>
507
508
  ) : null}
508
509
  {subtitle ? (
509
- <Text style={[styles.headerSub, { color: colors.mutedForeground }]} numberOfLines={1}>
510
+ <Text style={[styles.headerSub, ff, { color: colors.mutedForeground }]} numberOfLines={1}>
510
511
  {subtitle}
511
512
  </Text>
512
513
  ) : null}
@@ -569,7 +570,8 @@ export function DrawerProfile({
569
570
  accessibilityHint,
570
571
  style,
571
572
  }: DrawerProfileProps) {
572
- const { colors, radius, dark } = useKit();
573
+ const { colors, radius, dark, font } = useKit();
574
+ const ff = font.family ? { fontFamily: font.family } : null;
573
575
  const press = useRef(new Animated.Value(0)).current;
574
576
  const stacked = layout === 'stacked';
575
577
  const trailing =
@@ -587,11 +589,11 @@ export function DrawerProfile({
587
589
  ) : null;
588
590
  const text = (
589
591
  <View style={styles.profileText}>
590
- <Text numberOfLines={1} style={[styles.profileName, { color: colors.foreground }, stacked && styles.profileNameLg]}>
592
+ <Text numberOfLines={1} style={[styles.profileName, ff, { color: colors.foreground }, stacked && styles.profileNameLg]}>
591
593
  {name}
592
594
  </Text>
593
595
  {email ? (
594
- <Text numberOfLines={1} style={[styles.profileEmail, { color: colors.mutedForeground }]}>
596
+ <Text numberOfLines={1} style={[styles.profileEmail, ff, { color: colors.mutedForeground }]}>
595
597
  {email}
596
598
  </Text>
597
599
  ) : null}
@@ -637,7 +639,8 @@ export function DrawerProfile({
637
639
  }
638
640
 
639
641
  function ProfileAvatar({ name, src, size, status }: { name: string; src?: string; size: number; status?: DrawerPresence }) {
640
- const { colors, radius, dark } = useKit();
642
+ const { colors, radius, dark, font } = useKit();
643
+ const ff = font.family ? { fontFamily: font.family } : null;
641
644
  const ring = useContext(SurfaceContext) ?? colors.card;
642
645
  const hue = nameColor(name);
643
646
  const ink = dark ? mix(hue, colors.foreground, 0.35) : mix(hue, colors.foreground, 0.3);
@@ -670,7 +673,7 @@ function ProfileAvatar({ name, src, size, status }: { name: string; src?: string
670
673
  },
671
674
  ]}
672
675
  >
673
- <Text style={{ color: ink, fontSize: size * 0.36, fontWeight: '400', letterSpacing: -0.2 }}>{initials}</Text>
676
+ <Text style={[{ color: ink, fontSize: size * 0.36, fontWeight: '400', letterSpacing: -0.2 }, ff]}>{initials}</Text>
674
677
  {src ? <Image source={{ uri: src }} alt="" style={StyleSheet.absoluteFill} /> : null}
675
678
  </View>
676
679
  {status ? (
@@ -811,7 +814,8 @@ export function DrawerItem({
811
814
  disabled,
812
815
  style,
813
816
  }: DrawerItemProps) {
814
- const { colors, radius, dark } = useKit();
817
+ const { colors, radius, dark, font } = useKit();
818
+ const ff = font.family ? { fontFamily: font.family } : null;
815
819
  const drawer = useContext(DrawerContext);
816
820
  const reduceMotion = useReduceMotion();
817
821
  const danger = tone === 'danger';
@@ -910,7 +914,7 @@ export function DrawerItem({
910
914
  )}
911
915
  </Animated.View>
912
916
  ) : null}
913
- <Text numberOfLines={1} style={[styles.itemLabel, { color: labelColor }]}>
917
+ <Text numberOfLines={1} style={[styles.itemLabel, ff, { color: labelColor }]}>
914
918
  {label}
915
919
  </Text>
916
920
  {showBadge ? (
@@ -921,7 +925,7 @@ export function DrawerItem({
921
925
  { borderRadius: radius.capsule(22), backgroundColor: badgeColors.bg, transform: [{ scale: badgePop }] },
922
926
  ]}
923
927
  >
924
- <Text style={[styles.badgeText, { color: badgeColors.fg }]}>
928
+ <Text style={[styles.badgeText, ff, { color: badgeColors.fg }]}>
925
929
  {typeof badge === 'number' && badge > 99 ? '99+' : badge}
926
930
  </Text>
927
931
  </Animated.View>
@@ -943,12 +947,13 @@ export interface DrawerSectionProps {
943
947
 
944
948
  /** A quiet heading between groups of DrawerItems, with a hairline divider above it. */
945
949
  export function DrawerSection({ title, trailing, divider = true, style }: DrawerSectionProps) {
946
- const { colors, dark } = useKit();
950
+ const { colors, dark, font } = useKit();
951
+ const ff = font.family ? { fontFamily: font.family } : null;
947
952
  return (
948
953
  <View style={style}>
949
954
  {divider ? <View style={[styles.divider, { backgroundColor: alpha(colors.foreground, dark ? 0.1 : 0.07) }]} /> : null}
950
955
  <View style={[styles.section, !divider && styles.sectionFlush]}>
951
- <Text accessibilityRole="header" style={[styles.sectionText, { color: colors.mutedForeground }]}>
956
+ <Text accessibilityRole="header" style={[styles.sectionText, ff, { color: colors.mutedForeground }]}>
952
957
  {title}
953
958
  </Text>
954
959
  {trailing}
@@ -972,7 +977,8 @@ export interface DrawerFooterProps {
972
977
 
973
978
  /** A compact footer row — storage meter, app version, sync status — for the `footer` slot. */
974
979
  export function DrawerFooter({ label, detail, icon, progress, onPress, trailing, style }: DrawerFooterProps) {
975
- const { colors, radius, dark } = useKit();
980
+ const { colors, radius, dark, font } = useKit();
981
+ const ff = font.family ? { fontFamily: font.family } : null;
976
982
  const pct = progress === undefined ? undefined : Math.max(0, Math.min(1, progress));
977
983
  return (
978
984
  <Pressable
@@ -996,7 +1002,7 @@ export function DrawerFooter({ label, detail, icon, progress, onPress, trailing,
996
1002
  </View>
997
1003
  ) : null}
998
1004
  <View style={styles.footerText}>
999
- <Text numberOfLines={1} style={[styles.footerLabel, { color: colors.foreground }]}>
1005
+ <Text numberOfLines={1} style={[styles.footerLabel, ff, { color: colors.foreground }]}>
1000
1006
  {label}
1001
1007
  </Text>
1002
1008
  {pct !== undefined ? (
@@ -1012,7 +1018,7 @@ export function DrawerFooter({ label, detail, icon, progress, onPress, trailing,
1012
1018
  </View>
1013
1019
  ) : null}
1014
1020
  {detail ? (
1015
- <Text numberOfLines={1} style={[styles.footerDetail, { color: colors.mutedForeground }]}>
1021
+ <Text numberOfLines={1} style={[styles.footerDetail, ff, { color: colors.mutedForeground }]}>
1016
1022
  {detail}
1017
1023
  </Text>
1018
1024
  ) : null}
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "fintech-app",
3
+ "type": "template",
4
+ "title": "Business banking app",
5
+ "description": "A complete Expo Router app for a small-business bank account: onboarding once, phone + OTP sign in, a live revenue dashboard, cards you can reveal and freeze, slide-to-pay transfers, profile and settings.",
6
+ "tier": "pro",
7
+ "dependencies": [
8
+ "react-native-svg",
9
+ "expo-haptics",
10
+ "expo-router",
11
+ "expo-splash-screen",
12
+ "expo-status-bar",
13
+ "react-native-safe-area-context",
14
+ "react-native-screens",
15
+ "@react-native-async-storage/async-storage"
16
+ ],
17
+ "registryDependencies": [
18
+ "kit",
19
+ "bottom-nav",
20
+ "onboarding",
21
+ "sign-in",
22
+ "dashboard",
23
+ "payment-card",
24
+ "switch",
25
+ "avatar",
26
+ "chip-group",
27
+ "slide-to-confirm",
28
+ "profile",
29
+ "settings"
30
+ ],
31
+ "sharedFiles": [
32
+ "lib/kit.tsx",
33
+ "components/ui/bottom-nav.tsx",
34
+ "components/blocks/onboarding.tsx",
35
+ "components/ui/button.tsx",
36
+ "components/ui/floating-input.tsx",
37
+ "components/ui/password-input.tsx",
38
+ "components/ui/otp-input.tsx",
39
+ "components/blocks/sign-in.tsx",
40
+ "components/ui/avatar.tsx",
41
+ "components/ui/badge.tsx",
42
+ "components/ui/list-item.tsx",
43
+ "components/ui/number-ticker.tsx",
44
+ "components/ui/segmented-control.tsx",
45
+ "components/ui/skeleton.tsx",
46
+ "components/blocks/dashboard.tsx",
47
+ "components/ui/tilt-card.tsx",
48
+ "components/ui/flip-card.tsx",
49
+ "components/ui/payment-card.tsx",
50
+ "components/ui/switch.tsx",
51
+ "components/ui/chip-group.tsx",
52
+ "components/ui/slide-to-confirm.tsx",
53
+ "components/blocks/profile.tsx",
54
+ "components/blocks/settings.tsx"
55
+ ],
56
+ "filesDir": null
57
+ }
@@ -121,7 +121,7 @@ export function FlipCard({
121
121
  borderRadius: radius,
122
122
  opacity,
123
123
  transform: [{ perspective }, { [axis]: turn } as never, { scale: lift }],
124
- }) as Animated.WithAnimatedObject<ViewStyle>;
124
+ }) as Animated.WithAnimatedValue<ViewStyle>;
125
125
 
126
126
  // The shadow squeezes along the turning axis as the card goes edge-on (see SQUEEZE) and fades a little.
127
127
  const squeeze = p.interpolate(SQUEEZE);
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
1
+ import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState, type ComponentRef } from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Easing,
@@ -99,7 +99,7 @@ const NO_WEB_OUTLINE = Platform.OS === 'web' ? ({ outlineStyle: 'none', outlineW
99
99
  const AUTOFILL_CSS = `@keyframes ekAutofillOn{from{}to{}}@keyframes ekAutofillOff{from{}to{}}
100
100
  input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus{animation:ekAutofillOn 1ms;-webkit-text-fill-color:currentColor!important;caret-color:currentColor;transition:background-color 99999s 0s,color 99999s 0s!important}
101
101
  input:not(:-webkit-autofill){animation:ekAutofillOff 1ms}`;
102
- function useWebAutofill(inputRef: React.RefObject<TextInput | null>, onChange: (filled: boolean) => void) {
102
+ function useWebAutofill(inputRef: React.RefObject<ComponentRef<typeof TextInput> | null>, onChange: (filled: boolean) => void) {
103
103
  useEffect(() => {
104
104
  if (Platform.OS !== 'web' || typeof document === 'undefined') return;
105
105
  if (!document.getElementById('expokit-autofill')) {
@@ -126,7 +126,7 @@ function useWebAutofill(inputRef: React.RefObject<TextInput | null>, onChange: (
126
126
  * cutting a notch in the outline (Material-style). Focus draws a soft halo, errors shake,
127
127
  * success pops a check — all spring-driven.
128
128
  */
129
- export const FloatingInput = forwardRef<TextInput, FloatingInputProps>(function FloatingInput(
129
+ export const FloatingInput = forwardRef<ComponentRef<typeof TextInput>, FloatingInputProps>(function FloatingInput(
130
130
  {
131
131
  label,
132
132
  floatingLabel = true,
@@ -169,7 +169,7 @@ export const FloatingInput = forwardRef<TextInput, FloatingInputProps>(function
169
169
  const ff = font.family ? { fontFamily: font.family } : null;
170
170
  const S = FLOATING_INPUT_SIZES[size];
171
171
  // The user's border colour only replaces the resting one; numeric height / radius drive the layout maths.
172
- const { borderColor: userBorder, ...userField } = StyleSheet.flatten(fieldStyle) ?? {};
172
+ const { borderColor: userBorder, ...userField } = flattenView(fieldStyle);
173
173
  const userLabelColor = StyleSheet.flatten(labelStyleProp)?.color;
174
174
  const HEIGHT = typeof userField.height === 'number' ? userField.height : S.height;
175
175
  // Floated size stays relative to the resting size, so a bigger label floats bigger at the same ratio.
@@ -179,8 +179,8 @@ export const FloatingInput = forwardRef<TextInput, FloatingInputProps>(function
179
179
  const slot = S.icon + 10; // room for the clear button / check
180
180
  const tint = accent ?? colors.accent;
181
181
  const bg = surface ?? colors.background;
182
- const inputRef = useRef<TextInput>(null);
183
- useImperativeHandle(ref, () => inputRef.current as TextInput);
182
+ const inputRef = useRef<ComponentRef<typeof TextInput>>(null);
183
+ useImperativeHandle(ref, () => inputRef.current as ComponentRef<typeof TextInput>);
184
184
 
185
185
  const [focused, setFocused] = useState(false);
186
186
  const [inner, setInner] = useState(defaultValue ?? '');
@@ -500,6 +500,16 @@ function FieldMessage({ text, tone, color, style }: { text: string; tone: Messag
500
500
  );
501
501
  }
502
502
 
503
+ /**
504
+ * The user's field style as one object. Not StyleSheet.flatten: with TypeScript 6 its RN 0.88 signature
505
+ * rejects a plain StyleProp<ViewStyle> once expo/types adds web-only keys to ViewStyle.
506
+ */
507
+ function flattenView(style: StyleProp<ViewStyle>): ViewStyle {
508
+ if (!style || typeof style !== 'object') return {};
509
+ if (Array.isArray(style)) return Object.assign({}, ...style.map((s) => flattenView(s as StyleProp<ViewStyle>)));
510
+ return style as ViewStyle;
511
+ }
512
+
503
513
  const styles = StyleSheet.create({
504
514
  disabled: { opacity: 0.5 },
505
515
  halo: { position: 'absolute', top: -HALO, left: -HALO, right: -HALO, bottom: -HALO },
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "food-app",
3
+ "type": "template",
4
+ "title": "Food ordering app",
5
+ "description": "A complete Expo Router app for a donut shop: onboarding once, sign in, a rolling menu, coupons, bag with checkout, profile and settings with a working light / dark switch.",
6
+ "tier": "pro",
7
+ "dependencies": [
8
+ "react-native-svg",
9
+ "expo-haptics",
10
+ "expo-router",
11
+ "expo-splash-screen",
12
+ "expo-status-bar",
13
+ "react-native-safe-area-context",
14
+ "react-native-screens",
15
+ "@react-native-async-storage/async-storage"
16
+ ],
17
+ "registryDependencies": [
18
+ "kit",
19
+ "bottom-nav",
20
+ "onboarding",
21
+ "sign-in",
22
+ "donut-shop",
23
+ "offers",
24
+ "checkout",
25
+ "profile",
26
+ "settings"
27
+ ],
28
+ "sharedFiles": [
29
+ "lib/kit.tsx",
30
+ "components/ui/bottom-nav.tsx",
31
+ "components/blocks/onboarding.tsx",
32
+ "components/ui/button.tsx",
33
+ "components/ui/floating-input.tsx",
34
+ "components/ui/password-input.tsx",
35
+ "components/ui/otp-input.tsx",
36
+ "components/blocks/sign-in.tsx",
37
+ "components/blocks/donut-shop.tsx",
38
+ "components/ui/tilt-card.tsx",
39
+ "components/ui/flip-card.tsx",
40
+ "lib/ticket-shape.tsx",
41
+ "components/ui/coupon.tsx",
42
+ "components/ui/confetti.tsx",
43
+ "components/blocks/offers.tsx",
44
+ "components/ui/slide-to-confirm.tsx",
45
+ "components/ui/stepper.tsx",
46
+ "components/blocks/checkout.tsx",
47
+ "components/blocks/profile.tsx",
48
+ "components/ui/avatar.tsx",
49
+ "components/ui/badge.tsx",
50
+ "components/ui/segmented-control.tsx",
51
+ "components/ui/switch.tsx",
52
+ "components/blocks/settings.tsx"
53
+ ],
54
+ "filesDir": null
55
+ }