@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.
- 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/assistant-app.json +47 -0
- package/registry/_docs/avatar.json +1 -1
- package/registry/_docs/badge.json +1 -1
- package/registry/_docs/bottom-nav.json +6 -6
- package/registry/_docs/button-3d.json +2 -2
- package/registry/_docs/calendar.json +5 -5
- package/registry/_docs/checkout.json +7 -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/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/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/qr-code-button.json +122 -0
- package/registry/_docs/search-bar.json +3 -3
- package/registry/_docs/select.json +7 -7
- package/registry/_docs/settings.json +6 -0
- package/registry/_docs/skeleton.json +2 -2
- package/registry/_docs/slider.json +6 -6
- package/registry/_docs/spinner.json +1 -1
- package/registry/_docs/stepper.json +7 -7
- package/registry/_docs/textarea.json +2 -2
- 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.json +2 -2
- package/registry/_docs/toast.json +8 -8
- package/registry/accordion/files/src/components/ui/accordion.tsx +5 -4
- package/registry/accordion/files/src/lib/kit.tsx +8 -5
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +9 -8
- package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/alert/files/src/components/ui/alert.tsx +7 -4
- package/registry/alert/files/src/components/ui/button.tsx +4 -3
- 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 +21 -7
- package/registry/badge/files/src/lib/kit.tsx +8 -5
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +14 -7
- 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 +4 -3
- package/registry/button/files/src/lib/kit.tsx +8 -5
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +3 -2
- package/registry/button-3d/files/src/lib/kit.tsx +8 -5
- package/registry/calendar/files/src/components/ui/calendar.tsx +9 -5
- package/registry/calendar/files/src/lib/kit.tsx +8 -5
- package/registry/card/files/src/components/ui/card.tsx +8 -6
- package/registry/card/files/src/lib/kit.tsx +8 -5
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +6 -5
- package/registry/checkbox/files/src/lib/kit.tsx +8 -5
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +4 -2
- 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 +23 -20
- package/registry/coupon/files/src/components/ui/flip-card.tsx +1 -1
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +2 -2
- package/registry/coupon/files/src/lib/kit.tsx +8 -5
- package/registry/date-picker/files/src/components/ui/calendar.tsx +9 -5
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +2 -2
- package/registry/date-picker/files/src/lib/kit.tsx +8 -5
- package/registry/dialog/files/src/components/ui/dialog.tsx +12 -6
- 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 +25 -19
- 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 +1 -1
- package/registry/flip-card/files/src/lib/kit.tsx +8 -5
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +16 -6
- 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 +1 -1
- package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
- package/registry/list-item/files/src/components/ui/list-item.tsx +20 -16
- 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 +8 -6
- package/registry/otp-input/files/src/lib/kit.tsx +8 -5
- package/registry/password-input/files/src/components/ui/floating-input.tsx +16 -6
- package/registry/password-input/files/src/components/ui/password-input.tsx +2 -2
- package/registry/password-input/files/src/lib/kit.tsx +8 -5
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +1 -1
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +27 -14
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +2 -2
- package/registry/payment-card/files/src/lib/kit.tsx +8 -5
- package/registry/progress/files/src/components/ui/progress.tsx +8 -5
- package/registry/progress/files/src/lib/kit.tsx +8 -5
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +15 -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 +10 -9
- package/registry/radio-group/files/src/lib/kit.tsx +8 -5
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +13 -11
- package/registry/search-bar/files/src/lib/kit.tsx +8 -5
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +4 -2
- package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
- package/registry/select/files/src/components/ui/select.tsx +3 -3
- 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 +5 -4
- 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/lib/kit.tsx +8 -5
- package/registry/stepper/files/src/components/ui/stepper.tsx +8 -4
- package/registry/stepper/files/src/lib/kit.tsx +8 -5
- package/registry/switch/files/src/components/ui/switch.tsx +4 -3
- package/registry/switch/files/src/lib/kit.tsx +8 -5
- package/registry/tabs/files/src/components/ui/tabs.tsx +10 -8
- package/registry/tabs/files/src/lib/kit.tsx +8 -5
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +4 -4
- package/registry/text-animation/files/src/lib/kit.tsx +8 -5
- package/registry/textarea/files/src/components/ui/textarea.tsx +11 -9
- 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 +2 -2
- package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +2 -2
- 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 +12 -10
- package/registry/timeline/files/src/lib/kit.tsx +8 -5
- package/registry/toast/files/src/components/ui/button.tsx +4 -3
- package/registry/toast/files/src/components/ui/toast.tsx +13 -11
- 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
|
@@ -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
|
-
/**
|
|
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,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 =
|
|
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
|
|
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
|
-
/**
|
|
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,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.
|
|
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
|
-
/**
|
|
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,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
|
|
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 } =
|
|
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
|
|
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
|
-
/**
|
|
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,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
|
+
}
|