@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
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState, type ComponentRef } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
AccessibilityInfo,
|
|
3
4
|
Animated,
|
|
4
5
|
Easing,
|
|
5
6
|
Modal,
|
|
@@ -132,6 +133,9 @@ type FlatProps = DatePickerBaseProps & {
|
|
|
132
133
|
presets?: boolean | DatePickerPreset<Picked>[];
|
|
133
134
|
};
|
|
134
135
|
|
|
136
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
137
|
+
// content text scales all the way.
|
|
138
|
+
const FONT_CAP = 1.35;
|
|
135
139
|
const SIZES: Record<DatePickerSize, { h: number; font: number; pad: number; icon: number }> = {
|
|
136
140
|
sm: { h: 46, font: 15, pad: 12, icon: 17 },
|
|
137
141
|
md: { h: 56, font: 16, pad: 16, icon: 19 },
|
|
@@ -241,6 +245,10 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
241
245
|
const restIcon = iconColor ?? colors.mutedForeground;
|
|
242
246
|
const { width: winW, height: winH } = useWindowDimensions();
|
|
243
247
|
const ranged = mode === 'range';
|
|
248
|
+
const reduce = useReduceMotion();
|
|
249
|
+
// The pan responder is built once; it reads Reduce Motion through this ref.
|
|
250
|
+
const live = useRef({ reduce });
|
|
251
|
+
live.current = { reduce };
|
|
244
252
|
|
|
245
253
|
const [inner, setInner] = useState<Picked>(defaultValue);
|
|
246
254
|
const current = value !== undefined ? value : inner;
|
|
@@ -251,7 +259,7 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
251
259
|
const [draft, setDraft] = useState<Picked>(null);
|
|
252
260
|
const [calMonth, setCalMonth] = useState(new Date());
|
|
253
261
|
const [anchor, setAnchor] = useState({ x: 0, y: 0, w: 0, h: 0 });
|
|
254
|
-
const fieldRef = useRef<View
|
|
262
|
+
const fieldRef = useRef<ComponentRef<typeof View>>(null);
|
|
255
263
|
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
256
264
|
|
|
257
265
|
const ok = !error && !!success;
|
|
@@ -269,26 +277,36 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
269
277
|
const wide = presentation === 'dialog' || (presentation === 'auto' && winW >= 640);
|
|
270
278
|
|
|
271
279
|
useEffect(() => {
|
|
280
|
+
if (reduce) {
|
|
281
|
+
float.setValue(floating ? 1 : 0);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
272
284
|
Animated.spring(float, { toValue: floating ? 1 : 0, useNativeDriver: true, ...motion.snappy }).start();
|
|
273
|
-
}, [floating, float]);
|
|
285
|
+
}, [floating, float, reduce]);
|
|
274
286
|
useEffect(() => {
|
|
275
287
|
Animated.timing(focus, { toValue: open ? 1 : 0, duration: 180, easing: Easing.out(Easing.quad), useNativeDriver: false }).start();
|
|
276
|
-
|
|
277
|
-
|
|
288
|
+
// Reduce Motion: the halo fades in place (its scale interpolation is flat).
|
|
289
|
+
if (reduce) Animated.timing(halo, { toValue: open ? 1 : 0, duration: 150, useNativeDriver: true }).start();
|
|
290
|
+
else Animated.spring(halo, { toValue: open ? 1 : 0, useNativeDriver: true, ...motion.snappy }).start();
|
|
291
|
+
}, [open, focus, halo, reduce]);
|
|
278
292
|
useEffect(() => {
|
|
279
|
-
Animated.
|
|
280
|
-
|
|
293
|
+
if (reduce) Animated.timing(check, { toValue: ok ? 1 : 0, duration: 150, useNativeDriver: true }).start();
|
|
294
|
+
else Animated.spring(check, { toValue: ok ? 1 : 0, useNativeDriver: true, ...motion.bouncy }).start();
|
|
295
|
+
}, [ok, check, reduce]);
|
|
281
296
|
useEffect(() => {
|
|
282
|
-
|
|
297
|
+
// Reduce Motion: no shake — the red border and message carry the error.
|
|
298
|
+
if (!error || live.current.reduce) return;
|
|
283
299
|
shake.setValue(0);
|
|
284
300
|
Animated.sequence([10, -8, 6, -4, 0].map((x) => Animated.timing(shake, { toValue: x, duration: 55, useNativeDriver: true }))).start();
|
|
285
301
|
}, [error, shake]);
|
|
286
302
|
useEffect(() => {
|
|
287
303
|
if (open) {
|
|
288
304
|
drag.setValue(0);
|
|
289
|
-
|
|
305
|
+
// Reduce Motion: the panel fades in place (its translate / scale interpolations are flat).
|
|
306
|
+
if (reduce) Animated.timing(show, { toValue: 1, duration: 150, useNativeDriver: true }).start();
|
|
307
|
+
else Animated.spring(show, { toValue: 1, useNativeDriver: true, ...(asSheet ? motion.gentle : motion.snappy) }).start();
|
|
290
308
|
} else if (mounted) {
|
|
291
|
-
Animated.timing(show, { toValue: 0, duration: motion.fast + 40, easing: Easing.in(Easing.quad), useNativeDriver: true }).start(
|
|
309
|
+
Animated.timing(show, { toValue: 0, duration: reduce ? 150 : motion.fast + 40, easing: Easing.in(Easing.quad), useNativeDriver: true }).start(
|
|
292
310
|
({ finished }) => finished && setMounted(false)
|
|
293
311
|
);
|
|
294
312
|
}
|
|
@@ -395,21 +413,23 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
395
413
|
const same = (a: Picked, b: Picked) =>
|
|
396
414
|
isRange(a) && isRange(b) ? isSameDay(a.start, b.start) && isSameDay(a.end, b.end) : !isRange(a) && !isRange(b) && isSameDay(a, b);
|
|
397
415
|
|
|
398
|
-
const pan = useMemo(
|
|
399
|
-
() =>
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
416
|
+
const pan = useMemo(() => {
|
|
417
|
+
const settle = () =>
|
|
418
|
+
live.current.reduce
|
|
419
|
+
? Animated.timing(drag, { toValue: 0, duration: 150, easing: Easing.bezier(0.32, 0.72, 0, 1), useNativeDriver: true }).start()
|
|
420
|
+
: Animated.spring(drag, { toValue: 0, useNativeDriver: true, ...motion.snappy }).start();
|
|
421
|
+
return PanResponder.create({
|
|
422
|
+
onMoveShouldSetPanResponder: (_, g) => g.dy > 4 && Math.abs(g.dy) > Math.abs(g.dx),
|
|
423
|
+
onPanResponderMove: (_, g) => drag.setValue(Math.max(0, g.dy)),
|
|
424
|
+
onPanResponderRelease: (_, g) => {
|
|
425
|
+
if (g.dy > 110 || g.vy > 1.1) {
|
|
426
|
+
setOpen(false);
|
|
427
|
+
onOpenChange?.(false);
|
|
428
|
+
} else settle();
|
|
429
|
+
},
|
|
430
|
+
onPanResponderTerminate: settle,
|
|
431
|
+
});
|
|
432
|
+
}, [drag, onOpenChange]);
|
|
413
433
|
|
|
414
434
|
const active = error ? colors.danger : tint;
|
|
415
435
|
const rest = error ? colors.danger : ok ? colors.success : ((userBorder as string | undefined) ?? colors.input);
|
|
@@ -469,7 +489,7 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
469
489
|
},
|
|
470
490
|
]}
|
|
471
491
|
>
|
|
472
|
-
<Text style={[styles.presetText, ff, { color: on ? tint : colors.foreground }]}>{p.label}</Text>
|
|
492
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.presetText, ff, { color: on ? tint : colors.foreground }]}>{p.label}</Text>
|
|
473
493
|
</Pressable>
|
|
474
494
|
);
|
|
475
495
|
})}
|
|
@@ -498,14 +518,16 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
498
518
|
accessibilityRole="button"
|
|
499
519
|
style={({ pressed }) => [styles.footBtn, { borderRadius: radius.md, opacity: draft ? (pressed ? 0.6 : 1) : 0.4 }]}
|
|
500
520
|
>
|
|
501
|
-
<Text style={[styles.footText, ff, { color: colors.foreground }]}>
|
|
521
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.footText, ff, { color: colors.foreground }]}>
|
|
522
|
+
Clear
|
|
523
|
+
</Text>
|
|
502
524
|
</Pressable>
|
|
503
525
|
<Pressable
|
|
504
526
|
onPress={done}
|
|
505
527
|
accessibilityRole="button"
|
|
506
528
|
style={({ pressed }) => [styles.footBtn, styles.footMain, { backgroundColor: tint, borderRadius: radius.md, opacity: pressed ? 0.85 : 1 }]}
|
|
507
529
|
>
|
|
508
|
-
<Text style={[styles.footText, ff, { color: readableOn(tint) }]}>
|
|
530
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.footText, ff, { color: readableOn(tint) }]}>
|
|
509
531
|
{draftDays > 1 ? `Done · ${draftDays} days` : 'Done'}
|
|
510
532
|
</Text>
|
|
511
533
|
</Pressable>
|
|
@@ -558,13 +580,14 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
558
580
|
borderRadius: fieldRadius + 4,
|
|
559
581
|
borderColor: alpha(active, 0.2),
|
|
560
582
|
opacity: halo,
|
|
561
|
-
transform: [{ scale: halo.interpolate({ inputRange: [0, 1], outputRange: [0.97, 1] }) }],
|
|
583
|
+
transform: [{ scale: halo.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 1 : 0.97, 1] }) }],
|
|
562
584
|
},
|
|
563
585
|
]}
|
|
564
586
|
/>
|
|
565
|
-
<Animated.View style={[styles.field, {
|
|
587
|
+
<Animated.View style={[styles.field, { minHeight: sz.h, borderRadius: fieldRadius, backgroundColor: bg }, userField, { borderColor }]}>
|
|
566
588
|
<Text
|
|
567
589
|
numberOfLines={1}
|
|
590
|
+
maxFontSizeMultiplier={FONT_CAP}
|
|
568
591
|
style={[
|
|
569
592
|
styles.value,
|
|
570
593
|
ff,
|
|
@@ -592,7 +615,7 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
592
615
|
]}
|
|
593
616
|
>
|
|
594
617
|
{/* The notch: the label paints the surface over the border, so it matches the rendered width. */}
|
|
595
|
-
<Animated.Text numberOfLines={1} style={[styles.label, ff, { fontSize: sz.font, backgroundColor: bg }, labelStyle, { color: labelColor }]}>
|
|
618
|
+
<Animated.Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[styles.label, ff, { fontSize: sz.font, backgroundColor: bg }, labelStyle, { color: labelColor }]}>
|
|
596
619
|
{label}
|
|
597
620
|
</Animated.Text>
|
|
598
621
|
</Animated.View>
|
|
@@ -603,7 +626,7 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
603
626
|
style={{
|
|
604
627
|
display: ok ? 'flex' : 'none',
|
|
605
628
|
opacity: check,
|
|
606
|
-
transform: [{ scale: check.interpolate({ inputRange: [0, 1], outputRange: [0.3, 1] }) }],
|
|
629
|
+
transform: [{ scale: check.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 1 : 0.3, 1] }) }],
|
|
607
630
|
}}
|
|
608
631
|
>
|
|
609
632
|
<Icon name="check" size={sz.icon} color={colors.success} />
|
|
@@ -623,7 +646,7 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
623
646
|
</View>
|
|
624
647
|
</Animated.View>
|
|
625
648
|
</Pressable>
|
|
626
|
-
{message ? <FieldMessage key={tone + message} text={message} tone={tone} style={messageStyle} /> : null}
|
|
649
|
+
{message ? <FieldMessage key={tone + message} text={message} tone={tone} reduce={reduce} style={messageStyle} /> : null}
|
|
627
650
|
|
|
628
651
|
<Modal visible={mounted} transparent animationType="none" statusBarTranslucent onRequestClose={close}>
|
|
629
652
|
<Animated.View style={[StyleSheet.absoluteFill, { backgroundColor: asSheet ? colors.overlay : 'transparent', opacity: show }]}>
|
|
@@ -644,11 +667,15 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
644
667
|
? {
|
|
645
668
|
opacity: show,
|
|
646
669
|
transform: [
|
|
647
|
-
{ scale: show.interpolate({ inputRange: [0, 1], outputRange: [0.94, 1] }) },
|
|
648
|
-
{ translateY: Animated.add(show.interpolate({ inputRange: [0, 1], outputRange: [16, 0] }), drag) },
|
|
670
|
+
{ scale: show.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 1 : 0.94, 1] }) },
|
|
671
|
+
{ translateY: Animated.add(show.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 0 : 16, 0] }), drag) },
|
|
649
672
|
],
|
|
650
673
|
}
|
|
651
|
-
: {
|
|
674
|
+
: {
|
|
675
|
+
// Reduce Motion: the sheet fades in place instead of sliding up.
|
|
676
|
+
opacity: reduce ? show : 1,
|
|
677
|
+
transform: [{ translateY: Animated.add(show.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 0 : winH * 0.85, 0] }), drag) }],
|
|
678
|
+
},
|
|
652
679
|
]}
|
|
653
680
|
>
|
|
654
681
|
<View {...pan.panHandlers} style={styles.sheetHeader}>
|
|
@@ -681,8 +708,8 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
681
708
|
{
|
|
682
709
|
opacity: show,
|
|
683
710
|
transform: [
|
|
684
|
-
{ translateY: show.interpolate({ inputRange: [0, 1], outputRange: [below ? -8 : 8, 0] }) },
|
|
685
|
-
{ scale: show.interpolate({ inputRange: [0, 1], outputRange: [0.96, 1] }) },
|
|
711
|
+
{ translateY: show.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 0 : below ? -8 : 8, 0] }) },
|
|
712
|
+
{ scale: show.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 1 : 0.96, 1] }) },
|
|
686
713
|
],
|
|
687
714
|
},
|
|
688
715
|
below ? { top: anchor.y + anchor.h + 6 } : { bottom: Math.max(12, winH - anchor.y + 6) },
|
|
@@ -697,17 +724,28 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
697
724
|
}
|
|
698
725
|
|
|
699
726
|
/** Helper / error / success line that slides in; error and success carry an icon. */
|
|
700
|
-
function FieldMessage({
|
|
727
|
+
function FieldMessage({
|
|
728
|
+
text,
|
|
729
|
+
tone,
|
|
730
|
+
reduce,
|
|
731
|
+
style,
|
|
732
|
+
}: {
|
|
733
|
+
text: string;
|
|
734
|
+
tone: 'error' | 'success' | 'helper';
|
|
735
|
+
reduce: boolean;
|
|
736
|
+
style?: StyleProp<TextStyle>;
|
|
737
|
+
}) {
|
|
701
738
|
const { colors, font } = useKit();
|
|
702
739
|
const enter = useRef(new Animated.Value(tone === 'helper' ? 1 : 0)).current; // native
|
|
703
740
|
useEffect(() => {
|
|
704
|
-
Animated.
|
|
705
|
-
|
|
741
|
+
if (reduce) Animated.timing(enter, { toValue: 1, duration: 150, useNativeDriver: true }).start();
|
|
742
|
+
else Animated.spring(enter, { toValue: 1, useNativeDriver: true, ...motion.snappy }).start();
|
|
743
|
+
}, [enter, reduce]);
|
|
706
744
|
const color = tone === 'error' ? colors.danger : tone === 'success' ? colors.success : colors.mutedForeground;
|
|
707
745
|
return (
|
|
708
746
|
<Animated.View
|
|
709
747
|
accessibilityLiveRegion="polite"
|
|
710
|
-
style={[styles.message, { opacity: enter, transform: [{ translateY: enter.interpolate({ inputRange: [0, 1], outputRange: [4, 0] }) }] }]}
|
|
748
|
+
style={[styles.message, { opacity: enter, transform: [{ translateY: enter.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 0 : 4, 0] }) }] }]}
|
|
711
749
|
>
|
|
712
750
|
{tone !== 'helper' ? <Icon name={tone === 'error' ? 'alert' : 'checkCircle'} size={14} color={color} /> : null}
|
|
713
751
|
<Text style={[styles.helper, font.family ? { fontFamily: font.family } : null, { color }, style, tone !== 'helper' && { color }]}>{text}</Text>
|
|
@@ -747,6 +785,22 @@ function Icon({ name, size, color, strokeWidth = 1.8 }: { name: IconName; size:
|
|
|
747
785
|
);
|
|
748
786
|
}
|
|
749
787
|
|
|
788
|
+
function useReduceMotion() {
|
|
789
|
+
const [reduce, setReduce] = useState(false);
|
|
790
|
+
useEffect(() => {
|
|
791
|
+
let alive = true;
|
|
792
|
+
AccessibilityInfo.isReduceMotionEnabled()
|
|
793
|
+
.then((r) => alive && setReduce(r))
|
|
794
|
+
.catch(() => {});
|
|
795
|
+
const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduce);
|
|
796
|
+
return () => {
|
|
797
|
+
alive = false;
|
|
798
|
+
sub.remove();
|
|
799
|
+
};
|
|
800
|
+
}, []);
|
|
801
|
+
return reduce;
|
|
802
|
+
}
|
|
803
|
+
|
|
750
804
|
const styles = StyleSheet.create({
|
|
751
805
|
field: { borderWidth: 1.5, justifyContent: 'center' },
|
|
752
806
|
halo: { position: 'absolute', top: -4, left: -4, right: -4, bottom: -4, borderWidth: 4 },
|
|
@@ -775,11 +829,11 @@ const styles = StyleSheet.create({
|
|
|
775
829
|
presetScroll: { flexGrow: 0, flexShrink: 0 },
|
|
776
830
|
calendarScroll: { flexGrow: 0, flexShrink: 1 },
|
|
777
831
|
presets: { gap: 8, paddingHorizontal: 16, paddingVertical: 8 },
|
|
778
|
-
preset: {
|
|
832
|
+
preset: { minHeight: 34, paddingHorizontal: 14, borderWidth: 1, alignItems: 'center', justifyContent: 'center' },
|
|
779
833
|
presetText: { fontSize: 13.5, fontWeight: '400' },
|
|
780
834
|
calendar: { paddingHorizontal: 16, paddingTop: 4, paddingBottom: 6 },
|
|
781
835
|
footer: { flexDirection: 'row', flexShrink: 0, gap: 8, paddingTop: 12, paddingHorizontal: 16, borderTopWidth: StyleSheet.hairlineWidth },
|
|
782
|
-
footBtn: {
|
|
836
|
+
footBtn: { minHeight: 46, paddingHorizontal: 20, alignItems: 'center', justifyContent: 'center' },
|
|
783
837
|
footMain: { flex: 1 },
|
|
784
838
|
footText: { fontSize: 15, fontWeight: '400' },
|
|
785
839
|
popover: {
|
|
@@ -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
|
};
|
|
@@ -104,6 +104,9 @@ export interface DialogProps {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
const SHAKE = [10, -9, 7, -5, 3, 0];
|
|
107
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
108
|
+
// content text scales all the way.
|
|
109
|
+
const FONT_CAP = 1.35;
|
|
107
110
|
|
|
108
111
|
/**
|
|
109
112
|
* A centred alert dialog. The scrim fades in while the card springs from 0.92 to full size; an
|
|
@@ -137,7 +140,8 @@ export function Dialog({
|
|
|
137
140
|
accent,
|
|
138
141
|
style,
|
|
139
142
|
}: DialogProps) {
|
|
140
|
-
const { colors, radius, dark } = useKit();
|
|
143
|
+
const { colors, radius, dark, font } = useKit();
|
|
144
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
141
145
|
const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
142
146
|
const reduceMotion = useReduceMotion();
|
|
143
147
|
const destructive = variant === 'destructive';
|
|
@@ -324,6 +328,7 @@ export function Dialog({
|
|
|
324
328
|
maxLength={prompt.maxLength}
|
|
325
329
|
selectTextOnFocus={prompt.selectTextOnFocus ?? true}
|
|
326
330
|
autoFocus
|
|
331
|
+
maxFontSizeMultiplier={FONT_CAP}
|
|
327
332
|
returnKeyType="done"
|
|
328
333
|
onSubmitEditing={() => run(list[confirmIndex], confirmIndex)}
|
|
329
334
|
onFocus={() => setFocused(true)}
|
|
@@ -331,6 +336,7 @@ export function Dialog({
|
|
|
331
336
|
selectionColor={accent ?? colors.accent}
|
|
332
337
|
style={[
|
|
333
338
|
ios ? styles.inputIos : styles.input,
|
|
339
|
+
ff,
|
|
334
340
|
{
|
|
335
341
|
color: colors.foreground,
|
|
336
342
|
backgroundColor: alpha(colors.foreground, dark ? 0.07 : 0.04),
|
|
@@ -351,19 +357,19 @@ export function Dialog({
|
|
|
351
357
|
{title ? (
|
|
352
358
|
<Text
|
|
353
359
|
accessibilityRole="header"
|
|
354
|
-
style={[ios ? styles.titleIos : styles.title, { color: colors.foreground }, centred && styles.textCenter]}
|
|
360
|
+
style={[ios ? styles.titleIos : styles.title, ff, { color: colors.foreground }, centred && styles.textCenter]}
|
|
355
361
|
>
|
|
356
362
|
{title}
|
|
357
363
|
</Text>
|
|
358
364
|
) : null}
|
|
359
365
|
{description ? (
|
|
360
|
-
<Text style={[ios ? styles.descIos : styles.desc, { color: colors.mutedForeground }, centred && styles.textCenter]}>
|
|
366
|
+
<Text style={[ios ? styles.descIos : styles.desc, ff, { color: colors.mutedForeground }, centred && styles.textCenter]}>
|
|
361
367
|
{description}
|
|
362
368
|
</Text>
|
|
363
369
|
) : null}
|
|
364
370
|
{field ? <View style={ios ? styles.fieldIos : styles.field}>{field}</View> : null}
|
|
365
371
|
{error ? (
|
|
366
|
-
<Text accessibilityLiveRegion="polite" style={[styles.error, { color: colors.danger }, centred && styles.textCenter]}>
|
|
372
|
+
<Text accessibilityLiveRegion="polite" style={[styles.error, ff, { color: colors.danger }, centred && styles.textCenter]}>
|
|
367
373
|
{error}
|
|
368
374
|
</Text>
|
|
369
375
|
) : null}
|
|
@@ -514,6 +520,8 @@ function PillButton({
|
|
|
514
520
|
onPress: () => void;
|
|
515
521
|
style?: StyleProp<ViewStyle>;
|
|
516
522
|
}) {
|
|
523
|
+
const { font } = useKit();
|
|
524
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
517
525
|
const press = useRef(new Animated.Value(0)).current;
|
|
518
526
|
const to = (v: number) => Animated.spring(press, { toValue: v, useNativeDriver: true, ...motion.snappy }).start();
|
|
519
527
|
return (
|
|
@@ -538,7 +546,7 @@ function PillButton({
|
|
|
538
546
|
},
|
|
539
547
|
]}
|
|
540
548
|
>
|
|
541
|
-
<Text numberOfLines={1} style={[styles.pillText, { color: fg, opacity: loading ? 0 : 1 }]}>
|
|
549
|
+
<Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[styles.pillText, ff, { color: fg, opacity: loading ? 0 : 1 }]}>
|
|
542
550
|
{label}
|
|
543
551
|
</Text>
|
|
544
552
|
{loading ? <ActivityIndicator style={StyleSheet.absoluteFill} size="small" color={fg} /> : null}
|
|
@@ -565,6 +573,8 @@ function IosButton({
|
|
|
565
573
|
onPress: () => void;
|
|
566
574
|
style?: StyleProp<ViewStyle>;
|
|
567
575
|
}) {
|
|
576
|
+
const { font } = useKit();
|
|
577
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
568
578
|
const hl = useRef(new Animated.Value(0)).current;
|
|
569
579
|
return (
|
|
570
580
|
<Pressable
|
|
@@ -581,7 +591,7 @@ function IosButton({
|
|
|
581
591
|
style={[styles.iosButton, style]}
|
|
582
592
|
>
|
|
583
593
|
<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 }]}>
|
|
594
|
+
<Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[styles.iosText, ff, { color, opacity: loading ? 0 : disabled ? 0.4 : 1 }]}>
|
|
585
595
|
{label}
|
|
586
596
|
</Text>
|
|
587
597
|
{loading ? <ActivityIndicator style={StyleSheet.absoluteFill} size="small" color={color} /> : null}
|
|
@@ -744,14 +754,14 @@ const styles = StyleSheet.create({
|
|
|
744
754
|
textCenter: { textAlign: 'center' },
|
|
745
755
|
field: { marginTop: 16 },
|
|
746
756
|
fieldIos: { marginTop: 14 },
|
|
747
|
-
input: {
|
|
748
|
-
inputIos: {
|
|
757
|
+
input: { minHeight: 48, paddingHorizontal: 14, fontSize: 16, borderWidth: 1 },
|
|
758
|
+
inputIos: { minHeight: 36, paddingHorizontal: 10, fontSize: 14, borderWidth: StyleSheet.hairlineWidth },
|
|
749
759
|
error: { fontSize: 13, lineHeight: 18, fontWeight: '400', marginTop: 8 },
|
|
750
760
|
children: { marginTop: 14 },
|
|
751
761
|
row: { flexDirection: 'row' },
|
|
752
762
|
flex: { flex: 1 },
|
|
753
763
|
actions: { gap: 10, padding: 22, paddingTop: 22 },
|
|
754
|
-
pill: {
|
|
764
|
+
pill: { minHeight: 48, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 18 },
|
|
755
765
|
pillText: { fontSize: 16, lineHeight: 21, fontWeight: '400', letterSpacing: -0.1 },
|
|
756
766
|
iosActions: { borderTopWidth: StyleSheet.hairlineWidth },
|
|
757
767
|
iosButton: { minHeight: 46, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 12 },
|
|
@@ -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,
|
|
@@ -38,6 +38,10 @@ const INLINE_BOTTOM = Platform.select({ ios: 18, android: 12, default: 10 }) ??
|
|
|
38
38
|
const MODAL_TOP = Platform.select({ ios: 52, android: (StatusBar.currentHeight ?? 24) + 8, default: 16 }) ?? 16;
|
|
39
39
|
const MODAL_BOTTOM = Platform.select({ ios: 28, android: 16, default: 12 }) ?? 12;
|
|
40
40
|
|
|
41
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
42
|
+
// content text scales all the way.
|
|
43
|
+
const FONT_CAP = 1.35;
|
|
44
|
+
|
|
41
45
|
/** Panel surface hidden past the screen edge, so overshoot and rubber-banding never show a gap. */
|
|
42
46
|
const EXTRA = 48;
|
|
43
47
|
/** Extra travel when closed so the panel's shadow is off-screen too. */
|
|
@@ -45,7 +49,7 @@ const SHADOW_PAD = 28;
|
|
|
45
49
|
const PUSH_SCALE = 0.92;
|
|
46
50
|
const PUSH_GAP = 14;
|
|
47
51
|
/** Kit snappy with a touch more damping: the panel lands with a hint of overshoot, not a wobble. */
|
|
48
|
-
const OPEN_SPRING =
|
|
52
|
+
const OPEN_SPRING = motion.snappy;
|
|
49
53
|
/** Closing is clamped, so it ends on the first crossing — exits feel faster than entrances. */
|
|
50
54
|
const CLOSE_SPRING = { ...motion.snappy, overshootClamping: true };
|
|
51
55
|
|
|
@@ -226,7 +230,7 @@ export function Drawer({
|
|
|
226
230
|
}, [isOpen, inline]);
|
|
227
231
|
|
|
228
232
|
// Root position on the page, for the edge-swipe zone (gesture coordinates are page-based).
|
|
229
|
-
const rootRef = useRef<View
|
|
233
|
+
const rootRef = useRef<ComponentRef<typeof View>>(null);
|
|
230
234
|
const rootX = useRef(0);
|
|
231
235
|
const measure = useCallback(() => {
|
|
232
236
|
rootRef.current?.measure((_x, _y, _w, _h, pageX) => {
|
|
@@ -492,7 +496,8 @@ export interface DrawerHeaderProps {
|
|
|
492
496
|
|
|
493
497
|
/** Title block with optional leading node and close button — drop into `header`. */
|
|
494
498
|
export function DrawerHeader({ title, subtitle, leading, showClose = false, onClose, children, style }: DrawerHeaderProps) {
|
|
495
|
-
const { colors, radius, dark } = useKit();
|
|
499
|
+
const { colors, radius, dark, font } = useKit();
|
|
500
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
496
501
|
const drawerCtx = useContext(DrawerContext);
|
|
497
502
|
const close = onClose ?? (() => drawerCtx?.close());
|
|
498
503
|
const canClose = showClose || onClose !== undefined;
|
|
@@ -501,12 +506,12 @@ export function DrawerHeader({ title, subtitle, leading, showClose = false, onCl
|
|
|
501
506
|
{leading ? <View>{leading}</View> : null}
|
|
502
507
|
<View style={styles.headerText}>
|
|
503
508
|
{title ? (
|
|
504
|
-
<Text accessibilityRole="header" style={[styles.headerTitle, { color: colors.foreground }]} numberOfLines={1}>
|
|
509
|
+
<Text accessibilityRole="header" style={[styles.headerTitle, ff, { color: colors.foreground }]} numberOfLines={1}>
|
|
505
510
|
{title}
|
|
506
511
|
</Text>
|
|
507
512
|
) : null}
|
|
508
513
|
{subtitle ? (
|
|
509
|
-
<Text style={[styles.headerSub, { color: colors.mutedForeground }]} numberOfLines={1}>
|
|
514
|
+
<Text style={[styles.headerSub, ff, { color: colors.mutedForeground }]} numberOfLines={1}>
|
|
510
515
|
{subtitle}
|
|
511
516
|
</Text>
|
|
512
517
|
) : null}
|
|
@@ -569,7 +574,8 @@ export function DrawerProfile({
|
|
|
569
574
|
accessibilityHint,
|
|
570
575
|
style,
|
|
571
576
|
}: DrawerProfileProps) {
|
|
572
|
-
const { colors, radius, dark } = useKit();
|
|
577
|
+
const { colors, radius, dark, font } = useKit();
|
|
578
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
573
579
|
const press = useRef(new Animated.Value(0)).current;
|
|
574
580
|
const stacked = layout === 'stacked';
|
|
575
581
|
const trailing =
|
|
@@ -587,11 +593,11 @@ export function DrawerProfile({
|
|
|
587
593
|
) : null;
|
|
588
594
|
const text = (
|
|
589
595
|
<View style={styles.profileText}>
|
|
590
|
-
<Text numberOfLines={1} style={[styles.profileName, { color: colors.foreground }, stacked && styles.profileNameLg]}>
|
|
596
|
+
<Text numberOfLines={1} style={[styles.profileName, ff, { color: colors.foreground }, stacked && styles.profileNameLg]}>
|
|
591
597
|
{name}
|
|
592
598
|
</Text>
|
|
593
599
|
{email ? (
|
|
594
|
-
<Text numberOfLines={1} style={[styles.profileEmail, { color: colors.mutedForeground }]}>
|
|
600
|
+
<Text numberOfLines={1} style={[styles.profileEmail, ff, { color: colors.mutedForeground }]}>
|
|
595
601
|
{email}
|
|
596
602
|
</Text>
|
|
597
603
|
) : null}
|
|
@@ -637,7 +643,8 @@ export function DrawerProfile({
|
|
|
637
643
|
}
|
|
638
644
|
|
|
639
645
|
function ProfileAvatar({ name, src, size, status }: { name: string; src?: string; size: number; status?: DrawerPresence }) {
|
|
640
|
-
const { colors, radius, dark } = useKit();
|
|
646
|
+
const { colors, radius, dark, font } = useKit();
|
|
647
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
641
648
|
const ring = useContext(SurfaceContext) ?? colors.card;
|
|
642
649
|
const hue = nameColor(name);
|
|
643
650
|
const ink = dark ? mix(hue, colors.foreground, 0.35) : mix(hue, colors.foreground, 0.3);
|
|
@@ -670,7 +677,7 @@ function ProfileAvatar({ name, src, size, status }: { name: string; src?: string
|
|
|
670
677
|
},
|
|
671
678
|
]}
|
|
672
679
|
>
|
|
673
|
-
<Text style={{ color: ink, fontSize: size * 0.36, fontWeight: '400', letterSpacing: -0.2 }}>{initials}</Text>
|
|
680
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[{ color: ink, fontSize: size * 0.36, fontWeight: '400', letterSpacing: -0.2 }, ff]}>{initials}</Text>
|
|
674
681
|
{src ? <Image source={{ uri: src }} alt="" style={StyleSheet.absoluteFill} /> : null}
|
|
675
682
|
</View>
|
|
676
683
|
{status ? (
|
|
@@ -811,7 +818,8 @@ export function DrawerItem({
|
|
|
811
818
|
disabled,
|
|
812
819
|
style,
|
|
813
820
|
}: DrawerItemProps) {
|
|
814
|
-
const { colors, radius, dark } = useKit();
|
|
821
|
+
const { colors, radius, dark, font } = useKit();
|
|
822
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
815
823
|
const drawer = useContext(DrawerContext);
|
|
816
824
|
const reduceMotion = useReduceMotion();
|
|
817
825
|
const danger = tone === 'danger';
|
|
@@ -910,7 +918,7 @@ export function DrawerItem({
|
|
|
910
918
|
)}
|
|
911
919
|
</Animated.View>
|
|
912
920
|
) : null}
|
|
913
|
-
<Text numberOfLines={1} style={[styles.itemLabel, { color: labelColor }]}>
|
|
921
|
+
<Text numberOfLines={1} style={[styles.itemLabel, ff, { color: labelColor }]}>
|
|
914
922
|
{label}
|
|
915
923
|
</Text>
|
|
916
924
|
{showBadge ? (
|
|
@@ -921,7 +929,7 @@ export function DrawerItem({
|
|
|
921
929
|
{ borderRadius: radius.capsule(22), backgroundColor: badgeColors.bg, transform: [{ scale: badgePop }] },
|
|
922
930
|
]}
|
|
923
931
|
>
|
|
924
|
-
<Text style={[styles.badgeText, { color: badgeColors.fg }]}>
|
|
932
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.badgeText, ff, { color: badgeColors.fg }]}>
|
|
925
933
|
{typeof badge === 'number' && badge > 99 ? '99+' : badge}
|
|
926
934
|
</Text>
|
|
927
935
|
</Animated.View>
|
|
@@ -943,12 +951,13 @@ export interface DrawerSectionProps {
|
|
|
943
951
|
|
|
944
952
|
/** A quiet heading between groups of DrawerItems, with a hairline divider above it. */
|
|
945
953
|
export function DrawerSection({ title, trailing, divider = true, style }: DrawerSectionProps) {
|
|
946
|
-
const { colors, dark } = useKit();
|
|
954
|
+
const { colors, dark, font } = useKit();
|
|
955
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
947
956
|
return (
|
|
948
957
|
<View style={style}>
|
|
949
958
|
{divider ? <View style={[styles.divider, { backgroundColor: alpha(colors.foreground, dark ? 0.1 : 0.07) }]} /> : null}
|
|
950
959
|
<View style={[styles.section, !divider && styles.sectionFlush]}>
|
|
951
|
-
<Text accessibilityRole="header" style={[styles.sectionText, { color: colors.mutedForeground }]}>
|
|
960
|
+
<Text accessibilityRole="header" style={[styles.sectionText, ff, { color: colors.mutedForeground }]}>
|
|
952
961
|
{title}
|
|
953
962
|
</Text>
|
|
954
963
|
{trailing}
|
|
@@ -972,7 +981,8 @@ export interface DrawerFooterProps {
|
|
|
972
981
|
|
|
973
982
|
/** A compact footer row — storage meter, app version, sync status — for the `footer` slot. */
|
|
974
983
|
export function DrawerFooter({ label, detail, icon, progress, onPress, trailing, style }: DrawerFooterProps) {
|
|
975
|
-
const { colors, radius, dark } = useKit();
|
|
984
|
+
const { colors, radius, dark, font } = useKit();
|
|
985
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
976
986
|
const pct = progress === undefined ? undefined : Math.max(0, Math.min(1, progress));
|
|
977
987
|
return (
|
|
978
988
|
<Pressable
|
|
@@ -996,7 +1006,7 @@ export function DrawerFooter({ label, detail, icon, progress, onPress, trailing,
|
|
|
996
1006
|
</View>
|
|
997
1007
|
) : null}
|
|
998
1008
|
<View style={styles.footerText}>
|
|
999
|
-
<Text numberOfLines={1} style={[styles.footerLabel, { color: colors.foreground }]}>
|
|
1009
|
+
<Text numberOfLines={1} style={[styles.footerLabel, ff, { color: colors.foreground }]}>
|
|
1000
1010
|
{label}
|
|
1001
1011
|
</Text>
|
|
1002
1012
|
{pct !== undefined ? (
|
|
@@ -1012,7 +1022,7 @@ export function DrawerFooter({ label, detail, icon, progress, onPress, trailing,
|
|
|
1012
1022
|
</View>
|
|
1013
1023
|
) : null}
|
|
1014
1024
|
{detail ? (
|
|
1015
|
-
<Text numberOfLines={1} style={[styles.footerDetail, { color: colors.mutedForeground }]}>
|
|
1025
|
+
<Text numberOfLines={1} style={[styles.footerDetail, ff, { color: colors.mutedForeground }]}>
|
|
1016
1026
|
{detail}
|
|
1017
1027
|
</Text>
|
|
1018
1028
|
) : null}
|
|
@@ -1111,7 +1121,7 @@ const styles = StyleSheet.create({
|
|
|
1111
1121
|
item: { minHeight: ITEM_H, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, gap: 14 },
|
|
1112
1122
|
itemIcon: { width: 24, alignItems: 'center', justifyContent: 'center' },
|
|
1113
1123
|
itemLabel: { flex: 1, fontSize: 15.5, fontWeight: '400', letterSpacing: -0.15 },
|
|
1114
|
-
badge: { minWidth: 22,
|
|
1124
|
+
badge: { minWidth: 22, minHeight: 22, paddingHorizontal: 7, alignItems: 'center', justifyContent: 'center' },
|
|
1115
1125
|
badgePlain: { paddingHorizontal: 2, minWidth: 0 },
|
|
1116
1126
|
badgeText: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },
|
|
1117
1127
|
section: {
|
|
@@ -1125,7 +1135,7 @@ const styles = StyleSheet.create({
|
|
|
1125
1135
|
paddingBottom: 2,
|
|
1126
1136
|
},
|
|
1127
1137
|
sectionFlush: { paddingTop: 10 },
|
|
1128
|
-
sectionText: { fontSize: 13, lineHeight: 18, fontWeight: '400', letterSpacing: 0.1 },
|
|
1138
|
+
sectionText: { flexShrink: 1, fontSize: 13, lineHeight: 18, fontWeight: '400', letterSpacing: 0.1 },
|
|
1129
1139
|
divider: { height: StyleSheet.hairlineWidth, marginHorizontal: 14, marginTop: 10, marginBottom: 4 },
|
|
1130
1140
|
footerRow: { minHeight: 48, flexDirection: 'row', alignItems: 'center', gap: 14, paddingHorizontal: 12, paddingVertical: 8 },
|
|
1131
1141
|
footerText: { flex: 1, minWidth: 0, gap: 5 },
|