@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
|
@@ -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>
|
|
@@ -93,6 +93,10 @@ export interface TimelineProps {
|
|
|
93
93
|
style?: StyleProp<ViewStyle>;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
97
|
+
// content text scales all the way.
|
|
98
|
+
const FONT_CAP = 1.35;
|
|
99
|
+
|
|
96
100
|
const ENTER_STAGGER = 70;
|
|
97
101
|
const FILL_START = 160;
|
|
98
102
|
/** Where the fill point sits in the viewport at the top of the scroll (it slides to the bottom by the end). */
|
|
@@ -244,7 +248,7 @@ function ScrollTimeline({
|
|
|
244
248
|
const dottedFrom = Math.max(0, reach);
|
|
245
249
|
const dottedH = measured && reach < n - 1 ? nodes[n - 1] - nodes[dottedFrom] : 0;
|
|
246
250
|
|
|
247
|
-
let fillStyle: Animated.
|
|
251
|
+
let fillStyle: Animated.WithAnimatedValue<ViewStyle> | null = null;
|
|
248
252
|
if (fillH > 0) {
|
|
249
253
|
if (frozen) {
|
|
250
254
|
fillStyle = {};
|
|
@@ -379,7 +383,8 @@ function Row({
|
|
|
379
383
|
lit,
|
|
380
384
|
onNode,
|
|
381
385
|
}: RowProps) {
|
|
382
|
-
const { colors, radius, dark } = useKit();
|
|
386
|
+
const { colors, radius, dark, font } = useKit();
|
|
387
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
383
388
|
const compact = variant === 'compact';
|
|
384
389
|
const card = variant === 'card';
|
|
385
390
|
const scroll = variant === 'scroll';
|
|
@@ -454,21 +459,21 @@ function Row({
|
|
|
454
459
|
<View style={[styles.titleRow, compact && styles.compactRow]}>
|
|
455
460
|
<Text
|
|
456
461
|
numberOfLines={compact ? 1 : 2}
|
|
457
|
-
style={[styles.title, { fontSize: titleSize, lineHeight: titleLine, color: titleColor }]}
|
|
462
|
+
style={[styles.title, ff, { fontSize: titleSize, lineHeight: titleLine, color: titleColor }]}
|
|
458
463
|
>
|
|
459
464
|
{item.title}
|
|
460
465
|
</Text>
|
|
461
466
|
{item.time ? (
|
|
462
467
|
<Text
|
|
463
468
|
numberOfLines={1}
|
|
464
|
-
style={[styles.time, { lineHeight: titleLine, color: current ? tint : colors.mutedForeground }]}
|
|
469
|
+
style={[styles.time, ff, { lineHeight: titleLine, color: current ? tint : colors.mutedForeground }]}
|
|
465
470
|
>
|
|
466
471
|
{item.time}
|
|
467
472
|
</Text>
|
|
468
473
|
) : null}
|
|
469
474
|
</View>
|
|
470
475
|
{item.description && !compact ? (
|
|
471
|
-
<Text style={[styles.desc, { color: colors.mutedForeground }]}>{item.description}</Text>
|
|
476
|
+
<Text style={[styles.desc, ff, { color: colors.mutedForeground }]}>{item.description}</Text>
|
|
472
477
|
) : null}
|
|
473
478
|
{photos.length ? <TimelineGallery images={photos} dim={muted} /> : null}
|
|
474
479
|
{item.tags?.length || hasLinks ? (
|
|
@@ -478,7 +483,7 @@ function Row({
|
|
|
478
483
|
key={`t-${t}`}
|
|
479
484
|
style={[styles.tag, { borderRadius: radius.capsule(22), backgroundColor: alpha(tint, dark ? 0.2 : 0.1) }]}
|
|
480
485
|
>
|
|
481
|
-
<Text style={[styles.tagText, { color: tint }]}>{t}</Text>
|
|
486
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.tagText, ff, { color: tint }]}>{t}</Text>
|
|
482
487
|
</View>
|
|
483
488
|
))}
|
|
484
489
|
{item.links?.map((l) => (
|
|
@@ -490,14 +495,14 @@ function Row({
|
|
|
490
495
|
hitSlop={12}
|
|
491
496
|
style={({ pressed }) => [styles.link, { opacity: pressed ? 0.55 : 1 }]}
|
|
492
497
|
>
|
|
493
|
-
<Text style={[styles.linkText, { color: tint }]}>{l.label} →</Text>
|
|
498
|
+
<Text style={[styles.linkText, ff, { color: tint }]}>{l.label} →</Text>
|
|
494
499
|
</Pressable>
|
|
495
500
|
))}
|
|
496
501
|
</View>
|
|
497
502
|
) : null}
|
|
498
503
|
{item.content ? (
|
|
499
504
|
<View style={styles.content}>
|
|
500
|
-
{isTextNode(item.content) ? <Text style={{ color: colors.foreground }}>{item.content}</Text> : item.content}
|
|
505
|
+
{isTextNode(item.content) ? <Text style={[ff, { color: colors.foreground }]}>{item.content}</Text> : item.content}
|
|
501
506
|
</View>
|
|
502
507
|
) : null}
|
|
503
508
|
</>
|
|
@@ -927,7 +932,8 @@ function TimelineGallery({ images, dim }: { images: TimelineImageSource[]; dim:
|
|
|
927
932
|
}
|
|
928
933
|
|
|
929
934
|
function Photo({ uri, delay, style, more }: { uri: string; delay: number; style: StyleProp<ViewStyle>; more: number }) {
|
|
930
|
-
const { colors } = useKit();
|
|
935
|
+
const { colors, font } = useKit();
|
|
936
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
931
937
|
const shown = useRef(new Animated.Value(0)).current;
|
|
932
938
|
return (
|
|
933
939
|
<View style={[styles.photo, { backgroundColor: colors.muted }, style]}>
|
|
@@ -939,7 +945,7 @@ function Photo({ uri, delay, style, more }: { uri: string; delay: number; style:
|
|
|
939
945
|
/>
|
|
940
946
|
{more > 0 ? (
|
|
941
947
|
<View style={[StyleSheet.absoluteFill, styles.more]}>
|
|
942
|
-
<Text style={styles.moreText}>+{more}</Text>
|
|
948
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.moreText, ff]}>+{more}</Text>
|
|
943
949
|
</View>
|
|
944
950
|
) : null}
|
|
945
951
|
</View>
|
|
@@ -1037,10 +1043,11 @@ const styles = StyleSheet.create({
|
|
|
1037
1043
|
mini: { position: 'absolute', alignItems: 'center', justifyContent: 'center' },
|
|
1038
1044
|
bodyWrap: { flex: 1, minWidth: 0 },
|
|
1039
1045
|
card: { borderWidth: 1 },
|
|
1040
|
-
|
|
1046
|
+
// Wraps only when the time no longer fits beside the title (large text sizes): it drops below.
|
|
1047
|
+
titleRow: { flexDirection: 'row', flexWrap: 'wrap', alignItems: 'flex-start', gap: 10 },
|
|
1041
1048
|
compactRow: { alignItems: 'center' },
|
|
1042
1049
|
title: { flex: 1, fontWeight: '400', letterSpacing: -0.2 },
|
|
1043
|
-
time: { fontSize: 12.5, fontWeight: '400', fontVariant: ['tabular-nums'] },
|
|
1050
|
+
time: { flexShrink: 1, fontSize: 12.5, fontWeight: '400', fontVariant: ['tabular-nums'] },
|
|
1044
1051
|
desc: { fontSize: 14, fontWeight: '400', lineHeight: 20, marginTop: 3 },
|
|
1045
1052
|
gallery: { marginTop: 12, width: '100%', overflow: 'hidden', borderWidth: 1 },
|
|
1046
1053
|
grid: { flexDirection: 'row', gap: 3 },
|
|
@@ -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,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 },
|
|
@@ -271,6 +271,9 @@ export interface ToasterProps {
|
|
|
271
271
|
|
|
272
272
|
const PEEK = 12;
|
|
273
273
|
const GUESS_HEIGHT = 64;
|
|
274
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
275
|
+
// content text scales all the way.
|
|
276
|
+
const FONT_CAP = 1.35;
|
|
274
277
|
|
|
275
278
|
/**
|
|
276
279
|
* Hosts the toasts. It fills its parent absolutely (touches pass through), so drop it at the
|
|
@@ -417,7 +420,8 @@ function ToastItem({
|
|
|
417
420
|
onRemoved,
|
|
418
421
|
}: ToastItemProps) {
|
|
419
422
|
const toast = useToast();
|
|
420
|
-
const { colors, radius, dark } = useKit();
|
|
423
|
+
const { colors, radius, dark, font } = useKit();
|
|
424
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
421
425
|
const toward = edge === 'bottom' ? 1 : -1; // swipe direction that dismisses toward the edge
|
|
422
426
|
const last = useRef(layout ?? { y: 0, scale: 1, hidden: false, faded: false });
|
|
423
427
|
if (layout) last.current = layout;
|
|
@@ -505,8 +509,8 @@ function ToastItem({
|
|
|
505
509
|
},
|
|
506
510
|
onPanResponderTerminate: () => {
|
|
507
511
|
setTouching(false);
|
|
508
|
-
Animated.spring(dragX, { toValue: 0, useNativeDriver: true }).start();
|
|
509
|
-
Animated.spring(dragY, { toValue: 0, useNativeDriver: true }).start();
|
|
512
|
+
Animated.spring(dragX, { toValue: 0, ...motion.snappy, useNativeDriver: true }).start();
|
|
513
|
+
Animated.spring(dragY, { toValue: 0, ...motion.snappy, useNativeDriver: true }).start();
|
|
510
514
|
},
|
|
511
515
|
});
|
|
512
516
|
})(),
|
|
@@ -572,11 +576,11 @@ function ToastItem({
|
|
|
572
576
|
<Animated.View style={[styles.row, minimal ? styles.padMinimal : icon ? styles.padIcon : styles.pad, { opacity: content }]}>
|
|
573
577
|
{icon ? <View style={!minimal && data.description ? styles.iconTop : null}>{icon}</View> : null}
|
|
574
578
|
<View style={minimal ? styles.textMinimal : styles.text}>
|
|
575
|
-
<Text numberOfLines={minimal ? 1 : 3} style={[styles.title, { color: look.title }]}>
|
|
579
|
+
<Text numberOfLines={minimal ? 1 : 3} style={[styles.title, ff, { color: look.title }]}>
|
|
576
580
|
{data.title}
|
|
577
581
|
</Text>
|
|
578
582
|
{data.description ? (
|
|
579
|
-
<Text numberOfLines={minimal ? 2 : 4} style={[styles.desc, minimal && styles.descMinimal, { color: look.desc }]}>
|
|
583
|
+
<Text numberOfLines={minimal ? 2 : 4} style={[styles.desc, minimal && styles.descMinimal, ff, { color: look.desc }]}>
|
|
580
584
|
{data.description}
|
|
581
585
|
</Text>
|
|
582
586
|
) : null}
|
|
@@ -594,7 +598,9 @@ function ToastItem({
|
|
|
594
598
|
{ borderRadius: radius.capsule(32), backgroundColor: look.actionBg, opacity: pressed ? 0.7 : 1 },
|
|
595
599
|
]}
|
|
596
600
|
>
|
|
597
|
-
<Text style={[styles.actionText, { color: look.actionFg }]}>
|
|
601
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.actionText, ff, { color: look.actionFg }]}>
|
|
602
|
+
{data.action.label}
|
|
603
|
+
</Text>
|
|
598
604
|
</Pressable>
|
|
599
605
|
) : null}
|
|
600
606
|
{closeButton ? (
|
|
@@ -648,7 +654,8 @@ interface IslandToasterProps {
|
|
|
648
654
|
* fade are native-driven on their own wrapper views.
|
|
649
655
|
*/
|
|
650
656
|
function IslandToaster({ store, toasts, duration, offset, haptics, reduceMotion, style }: IslandToasterProps) {
|
|
651
|
-
const { colors, radius, dark } = useKit();
|
|
657
|
+
const { colors, radius, dark, font } = useKit();
|
|
658
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
652
659
|
const active = toasts.filter((t) => !t.dismissed);
|
|
653
660
|
const front = active[0];
|
|
654
661
|
|
|
@@ -893,18 +900,20 @@ function IslandToaster({ store, toasts, duration, offset, haptics, reduceMotion,
|
|
|
893
900
|
>
|
|
894
901
|
{icon ? <View style={expanded && shown.description ? styles.iconTop : null}>{icon}</View> : null}
|
|
895
902
|
<View style={styles.text}>
|
|
896
|
-
<Text numberOfLines={expanded ? 3 : 1} style={[styles.title, { color: fg }]}>
|
|
903
|
+
<Text numberOfLines={expanded ? 3 : 1} style={[styles.title, ff, { color: fg }]}>
|
|
897
904
|
{shown.title}
|
|
898
905
|
</Text>
|
|
899
906
|
{shown.description ? (
|
|
900
|
-
<Text numberOfLines={expanded ? undefined : 1} style={[styles.desc, { color: alpha(fg, 0.62) }]}>
|
|
907
|
+
<Text numberOfLines={expanded ? undefined : 1} style={[styles.desc, ff, { color: alpha(fg, 0.62) }]}>
|
|
901
908
|
{shown.description}
|
|
902
909
|
</Text>
|
|
903
910
|
) : null}
|
|
904
911
|
</View>
|
|
905
912
|
{!expanded && shown.action ? (
|
|
906
913
|
<View style={[styles.islandChip, { borderRadius: radius.capsule(24), backgroundColor: alpha(fg, 0.14) }]}>
|
|
907
|
-
<Text style={[styles.islandChipText, { color: fg }]}>
|
|
914
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.islandChipText, ff, { color: fg }]}>
|
|
915
|
+
{shown.action.label}
|
|
916
|
+
</Text>
|
|
908
917
|
</View>
|
|
909
918
|
) : null}
|
|
910
919
|
</Pressable>
|
|
@@ -921,7 +930,9 @@ function IslandToaster({ store, toasts, duration, offset, haptics, reduceMotion,
|
|
|
921
930
|
{ borderRadius: radius.capsule(44), backgroundColor: alpha(fg, pressed ? 0.24 : 0.16) },
|
|
922
931
|
]}
|
|
923
932
|
>
|
|
924
|
-
<Text style={[styles.title, { color: fg }]}>
|
|
933
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.title, ff, { color: fg }]}>
|
|
934
|
+
{shown.action.label}
|
|
935
|
+
</Text>
|
|
925
936
|
</Pressable>
|
|
926
937
|
</View>
|
|
927
938
|
) : null}
|
|
@@ -1265,7 +1276,7 @@ const styles = StyleSheet.create({
|
|
|
1265
1276
|
title: { fontSize: 14.5, fontWeight: '400', lineHeight: 20, letterSpacing: -0.1 },
|
|
1266
1277
|
desc: { fontSize: 13.5, fontWeight: '400', lineHeight: 19 },
|
|
1267
1278
|
descMinimal: { fontSize: 13, lineHeight: 17 },
|
|
1268
|
-
action: { paddingHorizontal: 12,
|
|
1279
|
+
action: { paddingHorizontal: 12, minHeight: 32, alignItems: 'center', justifyContent: 'center' },
|
|
1269
1280
|
actionText: { fontSize: 13, fontWeight: '400' },
|
|
1270
1281
|
close: { alignSelf: 'flex-start', padding: 2, marginRight: -4 },
|
|
1271
1282
|
bar: { position: 'absolute', left: 0, right: 0, bottom: 0, height: 2, transformOrigin: 'left' },
|
|
@@ -1275,10 +1286,10 @@ const styles = StyleSheet.create({
|
|
|
1275
1286
|
islandPlace: { position: 'absolute', top: 0 },
|
|
1276
1287
|
islandPad: { paddingVertical: 14, paddingHorizontal: 20 },
|
|
1277
1288
|
islandPadIcon: { paddingTop: 13, paddingBottom: 15, paddingLeft: 14, paddingRight: 18 },
|
|
1278
|
-
islandChip: { paddingHorizontal: 10,
|
|
1289
|
+
islandChip: { paddingHorizontal: 10, minHeight: 24, justifyContent: 'center' },
|
|
1279
1290
|
islandChipText: { fontSize: 12.5, fontWeight: '400' },
|
|
1280
1291
|
islandActions: { paddingHorizontal: 14, paddingBottom: 16, marginTop: -2 },
|
|
1281
|
-
islandAction: {
|
|
1292
|
+
islandAction: { minHeight: 44, alignItems: 'center', justifyContent: 'center' },
|
|
1282
1293
|
islandTrack: { position: 'absolute', left: 28, right: 28, bottom: 6, height: 2, borderRadius: 1, overflow: 'hidden' },
|
|
1283
1294
|
islandFill: { flex: 1, transformOrigin: 'left' },
|
|
1284
1295
|
});
|
|
@@ -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,82 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "color-swatch",
|
|
3
|
-
"category": "Selection",
|
|
4
|
-
"aliases": [],
|
|
5
|
-
"exportName": "ColorSwatch",
|
|
6
|
-
"files": [
|
|
7
|
-
"src/components/ui/color-swatch.tsx"
|
|
8
|
-
],
|
|
9
|
-
"summary": "A colour picker where the chosen swatch settles in as a ring springs out around it.",
|
|
10
|
-
"props": [
|
|
11
|
-
[
|
|
12
|
-
"colors",
|
|
13
|
-
"(string | { color, label? })[]",
|
|
14
|
-
"10-colour palette",
|
|
15
|
-
"Your swatches (hex)."
|
|
16
|
-
],
|
|
17
|
-
[
|
|
18
|
-
"value / onValueChange",
|
|
19
|
-
"string",
|
|
20
|
-
"—",
|
|
21
|
-
"Controlled selection (or use defaultValue)."
|
|
22
|
-
],
|
|
23
|
-
[
|
|
24
|
-
"defaultValue",
|
|
25
|
-
"string",
|
|
26
|
-
"—",
|
|
27
|
-
"Initial selection when uncontrolled."
|
|
28
|
-
],
|
|
29
|
-
[
|
|
30
|
-
"variant",
|
|
31
|
-
"\"circle\" | \"square\" | \"pill\"",
|
|
32
|
-
"\"circle\"",
|
|
33
|
-
"Swatch shape."
|
|
34
|
-
],
|
|
35
|
-
[
|
|
36
|
-
"size",
|
|
37
|
-
"\"sm\" | \"md\" | \"lg\"",
|
|
38
|
-
"\"md\"",
|
|
39
|
-
"28 / 36 / 44pt swatches."
|
|
40
|
-
],
|
|
41
|
-
[
|
|
42
|
-
"scrollable",
|
|
43
|
-
"boolean",
|
|
44
|
-
"false",
|
|
45
|
-
"One horizontally scrolling row instead of a wrapping grid."
|
|
46
|
-
],
|
|
47
|
-
[
|
|
48
|
-
"showLabels",
|
|
49
|
-
"boolean",
|
|
50
|
-
"false",
|
|
51
|
-
"Colour names under each swatch."
|
|
52
|
-
],
|
|
53
|
-
[
|
|
54
|
-
"disabled",
|
|
55
|
-
"boolean",
|
|
56
|
-
"false",
|
|
57
|
-
"Dims and blocks presses."
|
|
58
|
-
],
|
|
59
|
-
[
|
|
60
|
-
"style",
|
|
61
|
-
"StyleProp<ViewStyle>",
|
|
62
|
-
"—",
|
|
63
|
-
"Style for the wrapping grid (the ScrollView when scrollable)."
|
|
64
|
-
]
|
|
65
|
-
],
|
|
66
|
-
"behavior": [
|
|
67
|
-
[
|
|
68
|
-
"Touch",
|
|
69
|
-
"Tap a swatch to choose it. Each swatch's hit area is padded out to 44pt."
|
|
70
|
-
],
|
|
71
|
-
[
|
|
72
|
-
"Motion",
|
|
73
|
-
"The chosen swatch settles inward while a ring and check spring out. The check uses whichever ink reads best on that colour."
|
|
74
|
-
],
|
|
75
|
-
[
|
|
76
|
-
"Screen readers",
|
|
77
|
-
"A radiogroup labelled \"Colour\" by default; each swatch is a radio named by its label or hex value."
|
|
78
|
-
]
|
|
79
|
-
],
|
|
80
|
-
"usage": null,
|
|
81
|
-
"examples": []
|
|
82
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "like-button",
|
|
3
|
-
"category": "Buttons",
|
|
4
|
-
"aliases": [],
|
|
5
|
-
"exportName": "LikeButton",
|
|
6
|
-
"files": [
|
|
7
|
-
"src/components/ui/like-button.tsx"
|
|
8
|
-
],
|
|
9
|
-
"summary": "A heart, star or bookmark that pops and bursts into particles when liked.",
|
|
10
|
-
"props": [
|
|
11
|
-
[
|
|
12
|
-
"liked / onChange",
|
|
13
|
-
"boolean",
|
|
14
|
-
"—",
|
|
15
|
-
"Controlled state (or use defaultLiked)."
|
|
16
|
-
],
|
|
17
|
-
[
|
|
18
|
-
"defaultLiked",
|
|
19
|
-
"boolean",
|
|
20
|
-
"false",
|
|
21
|
-
"Initial state when uncontrolled; `count` is the count for this state."
|
|
22
|
-
],
|
|
23
|
-
[
|
|
24
|
-
"variant",
|
|
25
|
-
"\"heart\" | \"star\" | \"bookmark\"",
|
|
26
|
-
"\"heart\"",
|
|
27
|
-
"Heart, star or bookmark icon; also picks the default liked colour and label."
|
|
28
|
-
],
|
|
29
|
-
[
|
|
30
|
-
"count",
|
|
31
|
-
"number",
|
|
32
|
-
"—",
|
|
33
|
-
"Shows a rolling counter (1.2K formatting). Uncontrolled: the count for defaultLiked; the component adds the user's like."
|
|
34
|
-
],
|
|
35
|
-
[
|
|
36
|
-
"size",
|
|
37
|
-
"number",
|
|
38
|
-
"26",
|
|
39
|
-
"Icon size."
|
|
40
|
-
],
|
|
41
|
-
[
|
|
42
|
-
"color",
|
|
43
|
-
"string",
|
|
44
|
-
"danger / warning / accent",
|
|
45
|
-
"Liked colour (per variant default)."
|
|
46
|
-
],
|
|
47
|
-
[
|
|
48
|
-
"particleColors",
|
|
49
|
-
"string[]",
|
|
50
|
-
"liked colour + theme",
|
|
51
|
-
"Colours of the burst dots."
|
|
52
|
-
],
|
|
53
|
-
[
|
|
54
|
-
"haptics",
|
|
55
|
-
"boolean",
|
|
56
|
-
"true",
|
|
57
|
-
"Tap when liking."
|
|
58
|
-
],
|
|
59
|
-
[
|
|
60
|
-
"disabled",
|
|
61
|
-
"boolean",
|
|
62
|
-
"false",
|
|
63
|
-
"Blocks presses."
|
|
64
|
-
],
|
|
65
|
-
[
|
|
66
|
-
"style",
|
|
67
|
-
"StyleProp<ViewStyle>",
|
|
68
|
-
"—",
|
|
69
|
-
"Style for the pressable row holding the icon and counter."
|
|
70
|
-
]
|
|
71
|
-
],
|
|
72
|
-
"behavior": [
|
|
73
|
-
[
|
|
74
|
-
"Motion",
|
|
75
|
-
"Liking pops the icon, fills it on a bouncy spring and throws a ring and a burst of coloured dots. The counter rolls up or down to the new number."
|
|
76
|
-
],
|
|
77
|
-
[
|
|
78
|
-
"Haptics",
|
|
79
|
-
"A light impact when it is liked; un-liking is silent."
|
|
80
|
-
],
|
|
81
|
-
[
|
|
82
|
-
"Screen readers",
|
|
83
|
-
"A button labelled \"Like\", \"Favourite\" or \"Bookmark\" (or your label), with a selected state and the count as its value."
|
|
84
|
-
],
|
|
85
|
-
[
|
|
86
|
-
"Touch",
|
|
87
|
-
"Small icons get a hitSlop that brings the target up to 44pt."
|
|
88
|
-
]
|
|
89
|
-
],
|
|
90
|
-
"usage": null,
|
|
91
|
-
"examples": []
|
|
92
|
-
}
|