@expokit/cli 0.3.0 → 0.4.1
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.
- package/README.md +8 -4
- package/bin/expo-kit.js +137 -24
- package/bin/lib.mjs +110 -1
- package/bin/mcp.mjs +40 -18
- package/package.json +2 -2
- package/registry/_docs/accordion.json +1 -1
- package/registry/_docs/assistant-app.json +47 -0
- package/registry/_docs/avatar.json +6 -6
- package/registry/_docs/badge.json +4 -4
- package/registry/_docs/bottom-nav.json +6 -6
- package/registry/_docs/bottom-sheet.json +3 -3
- package/registry/_docs/bullet.json +6 -0
- package/registry/_docs/button-3d.json +3 -3
- package/registry/_docs/button.json +2 -2
- package/registry/_docs/calendar.json +5 -5
- package/registry/_docs/card.json +1 -1
- package/registry/_docs/checkout.json +7 -1
- package/registry/_docs/chip-group.json +1 -1
- package/registry/_docs/confetti.json +3 -3
- package/registry/_docs/date-picker.json +4 -4
- package/registry/_docs/donut-shop.json +88 -0
- package/registry/_docs/drawer.json +4 -4
- package/registry/_docs/fintech-app.json +49 -0
- package/registry/_docs/flip-card.json +4 -4
- package/registry/_docs/floating-input.json +5 -5
- package/registry/_docs/food-app.json +47 -0
- package/registry/_docs/glass.json +1 -1
- package/registry/_docs/grain.json +80 -0
- package/registry/_docs/isometric-bar.json +6 -0
- package/registry/_docs/liquidglass-tab.json +1 -1
- package/registry/_docs/morphing-button-group.json +96 -0
- package/registry/_docs/orb-reveal.json +132 -0
- package/registry/_docs/otp-input.json +4 -4
- package/registry/_docs/password-input.json +3 -3
- package/registry/_docs/payment-card.json +3 -3
- package/registry/_docs/pixel-bar.json +6 -0
- package/registry/_docs/polar-area.json +6 -0
- package/registry/_docs/progress-steps.json +4 -4
- package/registry/_docs/progress.json +2 -2
- package/registry/_docs/qr-code-button.json +122 -0
- package/registry/_docs/radio-group.json +1 -1
- package/registry/_docs/search-bar.json +4 -4
- package/registry/_docs/segmented-control.json +1 -1
- package/registry/_docs/select.json +7 -7
- package/registry/_docs/settings.json +6 -0
- package/registry/_docs/skeleton.json +3 -3
- package/registry/_docs/slider.json +6 -6
- package/registry/_docs/spinner.json +3 -3
- package/registry/_docs/stateful-button.json +1 -1
- package/registry/_docs/stepper.json +7 -7
- package/registry/_docs/tabs.json +2 -2
- package/registry/_docs/text-animation.json +4 -4
- package/registry/_docs/textarea.json +3 -3
- package/registry/_docs/ticket-shape.json +1 -1
- package/registry/_docs/tilt-card.json +4 -4
- package/registry/_docs/time-picker.json +6 -6
- package/registry/_docs/timeline-chart.json +6 -0
- package/registry/_docs/timeline.json +6 -6
- package/registry/_docs/toast.json +8 -8
- package/registry/_docs/treemap.json +6 -0
- package/registry/_docs/waterfall.json +6 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +36 -7
- package/registry/accordion/files/src/lib/kit.tsx +8 -5
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +12 -8
- package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/alert/files/src/components/ui/alert.tsx +11 -5
- package/registry/alert/files/src/components/ui/button.tsx +35 -6
- package/registry/alert/files/src/lib/kit.tsx +8 -5
- package/registry/assistant-app.json +49 -0
- package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
- package/registry/avatar/files/src/lib/kit.tsx +8 -5
- package/registry/badge/files/src/components/ui/badge.tsx +23 -9
- package/registry/badge/files/src/lib/kit.tsx +8 -5
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +21 -8
- package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
- package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
- package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/button/files/src/components/ui/button.tsx +35 -6
- package/registry/button/files/src/lib/kit.tsx +8 -5
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +35 -7
- package/registry/button-3d/files/src/lib/kit.tsx +8 -5
- package/registry/calendar/files/src/components/ui/calendar.tsx +68 -19
- package/registry/calendar/files/src/lib/kit.tsx +8 -5
- package/registry/card/files/src/components/ui/card.tsx +32 -10
- package/registry/card/files/src/lib/kit.tsx +8 -5
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +31 -8
- package/registry/checkbox/files/src/lib/kit.tsx +8 -5
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +39 -7
- package/registry/chip-group/files/src/lib/kit.tsx +8 -5
- package/registry/confetti/files/src/lib/kit.tsx +8 -5
- package/registry/coupon/files/src/components/ui/coupon.tsx +55 -28
- package/registry/coupon/files/src/components/ui/flip-card.tsx +23 -4
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +59 -18
- package/registry/coupon/files/src/lib/kit.tsx +8 -5
- package/registry/date-picker/files/src/components/ui/calendar.tsx +68 -19
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +98 -44
- package/registry/date-picker/files/src/lib/kit.tsx +8 -5
- package/registry/dialog/files/src/components/ui/dialog.tsx +19 -9
- package/registry/dialog/files/src/lib/kit.tsx +8 -5
- package/registry/donut-shop.json +18 -0
- package/registry/drawer/files/src/components/ui/drawer.tsx +31 -21
- package/registry/drawer/files/src/lib/kit.tsx +8 -5
- package/registry/fintech-app.json +57 -0
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +23 -4
- package/registry/flip-card/files/src/lib/kit.tsx +8 -5
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +91 -32
- package/registry/floating-input/files/src/lib/kit.tsx +8 -5
- package/registry/food-app.json +55 -0
- package/registry/grain/files/src/components/ui/grain.tsx +113 -0
- package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
- package/registry/grain.json +17 -0
- package/registry/kit/files/src/lib/kit.tsx +8 -5
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +2 -2
- package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
- package/registry/list-item/files/src/components/ui/list-item.tsx +28 -17
- package/registry/list-item/files/src/lib/kit.tsx +8 -5
- package/registry/morphing-button-group.json +18 -0
- package/registry/orb-reveal.json +20 -0
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +57 -24
- package/registry/otp-input/files/src/lib/kit.tsx +8 -5
- package/registry/password-input/files/src/components/ui/floating-input.tsx +91 -32
- package/registry/password-input/files/src/components/ui/password-input.tsx +55 -21
- package/registry/password-input/files/src/lib/kit.tsx +8 -5
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +23 -4
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +35 -18
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +59 -18
- package/registry/payment-card/files/src/lib/kit.tsx +8 -5
- package/registry/progress/files/src/components/ui/progress.tsx +16 -6
- package/registry/progress/files/src/lib/kit.tsx +8 -5
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +22 -11
- package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
- package/registry/qr-code-button.json +18 -0
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +48 -17
- package/registry/radio-group/files/src/lib/kit.tsx +8 -5
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +81 -26
- package/registry/search-bar/files/src/lib/kit.tsx +8 -5
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +40 -10
- package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
- package/registry/select/files/src/components/ui/select.tsx +161 -86
- package/registry/select/files/src/lib/kit.tsx +8 -5
- package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
- package/registry/skeleton/files/src/lib/kit.tsx +8 -5
- package/registry/slider/files/src/components/ui/slider.tsx +56 -27
- package/registry/slider/files/src/lib/kit.tsx +8 -5
- package/registry/spinner/files/src/components/ui/spinner.tsx +17 -12
- package/registry/spinner/files/src/lib/kit.tsx +8 -5
- package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +12 -4
- package/registry/stateful-button/files/src/lib/kit.tsx +8 -5
- package/registry/stepper/files/src/components/ui/stepper.tsx +83 -29
- package/registry/stepper/files/src/lib/kit.tsx +8 -5
- package/registry/switch/files/src/components/ui/switch.tsx +60 -22
- package/registry/switch/files/src/lib/kit.tsx +8 -5
- package/registry/tabs/files/src/components/ui/tabs.tsx +15 -10
- package/registry/tabs/files/src/lib/kit.tsx +8 -5
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +11 -6
- package/registry/text-animation/files/src/lib/kit.tsx +8 -5
- package/registry/textarea/files/src/components/ui/textarea.tsx +115 -45
- package/registry/textarea/files/src/lib/kit.tsx +8 -5
- package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +59 -18
- package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +156 -75
- package/registry/time-picker/files/src/lib/kit.tsx +8 -5
- package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
- package/registry/timeline/files/src/components/ui/timeline.tsx +19 -12
- package/registry/timeline/files/src/lib/kit.tsx +8 -5
- package/registry/toast/files/src/components/ui/button.tsx +35 -6
- package/registry/toast/files/src/components/ui/toast.tsx +25 -14
- package/registry/toast/files/src/lib/kit.tsx +8 -5
- package/schema/components.schema.json +1 -1
- package/registry/_docs/color-swatch.json +0 -82
- package/registry/_docs/kpi-card.json +0 -14
- package/registry/_docs/like-button.json +0 -92
- package/registry/_docs/morph-button.json +0 -128
- package/registry/_docs/pagination-dots.json +0 -108
- package/registry/_docs/shimmer-button.json +0 -98
- package/registry/_docs/tag-input.json +0 -148
- package/registry/color-swatch.json +0 -17
- package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
- package/registry/kpi-card.json +0 -14
- package/registry/like-button.json +0 -18
- package/registry/morph-button.json +0 -18
- package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
- package/registry/pagination-dots/files/src/lib/kit.tsx +0 -219
- package/registry/pagination-dots.json +0 -15
- package/registry/shimmer-button.json +0 -17
- package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
- 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
|
-
/**
|
|
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:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
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:
|
|
194
|
+
gentle: { damping: 27, stiffness: 160, mass: 1 },
|
|
192
195
|
fast: 160,
|
|
193
196
|
normal: 240,
|
|
194
197
|
};
|
|
@@ -64,6 +64,9 @@ export interface AlertProps {
|
|
|
64
64
|
style?: StyleProp<ViewStyle>;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
68
|
+
// content text scales all the way.
|
|
69
|
+
const FONT_CAP = 1.35;
|
|
67
70
|
const HALO = 34;
|
|
68
71
|
const HALO_COMPACT = 28;
|
|
69
72
|
|
|
@@ -89,7 +92,8 @@ export function Alert({
|
|
|
89
92
|
color,
|
|
90
93
|
style,
|
|
91
94
|
}: AlertProps) {
|
|
92
|
-
const { colors, radius, dark } = useKit();
|
|
95
|
+
const { colors, radius, dark, font } = useKit();
|
|
96
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
93
97
|
const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
94
98
|
const reduceMotion = useReduceMotion();
|
|
95
99
|
const tones: Record<AlertVariant, string> = {
|
|
@@ -322,9 +326,9 @@ export function Alert({
|
|
|
322
326
|
<>
|
|
323
327
|
{iconNode}
|
|
324
328
|
<View style={[styles.body, { paddingTop: textTop }]}>
|
|
325
|
-
{title ? <Text style={[styles.title, { color: fg }]}>{title}</Text> : null}
|
|
329
|
+
{title ? <Text style={[styles.title, ff, { color: fg }]}>{title}</Text> : null}
|
|
326
330
|
{typeof description === 'string' ? (
|
|
327
|
-
<Text style={[styles.desc, { color: title ? sub : fg }]}>{description}</Text>
|
|
331
|
+
<Text style={[styles.desc, ff, { color: title ? sub : fg }]}>{description}</Text>
|
|
328
332
|
) : (
|
|
329
333
|
description
|
|
330
334
|
)}
|
|
@@ -443,6 +447,8 @@ function Pill({
|
|
|
443
447
|
bg: string;
|
|
444
448
|
fg: string;
|
|
445
449
|
}) {
|
|
450
|
+
const { font } = useKit();
|
|
451
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
446
452
|
return (
|
|
447
453
|
<Pressable
|
|
448
454
|
accessibilityRole="button"
|
|
@@ -451,11 +457,11 @@ function Pill({
|
|
|
451
457
|
hitSlop={(44 - height) / 2}
|
|
452
458
|
style={({ pressed }) => [
|
|
453
459
|
styles.pill,
|
|
454
|
-
{ height, borderRadius: radius, backgroundColor: bg, paddingHorizontal: height >= 32 ? 14 : 12 },
|
|
460
|
+
{ minHeight: height, borderRadius: radius, backgroundColor: bg, paddingHorizontal: height >= 32 ? 14 : 12 },
|
|
455
461
|
pressed ? { opacity: 0.75, transform: [{ scale: 0.97 }] } : null,
|
|
456
462
|
]}
|
|
457
463
|
>
|
|
458
|
-
<Text style={[styles.pillText, { color: fg }]} numberOfLines={1}>
|
|
464
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.pillText, ff, { color: fg }]} numberOfLines={1}>
|
|
459
465
|
{action.label}
|
|
460
466
|
</Text>
|
|
461
467
|
</Pressable>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as Haptics from 'expo-haptics';
|
|
2
|
-
import React, { useId, useRef } from 'react';
|
|
2
|
+
import React, { useEffect, useId, useRef, useState } from 'react';
|
|
3
3
|
import {
|
|
4
|
+
AccessibilityInfo,
|
|
4
5
|
ActivityIndicator,
|
|
5
6
|
Animated,
|
|
6
7
|
Pressable,
|
|
@@ -55,6 +56,10 @@ export interface ButtonProps extends Omit<PressableProps, 'style' | 'children'>
|
|
|
55
56
|
textStyle?: StyleProp<TextStyle>;
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
60
|
+
// content text scales all the way.
|
|
61
|
+
const FONT_CAP = 1.35;
|
|
62
|
+
|
|
58
63
|
const SIZES: Record<ButtonSize, { h: number; px: number; font: number; gap: number }> = {
|
|
59
64
|
sm: { h: 36, px: 14, font: 14, gap: 6 },
|
|
60
65
|
md: { h: 46, px: 18, font: 15.5, gap: 8 },
|
|
@@ -116,8 +121,10 @@ export function Button({
|
|
|
116
121
|
onPressOut,
|
|
117
122
|
...props
|
|
118
123
|
}: ButtonProps) {
|
|
119
|
-
const { colors, radius, dark } = useKit();
|
|
124
|
+
const { colors, radius, dark, font } = useKit();
|
|
125
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
120
126
|
const gid = 'btn' + useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
127
|
+
const reduce = useReduceMotion();
|
|
121
128
|
const scale = useRef(new Animated.Value(1)).current;
|
|
122
129
|
const isDisabled = disabled || loading;
|
|
123
130
|
const tint = color ?? colors.accent;
|
|
@@ -144,12 +151,17 @@ export function Button({
|
|
|
144
151
|
const solid = bg !== 'transparent';
|
|
145
152
|
const quiet = variant === 'ghost' || variant === 'outline' || variant === 'link';
|
|
146
153
|
|
|
147
|
-
|
|
154
|
+
// Reduce Motion: no squish; the pressed wash / opacity still marks the press.
|
|
155
|
+
const spring = (to: number) => {
|
|
156
|
+
if (reduce) return scale.setValue(1);
|
|
157
|
+
Animated.spring(scale, { toValue: to, useNativeDriver: true, ...motion.bouncy }).start();
|
|
158
|
+
};
|
|
148
159
|
|
|
149
160
|
const label = isTextNode(children) ? (
|
|
150
161
|
<Text
|
|
151
162
|
numberOfLines={1}
|
|
152
|
-
|
|
163
|
+
maxFontSizeMultiplier={FONT_CAP}
|
|
164
|
+
style={[styles.label, ff, { color: fg, fontSize: s.font }, variant === 'link' && styles.link, textStyle]}
|
|
153
165
|
>
|
|
154
166
|
{children}
|
|
155
167
|
</Text>
|
|
@@ -184,7 +196,8 @@ export function Button({
|
|
|
184
196
|
style={({ pressed }) => [
|
|
185
197
|
styles.base,
|
|
186
198
|
{
|
|
187
|
-
|
|
199
|
+
minHeight: s.h,
|
|
200
|
+
height: size === 'icon' ? s.h : undefined,
|
|
188
201
|
width: size === 'icon' ? s.h : undefined,
|
|
189
202
|
paddingHorizontal: variant === 'link' ? 0 : s.px,
|
|
190
203
|
gap: s.gap,
|
|
@@ -235,7 +248,7 @@ export function Button({
|
|
|
235
248
|
<View style={styles.row}>
|
|
236
249
|
<ActivityIndicator size="small" color={fg} />
|
|
237
250
|
{size !== 'icon' && isTextNode(children) ? (
|
|
238
|
-
<Text style={[styles.label, { color: fg, fontSize: s.font, opacity: 0.85 }]}>{children}</Text>
|
|
251
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.label, ff, { color: fg, fontSize: s.font, opacity: 0.85 }]}>{children}</Text>
|
|
239
252
|
) : null}
|
|
240
253
|
</View>
|
|
241
254
|
) : (
|
|
@@ -252,6 +265,22 @@ export function Button({
|
|
|
252
265
|
);
|
|
253
266
|
}
|
|
254
267
|
|
|
268
|
+
function useReduceMotion() {
|
|
269
|
+
const [reduce, setReduce] = useState(false);
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
let alive = true;
|
|
272
|
+
AccessibilityInfo.isReduceMotionEnabled()
|
|
273
|
+
.then((r) => alive && setReduce(r))
|
|
274
|
+
.catch(() => {});
|
|
275
|
+
const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduce);
|
|
276
|
+
return () => {
|
|
277
|
+
alive = false;
|
|
278
|
+
sub.remove();
|
|
279
|
+
};
|
|
280
|
+
}, []);
|
|
281
|
+
return reduce;
|
|
282
|
+
}
|
|
283
|
+
|
|
255
284
|
const styles = StyleSheet.create({
|
|
256
285
|
base: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
|
|
257
286
|
row: { flexDirection: 'row', alignItems: 'center', gap: 8 },
|
|
@@ -181,14 +181,17 @@ export function useKit(): Theme {
|
|
|
181
181
|
return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
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:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
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:
|
|
194
|
+
gentle: { damping: 27, stiffness: 160, mass: 1 },
|
|
192
195
|
fast: 160,
|
|
193
196
|
normal: 240,
|
|
194
197
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "assistant-app",
|
|
3
|
+
"type": "template",
|
|
4
|
+
"title": "AI assistant app",
|
|
5
|
+
"description": "A complete Expo Router app for an AI chat assistant: onboarding once, Apple / Google / email-code sign in, a streaming chat with suggestions and stop, conversation history, 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
|
+
"chat",
|
|
23
|
+
"inbox",
|
|
24
|
+
"profile",
|
|
25
|
+
"settings"
|
|
26
|
+
],
|
|
27
|
+
"sharedFiles": [
|
|
28
|
+
"lib/kit.tsx",
|
|
29
|
+
"components/ui/bottom-nav.tsx",
|
|
30
|
+
"components/blocks/onboarding.tsx",
|
|
31
|
+
"components/ui/button.tsx",
|
|
32
|
+
"components/ui/floating-input.tsx",
|
|
33
|
+
"components/ui/password-input.tsx",
|
|
34
|
+
"components/ui/otp-input.tsx",
|
|
35
|
+
"components/blocks/sign-in.tsx",
|
|
36
|
+
"components/blocks/chat.tsx",
|
|
37
|
+
"components/ui/avatar.tsx",
|
|
38
|
+
"components/ui/badge.tsx",
|
|
39
|
+
"components/ui/chip-group.tsx",
|
|
40
|
+
"components/ui/search-bar.tsx",
|
|
41
|
+
"components/ui/swipeable-row.tsx",
|
|
42
|
+
"components/blocks/inbox.tsx",
|
|
43
|
+
"components/blocks/profile.tsx",
|
|
44
|
+
"components/ui/segmented-control.tsx",
|
|
45
|
+
"components/ui/switch.tsx",
|
|
46
|
+
"components/blocks/settings.tsx"
|
|
47
|
+
],
|
|
48
|
+
"filesDir": null
|
|
49
|
+
}
|
|
@@ -131,7 +131,8 @@ export function Avatar({
|
|
|
131
131
|
haptics = true,
|
|
132
132
|
style,
|
|
133
133
|
}: AvatarProps) {
|
|
134
|
-
const { colors, radius, dark } = useKit();
|
|
134
|
+
const { colors, radius, dark, font } = useKit();
|
|
135
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
135
136
|
const reduceMotion = useReduceMotion();
|
|
136
137
|
const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
137
138
|
const px = resolveSize(size);
|
|
@@ -280,6 +281,7 @@ export function Avatar({
|
|
|
280
281
|
numberOfLines={1}
|
|
281
282
|
style={[
|
|
282
283
|
styles.initials,
|
|
284
|
+
ff,
|
|
283
285
|
{
|
|
284
286
|
color: ink,
|
|
285
287
|
fontSize: Math.max(9, px * (initials.length > 1 ? 0.37 : 0.42)),
|
|
@@ -448,7 +450,7 @@ export function Avatar({
|
|
|
448
450
|
{badgeText ? (
|
|
449
451
|
<Text
|
|
450
452
|
allowFontScaling={false}
|
|
451
|
-
style={[styles.badgeText, { color: colors.dangerForeground, fontSize: Math.max(9, badgeH * 0.6) }]}
|
|
453
|
+
style={[styles.badgeText, ff, { color: colors.dangerForeground, fontSize: Math.max(9, badgeH * 0.6) }]}
|
|
452
454
|
>
|
|
453
455
|
{badgeText}
|
|
454
456
|
</Text>
|
|
@@ -623,7 +625,8 @@ export function AvatarGroup({
|
|
|
623
625
|
onPressMore,
|
|
624
626
|
style,
|
|
625
627
|
}: AvatarGroupProps) {
|
|
626
|
-
const { colors, radius, dark } = useKit();
|
|
628
|
+
const { colors, radius, dark, font } = useKit();
|
|
629
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
627
630
|
const reduceMotion = useReduceMotion();
|
|
628
631
|
const px = resolveSize(size);
|
|
629
632
|
const bg = surface ?? colors.background;
|
|
@@ -699,7 +702,7 @@ export function AvatarGroup({
|
|
|
699
702
|
<Text
|
|
700
703
|
allowFontScaling={false}
|
|
701
704
|
numberOfLines={1}
|
|
702
|
-
style={[styles.moreText, { color: colors.foreground, fontSize: Math.max(8, px * (extraText.length <= 2 ? 0.34 : extraText.length === 3 ? 0.29 : 0.24)) }]}
|
|
705
|
+
style={[styles.moreText, ff, { color: colors.foreground, fontSize: Math.max(8, px * (extraText.length <= 2 ? 0.34 : extraText.length === 3 ? 0.29 : 0.24)) }]}
|
|
703
706
|
>
|
|
704
707
|
+{extraText}
|
|
705
708
|
</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
|
-
/**
|
|
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:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
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:
|
|
194
|
+
gentle: { damping: 27, stiffness: 160, mass: 1 },
|
|
192
195
|
fast: 160,
|
|
193
196
|
normal: 240,
|
|
194
197
|
};
|
|
@@ -222,7 +222,7 @@ function span(v: Animated.Value, start: number, end: number, from: number, to: n
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
const easeIn = Easing.in(Easing.cubic);
|
|
225
|
-
const
|
|
225
|
+
const easeSettle = Easing.bezier(0.32, 0.72, 0, 1);
|
|
226
226
|
const easeLinear = Easing.linear;
|
|
227
227
|
|
|
228
228
|
/** One side of a label roll: letters (or the whole label) slide up out of / up into the clipped slot. */
|
|
@@ -243,6 +243,8 @@ function RollingLabel({
|
|
|
243
243
|
perLetter: boolean;
|
|
244
244
|
duration: number;
|
|
245
245
|
}) {
|
|
246
|
+
const { font } = useKit();
|
|
247
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
246
248
|
const pieces = perLetter ? [...text].map((ch) => (ch === ' ' ? ' ' : ch)) : [text];
|
|
247
249
|
const distance = Math.ceil(fontSize * 1.35);
|
|
248
250
|
return (
|
|
@@ -258,11 +260,11 @@ function RollingLabel({
|
|
|
258
260
|
style={{
|
|
259
261
|
opacity: out ? span(progress, begin, fade, 1, 0, easeLinear) : span(progress, begin, fade, 0, 1, easeLinear),
|
|
260
262
|
transform: [
|
|
261
|
-
{ translateY: out ? span(progress, begin, end, 0, -distance, easeIn) : span(progress, begin, end, distance, 0,
|
|
263
|
+
{ translateY: out ? span(progress, begin, end, 0, -distance, easeIn) : span(progress, begin, end, distance, 0, easeSettle) },
|
|
262
264
|
],
|
|
263
265
|
}}
|
|
264
266
|
>
|
|
265
|
-
<Text numberOfLines={1} allowFontScaling={false} style={[styles.label, { color, fontSize }]}>
|
|
267
|
+
<Text numberOfLines={1} allowFontScaling={false} style={[styles.label, ff, { color, fontSize }]}>
|
|
266
268
|
{piece}
|
|
267
269
|
</Text>
|
|
268
270
|
</Animated.View>
|
|
@@ -288,7 +290,8 @@ export function Badge({
|
|
|
288
290
|
animateChanges = true,
|
|
289
291
|
style,
|
|
290
292
|
}: BadgeProps) {
|
|
291
|
-
const { colors, radius, dark } = useKit();
|
|
293
|
+
const { colors, radius, dark, font } = useKit();
|
|
294
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
292
295
|
const reduce = useReduceMotion();
|
|
293
296
|
const s = SIZES[size];
|
|
294
297
|
const c = color ?? toneColor(colors, tone);
|
|
@@ -328,7 +331,7 @@ export function Badge({
|
|
|
328
331
|
const natural = useRef<number | null>(null);
|
|
329
332
|
const rowWidth = useRef<number | null>(null);
|
|
330
333
|
const widthV = useRef(new Animated.Value(0)).current;
|
|
331
|
-
const flat =
|
|
334
|
+
const flat = flattenView(style);
|
|
332
335
|
const canSize = flat.width == null && flat.minWidth == null && flat.flex == null && flat.flexGrow == null && flat.alignSelf !== 'stretch';
|
|
333
336
|
|
|
334
337
|
const textChanged = text !== last.text;
|
|
@@ -410,7 +413,7 @@ export function Badge({
|
|
|
410
413
|
<Animated.Text
|
|
411
414
|
numberOfLines={1}
|
|
412
415
|
allowFontScaling={false}
|
|
413
|
-
style={[styles.label, { color: from && !roll ? mix(from.fg, fg) : fg, fontSize: s.font }, roll ? styles.hidden : null]}
|
|
416
|
+
style={[styles.label, ff, { color: from && !roll ? mix(from.fg, fg) : fg, fontSize: s.font }, roll ? styles.hidden : null]}
|
|
414
417
|
>
|
|
415
418
|
{text}
|
|
416
419
|
</Animated.Text>
|
|
@@ -540,7 +543,8 @@ export function NotificationBadge({
|
|
|
540
543
|
offset = [0, 0],
|
|
541
544
|
style,
|
|
542
545
|
}: NotificationBadgeProps) {
|
|
543
|
-
const { colors } = useKit();
|
|
546
|
+
const { colors, font } = useKit();
|
|
547
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
544
548
|
const c = color ?? toneColor(colors, tone);
|
|
545
549
|
const fg = !color && tone === 'accent' ? colors.accentForeground : readableOn(c);
|
|
546
550
|
const visible = count > 0 || !!showZero;
|
|
@@ -556,7 +560,7 @@ export function NotificationBadge({
|
|
|
556
560
|
Animated.spring(show, { toValue: visible ? 1 : 0, useNativeDriver: true, ...motion.bouncy }).start();
|
|
557
561
|
if (visible && prev.current !== count && prev.current > 0) {
|
|
558
562
|
pop.setValue(1.35);
|
|
559
|
-
Animated.spring(pop, { toValue: 1, useNativeDriver: true, damping:
|
|
563
|
+
Animated.spring(pop, { toValue: 1, useNativeDriver: true, damping: 29, stiffness: 320, mass: 0.6 }).start();
|
|
560
564
|
}
|
|
561
565
|
prev.current = count;
|
|
562
566
|
}, [count, visible, show, pop]);
|
|
@@ -587,7 +591,7 @@ export function NotificationBadge({
|
|
|
587
591
|
]}
|
|
588
592
|
>
|
|
589
593
|
{dot ? null : (
|
|
590
|
-
<Text allowFontScaling={false} numberOfLines={1} style={[styles.count, { color: fg, fontSize: size === 'sm' ? 10 : 11.5 }]}>
|
|
594
|
+
<Text allowFontScaling={false} numberOfLines={1} style={[styles.count, ff, { color: fg, fontSize: size === 'sm' ? 10 : 11.5 }]}>
|
|
591
595
|
{text}
|
|
592
596
|
</Text>
|
|
593
597
|
)}
|
|
@@ -610,6 +614,16 @@ export function NotificationBadge({
|
|
|
610
614
|
);
|
|
611
615
|
}
|
|
612
616
|
|
|
617
|
+
/**
|
|
618
|
+
* The user's root style as one object. Not StyleSheet.flatten: with TypeScript 6 its RN 0.88 signature
|
|
619
|
+
* rejects a plain StyleProp<ViewStyle> once expo/types adds web-only keys to ViewStyle.
|
|
620
|
+
*/
|
|
621
|
+
function flattenView(style: StyleProp<ViewStyle>): ViewStyle {
|
|
622
|
+
if (!style || typeof style !== 'object') return {};
|
|
623
|
+
if (Array.isArray(style)) return Object.assign({}, ...style.map((s) => flattenView(s as StyleProp<ViewStyle>)));
|
|
624
|
+
return style as ViewStyle;
|
|
625
|
+
}
|
|
626
|
+
|
|
613
627
|
const styles = StyleSheet.create({
|
|
614
628
|
base: { flexDirection: 'row', alignItems: 'center', alignSelf: 'flex-start', borderWidth: 1 },
|
|
615
629
|
label: { fontWeight: '400', letterSpacing: 0.1 },
|
|
@@ -181,14 +181,17 @@ export function useKit(): Theme {
|
|
|
181
181
|
return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
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:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
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:
|
|
194
|
+
gentle: { damping: 27, stiffness: 160, mass: 1 },
|
|
192
195
|
fast: 160,
|
|
193
196
|
normal: 240,
|
|
194
197
|
};
|
|
@@ -99,6 +99,10 @@ export interface BottomNavProps {
|
|
|
99
99
|
style?: StyleProp<ViewStyle>;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
103
|
+
// content text scales all the way.
|
|
104
|
+
const FONT_CAP = 1.35;
|
|
105
|
+
|
|
102
106
|
const ROW_H = 60;
|
|
103
107
|
const PAD_X = 6;
|
|
104
108
|
const PAD_TOP = 6;
|
|
@@ -109,8 +113,8 @@ const PILL_H = 42;
|
|
|
109
113
|
/** Flex weight of the active item in the `pill` look (the others are 1). */
|
|
110
114
|
const PILL_GROW = 2.3;
|
|
111
115
|
// Liquid move: the edge in the direction of travel leads, the other one trails behind.
|
|
112
|
-
const LEAD = { damping:
|
|
113
|
-
const TRAIL = { damping:
|
|
116
|
+
const LEAD = { damping: 31, stiffness: 320, mass: 0.7 };
|
|
117
|
+
const TRAIL = { damping: 24, stiffness: 150, mass: 0.9 };
|
|
114
118
|
|
|
115
119
|
/**
|
|
116
120
|
* Bottom navigation in five looks — minimal dot, floating capsule, expanding pill, notched centre
|
|
@@ -444,6 +448,8 @@ interface NavButtonProps {
|
|
|
444
448
|
}
|
|
445
449
|
|
|
446
450
|
function NavButton({ item, active, look, tint, idle, surface, fab, showLabels, reduce, onPress }: NavButtonProps) {
|
|
451
|
+
const { font } = useKit();
|
|
452
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
447
453
|
const press = useRef(new Animated.Value(1)).current; // press squish (native)
|
|
448
454
|
const pop = useRef(new Animated.Value(active ? 1 : 0)).current; // active state (native)
|
|
449
455
|
const grow = useRef(new Animated.Value(active ? 1 : 0)).current; // pill width (JS — layout)
|
|
@@ -510,8 +516,10 @@ function NavButton({ item, active, look, tint, idle, surface, fab, showLabels, r
|
|
|
510
516
|
>
|
|
511
517
|
<Animated.Text
|
|
512
518
|
numberOfLines={1}
|
|
519
|
+
maxFontSizeMultiplier={FONT_CAP}
|
|
513
520
|
style={[
|
|
514
521
|
styles.pillLabel,
|
|
522
|
+
ff,
|
|
515
523
|
{
|
|
516
524
|
width: labelW + 1,
|
|
517
525
|
color: tint,
|
|
@@ -528,7 +536,7 @@ function NavButton({ item, active, look, tint, idle, surface, fab, showLabels, r
|
|
|
528
536
|
{/* Invisible measurer: the label's natural width, so it never ellipsizes while growing. */}
|
|
529
537
|
{showLabels ? (
|
|
530
538
|
<View pointerEvents="none" style={styles.measure} accessibilityElementsHidden importantForAccessibility="no-hide-descendants">
|
|
531
|
-
<Text style={[styles.pillLabel, styles.selfStart]} onLayout={(e) => setLabelW(Math.ceil(e.nativeEvent.layout.width))}>
|
|
539
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.pillLabel, styles.selfStart, ff]} onLayout={(e) => setLabelW(Math.ceil(e.nativeEvent.layout.width))}>
|
|
532
540
|
{item.label}
|
|
533
541
|
</Text>
|
|
534
542
|
</View>
|
|
@@ -558,9 +566,11 @@ function NavButton({ item, active, look, tint, idle, surface, fab, showLabels, r
|
|
|
558
566
|
{showLabels ? (
|
|
559
567
|
<Animated.Text
|
|
560
568
|
numberOfLines={1}
|
|
569
|
+
maxFontSizeMultiplier={FONT_CAP}
|
|
561
570
|
style={[
|
|
562
571
|
styles.label,
|
|
563
572
|
styles.floatLabel,
|
|
573
|
+
ff,
|
|
564
574
|
{ color: tint, opacity: fadeIn, transform: [{ translateY: pop.interpolate({ inputRange: [0, 1], outputRange: [6, 0] }) }] },
|
|
565
575
|
]}
|
|
566
576
|
>
|
|
@@ -587,7 +597,7 @@ function NavButton({ item, active, look, tint, idle, surface, fab, showLabels, r
|
|
|
587
597
|
{badge}
|
|
588
598
|
</Animated.View>
|
|
589
599
|
{showLabels ? (
|
|
590
|
-
<Text numberOfLines={1} style={[styles.label, { color: active ? tint : idle }]}>
|
|
600
|
+
<Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[styles.label, ff, { color: active ? tint : idle }]}>
|
|
591
601
|
{item.label}
|
|
592
602
|
</Text>
|
|
593
603
|
) : null}
|
|
@@ -653,6 +663,8 @@ function CentreAction({
|
|
|
653
663
|
showLabel: boolean;
|
|
654
664
|
onPress: () => void;
|
|
655
665
|
}) {
|
|
666
|
+
const { font } = useKit();
|
|
667
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
656
668
|
const press = useRef(new Animated.Value(1)).current;
|
|
657
669
|
const pressTo = (v: number) =>
|
|
658
670
|
Animated.spring(press, { toValue: v, useNativeDriver: true, ...(v < 1 ? motion.snappy : motion.bouncy) }).start();
|
|
@@ -671,7 +683,7 @@ function CentreAction({
|
|
|
671
683
|
</FabFace>
|
|
672
684
|
</Animated.View>
|
|
673
685
|
{showLabel ? (
|
|
674
|
-
<Text numberOfLines={1} style={[styles.label, styles.abs, { top: labelTop, color: labelColor }]}>
|
|
686
|
+
<Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[styles.label, styles.abs, ff, { top: labelTop, color: labelColor }]}>
|
|
675
687
|
{item.label}
|
|
676
688
|
</Text>
|
|
677
689
|
) : null}
|
|
@@ -758,7 +770,8 @@ function FabFace({
|
|
|
758
770
|
|
|
759
771
|
/** Dot or count. Pops in with a spring when it appears or its value changes. */
|
|
760
772
|
function Badge({ value, ring, reduce }: { value: true | number; ring: string; reduce: boolean }) {
|
|
761
|
-
const { colors, radius } = useKit();
|
|
773
|
+
const { colors, radius, font } = useKit();
|
|
774
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
762
775
|
const s = useRef(new Animated.Value(reduce ? 1 : 0.3)).current;
|
|
763
776
|
useEffect(() => {
|
|
764
777
|
if (reduce) {
|
|
@@ -784,7 +797,7 @@ function Badge({ value, ring, reduce }: { value: true | number; ring: string; re
|
|
|
784
797
|
},
|
|
785
798
|
]}
|
|
786
799
|
>
|
|
787
|
-
{!dot && <Text style={[styles.badgeText, { color: colors.dangerForeground }]}>{text}</Text>}
|
|
800
|
+
{!dot && <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.badgeText, ff, { color: colors.dangerForeground }]}>{text}</Text>}
|
|
788
801
|
</Animated.View>
|
|
789
802
|
);
|
|
790
803
|
}
|
|
@@ -1036,7 +1049,7 @@ const styles = StyleSheet.create({
|
|
|
1036
1049
|
top: -6,
|
|
1037
1050
|
right: -11,
|
|
1038
1051
|
minWidth: 19,
|
|
1039
|
-
|
|
1052
|
+
minHeight: 19,
|
|
1040
1053
|
paddingHorizontal: 4,
|
|
1041
1054
|
alignItems: 'center',
|
|
1042
1055
|
justifyContent: 'center',
|
|
@@ -181,14 +181,17 @@ export function useKit(): Theme {
|
|
|
181
181
|
return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
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:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
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:
|
|
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,
|
|
@@ -75,7 +75,7 @@ export interface BottomSheetProps {
|
|
|
75
75
|
|
|
76
76
|
const DEFAULT_BOTTOM_INSET = Platform.select({ ios: 20, android: 12, default: 16 }) ?? 16;
|
|
77
77
|
/** Kit gentle, firmed up — glides between detents without bouncing past them. */
|
|
78
|
-
const SNAP_SPRING = { ...motion.gentle, damping:
|
|
78
|
+
const SNAP_SPRING = { ...motion.gentle, damping: 29, stiffness: 220, mass: 0.9 };
|
|
79
79
|
/** Extra sheet below the screen edge, so pulling up past the top snap never shows a gap. */
|
|
80
80
|
const OVERDRAG = 120;
|
|
81
81
|
/** Never let the sheet's top go closer than this to the top of the screen. */
|
|
@@ -122,7 +122,8 @@ export function BottomSheet({
|
|
|
122
122
|
style,
|
|
123
123
|
contentStyle,
|
|
124
124
|
}: BottomSheetProps) {
|
|
125
|
-
const { colors, radius, dark } = useKit();
|
|
125
|
+
const { colors, radius, dark, font } = useKit();
|
|
126
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
126
127
|
const reduceMotion = useReduceMotion();
|
|
127
128
|
const { height: screen } = useWindowDimensions();
|
|
128
129
|
|
|
@@ -164,7 +165,7 @@ export function BottomSheet({
|
|
|
164
165
|
const shown = useRef(false); // auto: slid in once measured
|
|
165
166
|
const index = useRef(initialSnap);
|
|
166
167
|
const scrollY = useRef(0);
|
|
167
|
-
const rootRef = useRef<View
|
|
168
|
+
const rootRef = useRef<ComponentRef<typeof View>>(null);
|
|
168
169
|
|
|
169
170
|
const bodyDrags = dragFromContent ?? scrollable;
|
|
170
171
|
const live = useRef({ stops, closed, max, dismissible, onClose, onSnapChange, haptics, reduceMotion, scrollable, bodyDrags, expanded });
|
|
@@ -473,12 +474,12 @@ export function BottomSheet({
|
|
|
473
474
|
<View style={[styles.header, !handle && { paddingTop: 20 }]}>
|
|
474
475
|
<View style={styles.headerText}>
|
|
475
476
|
{title ? (
|
|
476
|
-
<Text accessibilityRole="header" style={[styles.title, { color: colors.foreground }]} numberOfLines={1}>
|
|
477
|
+
<Text accessibilityRole="header" style={[styles.title, ff, { color: colors.foreground }]} numberOfLines={1}>
|
|
477
478
|
{title}
|
|
478
479
|
</Text>
|
|
479
480
|
) : null}
|
|
480
481
|
{subtitle ? (
|
|
481
|
-
<Text style={[styles.subtitle, { color: colors.mutedForeground }]} numberOfLines={1}>
|
|
482
|
+
<Text style={[styles.subtitle, ff, { color: colors.mutedForeground }]} numberOfLines={1}>
|
|
482
483
|
{subtitle}
|
|
483
484
|
</Text>
|
|
484
485
|
) : 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
|
-
/**
|
|
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:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
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:
|
|
194
|
+
gentle: { damping: 27, stiffness: 160, mass: 1 },
|
|
192
195
|
fast: 160,
|
|
193
196
|
normal: 240,
|
|
194
197
|
};
|