@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
@@ -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, { createContext, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';
1
+ import React, { createContext, useContext, useEffect, useId, useMemo, useRef, useState, type ComponentRef } from 'react';
2
2
  import {
3
3
  AccessibilityInfo,
4
4
  Animated,
@@ -131,7 +131,7 @@ interface Frame {
131
131
 
132
132
  const GroupContext = createContext<{ frame: Frame | null } | null>(null);
133
133
 
134
- function measure(node: View | null, e: LayoutChangeEvent, done: (f: Frame) => void) {
134
+ function measure(node: ComponentRef<typeof View> | null, e: LayoutChangeEvent, done: (f: Frame) => void) {
135
135
  const { x, y, width, height } = e.nativeEvent.layout;
136
136
  const fallback = () => done({ x, y, w: width, h: height });
137
137
  if (!node || typeof node.measureInWindow !== 'function') return fallback();
@@ -153,7 +153,7 @@ export interface SkeletonGroupProps {
153
153
  */
154
154
  export function SkeletonGroup({ children, style }: SkeletonGroupProps) {
155
155
  const parent = useContext(GroupContext);
156
- const ref = useRef<View>(null);
156
+ const ref = useRef<ComponentRef<typeof View>>(null);
157
157
  const [frame, setFrame] = useState<Frame | null>(null);
158
158
  const value = useMemo(() => ({ frame }), [frame]);
159
159
  if (parent) return <View style={style}>{children}</View>;
@@ -182,7 +182,7 @@ export function Skeleton({ width = '100%', height = 14, radius = 'sm', variant =
182
182
  const t = useClock(moving);
183
183
  const group = useContext(GroupContext);
184
184
  const win = useWindowDimensions();
185
- const ref = useRef<View>(null);
185
+ const ref = useRef<ComponentRef<typeof View>>(null);
186
186
  const [box, setBox] = useState<Frame | null>(null);
187
187
  const gid = 'sk' + useId().replace(/[^a-zA-Z0-9]/g, '');
188
188
 
@@ -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
  };
@@ -161,7 +161,8 @@ export function Slider(props: SliderProps) {
161
161
  const onSlidingStart = props.onSlidingStart as Callback;
162
162
  const onSlidingComplete = props.onSlidingComplete as Callback;
163
163
 
164
- const { colors, radius, dark } = useKit();
164
+ const { colors, radius, dark, font } = useKit();
165
+ const ff = font.family ? { fontFamily: font.family } : null;
165
166
  const tint = accent ?? colors.accent;
166
167
  const onTint = accent ? readableOn(accent) : colors.accentForeground;
167
168
  const sz = SIZES[size];
@@ -412,7 +413,7 @@ export function Slider(props: SliderProps) {
412
413
  <View style={[styles.barContent, { width: w, height: trackH, paddingHorizontal: Math.round(trackH * 0.32) }]}>
413
414
  <View>{icon !== undefined ? drawIcon(icon, iconColor) : null}</View>
414
415
  {showValue === 'always' ? (
415
- <Text numberOfLines={1} selectable={false} style={[styles.barValue, { color: textColor, fontSize: sz.font + 1 }, labelStyle]}>
416
+ <Text numberOfLines={1} selectable={false} style={[styles.barValue, ff, { color: textColor, fontSize: sz.font + 1 }, labelStyle]}>
416
417
  {range ? `${fmt(vals[0])} – ${fmt(vals[1])}` : fmt(vals[0])}
417
418
  </Text>
418
419
  ) : null}
@@ -424,7 +425,7 @@ export function Slider(props: SliderProps) {
424
425
  key={key}
425
426
  numberOfLines={1}
426
427
  selectable={false}
427
- style={[styles.markLabel, place, { fontSize: sz.font - 1, color: lit ? colors.foreground : colors.mutedForeground }, labelStyle]}
428
+ style={[styles.markLabel, place, ff, { fontSize: sz.font - 1, color: lit ? colors.foreground : colors.mutedForeground }, labelStyle]}
428
429
  >
429
430
  {text}
430
431
  </Text>
@@ -569,7 +570,7 @@ export function Slider(props: SliderProps) {
569
570
  ]}
570
571
  >
571
572
  <View style={[styles.bubble, { backgroundColor: colors.foreground, borderRadius: radius.capsule(BUBBLE_H) }]}>
572
- <Text numberOfLines={1} selectable={false} style={[styles.bubbleText, { color: colors.background, fontSize: sz.font }, labelStyle]}>
573
+ <Text numberOfLines={1} selectable={false} style={[styles.bubbleText, ff, { color: colors.background, fontSize: sz.font }, labelStyle]}>
573
574
  {fmt(v)}
574
575
  </Text>
575
576
  </View>
@@ -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
  };
@@ -694,25 +694,30 @@ function Ellipsis({
694
694
  color: string;
695
695
  fontSize: number;
696
696
  }) {
697
+ const { font } = useKit();
698
+ const ff = font.family ? { fontFamily: font.family } : null;
697
699
  const t = useLoop(1400 / speed, linear, true, !reduce);
698
700
  const base = text.replace(/(…|\.{1,3})\s*$/, '');
699
701
  return (
700
702
  <View style={styles.ellipsis}>
701
- <Text style={[styles.label, { color, fontSize }]}>{base}</Text>
703
+ <Text style={[styles.label, ff, { color, fontSize }]}>{base}</Text>
702
704
  {[0, 1, 2].map((i) => (
703
705
  <Animated.Text
704
706
  key={i}
705
- style={{
706
- color,
707
- fontSize,
708
- fontWeight: '400',
709
- opacity: reduce
710
- ? 1
711
- : t.interpolate({
712
- inputRange: [0, 0.15 + i * 0.18, 0.3 + i * 0.18, 0.85, 1],
713
- outputRange: [0, 0, 1, 1, 0],
714
- }),
715
- }}
707
+ style={[
708
+ ff,
709
+ {
710
+ color,
711
+ fontSize,
712
+ fontWeight: '400',
713
+ opacity: reduce
714
+ ? 1
715
+ : t.interpolate({
716
+ inputRange: [0, 0.15 + i * 0.18, 0.3 + i * 0.18, 0.85, 1],
717
+ outputRange: [0, 0, 1, 1, 0],
718
+ }),
719
+ },
720
+ ]}
716
721
  >
717
722
  .
718
723
  </Animated.Text>
@@ -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
  };
@@ -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
  };
@@ -118,7 +118,8 @@ export function Stepper({
118
118
  buttonStyle,
119
119
  valueStyle,
120
120
  }: StepperProps) {
121
- const { colors, radius } = useKit();
121
+ const { colors, radius, font } = useKit();
122
+ const ff = font.family ? { fontFamily: font.family } : null;
122
123
  const variant: StepperVariant = variantProp in LEGACY ? LEGACY[variantProp as StepperLegacyVariant] : (variantProp as StepperVariant);
123
124
  const vertical = variant !== 'cart' && (orientation ? orientation === 'vertical' : variantProp === 'vertical');
124
125
  const tint = accent ?? colors.accent;
@@ -199,6 +200,7 @@ export function Stepper({
199
200
  fontSize,
200
201
  lineHeight: Math.round(fontSize * 1.3),
201
202
  color: numColor,
203
+ ...ff,
202
204
  ...flatValue,
203
205
  };
204
206
 
@@ -312,8 +314,8 @@ export function Stepper({
312
314
  return (
313
315
  <View style={[styles.labelled, style]}>
314
316
  <View style={styles.texts}>
315
- {title ? <Text style={[styles.label, { color: colors.foreground }]}>{title}</Text> : null}
316
- {description ? <Text style={[styles.description, { color: colors.mutedForeground }]}>{description}</Text> : null}
317
+ {title ? <Text style={[styles.label, ff, { color: colors.foreground }]}>{title}</Text> : null}
318
+ {description ? <Text style={[styles.description, ff, { color: colors.mutedForeground }]}>{description}</Text> : null}
317
319
  </View>
318
320
  {control}
319
321
  </View>
@@ -361,6 +363,8 @@ function CartControl({
361
363
  onAdd: () => void;
362
364
  onAddEnd: () => void;
363
365
  } & React.ComponentProps<typeof View>) {
366
+ const { font } = useKit();
367
+ const ff = font.family ? { fontFamily: font.family } : null;
364
368
  // Natural widths, measured off-screen so the spring always lands on the real size.
365
369
  const [textW, setTextW] = useState(0);
366
370
  const [addW, setAddW] = useState(0);
@@ -438,7 +442,7 @@ function CartControl({
438
442
  style={({ pressed }) => [styles.add, { height: s.btn, paddingHorizontal: Math.round(s.btn * 0.36) }, pressed && { backgroundColor: alpha(fg, 0.16) }]}
439
443
  >
440
444
  <Glyph d={ICON.plus} size={s.icon} color={fg} />
441
- <Text style={[styles.addText, { color: fg, fontSize: s.font - 2 }]}>{addLabel}</Text>
445
+ <Text style={[styles.addText, ff, { color: fg, fontSize: s.font - 2 }]}>{addLabel}</Text>
442
446
  </Pressable>
443
447
  </Animated.View>
444
448
  </View>
@@ -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
  };
@@ -63,7 +63,8 @@ export function Switch({
63
63
  accessibilityLabel,
64
64
  style,
65
65
  }: SwitchProps) {
66
- const { colors, dark } = useKit();
66
+ const { colors, dark, font } = useKit();
67
+ const ff = font.family ? { fontFamily: font.family } : null;
67
68
  const gid = 'sw' + useId().replace(/[^a-zA-Z0-9]/g, '');
68
69
  const tint = accent ?? colors.accent;
69
70
  const [inner, setInner] = useState(defaultValue);
@@ -315,8 +316,8 @@ export function Switch({
315
316
  return (
316
317
  <Pressable {...a11y} style={[styles.row, { opacity: disabled ? 0.45 : 1 }, style]}>
317
318
  <View style={styles.text}>
318
- <Text style={[styles.label, { color: colors.foreground }]}>{label}</Text>
319
- {description ? <Text style={[styles.description, { color: colors.mutedForeground }]}>{description}</Text> : null}
319
+ <Text style={[styles.label, ff, { color: colors.foreground }]}>{label}</Text>
320
+ {description ? <Text style={[styles.description, ff, { color: colors.mutedForeground }]}>{description}</Text> : null}
320
321
  </View>
321
322
  {control}
322
323
  </Pressable>
@@ -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,5 +1,5 @@
1
1
  import * as Haptics from 'expo-haptics';
2
- import React, { useEffect, useMemo, useRef, useState } from 'react';
2
+ import React, { useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
3
3
  import {
4
4
  AccessibilityInfo,
5
5
  Animated,
@@ -64,7 +64,7 @@ type Box = { x: number; w: number };
64
64
 
65
65
  // Liquid move: the edge in the direction of travel leads, the other one trails behind.
66
66
  const LEAD = motion.snappy;
67
- const TRAIL = { damping: 20, stiffness: 150, mass: 0.9 };
67
+ const TRAIL = { damping: 24, stiffness: 150, mass: 0.9 };
68
68
  const UNDERLINE = 3;
69
69
 
70
70
  /**
@@ -123,7 +123,7 @@ export function Tabs({
123
123
  const release = useRef({ v: 0, dragged: false });
124
124
  const paging = useRef(false); // pager spring in flight (its JS value is stale until it ends)
125
125
  const [settle, setSettle] = useState(0);
126
- const scrollRef = useRef<ScrollView>(null);
126
+ const scrollRef = useRef<ComponentRef<typeof ScrollView>>(null);
127
127
 
128
128
  const edgesOf = (i: number): [number, number] | null => {
129
129
  const t = tabs.current[i];
@@ -173,7 +173,7 @@ export function Tabs({
173
173
  Animated.timing(pan, { toValue: to, duration: 180, useNativeDriver: true }).start();
174
174
  } else {
175
175
  paging.current = true;
176
- Animated.spring(pan, { toValue: to, velocity: v, useNativeDriver: true, ...motion.snappy, damping: 24 }).start(
176
+ Animated.spring(pan, { toValue: to, velocity: v, useNativeDriver: true, ...motion.snappy, damping: 30 }).start(
177
177
  ({ finished }) => {
178
178
  if (finished) paging.current = false;
179
179
  }
@@ -425,7 +425,8 @@ interface TabProps {
425
425
  }
426
426
 
427
427
  function Tab({ item, active, mode, height, fontSize, tint, reduce, onPress, onLayout, onLabelLayout }: TabProps) {
428
- const { colors } = useKit();
428
+ const { colors, font } = useKit();
429
+ const ff = font.family ? { fontFamily: font.family } : null;
429
430
  const a = useRef(new Animated.Value(active ? 1 : 0)).current; // label colour (JS)
430
431
  const press = useRef(new Animated.Value(1)).current; // squish (native)
431
432
 
@@ -462,7 +463,7 @@ function Tab({ item, active, mode, height, fontSize, tint, reduce, onPress, onLa
462
463
  {/* Measured: the underline spans exactly this (icon + label). */}
463
464
  <Animated.View onLayout={onLabelLayout} style={[styles.labelRow, { transform: [{ scale: press }] }]}>
464
465
  {icon}
465
- <Animated.Text numberOfLines={1} style={[styles.label, { color, fontSize }]}>
466
+ <Animated.Text numberOfLines={1} style={[styles.label, ff, { color, fontSize }]}>
466
467
  {item.label}
467
468
  </Animated.Text>
468
469
  </Animated.View>
@@ -473,7 +474,8 @@ function Tab({ item, active, mode, height, fontSize, tint, reduce, onPress, onLa
473
474
 
474
475
  /** Count bubble: accent when its tab is active, muted otherwise. Pops when the value changes. */
475
476
  function TabBadge({ value, active, tint, reduce }: { value: number | string; active: boolean; tint: string; reduce: boolean }) {
476
- const { colors, radius, dark } = useKit();
477
+ const { colors, radius, dark, font } = useKit();
478
+ const ff = font.family ? { fontFamily: font.family } : null;
477
479
  const s = useRef(new Animated.Value(1)).current;
478
480
  const first = useRef(true);
479
481
  useEffect(() => {
@@ -496,7 +498,7 @@ function TabBadge({ value, active, tint, reduce }: { value: number | string; act
496
498
  },
497
499
  ]}
498
500
  >
499
- <Animated.Text style={[styles.badgeText, { color: active ? readableOn(tint) : colors.mutedForeground }]}>{text}</Animated.Text>
501
+ <Animated.Text style={[styles.badgeText, ff, { color: active ? readableOn(tint) : colors.mutedForeground }]}>{text}</Animated.Text>
500
502
  </Animated.View>
501
503
  );
502
504
  }
@@ -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
  };
@@ -114,10 +114,10 @@ function nextStop(text: string, from: number) {
114
114
 
115
115
  const easeOut = (t: number) => 1 - Math.pow(1 - t, 3);
116
116
  const easeInOut = (t: number) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);
117
- /** A soft spring (~8% overshoot) sampled as a curve, so it can run on the native driver. */
118
- const soft = (t: number) => 1 - Math.exp(-6.5 * t) * Math.cos(8 * t);
119
- /** A firmer settle (~4% overshoot). */
120
- const settle = (t: number) => 1 - Math.exp(-7 * t) * Math.cos(7 * t);
117
+ /** A soft, critically damped settle (no overshoot), sampled as a curve so it runs on the native driver. */
118
+ const soft = (t: number) => 1 - Math.exp(-7 * t) * (1 + 7 * t);
119
+ /** A firmer settle, also without overshoot. */
120
+ const settle = (t: number) => 1 - Math.exp(-8 * t) * (1 + 8 * t);
121
121
 
122
122
  /**
123
123
  * Maps a millisecond clock onto `from → to` between `start` and `start + dur` through `ease`.
@@ -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,
@@ -97,7 +97,7 @@ const AnimatedCircle = Animated.createAnimatedComponent(Circle);
97
97
  * optional floating label, a focus halo, a counter that morphs into a progress ring near
98
98
  * `maxLength`, and a `toolbar` slot for chat composers.
99
99
  */
100
- export const Textarea = forwardRef<TextInput, TextareaProps>(function Textarea(
100
+ export const Textarea = forwardRef<ComponentRef<typeof TextInput>, TextareaProps>(function Textarea(
101
101
  {
102
102
  label,
103
103
  floatingLabel = true,
@@ -143,16 +143,18 @@ export const Textarea = forwardRef<TextInput, TextareaProps>(function Textarea(
143
143
  const LINE = typeof userInput.lineHeight === 'number' ? userInput.lineHeight : Math.round((FONT * S.line) / S.font);
144
144
  const corner = typeof userField.borderRadius === 'number' ? userField.borderRadius : radius.md;
145
145
  // The hidden mirror must measure text exactly like the input does.
146
- const metrics: TextStyle = { ...ff };
147
- if (userInput.fontFamily !== undefined) metrics.fontFamily = userInput.fontFamily;
148
- if (userInput.fontWeight !== undefined) metrics.fontWeight = userInput.fontWeight;
149
- if (userInput.fontStyle !== undefined) metrics.fontStyle = userInput.fontStyle;
150
- if (userInput.letterSpacing !== undefined) metrics.letterSpacing = userInput.letterSpacing;
146
+ const metrics: TextStyle = {
147
+ ...ff,
148
+ ...(userInput.fontFamily !== undefined ? { fontFamily: userInput.fontFamily } : null),
149
+ ...(userInput.fontWeight !== undefined ? { fontWeight: userInput.fontWeight } : null),
150
+ ...(userInput.fontStyle !== undefined ? { fontStyle: userInput.fontStyle } : null),
151
+ ...(userInput.letterSpacing !== undefined ? { letterSpacing: userInput.letterSpacing } : null),
152
+ };
151
153
  const tint = accent ?? colors.accent;
152
154
  const bg = surface ?? colors.background;
153
155
  const filled = variant === 'filled';
154
- const inputRef = useRef<TextInput>(null);
155
- useImperativeHandle(ref, () => inputRef.current as TextInput);
156
+ const inputRef = useRef<ComponentRef<typeof TextInput>>(null);
157
+ useImperativeHandle(ref, () => inputRef.current as ComponentRef<typeof TextInput>);
156
158
 
157
159
  const [focused, setFocused] = useState(false);
158
160
  const [inner, setInner] = useState(defaultValue ?? '');
@@ -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
  };
@@ -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, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { createContext, useContext, useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Easing,
@@ -267,7 +267,7 @@ export function TiltCard({
267
267
  const touching = useRef(false);
268
268
  const origin = useRef({ x: 0, y: 0 });
269
269
  const moved = useRef(false);
270
- const viewRef = useRef<View>(null);
270
+ const viewRef = useRef<ComponentRef<typeof View>>(null);
271
271
  const disabledRef = useRef(disabled);
272
272
  disabledRef.current = disabled;
273
273
 
@@ -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, { memo, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { memo, useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Easing,
@@ -255,7 +255,7 @@ const WheelColumn = memo(function WheelColumn({ items, index, onIndexChange, lab
255
255
  const { colors, font } = useKit();
256
256
  const ff = font.family ? { fontFamily: font.family } : null;
257
257
  const scrollY = useRef(new Animated.Value(index * ITEM)).current; // native, driven by the scroll view
258
- const ref = useRef<ScrollView>(null);
258
+ const ref = useRef<ComponentRef<typeof ScrollView>>(null);
259
259
  const settled = useRef(index);
260
260
  const dragging = useRef(false);
261
261
  const lastY = useRef(index * ITEM);