@expokit/cli 0.4.4 → 0.5.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 +30 -3
- package/bin/expo-kit.js +749 -288
- package/bin/lib.mjs +310 -3
- package/package.json +1 -1
- package/registry/_docs/action-sheet.json +1 -1
- package/registry/_docs/alert.json +6 -0
- package/registry/_docs/bottom-sheet.json +6 -0
- package/registry/_docs/bullet.json +6 -0
- package/registry/_docs/dialog.json +1 -1
- package/registry/_docs/drawer.json +3 -3
- package/registry/_docs/glass-search-bar.json +1 -1
- package/registry/_docs/grain.json +6 -1
- package/registry/_docs/isometric-bar.json +6 -0
- package/registry/_docs/liquidglass-tab.json +1 -1
- package/registry/_docs/password-input.json +1 -1
- package/registry/_docs/pixel-bar.json +6 -0
- package/registry/_docs/polar-area.json +6 -0
- package/registry/_docs/qr-code-button.json +9 -3
- package/registry/_docs/scratch-card.json +7 -1
- package/registry/_docs/search-bar.json +1 -1
- package/registry/_docs/select.json +2 -2
- package/registry/_docs/slide-to-confirm.json +2 -2
- package/registry/_docs/spinner.json +1 -1
- package/registry/_docs/stateful-button.json +2 -2
- package/registry/_docs/swipe-deck.json +1 -1
- package/registry/_docs/time-picker.json +2 -2
- package/registry/_docs/timeline-chart.json +6 -0
- package/registry/_docs/toast.json +15 -3
- package/registry/_docs/treemap.json +6 -0
- package/registry/_docs/waterfall.json +6 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +6 -4
- package/registry/accordion/files/src/lib/kit.tsx +363 -11
- package/registry/accordion.json +1 -0
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +43 -48
- package/registry/action-sheet/files/src/lib/kit.tsx +363 -11
- package/registry/action-sheet.json +1 -0
- package/registry/alert/files/src/components/ui/alert.tsx +19 -12
- package/registry/alert/files/src/lib/kit.tsx +363 -11
- package/registry/alert.json +3 -4
- package/registry/assistant-app.json +1 -0
- package/registry/avatar/files/src/components/ui/avatar.tsx +84 -66
- package/registry/avatar/files/src/lib/kit.tsx +363 -11
- package/registry/avatar.json +1 -0
- package/registry/badge/files/src/components/ui/badge.tsx +34 -34
- package/registry/badge/files/src/lib/kit.tsx +363 -11
- package/registry/badge.json +1 -0
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +28 -27
- package/registry/bottom-nav/files/src/lib/kit.tsx +363 -11
- package/registry/bottom-nav.json +5 -0
- package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +353 -289
- package/registry/bottom-sheet/files/src/lib/kit.tsx +363 -11
- package/registry/bottom-sheet.json +5 -1
- package/registry/bullet.json +1 -0
- package/registry/button/files/src/components/ui/button.tsx +18 -16
- package/registry/button/files/src/lib/kit.tsx +363 -11
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +2 -2
- package/registry/button-3d/files/src/lib/kit.tsx +363 -11
- package/registry/button-3d.json +1 -0
- package/registry/button.json +1 -0
- package/registry/calendar/files/src/components/ui/calendar.tsx +74 -76
- package/registry/calendar/files/src/lib/kit.tsx +363 -11
- package/registry/calendar.json +1 -0
- package/registry/card/files/src/components/ui/card.tsx +3 -3
- package/registry/card/files/src/lib/kit.tsx +363 -11
- package/registry/card-stack.json +1 -0
- package/registry/card.json +1 -0
- package/registry/carousel.json +1 -0
- package/registry/chat.json +7 -0
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +4 -4
- package/registry/checkbox/files/src/lib/kit.tsx +363 -11
- package/registry/checkbox.json +1 -0
- package/registry/checkout.json +1 -0
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +22 -22
- package/registry/chip-group/files/src/lib/kit.tsx +363 -11
- package/registry/chip-group.json +1 -0
- package/registry/confetti/files/src/components/ui/confetti.tsx +15 -14
- package/registry/confetti.json +3 -6
- package/registry/coupon/files/src/components/ui/coupon.tsx +42 -42
- package/registry/coupon/files/src/components/ui/flip-card.tsx +6 -5
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +76 -91
- package/registry/coupon/files/src/lib/kit.tsx +363 -11
- package/registry/coupon/files/src/lib/ticket-shape.tsx +54 -44
- package/registry/coupon.json +1 -0
- package/registry/cube-pager.json +1 -0
- package/registry/dashboard.json +1 -0
- package/registry/date-picker/files/src/components/ui/calendar.tsx +74 -76
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +55 -56
- package/registry/date-picker/files/src/lib/kit.tsx +363 -11
- package/registry/date-picker.json +1 -0
- package/registry/dialog/files/src/components/ui/dialog.tsx +60 -56
- package/registry/dialog/files/src/lib/kit.tsx +363 -11
- package/registry/dialog.json +1 -0
- package/registry/donut-shop.json +1 -0
- package/registry/drawer/files/src/components/ui/drawer.tsx +73 -69
- package/registry/drawer/files/src/lib/kit.tsx +363 -11
- package/registry/drawer.json +1 -0
- package/registry/fab.json +1 -0
- package/registry/fintech-app.json +1 -0
- package/registry/flip-calendar/files/src/components/ui/flip-calendar.tsx +92 -86
- package/registry/flip-calendar/files/src/lib/kit.tsx +363 -11
- package/registry/flip-calendar.json +7 -0
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +6 -5
- package/registry/flip-card/files/src/lib/kit.tsx +363 -11
- package/registry/flip-card.json +1 -0
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +33 -33
- package/registry/floating-input/files/src/lib/kit.tsx +363 -11
- package/registry/floating-input.json +1 -0
- package/registry/food-app.json +1 -0
- package/registry/gift-card.json +1 -0
- package/registry/glass-button.json +5 -0
- package/registry/glass-nav-bar.json +1 -0
- package/registry/glass-search-bar.json +1 -0
- package/registry/glass-segmented.json +1 -0
- package/registry/glass-sheet.json +1 -0
- package/registry/glass-tab-bar.json +5 -0
- package/registry/glass.json +1 -0
- package/registry/grain/files/src/components/ui/grain.tsx +16 -14
- package/registry/grain.json +8 -6
- package/registry/inbox.json +1 -0
- package/registry/isometric-bar.json +1 -0
- package/registry/kit/files/src/lib/kit.tsx +363 -11
- package/registry/kit.json +1 -0
- package/registry/liquidglass-tab/files/src/app/_layout.tsx +0 -1
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +23 -9
- package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +2 -2
- package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +1 -1
- package/registry/liquidglass-tab.json +1 -0
- package/registry/list-item/files/src/components/ui/list-item.tsx +42 -42
- package/registry/list-item/files/src/lib/kit.tsx +363 -11
- package/registry/list-item.json +1 -0
- package/registry/loyalty-card.json +1 -0
- package/registry/morphing-button-group.json +1 -0
- package/registry/number-ticker.json +1 -0
- package/registry/offers.json +1 -0
- package/registry/onboarding.json +1 -0
- package/registry/orb-reveal.json +1 -0
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +48 -29
- package/registry/otp-input/files/src/lib/kit.tsx +363 -11
- package/registry/otp-input.json +1 -0
- package/registry/parallax-layers.json +1 -0
- package/registry/parallax-scroll.json +1 -0
- package/registry/password-input/files/src/components/ui/floating-input.tsx +33 -33
- package/registry/password-input/files/src/components/ui/password-input.tsx +29 -25
- package/registry/password-input/files/src/lib/kit.tsx +363 -11
- package/registry/password-input.json +1 -0
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +6 -5
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +17 -17
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +76 -91
- package/registry/payment-card/files/src/lib/kit.tsx +363 -11
- package/registry/payment-card.json +1 -0
- package/registry/paywall.json +1 -0
- package/registry/pixel-bar.json +1 -0
- package/registry/polar-area.json +1 -0
- package/registry/profile.json +1 -0
- package/registry/progress/files/src/components/ui/progress.tsx +29 -29
- package/registry/progress/files/src/lib/kit.tsx +363 -11
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +20 -20
- package/registry/progress-steps/files/src/lib/kit.tsx +363 -11
- package/registry/progress-steps.json +1 -0
- package/registry/progress.json +1 -0
- package/registry/qr-code-button.json +1 -0
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +19 -19
- package/registry/radio-group/files/src/lib/kit.tsx +363 -11
- package/registry/radio-group.json +1 -0
- package/registry/rating.json +1 -0
- package/registry/ring-carousel.json +1 -0
- package/registry/ruler-time-picker/files/src/components/ui/ruler-time-picker.tsx +98 -82
- package/registry/ruler-time-picker/files/src/lib/kit.tsx +363 -11
- package/registry/ruler-time-picker.json +7 -0
- package/registry/scratch-card.json +1 -0
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +25 -21
- package/registry/search-bar/files/src/lib/kit.tsx +363 -11
- package/registry/search-bar.json +1 -0
- package/registry/search-results.json +4 -0
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +8 -7
- package/registry/segmented-control/files/src/lib/kit.tsx +363 -11
- package/registry/segmented-control.json +1 -0
- package/registry/select/files/src/components/ui/select.tsx +93 -88
- package/registry/select/files/src/lib/kit.tsx +363 -11
- package/registry/select.json +6 -0
- package/registry/settings.json +1 -0
- package/registry/sign-in.json +1 -0
- package/registry/skeleton/files/src/components/ui/skeleton.tsx +149 -80
- package/registry/skeleton/files/src/lib/kit.tsx +363 -11
- package/registry/skeleton.json +1 -0
- package/registry/slide-to-confirm.json +1 -0
- package/registry/slider/files/src/components/ui/slider.tsx +103 -96
- package/registry/slider/files/src/lib/kit.tsx +363 -11
- package/registry/slider.json +5 -0
- package/registry/spin-wheel.json +1 -0
- package/registry/spinner/files/src/components/ui/spinner.tsx +39 -29
- package/registry/spinner/files/src/lib/kit.tsx +363 -11
- package/registry/spinner.json +6 -0
- package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +39 -32
- package/registry/stateful-button/files/src/lib/kit.tsx +363 -11
- package/registry/stateful-button.json +5 -0
- package/registry/stepper/files/src/components/ui/stepper.tsx +38 -36
- package/registry/stepper/files/src/lib/kit.tsx +363 -11
- package/registry/stepper.json +1 -0
- package/registry/swipe-deck.json +1 -0
- package/registry/swipeable-row.json +4 -0
- package/registry/switch/files/src/components/ui/switch.tsx +39 -42
- package/registry/switch/files/src/lib/kit.tsx +363 -11
- package/registry/switch.json +1 -0
- package/registry/tabs/files/src/components/ui/tabs.tsx +59 -62
- package/registry/tabs/files/src/lib/kit.tsx +363 -11
- package/registry/tabs.json +1 -0
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +55 -42
- package/registry/text-animation/files/src/lib/kit.tsx +363 -11
- package/registry/text-animation.json +7 -0
- package/registry/textarea/files/src/components/ui/textarea.tsx +10 -10
- package/registry/textarea/files/src/lib/kit.tsx +363 -11
- package/registry/textarea.json +1 -0
- package/registry/ticket-card.json +1 -0
- package/registry/ticket-shape/files/src/lib/kit.tsx +363 -11
- package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +54 -44
- package/registry/ticket-shape.json +1 -0
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +76 -91
- package/registry/tilt-card/files/src/lib/kit.tsx +363 -11
- package/registry/tilt-card.json +1 -0
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +122 -119
- package/registry/time-picker/files/src/lib/kit.tsx +363 -11
- package/registry/time-picker.json +1 -0
- package/registry/timeline/files/src/components/ui/avatar.tsx +84 -66
- package/registry/timeline/files/src/components/ui/timeline.tsx +44 -41
- package/registry/timeline/files/src/lib/kit.tsx +363 -11
- package/registry/timeline-chart.json +1 -0
- package/registry/timeline.json +1 -0
- package/registry/toast/files/src/components/ui/toast.tsx +541 -463
- package/registry/toast/files/src/lib/kit.tsx +363 -11
- package/registry/toast.json +7 -5
- package/registry/treemap.json +1 -0
- package/registry/waterfall.json +1 -0
- package/registry/wheel-list.json +1 -0
- package/schema/components.schema.json +1 -0
- package/registry/alert/files/src/components/ui/button.tsx +0 -290
- package/registry/confetti/files/src/lib/kit.tsx +0 -320
- package/registry/grain/files/src/lib/kit.tsx +0 -320
- package/registry/toast/files/src/components/ui/button.tsx +0 -290
|
@@ -44,27 +44,27 @@ export interface TimelineLink {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export interface TimelineItem {
|
|
47
|
-
key?: string;
|
|
47
|
+
key?: string | undefined;
|
|
48
48
|
title: string;
|
|
49
|
-
description?: string;
|
|
49
|
+
description?: string | undefined;
|
|
50
50
|
/** Timestamp or meta, e.g. "Oct 12 · 2:14 PM" or "2h". */
|
|
51
|
-
time?: string;
|
|
51
|
+
time?: string | undefined;
|
|
52
52
|
/** Default 'done'. */
|
|
53
|
-
status?: TimelineStatus;
|
|
53
|
+
status?: TimelineStatus | undefined;
|
|
54
54
|
/** Icon inside a larger dot. */
|
|
55
|
-
icon?: TimelineIconName | ((props: TimelineIconProps) => React.ReactNode);
|
|
55
|
+
icon?: (TimelineIconName | ((props: TimelineIconProps) => React.ReactNode)) | undefined;
|
|
56
56
|
/** Photo (initials fallback) instead of a dot. */
|
|
57
|
-
avatar?: { src?: string; name: string };
|
|
57
|
+
avatar?: { src?: string; name: string } | undefined;
|
|
58
58
|
/** A photo under the text — a URL, or `{ uri, aspectRatio }` (default 16:9). */
|
|
59
|
-
image?: TimelineImageSource;
|
|
59
|
+
image?: TimelineImageSource | undefined;
|
|
60
60
|
/** Several photos in a 1-, 2- or 3-up grid (more than three show a "+N" tile). Combined with `image`. */
|
|
61
|
-
images?: TimelineImageSource[];
|
|
61
|
+
images?: TimelineImageSource[] | undefined;
|
|
62
62
|
/** Small accent chips under the text. */
|
|
63
|
-
tags?: string[];
|
|
63
|
+
tags?: string[] | undefined;
|
|
64
64
|
/** Tappable accent links under the text. */
|
|
65
|
-
links?: TimelineLink[];
|
|
65
|
+
links?: TimelineLink[] | undefined;
|
|
66
66
|
/** Anything under the description: a card, a quote, tags, buttons. */
|
|
67
|
-
content?: React.ReactNode;
|
|
67
|
+
content?: React.ReactNode | undefined;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export interface TimelineProps {
|
|
@@ -73,23 +73,23 @@ export interface TimelineProps {
|
|
|
73
73
|
* 'default' roomy rows · 'compact' small dots, one-line rows · 'card' each event on a card ·
|
|
74
74
|
* 'scroll' renders its own scroll view and fills the connector as you scroll (give it a height).
|
|
75
75
|
*/
|
|
76
|
-
variant?: TimelineVariant;
|
|
76
|
+
variant?: TimelineVariant | undefined;
|
|
77
77
|
/** Colour of done / current dots and the filled line (default: theme accent). */
|
|
78
|
-
accent?: string;
|
|
78
|
+
accent?: string | undefined;
|
|
79
79
|
/** Draw a check in done dots (default true). */
|
|
80
|
-
checks?: boolean;
|
|
80
|
+
checks?: boolean | undefined;
|
|
81
81
|
/** Entrance + connector fill, or the scroll-driven fill for 'scroll' (default true; off with reduce motion). */
|
|
82
|
-
animated?: boolean;
|
|
82
|
+
animated?: boolean | undefined;
|
|
83
83
|
/** Colour behind the rail — fills hollow dots and the mini status badges (default: theme background). */
|
|
84
|
-
surface?: string;
|
|
84
|
+
surface?: string | undefined;
|
|
85
85
|
/** 'scroll' only: rendered above the timeline inside the scroll view. */
|
|
86
|
-
header?: React.ReactNode;
|
|
86
|
+
header?: React.ReactNode | undefined;
|
|
87
87
|
/** 'scroll' only: rendered below the timeline inside the scroll view. */
|
|
88
|
-
footer?: React.ReactNode;
|
|
88
|
+
footer?: React.ReactNode | undefined;
|
|
89
89
|
/** 'scroll' only: style for the scroll view's content container (default `{ paddingVertical: 12 }`). */
|
|
90
|
-
contentContainerStyle?: StyleProp<ViewStyle
|
|
90
|
+
contentContainerStyle?: StyleProp<ViewStyle> | undefined;
|
|
91
91
|
/** Root style. For 'scroll' this is the scroll view, so set a `height` (or `flex: 1` on a screen). */
|
|
92
|
-
style?: StyleProp<ViewStyle
|
|
92
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
@@ -185,7 +185,7 @@ function ScrollTimeline({
|
|
|
185
185
|
const n = items.length;
|
|
186
186
|
const rail = 34;
|
|
187
187
|
|
|
188
|
-
const scrollY =
|
|
188
|
+
const [scrollY] = useState(() => new Animated.Value(0));
|
|
189
189
|
const lastY = useRef(0);
|
|
190
190
|
const [viewH, setViewH] = useState(0);
|
|
191
191
|
const [contentH, setContentH] = useState(0);
|
|
@@ -215,7 +215,7 @@ function ScrollTimeline({
|
|
|
215
215
|
if (!scrolls) return [] as number[];
|
|
216
216
|
const anchor = viewH * SCROLL_ANCHOR;
|
|
217
217
|
const k = 1 + (viewH - anchor) / maxScroll;
|
|
218
|
-
return items.map((_, i) => (listY + nodes[i] - anchor) / k);
|
|
218
|
+
return items.map((_, i) => (listY + (nodes[i] ?? 0) - anchor) / k);
|
|
219
219
|
}, [scrolls, viewH, maxScroll, listY, nodes, items]);
|
|
220
220
|
|
|
221
221
|
// progress = lit * 2 + (near ? 1 : 0), one number so React can skip identical updates.
|
|
@@ -225,8 +225,8 @@ function ScrollTimeline({
|
|
|
225
225
|
const update = (y: number) => {
|
|
226
226
|
lastY.current = y;
|
|
227
227
|
let lit = 0;
|
|
228
|
-
while (lit <= reach && triggers[lit] <= y) lit++;
|
|
229
|
-
const near = lit <= reach && y >= triggers[lit] - viewH * NEAR;
|
|
228
|
+
while (lit <= reach && (triggers[lit] ?? Infinity) <= y) lit++;
|
|
229
|
+
const near = lit <= reach && y >= (triggers[lit] ?? Infinity) - viewH * NEAR;
|
|
230
230
|
setProgress(lit * 2 + (near ? 1 : 0));
|
|
231
231
|
};
|
|
232
232
|
update(lastY.current);
|
|
@@ -243,17 +243,17 @@ function ScrollTimeline({
|
|
|
243
243
|
);
|
|
244
244
|
|
|
245
245
|
const top = nodes[0] ?? 0;
|
|
246
|
-
const fillH = reach > 0 && measured ? nodes[reach] - top : 0;
|
|
246
|
+
const fillH = reach > 0 && measured ? (nodes[reach] ?? top) - top : 0;
|
|
247
247
|
const dottedFrom = Math.max(0, reach);
|
|
248
|
-
const dottedH = measured && reach < n - 1 ? nodes[n - 1] - nodes[dottedFrom] : 0;
|
|
248
|
+
const dottedH = measured && reach < n - 1 ? (nodes[n - 1] ?? 0) - (nodes[dottedFrom] ?? 0) : 0;
|
|
249
249
|
|
|
250
250
|
let fillStyle: Animated.WithAnimatedValue<ViewStyle> | null = null;
|
|
251
251
|
if (fillH > 0) {
|
|
252
252
|
if (frozen) {
|
|
253
253
|
fillStyle = {};
|
|
254
254
|
} else if (triggers.length) {
|
|
255
|
-
const t0 = triggers[0];
|
|
256
|
-
const t1 = Math.max(triggers[reach], t0 + 1);
|
|
255
|
+
const t0 = triggers[0] ?? 0;
|
|
256
|
+
const t1 = Math.max(triggers[reach] ?? t0, t0 + 1);
|
|
257
257
|
const p = scrollY.interpolate({ inputRange: [t0, t1], outputRange: [0.0001, 1], extrapolate: 'clamp' });
|
|
258
258
|
fillStyle = {
|
|
259
259
|
transform: [
|
|
@@ -358,11 +358,11 @@ interface RowProps {
|
|
|
358
358
|
arriveDelay: number;
|
|
359
359
|
fillMs: number;
|
|
360
360
|
/** 'scroll': what the dot shows right now (it lights up when the fill reaches it). */
|
|
361
|
-
dotStatus?: TimelineStatus;
|
|
361
|
+
dotStatus?: TimelineStatus | undefined;
|
|
362
362
|
/** 'scroll': the fill has reached this dot — pops it. */
|
|
363
|
-
lit?: boolean;
|
|
363
|
+
lit?: boolean | undefined;
|
|
364
364
|
/** 'scroll': reports the dot centre's y inside the list. */
|
|
365
|
-
onNode?: (y: number) => void;
|
|
365
|
+
onNode?: ((y: number) => void) | undefined;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
function Row({
|
|
@@ -398,9 +398,9 @@ function Row({
|
|
|
398
398
|
const gapBelow = compact ? 14 : card ? 12 : scroll ? 32 : 26;
|
|
399
399
|
const entrance = !still && !scroll;
|
|
400
400
|
|
|
401
|
-
const enter =
|
|
402
|
-
const fill =
|
|
403
|
-
const pop =
|
|
401
|
+
const [enter] = useState(() => new Animated.Value(entrance ? 0 : 1));
|
|
402
|
+
const [fill] = useState(() => new Animated.Value(still ? 1 : 0));
|
|
403
|
+
const [pop] = useState(() => new Animated.Value(1));
|
|
404
404
|
const wasLit = useRef(!!lit);
|
|
405
405
|
const [lineH, setLineH] = useState(0);
|
|
406
406
|
|
|
@@ -685,8 +685,8 @@ function Dot({
|
|
|
685
685
|
}) {
|
|
686
686
|
const { colors, radius, dark } = useKit();
|
|
687
687
|
const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
688
|
-
const draw =
|
|
689
|
-
const pulse =
|
|
688
|
+
const [draw] = useState(() => new Animated.Value(still ? 1 : 0)); // JS: dash offsets
|
|
689
|
+
const [pulse] = useState(() => new Animated.Value(0)); // native: halo
|
|
690
690
|
const current = status === 'current';
|
|
691
691
|
const marked = status === 'error' || (status === 'done' && checks);
|
|
692
692
|
|
|
@@ -894,12 +894,15 @@ function TimelineGallery({ images, dim }: { images: TimelineImageSource[]; dim:
|
|
|
894
894
|
styles.gallery,
|
|
895
895
|
{ borderRadius: radius.md, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06), opacity: dim ? 0.6 : 1 },
|
|
896
896
|
];
|
|
897
|
-
const tile = (i: number, flex: ViewStyle) =>
|
|
898
|
-
|
|
899
|
-
|
|
897
|
+
const tile = (i: number, flex: ViewStyle) => {
|
|
898
|
+
const image = shown[i];
|
|
899
|
+
return image ? (
|
|
900
|
+
<Photo key={i} uri={image.uri} delay={i * 90} style={[flex, { borderRadius: radius.scale(4) }]} more={i === 2 && extra > 0 ? extra : 0} />
|
|
901
|
+
) : null;
|
|
902
|
+
};
|
|
900
903
|
|
|
901
904
|
if (shown.length === 1) {
|
|
902
|
-
return <View style={[frame, { aspectRatio: shown[0]
|
|
905
|
+
return <View style={[frame, { aspectRatio: shown[0]?.ratio }]}>{tile(0, styles.fill)}</View>;
|
|
903
906
|
}
|
|
904
907
|
if (shown.length === 2) {
|
|
905
908
|
return (
|
|
@@ -923,7 +926,7 @@ function TimelineGallery({ images, dim }: { images: TimelineImageSource[]; dim:
|
|
|
923
926
|
function Photo({ uri, delay, style, more }: { uri: string; delay: number; style: StyleProp<ViewStyle>; more: number }) {
|
|
924
927
|
const { colors, font } = useKit();
|
|
925
928
|
const ff = font.family ? { fontFamily: font.family } : null;
|
|
926
|
-
const shown =
|
|
929
|
+
const [shown] = useState(() => new Animated.Value(0));
|
|
927
930
|
return (
|
|
928
931
|
<View style={[styles.photo, { backgroundColor: colors.muted }, style]}>
|
|
929
932
|
<Animated.Image
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import React, { createContext, useContext, useMemo } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { createContext, useContext, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
PanResponder,
|
|
4
|
+
Platform,
|
|
5
|
+
useColorScheme,
|
|
6
|
+
useWindowDimensions,
|
|
7
|
+
type GestureResponderHandlers,
|
|
8
|
+
type PanResponderCallbacks,
|
|
9
|
+
type TextStyle,
|
|
10
|
+
type ViewStyle,
|
|
11
|
+
} from 'react-native';
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
14
|
* ExpoKit design tokens. Every component reads them through useKit(), so one
|
|
@@ -9,9 +18,9 @@ import { Platform, useColorScheme, useWindowDimensions, type TextStyle, type Vie
|
|
|
9
18
|
|
|
10
19
|
/**
|
|
11
20
|
* Bumped whenever components start using a new kit API (2: radius.scale / radius.capsule,
|
|
12
|
-
* 3: useLayout / shadow). The CLI won't keep an edited kit.tsx older than the component being added needs.
|
|
21
|
+
* 3: useLayout / shadow, 4: labels, 5: visible labels such as Cancel / Done / Today, AM / PM, usePanResponder / useLatest). The CLI won't keep an edited kit.tsx older than the component being added needs.
|
|
13
22
|
*/
|
|
14
|
-
export const KIT_VERSION =
|
|
23
|
+
export const KIT_VERSION = 5;
|
|
15
24
|
|
|
16
25
|
export type ColorScheme = 'light' | 'dark';
|
|
17
26
|
|
|
@@ -60,10 +69,298 @@ export interface Theme {
|
|
|
60
69
|
space: (n: number) => number;
|
|
61
70
|
/** The same soft shadow on iOS, Android and web: `style={[styles.card, shadow('md')]}`. */
|
|
62
71
|
shadow: (level: ShadowLevel, color?: string) => ViewStyle;
|
|
63
|
-
font: { family?: string; mono: string };
|
|
72
|
+
font: { family?: string | undefined; mono: string };
|
|
64
73
|
text: Record<'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', TextStyle>;
|
|
74
|
+
/** Text the kit shows or speaks on its own (buttons, placeholders, screen-reader names). Translate with <KitProvider labels={...}>. */
|
|
75
|
+
labels: Labels;
|
|
65
76
|
}
|
|
66
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Every string the kit shows or speaks on its own: button text and placeholders (Cancel, Done, Today,
|
|
80
|
+
* Search…) and what screen readers say (close buttons, grabbers, hints). A prop on a component, like
|
|
81
|
+
* `cancelLabel` or `placeholder`, still wins over these.
|
|
82
|
+
* Pass a partial map to <KitProvider labels={...}>; keys you leave out stay English.
|
|
83
|
+
* Functions get the dynamic part, so word order can change per language.
|
|
84
|
+
*/
|
|
85
|
+
export interface Labels {
|
|
86
|
+
close: string;
|
|
87
|
+
dismiss: string;
|
|
88
|
+
closeSheet: string;
|
|
89
|
+
sheetGrabber: string;
|
|
90
|
+
sheetResizeHint: string;
|
|
91
|
+
sheetResizeOrCloseHint: string;
|
|
92
|
+
navigationDrawer: string;
|
|
93
|
+
closeDrawer: string;
|
|
94
|
+
closeMenu: string;
|
|
95
|
+
switchAccount: string;
|
|
96
|
+
openActions: string;
|
|
97
|
+
closeActions: string;
|
|
98
|
+
action: string;
|
|
99
|
+
loading: string;
|
|
100
|
+
clearSearch: string;
|
|
101
|
+
cancelSearch: string;
|
|
102
|
+
clearField: (field: string) => string;
|
|
103
|
+
back: string;
|
|
104
|
+
previous: string;
|
|
105
|
+
next: string;
|
|
106
|
+
goToToday: string;
|
|
107
|
+
goToSlide: (n: number) => string;
|
|
108
|
+
showAllCards: string;
|
|
109
|
+
expandNotification: string;
|
|
110
|
+
collapseNotification: string;
|
|
111
|
+
moreCount: (n: number) => string;
|
|
112
|
+
peopleCount: (n: number) => string;
|
|
113
|
+
namesAndMore: (names: string, n: number) => string;
|
|
114
|
+
newCount: (badge: string) => string;
|
|
115
|
+
remove: (item: string) => string;
|
|
116
|
+
selectedCount: (n: number) => string;
|
|
117
|
+
noneSelected: string;
|
|
118
|
+
notSet: string;
|
|
119
|
+
date: string;
|
|
120
|
+
time: string;
|
|
121
|
+
hour: string;
|
|
122
|
+
minute: string;
|
|
123
|
+
hours: (n: string) => string;
|
|
124
|
+
minutes: (n: number) => string;
|
|
125
|
+
booked: string;
|
|
126
|
+
unavailable: string;
|
|
127
|
+
timeDialHint: string;
|
|
128
|
+
flipCalendarHint: string;
|
|
129
|
+
increase: string;
|
|
130
|
+
decrease: string;
|
|
131
|
+
increaseItem: (item: string) => string;
|
|
132
|
+
decreaseItem: (item: string) => string;
|
|
133
|
+
range: string;
|
|
134
|
+
rangeMinimum: (name: string) => string;
|
|
135
|
+
rangeMaximum: (name: string) => string;
|
|
136
|
+
ratingStars: string;
|
|
137
|
+
ratingHearts: string;
|
|
138
|
+
showPassword: string;
|
|
139
|
+
hidePassword: string;
|
|
140
|
+
passwordStrength: (level: string) => string;
|
|
141
|
+
ruleMet: (rule: string) => string;
|
|
142
|
+
ruleUnmet: (rule: string) => string;
|
|
143
|
+
stepOf: (step: number, total: number, label: string) => string;
|
|
144
|
+
allStepsComplete: string;
|
|
145
|
+
stepCurrent: string;
|
|
146
|
+
stepCompleted: string;
|
|
147
|
+
stepNeedsAttention: string;
|
|
148
|
+
stepNotStarted: string;
|
|
149
|
+
goBackToStep: string;
|
|
150
|
+
activateCodeField: string;
|
|
151
|
+
qrCodeFor: (value: string) => string;
|
|
152
|
+
undoStroke: string;
|
|
153
|
+
signaturePad: string;
|
|
154
|
+
signaturePadSigned: string;
|
|
155
|
+
flipToBack: string;
|
|
156
|
+
flipToFront: string;
|
|
157
|
+
revealHint: string;
|
|
158
|
+
confirmHint: string;
|
|
159
|
+
prizeWheel: string;
|
|
160
|
+
spinHint: string;
|
|
161
|
+
prizes: (n: number, list: string) => string;
|
|
162
|
+
orbReveal: string;
|
|
163
|
+
orbBack: string;
|
|
164
|
+
// Visible text
|
|
165
|
+
cancel: string;
|
|
166
|
+
done: string;
|
|
167
|
+
doneCount: (n: number) => string;
|
|
168
|
+
doneDays: (n: number) => string;
|
|
169
|
+
clear: string;
|
|
170
|
+
continue: string;
|
|
171
|
+
delete: string;
|
|
172
|
+
failed: string;
|
|
173
|
+
search: string;
|
|
174
|
+
noResults: string;
|
|
175
|
+
noOptions: string;
|
|
176
|
+
nothingMatches: (query: string) => string;
|
|
177
|
+
selectPlaceholder: string;
|
|
178
|
+
selectDate: string;
|
|
179
|
+
selectDates: string;
|
|
180
|
+
pickStartDate: string;
|
|
181
|
+
pickEndDate: string;
|
|
182
|
+
selectTime: string;
|
|
183
|
+
setTime: (time: string) => string;
|
|
184
|
+
/** Clock periods, shown on 12-hour pickers and times. */
|
|
185
|
+
am: string;
|
|
186
|
+
pm: string;
|
|
187
|
+
/** Read for the AM / PM column. */
|
|
188
|
+
amPm: string;
|
|
189
|
+
morning: string;
|
|
190
|
+
afternoon: string;
|
|
191
|
+
evening: string;
|
|
192
|
+
slotsOpen: (n: number) => string;
|
|
193
|
+
full: string;
|
|
194
|
+
today: string;
|
|
195
|
+
tomorrow: string;
|
|
196
|
+
nextMonday: string;
|
|
197
|
+
inAWeek: string;
|
|
198
|
+
inAMonth: string;
|
|
199
|
+
next7Days: string;
|
|
200
|
+
nextWeekend: string;
|
|
201
|
+
thisMonth: string;
|
|
202
|
+
last7Days: string;
|
|
203
|
+
last30Days: string;
|
|
204
|
+
chooseOneOrMore: string;
|
|
205
|
+
/** After a rolling count, as in "2 selected". */
|
|
206
|
+
selectedSuffix: string;
|
|
207
|
+
chooseMonth: string;
|
|
208
|
+
chooseYear: string;
|
|
209
|
+
backToDays: string;
|
|
210
|
+
dayAndWeek: (day: number, week: number) => string;
|
|
211
|
+
daysLeft: (n: number) => string;
|
|
212
|
+
/** Too short, Weak, Okay, Strong, Very strong. */
|
|
213
|
+
passwordLevels: [string, string, string, string, string];
|
|
214
|
+
passwordsMatch: string;
|
|
215
|
+
passwordsDontMatch: string;
|
|
216
|
+
showQrCode: string;
|
|
217
|
+
copy: string;
|
|
218
|
+
copied: string;
|
|
219
|
+
slideToConfirm: string;
|
|
220
|
+
confirmed: string;
|
|
221
|
+
like: string;
|
|
222
|
+
nope: string;
|
|
223
|
+
swipeRight: string;
|
|
224
|
+
swipeLeft: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const defaultLabels: Labels = {
|
|
228
|
+
close: 'Close',
|
|
229
|
+
dismiss: 'Dismiss',
|
|
230
|
+
closeSheet: 'Close sheet',
|
|
231
|
+
sheetGrabber: 'Sheet grabber',
|
|
232
|
+
sheetResizeHint: 'Swipe up or down to resize',
|
|
233
|
+
sheetResizeOrCloseHint: 'Swipe up or down to resize or close',
|
|
234
|
+
navigationDrawer: 'Navigation drawer',
|
|
235
|
+
closeDrawer: 'Close drawer',
|
|
236
|
+
closeMenu: 'Close menu',
|
|
237
|
+
switchAccount: 'Switch account',
|
|
238
|
+
openActions: 'Open actions',
|
|
239
|
+
closeActions: 'Close actions',
|
|
240
|
+
action: 'Action',
|
|
241
|
+
loading: 'Loading',
|
|
242
|
+
clearSearch: 'Clear search',
|
|
243
|
+
cancelSearch: 'Cancel search',
|
|
244
|
+
clearField: (field) => `Clear ${field}`,
|
|
245
|
+
back: 'Back',
|
|
246
|
+
previous: 'Previous',
|
|
247
|
+
next: 'Next',
|
|
248
|
+
goToToday: 'Go to today',
|
|
249
|
+
goToSlide: (n) => `Go to slide ${n}`,
|
|
250
|
+
showAllCards: 'Shows all cards',
|
|
251
|
+
expandNotification: 'Expands the notification',
|
|
252
|
+
collapseNotification: 'Collapses the notification',
|
|
253
|
+
moreCount: (n) => `${n} more`,
|
|
254
|
+
peopleCount: (n) => `${n} people`,
|
|
255
|
+
namesAndMore: (names, n) => `${names} and ${n} more`,
|
|
256
|
+
newCount: (badge) => `${badge} new`,
|
|
257
|
+
remove: (item) => `Remove ${item}`,
|
|
258
|
+
selectedCount: (n) => `${n} selected`,
|
|
259
|
+
noneSelected: 'None selected',
|
|
260
|
+
notSet: 'Not set',
|
|
261
|
+
date: 'Date',
|
|
262
|
+
time: 'Time',
|
|
263
|
+
hour: 'Hour',
|
|
264
|
+
minute: 'Minute',
|
|
265
|
+
hours: (n) => `${n} hours`,
|
|
266
|
+
minutes: (n) => `${n} minutes`,
|
|
267
|
+
booked: 'booked',
|
|
268
|
+
unavailable: 'unavailable',
|
|
269
|
+
timeDialHint: 'Drag around the dial, or swipe up or down to adjust.',
|
|
270
|
+
flipCalendarHint: 'Swipe up to turn to the next day, down for the previous one.',
|
|
271
|
+
increase: 'Increase',
|
|
272
|
+
decrease: 'Decrease',
|
|
273
|
+
increaseItem: (item) => `Increase ${item}`,
|
|
274
|
+
decreaseItem: (item) => `Decrease ${item}`,
|
|
275
|
+
range: 'Range',
|
|
276
|
+
rangeMinimum: (name) => `${name} minimum`,
|
|
277
|
+
rangeMaximum: (name) => `${name} maximum`,
|
|
278
|
+
ratingStars: 'Rating, stars',
|
|
279
|
+
ratingHearts: 'Rating, hearts',
|
|
280
|
+
showPassword: 'Show password',
|
|
281
|
+
hidePassword: 'Hide password',
|
|
282
|
+
passwordStrength: (level) => `Password strength: ${level}`,
|
|
283
|
+
ruleMet: (rule) => `${rule}: done`,
|
|
284
|
+
ruleUnmet: (rule) => `${rule}: not yet`,
|
|
285
|
+
stepOf: (step, total, label) => `Step ${step} of ${total}, ${label}`,
|
|
286
|
+
allStepsComplete: 'All steps complete',
|
|
287
|
+
stepCurrent: 'current',
|
|
288
|
+
stepCompleted: 'completed',
|
|
289
|
+
stepNeedsAttention: 'needs attention',
|
|
290
|
+
stepNotStarted: 'not started',
|
|
291
|
+
goBackToStep: 'Goes back to this step',
|
|
292
|
+
activateCodeField: 'Activates the code field',
|
|
293
|
+
qrCodeFor: (value) => `QR code for ${value}`,
|
|
294
|
+
undoStroke: 'Undo last stroke',
|
|
295
|
+
signaturePad: 'Signature pad, draw your signature',
|
|
296
|
+
signaturePadSigned: 'Signature pad, signed',
|
|
297
|
+
flipToBack: 'Tap to flip to the back.',
|
|
298
|
+
flipToFront: 'Showing the back. Tap to flip to the front.',
|
|
299
|
+
revealHint: 'Double-tap to reveal',
|
|
300
|
+
confirmHint: 'Double tap to confirm',
|
|
301
|
+
prizeWheel: 'Prize wheel',
|
|
302
|
+
spinHint: 'Spins the wheel for a prize',
|
|
303
|
+
prizes: (n, list) => `${n} prizes: ${list}`,
|
|
304
|
+
orbReveal: 'Reveal',
|
|
305
|
+
orbBack: 'Glass orb, drag down to go back',
|
|
306
|
+
cancel: 'Cancel',
|
|
307
|
+
done: 'Done',
|
|
308
|
+
doneCount: (n) => `Done · ${n}`,
|
|
309
|
+
doneDays: (n) => `Done · ${n} days`,
|
|
310
|
+
clear: 'Clear',
|
|
311
|
+
continue: 'Continue',
|
|
312
|
+
delete: 'Delete',
|
|
313
|
+
failed: 'Failed',
|
|
314
|
+
search: 'Search',
|
|
315
|
+
noResults: 'No results',
|
|
316
|
+
noOptions: 'No options',
|
|
317
|
+
nothingMatches: (query) => `Nothing matches “${query}”.`,
|
|
318
|
+
selectPlaceholder: 'Select…',
|
|
319
|
+
selectDate: 'Select a date',
|
|
320
|
+
selectDates: 'Select dates',
|
|
321
|
+
pickStartDate: 'Pick a start date',
|
|
322
|
+
pickEndDate: 'pick an end date',
|
|
323
|
+
selectTime: 'Select a time',
|
|
324
|
+
setTime: (time) => `Set ${time}`,
|
|
325
|
+
am: 'AM',
|
|
326
|
+
pm: 'PM',
|
|
327
|
+
amPm: 'AM or PM',
|
|
328
|
+
morning: 'Morning',
|
|
329
|
+
afternoon: 'Afternoon',
|
|
330
|
+
evening: 'Evening',
|
|
331
|
+
slotsOpen: (n) => `${n} open`,
|
|
332
|
+
full: 'Full',
|
|
333
|
+
today: 'Today',
|
|
334
|
+
tomorrow: 'Tomorrow',
|
|
335
|
+
nextMonday: 'Next Monday',
|
|
336
|
+
inAWeek: 'In a week',
|
|
337
|
+
inAMonth: 'In a month',
|
|
338
|
+
next7Days: 'Next 7 days',
|
|
339
|
+
nextWeekend: 'Next weekend',
|
|
340
|
+
thisMonth: 'This month',
|
|
341
|
+
last7Days: 'Last 7 days',
|
|
342
|
+
last30Days: 'Last 30 days',
|
|
343
|
+
chooseOneOrMore: 'Choose one or more',
|
|
344
|
+
selectedSuffix: 'selected',
|
|
345
|
+
chooseMonth: 'choose month',
|
|
346
|
+
chooseYear: 'choose year',
|
|
347
|
+
backToDays: 'back to days',
|
|
348
|
+
dayAndWeek: (day, week) => `Day ${day} · Week ${week}`,
|
|
349
|
+
daysLeft: (n) => `${n} left`,
|
|
350
|
+
passwordLevels: ['Too short', 'Weak', 'Okay', 'Strong', 'Very strong'],
|
|
351
|
+
passwordsMatch: 'Passwords match',
|
|
352
|
+
passwordsDontMatch: "Passwords don't match",
|
|
353
|
+
showQrCode: 'Show QR Code',
|
|
354
|
+
copy: 'Copy',
|
|
355
|
+
copied: 'Copied',
|
|
356
|
+
slideToConfirm: 'Slide to confirm',
|
|
357
|
+
confirmed: 'Confirmed',
|
|
358
|
+
like: 'LIKE',
|
|
359
|
+
nope: 'NOPE',
|
|
360
|
+
swipeRight: 'Right',
|
|
361
|
+
swipeLeft: 'Left',
|
|
362
|
+
};
|
|
363
|
+
|
|
67
364
|
const light: Colors = {
|
|
68
365
|
background: '#ffffff',
|
|
69
366
|
foreground: '#09090b',
|
|
@@ -149,20 +446,27 @@ function build(scheme: ColorScheme, overrides?: ThemeOverrides): Theme {
|
|
|
149
446
|
xl: { fontSize: 22, lineHeight: 28, fontWeight: '400', letterSpacing: -0.3 },
|
|
150
447
|
xxl: { fontSize: 30, lineHeight: 36, fontWeight: '400', letterSpacing: -0.8 },
|
|
151
448
|
},
|
|
449
|
+
labels: overrides?.labels ? { ...defaultLabels, ...overrides.labels } : defaultLabels,
|
|
152
450
|
};
|
|
153
451
|
}
|
|
154
452
|
|
|
155
453
|
export interface ThemeOverrides {
|
|
156
454
|
/** Brand colour used for focus rings, selected states and accent buttons. */
|
|
157
|
-
accent?: string;
|
|
455
|
+
accent?: string | undefined;
|
|
456
|
+
|
|
158
457
|
/** Base corner radius in points (default 12). */
|
|
159
|
-
radius?: number;
|
|
160
|
-
|
|
458
|
+
radius?: number | undefined;
|
|
459
|
+
|
|
460
|
+
fontFamily?: string | undefined;
|
|
461
|
+
|
|
161
462
|
/** Override any token per scheme. */
|
|
162
|
-
colors?: Partial<Record<ColorScheme, Partial<Colors
|
|
463
|
+
colors?: Partial<Record<ColorScheme, Partial<Colors>>> | undefined;
|
|
464
|
+
|
|
465
|
+
/** Screen-reader strings in your language, e.g. { close: "बंद करें" }. Keys you leave out stay English. */
|
|
466
|
+
labels?: Partial<Labels> | undefined;
|
|
163
467
|
}
|
|
164
468
|
|
|
165
|
-
const KitContext = createContext<{ scheme?: ColorScheme; overrides?: ThemeOverrides } | null>(null);
|
|
469
|
+
const KitContext = createContext<{ scheme?: ColorScheme | undefined; overrides?: ThemeOverrides | undefined } | null>(null);
|
|
166
470
|
|
|
167
471
|
export function KitProvider({
|
|
168
472
|
scheme,
|
|
@@ -172,7 +476,7 @@ export function KitProvider({
|
|
|
172
476
|
const value = useMemo(
|
|
173
477
|
() => ({ scheme: scheme === 'system' ? undefined : scheme, overrides }),
|
|
174
478
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
175
|
-
[scheme, overrides.accent, overrides.radius, overrides.fontFamily, overrides.colors]
|
|
479
|
+
[scheme, overrides.accent, overrides.radius, overrides.fontFamily, overrides.colors, overrides.labels]
|
|
176
480
|
);
|
|
177
481
|
return <KitContext.Provider value={value}>{children}</KitContext.Provider>;
|
|
178
482
|
}
|
|
@@ -318,3 +622,51 @@ export function isTextNode(node: React.ReactNode): node is string | number | (st
|
|
|
318
622
|
if (typeof node === 'string' || typeof node === 'number') return true;
|
|
319
623
|
return Array.isArray(node) && node.length > 0 && node.every((n) => typeof n === 'string' || typeof n === 'number');
|
|
320
624
|
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* A ref that always holds the latest `value`, for gesture and timer callbacks that must not go stale.
|
|
628
|
+
* Read `.current` only inside callbacks and effects, never while rendering.
|
|
629
|
+
*/
|
|
630
|
+
export function useLatest<T>(value: T): React.MutableRefObject<T> {
|
|
631
|
+
const ref = useRef(value);
|
|
632
|
+
useLayoutEffect(() => {
|
|
633
|
+
ref.current = value;
|
|
634
|
+
});
|
|
635
|
+
return ref;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const panKeys = [
|
|
639
|
+
'onStartShouldSetPanResponder',
|
|
640
|
+
'onStartShouldSetPanResponderCapture',
|
|
641
|
+
'onMoveShouldSetPanResponder',
|
|
642
|
+
'onMoveShouldSetPanResponderCapture',
|
|
643
|
+
'onPanResponderGrant',
|
|
644
|
+
'onPanResponderStart',
|
|
645
|
+
'onPanResponderMove',
|
|
646
|
+
'onPanResponderEnd',
|
|
647
|
+
'onPanResponderRelease',
|
|
648
|
+
'onPanResponderReject',
|
|
649
|
+
'onPanResponderTerminate',
|
|
650
|
+
] as const;
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* PanResponder handlers that always call the latest `config`, created once. Write the callbacks inline:
|
|
654
|
+
* they may read refs and props freely, and nothing is recreated between renders, so a drag in progress
|
|
655
|
+
* is never cut off. Spread the result: `<View {...pan} />`.
|
|
656
|
+
*/
|
|
657
|
+
export function usePanResponder(config: PanResponderCallbacks): GestureResponderHandlers {
|
|
658
|
+
const latest = useLatest(config);
|
|
659
|
+
const [handlers] = useState(() => {
|
|
660
|
+
const forward: Record<string, (...args: never[]) => unknown> = {};
|
|
661
|
+
for (const key of panKeys) {
|
|
662
|
+
forward[key] = (...args) => (latest.current[key] as ((...a: never[]) => unknown) | undefined)?.(...args);
|
|
663
|
+
}
|
|
664
|
+
// React Native's defaults when these are left out are `true`, so keep them when the config has none.
|
|
665
|
+
forward.onPanResponderTerminationRequest = (...args) =>
|
|
666
|
+
(latest.current.onPanResponderTerminationRequest as ((...a: never[]) => boolean) | undefined)?.(...args) ?? true;
|
|
667
|
+
forward.onShouldBlockNativeResponder = (...args) =>
|
|
668
|
+
(latest.current.onShouldBlockNativeResponder as ((...a: never[]) => boolean) | undefined)?.(...args) ?? true;
|
|
669
|
+
return PanResponder.create(forward as PanResponderCallbacks).panHandlers;
|
|
670
|
+
});
|
|
671
|
+
return handlers;
|
|
672
|
+
}
|
package/registry/timeline.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"title": "Timeline",
|
|
5
5
|
"description": "Vertical timeline for order tracking, activity feeds, travel logs and release history, in the preview's solid accent colour. Items rise in, then the connector fills down to the current step: done dots draw their check as the line arrives, the current dot pulses, upcoming dots stay hollow on a dotted track, and failures draw a red cross. The scroll variant ties the fill to the scroll position, lighting each node as the line reaches it. Entries can carry photo galleries, tags and links.",
|
|
6
6
|
"tier": "free",
|
|
7
|
+
"aliases": [],
|
|
7
8
|
"dependencies": [
|
|
8
9
|
"react-native-svg",
|
|
9
10
|
"expo-haptics"
|