@expokit/cli 0.4.3 → 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 +313 -4
- 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/flip-calendar.json +106 -0
- 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/paywall.json +144 -0
- 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/ruler-time-picker.json +147 -0
- 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 +462 -12
- package/registry/accordion.json +1 -0
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +50 -58
- package/registry/action-sheet/files/src/lib/kit.tsx +462 -12
- package/registry/action-sheet.json +1 -0
- package/registry/alert/files/src/components/ui/alert.tsx +20 -13
- package/registry/alert/files/src/lib/kit.tsx +462 -12
- package/registry/alert.json +3 -4
- package/registry/assistant-app.json +1 -0
- package/registry/avatar/files/src/components/ui/avatar.tsx +89 -74
- package/registry/avatar/files/src/lib/kit.tsx +462 -12
- package/registry/avatar.json +1 -0
- package/registry/badge/files/src/components/ui/badge.tsx +36 -36
- package/registry/badge/files/src/lib/kit.tsx +462 -12
- package/registry/badge.json +1 -0
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +39 -44
- package/registry/bottom-nav/files/src/lib/kit.tsx +462 -12
- package/registry/bottom-nav.json +5 -0
- package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +357 -300
- package/registry/bottom-sheet/files/src/lib/kit.tsx +462 -12
- package/registry/bottom-sheet.json +5 -1
- package/registry/bullet.json +1 -0
- package/registry/button/files/src/components/ui/button.tsx +23 -34
- package/registry/button/files/src/lib/kit.tsx +462 -12
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +6 -9
- package/registry/button-3d/files/src/lib/kit.tsx +462 -12
- package/registry/button-3d.json +1 -0
- package/registry/button.json +1 -0
- package/registry/calendar/files/src/components/ui/calendar.tsx +77 -79
- package/registry/calendar/files/src/lib/kit.tsx +462 -12
- package/registry/calendar.json +1 -0
- package/registry/card/files/src/components/ui/card.tsx +7 -6
- package/registry/card/files/src/lib/kit.tsx +462 -12
- 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 +462 -12
- package/registry/checkbox.json +1 -0
- package/registry/checkout.json +1 -0
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +24 -24
- package/registry/chip-group/files/src/lib/kit.tsx +462 -12
- 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 +47 -52
- package/registry/coupon/files/src/components/ui/flip-card.tsx +9 -14
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +80 -101
- package/registry/coupon/files/src/lib/kit.tsx +462 -12
- 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 +77 -79
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +64 -71
- package/registry/date-picker/files/src/lib/kit.tsx +462 -12
- package/registry/date-picker.json +1 -0
- package/registry/dialog/files/src/components/ui/dialog.tsx +66 -68
- package/registry/dialog/files/src/lib/kit.tsx +462 -12
- package/registry/dialog.json +1 -0
- package/registry/donut-shop.json +1 -0
- package/registry/drawer/files/src/components/ui/drawer.tsx +76 -88
- package/registry/drawer/files/src/lib/kit.tsx +462 -12
- 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 +632 -0
- package/registry/flip-calendar/files/src/lib/kit.tsx +672 -0
- package/registry/flip-calendar.json +24 -0
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +9 -14
- package/registry/flip-card/files/src/lib/kit.tsx +462 -12
- 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 +462 -12
- 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 +462 -12
- 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 +29 -16
- package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +33 -19
- 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 +43 -43
- package/registry/list-item/files/src/lib/kit.tsx +462 -12
- 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 +49 -30
- package/registry/otp-input/files/src/lib/kit.tsx +462 -12
- 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 +462 -12
- package/registry/password-input.json +1 -0
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +9 -14
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +26 -20
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +80 -101
- package/registry/payment-card/files/src/lib/kit.tsx +462 -12
- package/registry/payment-card.json +1 -0
- package/registry/paywall.json +23 -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 +31 -29
- package/registry/progress/files/src/lib/kit.tsx +462 -12
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +21 -21
- package/registry/progress-steps/files/src/lib/kit.tsx +462 -12
- 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 +462 -12
- 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 +832 -0
- package/registry/ruler-time-picker/files/src/lib/kit.tsx +672 -0
- package/registry/ruler-time-picker.json +26 -0
- package/registry/scratch-card.json +1 -0
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +28 -28
- package/registry/search-bar/files/src/lib/kit.tsx +462 -12
- 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 +11 -17
- package/registry/segmented-control/files/src/lib/kit.tsx +462 -12
- package/registry/segmented-control.json +1 -0
- package/registry/select/files/src/components/ui/select.tsx +107 -104
- package/registry/select/files/src/lib/kit.tsx +462 -12
- 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 +462 -12
- package/registry/skeleton.json +1 -0
- package/registry/slide-to-confirm.json +1 -0
- package/registry/slider/files/src/components/ui/slider.tsx +106 -108
- package/registry/slider/files/src/lib/kit.tsx +462 -12
- 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 +462 -12
- 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 +462 -12
- package/registry/stateful-button.json +5 -0
- package/registry/stepper/files/src/components/ui/stepper.tsx +40 -38
- package/registry/stepper/files/src/lib/kit.tsx +462 -12
- 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 +41 -47
- package/registry/switch/files/src/lib/kit.tsx +462 -12
- package/registry/switch.json +1 -0
- package/registry/tabs/files/src/components/ui/tabs.tsx +61 -64
- package/registry/tabs/files/src/lib/kit.tsx +462 -12
- 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 +462 -12
- 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 +462 -12
- package/registry/textarea.json +1 -0
- package/registry/ticket-card.json +1 -0
- package/registry/ticket-shape/files/src/lib/kit.tsx +462 -12
- 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 +80 -101
- package/registry/tilt-card/files/src/lib/kit.tsx +462 -12
- package/registry/tilt-card.json +1 -0
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +136 -131
- package/registry/time-picker/files/src/lib/kit.tsx +462 -12
- package/registry/time-picker.json +1 -0
- package/registry/timeline/files/src/components/ui/avatar.tsx +89 -74
- package/registry/timeline/files/src/components/ui/timeline.tsx +49 -57
- package/registry/timeline/files/src/lib/kit.tsx +462 -12
- package/registry/timeline-chart.json +1 -0
- package/registry/timeline.json +1 -0
- package/registry/toast/files/src/components/ui/toast.tsx +543 -465
- package/registry/toast/files/src/lib/kit.tsx +462 -12
- 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 -303
- package/registry/confetti/files/src/lib/kit.tsx +0 -222
- package/registry/grain/files/src/lib/kit.tsx +0 -222
- package/registry/toast/files/src/components/ui/button.tsx +0 -303
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Haptics from 'expo-haptics';
|
|
2
|
-
import React, { useEffect, useId,
|
|
2
|
+
import React, { useEffect, useId, useState } from 'react';
|
|
3
3
|
import {
|
|
4
4
|
AccessibilityInfo,
|
|
5
5
|
Animated,
|
|
@@ -25,41 +25,41 @@ export type AvatarFallback = 'soft' | 'solid' | 'gradient';
|
|
|
25
25
|
|
|
26
26
|
export interface AvatarProps {
|
|
27
27
|
/** Image URL. Initials show while it loads, and stay if it fails. */
|
|
28
|
-
src?: string;
|
|
28
|
+
src?: string | undefined;
|
|
29
29
|
/** Used for the initials, the fallback colour (same name → same colour) and the accessibility label. */
|
|
30
|
-
name?: string;
|
|
30
|
+
name?: string | undefined;
|
|
31
31
|
/** 'xs' 24 · 'sm' 32 · 'md' 40 · 'lg' 56 · 'xl' 72, or any number of points. */
|
|
32
|
-
size?: AvatarSize | number;
|
|
33
|
-
shape?: AvatarShape;
|
|
32
|
+
size?: AvatarSize | number | undefined;
|
|
33
|
+
shape?: AvatarShape | undefined;
|
|
34
34
|
/** Presence dot cut into the bottom-right edge. */
|
|
35
|
-
status?: AvatarStatus;
|
|
35
|
+
status?: AvatarStatus | undefined;
|
|
36
36
|
/** Soft pulse behind the status dot (default: on for 'online'). */
|
|
37
|
-
pulse?: boolean;
|
|
37
|
+
pulse?: boolean | undefined;
|
|
38
38
|
/** Ring around the face, separated by a clear gap. */
|
|
39
|
-
ring?: AvatarRing;
|
|
39
|
+
ring?: AvatarRing | undefined;
|
|
40
40
|
/** The story has been watched: the ring cross-fades to a quiet hairline. */
|
|
41
|
-
seen?: boolean;
|
|
41
|
+
seen?: boolean | undefined;
|
|
42
42
|
/** Ring colour override — the first entry is used as a solid colour (default: `accent`). */
|
|
43
|
-
ringColors?: string[];
|
|
43
|
+
ringColors?: string[] | undefined;
|
|
44
44
|
/** Spin a broken story ring — a "story is loading" state. */
|
|
45
|
-
ringSpin?: boolean;
|
|
45
|
+
ringSpin?: boolean | undefined;
|
|
46
46
|
/** Initials look when there is no photo. Default 'soft'. */
|
|
47
|
-
fallback?: AvatarFallback;
|
|
47
|
+
fallback?: AvatarFallback | undefined;
|
|
48
48
|
/** Soft layered shadow under the face (default true for 72pt and up). */
|
|
49
|
-
elevated?: boolean;
|
|
49
|
+
elevated?: boolean | undefined;
|
|
50
50
|
/** Notification count (`true` = dot), top-right. */
|
|
51
|
-
badge?: number | boolean;
|
|
51
|
+
badge?: number | boolean | undefined;
|
|
52
52
|
/** Verified seal with a check, bottom-right (bottom-left when a status dot is shown). */
|
|
53
|
-
verified?: boolean;
|
|
53
|
+
verified?: boolean | undefined;
|
|
54
54
|
/** Colour behind the avatar — the dot, badge and seal cut-outs are punched in it. Default: theme background. */
|
|
55
|
-
surface?: string;
|
|
55
|
+
surface?: string | undefined;
|
|
56
56
|
/** Ring and verified seal colour (default: theme accent). */
|
|
57
|
-
accent?: string;
|
|
57
|
+
accent?: string | undefined;
|
|
58
58
|
/** Makes it a button with a spring press and a light haptic tick. */
|
|
59
|
-
onPress?: () => void;
|
|
59
|
+
onPress?: (() => void) | undefined;
|
|
60
60
|
/** Haptic tick on press (default true). */
|
|
61
|
-
haptics?: boolean;
|
|
62
|
-
style?: StyleProp<ViewStyle
|
|
61
|
+
haptics?: boolean | undefined;
|
|
62
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const SIZES: Record<AvatarSize, number> = { xs: 24, sm: 32, md: 40, lg: 56, xl: 72 };
|
|
@@ -99,7 +99,9 @@ export function colorFromName(name = '') {
|
|
|
99
99
|
export function initialsFrom(name = '', max = 2) {
|
|
100
100
|
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
101
101
|
if (!parts.length) return '';
|
|
102
|
-
const
|
|
102
|
+
const first = parts[0] ?? '';
|
|
103
|
+
const last = parts[parts.length - 1] ?? '';
|
|
104
|
+
const letters = parts.length === 1 ? [first[0]] : [first[0], last[0]];
|
|
103
105
|
return letters.slice(0, max).join('').toUpperCase();
|
|
104
106
|
}
|
|
105
107
|
|
|
@@ -144,13 +146,13 @@ export function Avatar({
|
|
|
144
146
|
// Remember which URL failed so a new `src` gets a fresh try without resetting state in an effect.
|
|
145
147
|
const [failedSrc, setFailedSrc] = useState<string>();
|
|
146
148
|
const failed = !!src && failedSrc === src;
|
|
147
|
-
const loaded =
|
|
148
|
-
const press =
|
|
149
|
-
const spin =
|
|
150
|
-
const pulseV =
|
|
149
|
+
const [loaded] = useState(() => new Animated.Value(0));
|
|
150
|
+
const [press] = useState(() => new Animated.Value(1));
|
|
151
|
+
const [spin] = useState(() => new Animated.Value(0));
|
|
152
|
+
const [pulseV] = useState(() => new Animated.Value(0));
|
|
151
153
|
|
|
152
154
|
const isSeen = seen ?? ring === 'seen';
|
|
153
|
-
const seenV =
|
|
155
|
+
const [seenV] = useState(() => new Animated.Value(isSeen ? 1 : 0));
|
|
154
156
|
|
|
155
157
|
useEffect(() => {
|
|
156
158
|
loaded.setValue(0);
|
|
@@ -248,11 +250,8 @@ export function Avatar({
|
|
|
248
250
|
{ width: px, height: px, borderRadius: faceRadius, backgroundColor: faceBg },
|
|
249
251
|
showElevated
|
|
250
252
|
? {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
shadowRadius: px * 0.16,
|
|
254
|
-
shadowOffset: { width: 0, height: Math.max(2, px * 0.07) },
|
|
255
|
-
elevation: Math.round(px * 0.1),
|
|
253
|
+
// boxShadow scales with the avatar and looks the same on iOS, Android and web.
|
|
254
|
+
boxShadow: `0px ${Math.round(Math.max(2, px * 0.07))}px ${Math.round(px * 0.32)}px rgba(0,0,0,${dark ? 0.5 : 0.14})`,
|
|
256
255
|
}
|
|
257
256
|
: null,
|
|
258
257
|
]}
|
|
@@ -338,7 +337,7 @@ export function Avatar({
|
|
|
338
337
|
strokeWidth={width}
|
|
339
338
|
fill="none"
|
|
340
339
|
strokeLinecap="round"
|
|
341
|
-
|
|
340
|
+
{...(dashed ? { strokeDasharray: [circ * 0.7, circ * 0.3] } : null)}
|
|
342
341
|
/>
|
|
343
342
|
) : (
|
|
344
343
|
<Rect
|
|
@@ -571,12 +570,15 @@ const SEAL = (() => {
|
|
|
571
570
|
const rr = k % 2 === 0 ? 12 : 10.1;
|
|
572
571
|
return [12 + rr * Math.cos(a), 12 + rr * Math.sin(a)] as const;
|
|
573
572
|
});
|
|
574
|
-
|
|
573
|
+
type Pt = readonly [number, number];
|
|
574
|
+
const mid = (a: Pt, b: Pt): Pt => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
|
|
575
575
|
const f = (v: number) => v.toFixed(2);
|
|
576
|
-
|
|
576
|
+
// `pts` has 2n points and every index below is taken modulo its length.
|
|
577
|
+
const at = (k: number) => pts[k % pts.length]!;
|
|
578
|
+
const start = mid(at(pts.length - 1), at(0));
|
|
577
579
|
let d = `M${f(start[0])} ${f(start[1])}`;
|
|
578
580
|
pts.forEach((pt, i) => {
|
|
579
|
-
const m = mid(pt,
|
|
581
|
+
const m = mid(pt, at(i + 1));
|
|
580
582
|
d += `Q${f(pt[0])} ${f(pt[1])} ${f(m[0])} ${f(m[1])}`;
|
|
581
583
|
});
|
|
582
584
|
return d + 'Z';
|
|
@@ -596,20 +598,20 @@ export interface AvatarGroupProps {
|
|
|
596
598
|
/** `<Avatar>` elements. The group sets their size, shape and surface. */
|
|
597
599
|
children: React.ReactNode;
|
|
598
600
|
/** Show at most this many, then a "+N" chip. */
|
|
599
|
-
max?: number;
|
|
600
|
-
size?: AvatarSize | number;
|
|
601
|
-
shape?: AvatarShape;
|
|
601
|
+
max?: number | undefined;
|
|
602
|
+
size?: AvatarSize | number | undefined;
|
|
603
|
+
shape?: AvatarShape | undefined;
|
|
602
604
|
/** How far each avatar tucks under the previous one, as a fraction of its size (default 0.28). */
|
|
603
|
-
overlap?: number;
|
|
605
|
+
overlap?: number | undefined;
|
|
604
606
|
/** Real total when it is larger than the children you pass (e.g. 1,284 members). */
|
|
605
|
-
total?: number;
|
|
607
|
+
total?: number | undefined;
|
|
606
608
|
/** Staggered spring-in on mount (skipped with reduce motion). */
|
|
607
|
-
animated?: boolean;
|
|
609
|
+
animated?: boolean | undefined;
|
|
608
610
|
/** Colour of the separating borders — match what the group sits on (default: theme background). */
|
|
609
|
-
surface?: string;
|
|
611
|
+
surface?: string | undefined;
|
|
610
612
|
/** Makes the "+N" chip a button. */
|
|
611
|
-
onPressMore?: () => void;
|
|
612
|
-
style?: StyleProp<ViewStyle
|
|
613
|
+
onPressMore?: (() => void) | undefined;
|
|
614
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
613
615
|
}
|
|
614
616
|
|
|
615
617
|
/** Overlapping avatars separated by cut-out borders, with a "+N" chip and a staggered spring-in. */
|
|
@@ -625,7 +627,7 @@ export function AvatarGroup({
|
|
|
625
627
|
onPressMore,
|
|
626
628
|
style,
|
|
627
629
|
}: AvatarGroupProps) {
|
|
628
|
-
const { colors, radius, dark, font } = useKit();
|
|
630
|
+
const { colors, radius, dark, font, labels } = useKit();
|
|
629
631
|
const ff = font.family ? { fontFamily: font.family } : null;
|
|
630
632
|
const reduceMotion = useReduceMotion();
|
|
631
633
|
const px = resolveSize(size);
|
|
@@ -638,8 +640,13 @@ export function AvatarGroup({
|
|
|
638
640
|
const inner = shape === 'circle' ? radius.full : Math.min(px / 2, radius.scale(Math.round(px * 0.3)));
|
|
639
641
|
const wrapCorner = shape === 'circle' ? radius.full : inner + border;
|
|
640
642
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
+
// One value per avatar, grown (never shrunk) as children are added; this render already uses the grown list.
|
|
644
|
+
const [pool, setPool] = useState<Animated.Value[]>([]);
|
|
645
|
+
let anims = pool;
|
|
646
|
+
if (pool.length < count) {
|
|
647
|
+
anims = [...pool, ...Array.from({ length: count - pool.length }, () => new Animated.Value(animated ? 0 : 1))];
|
|
648
|
+
setPool(anims);
|
|
649
|
+
}
|
|
643
650
|
|
|
644
651
|
useEffect(() => {
|
|
645
652
|
if (!animated) return;
|
|
@@ -657,35 +664,41 @@ export function AvatarGroup({
|
|
|
657
664
|
const names = shown.map((c) => c.props.name).filter(Boolean) as string[];
|
|
658
665
|
const label =
|
|
659
666
|
names.length === 0
|
|
660
|
-
?
|
|
661
|
-
:
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
667
|
+
? labels.peopleCount(total ?? items.length)
|
|
668
|
+
: (total ?? items.length) - Math.min(3, names.length) > 0
|
|
669
|
+
? labels.namesAndMore(names.slice(0, 3).join(', '), (total ?? items.length) - Math.min(3, names.length))
|
|
670
|
+
: names.slice(0, 3).join(', ');
|
|
671
|
+
|
|
672
|
+
// `anims` was grown to `count` above, and i < count.
|
|
673
|
+
const wrap = (child: React.ReactNode, i: number, key: React.Key) => {
|
|
674
|
+
const v = anims[i]!;
|
|
675
|
+
return (
|
|
676
|
+
<Animated.View
|
|
677
|
+
key={key}
|
|
678
|
+
style={{
|
|
679
|
+
marginLeft: i === 0 ? 0 : -px * overlap,
|
|
680
|
+
borderRadius: wrapCorner,
|
|
681
|
+
padding: border,
|
|
682
|
+
backgroundColor: bg,
|
|
683
|
+
zIndex: count - i,
|
|
684
|
+
opacity: v,
|
|
685
|
+
transform: [
|
|
686
|
+
{ translateX: v.interpolate({ inputRange: [0, 1], outputRange: [-8, 0] }) },
|
|
687
|
+
{ scale: v.interpolate({ inputRange: [0, 1], outputRange: [0.6, 1] }) },
|
|
688
|
+
],
|
|
689
|
+
}}
|
|
690
|
+
>
|
|
691
|
+
{child}
|
|
692
|
+
</Animated.View>
|
|
693
|
+
);
|
|
694
|
+
};
|
|
682
695
|
|
|
683
696
|
const extraText = extra > 999 ? `${(extra / 1000).toFixed(extra >= 10000 ? 0 : 1).replace(/\.0$/, '')}k` : String(extra);
|
|
684
697
|
const more =
|
|
685
698
|
extra > 0 ? (
|
|
686
699
|
<Pressable
|
|
687
700
|
accessibilityRole={onPressMore ? 'button' : 'text'}
|
|
688
|
-
accessibilityLabel={
|
|
701
|
+
accessibilityLabel={labels.moreCount(extra)}
|
|
689
702
|
disabled={!onPressMore}
|
|
690
703
|
onPress={onPressMore}
|
|
691
704
|
hitSlop={px < 44 ? Math.ceil((44 - px) / 2) : undefined}
|
|
@@ -774,20 +787,22 @@ function luminance(hex: string) {
|
|
|
774
787
|
}
|
|
775
788
|
|
|
776
789
|
function contrast(a: string, b: string) {
|
|
777
|
-
const
|
|
790
|
+
const la = luminance(a);
|
|
791
|
+
const lb = luminance(b);
|
|
792
|
+
const [x, y] = la >= lb ? [la, lb] : [lb, la];
|
|
778
793
|
return (x + 0.05) / (y + 0.05);
|
|
779
794
|
}
|
|
780
795
|
|
|
781
796
|
const styles = StyleSheet.create({
|
|
782
797
|
face: { overflow: 'visible' },
|
|
783
798
|
clip: { flex: 1, overflow: 'hidden', alignItems: 'center', justifyContent: 'center' },
|
|
784
|
-
initials: { fontWeight: '400', includeFontPadding: false, textAlign: 'center' },
|
|
799
|
+
initials: { fontWeight: '400', includeFontPadding: false, textAlign: 'center', textAlignVertical: 'center' },
|
|
785
800
|
inkShadow: { textShadowColor: 'rgba(0,0,0,0.18)', textShadowRadius: 2, textShadowOffset: { width: 0, height: 0.5 } },
|
|
786
801
|
pulse: { position: 'absolute' },
|
|
787
802
|
dot: { alignItems: 'center', justifyContent: 'center', overflow: 'hidden' },
|
|
788
803
|
badge: { position: 'absolute', alignItems: 'center', justifyContent: 'center' },
|
|
789
|
-
badgeText: { fontWeight: '400', letterSpacing: -0.2, includeFontPadding: false },
|
|
804
|
+
badgeText: { fontWeight: '400', letterSpacing: -0.2, includeFontPadding: false, textAlignVertical: 'center' },
|
|
790
805
|
group: { flexDirection: 'row', alignItems: 'center' },
|
|
791
806
|
more: { alignItems: 'center', justifyContent: 'center' },
|
|
792
|
-
moreText: { fontWeight: '400', letterSpacing: -0.2 },
|
|
807
|
+
moreText: { fontWeight: '400', letterSpacing: -0.2, includeFontPadding: false, textAlignVertical: 'center' },
|
|
793
808
|
});
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
AccessibilityInfo,
|
|
4
4
|
Animated,
|
|
5
5
|
Easing,
|
|
6
|
-
Platform,
|
|
7
6
|
Pressable,
|
|
8
7
|
StyleSheet,
|
|
9
8
|
Text,
|
|
@@ -45,27 +44,27 @@ export interface TimelineLink {
|
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
export interface TimelineItem {
|
|
48
|
-
key?: string;
|
|
47
|
+
key?: string | undefined;
|
|
49
48
|
title: string;
|
|
50
|
-
description?: string;
|
|
49
|
+
description?: string | undefined;
|
|
51
50
|
/** Timestamp or meta, e.g. "Oct 12 · 2:14 PM" or "2h". */
|
|
52
|
-
time?: string;
|
|
51
|
+
time?: string | undefined;
|
|
53
52
|
/** Default 'done'. */
|
|
54
|
-
status?: TimelineStatus;
|
|
53
|
+
status?: TimelineStatus | undefined;
|
|
55
54
|
/** Icon inside a larger dot. */
|
|
56
|
-
icon?: TimelineIconName | ((props: TimelineIconProps) => React.ReactNode);
|
|
55
|
+
icon?: (TimelineIconName | ((props: TimelineIconProps) => React.ReactNode)) | undefined;
|
|
57
56
|
/** Photo (initials fallback) instead of a dot. */
|
|
58
|
-
avatar?: { src?: string; name: string };
|
|
57
|
+
avatar?: { src?: string; name: string } | undefined;
|
|
59
58
|
/** A photo under the text — a URL, or `{ uri, aspectRatio }` (default 16:9). */
|
|
60
|
-
image?: TimelineImageSource;
|
|
59
|
+
image?: TimelineImageSource | undefined;
|
|
61
60
|
/** Several photos in a 1-, 2- or 3-up grid (more than three show a "+N" tile). Combined with `image`. */
|
|
62
|
-
images?: TimelineImageSource[];
|
|
61
|
+
images?: TimelineImageSource[] | undefined;
|
|
63
62
|
/** Small accent chips under the text. */
|
|
64
|
-
tags?: string[];
|
|
63
|
+
tags?: string[] | undefined;
|
|
65
64
|
/** Tappable accent links under the text. */
|
|
66
|
-
links?: TimelineLink[];
|
|
65
|
+
links?: TimelineLink[] | undefined;
|
|
67
66
|
/** Anything under the description: a card, a quote, tags, buttons. */
|
|
68
|
-
content?: React.ReactNode;
|
|
67
|
+
content?: React.ReactNode | undefined;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
export interface TimelineProps {
|
|
@@ -74,23 +73,23 @@ export interface TimelineProps {
|
|
|
74
73
|
* 'default' roomy rows · 'compact' small dots, one-line rows · 'card' each event on a card ·
|
|
75
74
|
* 'scroll' renders its own scroll view and fills the connector as you scroll (give it a height).
|
|
76
75
|
*/
|
|
77
|
-
variant?: TimelineVariant;
|
|
76
|
+
variant?: TimelineVariant | undefined;
|
|
78
77
|
/** Colour of done / current dots and the filled line (default: theme accent). */
|
|
79
|
-
accent?: string;
|
|
78
|
+
accent?: string | undefined;
|
|
80
79
|
/** Draw a check in done dots (default true). */
|
|
81
|
-
checks?: boolean;
|
|
80
|
+
checks?: boolean | undefined;
|
|
82
81
|
/** Entrance + connector fill, or the scroll-driven fill for 'scroll' (default true; off with reduce motion). */
|
|
83
|
-
animated?: boolean;
|
|
82
|
+
animated?: boolean | undefined;
|
|
84
83
|
/** Colour behind the rail — fills hollow dots and the mini status badges (default: theme background). */
|
|
85
|
-
surface?: string;
|
|
84
|
+
surface?: string | undefined;
|
|
86
85
|
/** 'scroll' only: rendered above the timeline inside the scroll view. */
|
|
87
|
-
header?: React.ReactNode;
|
|
86
|
+
header?: React.ReactNode | undefined;
|
|
88
87
|
/** 'scroll' only: rendered below the timeline inside the scroll view. */
|
|
89
|
-
footer?: React.ReactNode;
|
|
88
|
+
footer?: React.ReactNode | undefined;
|
|
90
89
|
/** 'scroll' only: style for the scroll view's content container (default `{ paddingVertical: 12 }`). */
|
|
91
|
-
contentContainerStyle?: StyleProp<ViewStyle
|
|
90
|
+
contentContainerStyle?: StyleProp<ViewStyle> | undefined;
|
|
92
91
|
/** Root style. For 'scroll' this is the scroll view, so set a `height` (or `flex: 1` on a screen). */
|
|
93
|
-
style?: StyleProp<ViewStyle
|
|
92
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
@@ -186,7 +185,7 @@ function ScrollTimeline({
|
|
|
186
185
|
const n = items.length;
|
|
187
186
|
const rail = 34;
|
|
188
187
|
|
|
189
|
-
const scrollY =
|
|
188
|
+
const [scrollY] = useState(() => new Animated.Value(0));
|
|
190
189
|
const lastY = useRef(0);
|
|
191
190
|
const [viewH, setViewH] = useState(0);
|
|
192
191
|
const [contentH, setContentH] = useState(0);
|
|
@@ -216,7 +215,7 @@ function ScrollTimeline({
|
|
|
216
215
|
if (!scrolls) return [] as number[];
|
|
217
216
|
const anchor = viewH * SCROLL_ANCHOR;
|
|
218
217
|
const k = 1 + (viewH - anchor) / maxScroll;
|
|
219
|
-
return items.map((_, i) => (listY + nodes[i] - anchor) / k);
|
|
218
|
+
return items.map((_, i) => (listY + (nodes[i] ?? 0) - anchor) / k);
|
|
220
219
|
}, [scrolls, viewH, maxScroll, listY, nodes, items]);
|
|
221
220
|
|
|
222
221
|
// progress = lit * 2 + (near ? 1 : 0), one number so React can skip identical updates.
|
|
@@ -226,8 +225,8 @@ function ScrollTimeline({
|
|
|
226
225
|
const update = (y: number) => {
|
|
227
226
|
lastY.current = y;
|
|
228
227
|
let lit = 0;
|
|
229
|
-
while (lit <= reach && triggers[lit] <= y) lit++;
|
|
230
|
-
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;
|
|
231
230
|
setProgress(lit * 2 + (near ? 1 : 0));
|
|
232
231
|
};
|
|
233
232
|
update(lastY.current);
|
|
@@ -244,17 +243,17 @@ function ScrollTimeline({
|
|
|
244
243
|
);
|
|
245
244
|
|
|
246
245
|
const top = nodes[0] ?? 0;
|
|
247
|
-
const fillH = reach > 0 && measured ? nodes[reach] - top : 0;
|
|
246
|
+
const fillH = reach > 0 && measured ? (nodes[reach] ?? top) - top : 0;
|
|
248
247
|
const dottedFrom = Math.max(0, reach);
|
|
249
|
-
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;
|
|
250
249
|
|
|
251
250
|
let fillStyle: Animated.WithAnimatedValue<ViewStyle> | null = null;
|
|
252
251
|
if (fillH > 0) {
|
|
253
252
|
if (frozen) {
|
|
254
253
|
fillStyle = {};
|
|
255
254
|
} else if (triggers.length) {
|
|
256
|
-
const t0 = triggers[0];
|
|
257
|
-
const t1 = Math.max(triggers[reach], t0 + 1);
|
|
255
|
+
const t0 = triggers[0] ?? 0;
|
|
256
|
+
const t1 = Math.max(triggers[reach] ?? t0, t0 + 1);
|
|
258
257
|
const p = scrollY.interpolate({ inputRange: [t0, t1], outputRange: [0.0001, 1], extrapolate: 'clamp' });
|
|
259
258
|
fillStyle = {
|
|
260
259
|
transform: [
|
|
@@ -359,11 +358,11 @@ interface RowProps {
|
|
|
359
358
|
arriveDelay: number;
|
|
360
359
|
fillMs: number;
|
|
361
360
|
/** 'scroll': what the dot shows right now (it lights up when the fill reaches it). */
|
|
362
|
-
dotStatus?: TimelineStatus;
|
|
361
|
+
dotStatus?: TimelineStatus | undefined;
|
|
363
362
|
/** 'scroll': the fill has reached this dot — pops it. */
|
|
364
|
-
lit?: boolean;
|
|
363
|
+
lit?: boolean | undefined;
|
|
365
364
|
/** 'scroll': reports the dot centre's y inside the list. */
|
|
366
|
-
onNode?: (y: number) => void;
|
|
365
|
+
onNode?: ((y: number) => void) | undefined;
|
|
367
366
|
}
|
|
368
367
|
|
|
369
368
|
function Row({
|
|
@@ -383,7 +382,7 @@ function Row({
|
|
|
383
382
|
lit,
|
|
384
383
|
onNode,
|
|
385
384
|
}: RowProps) {
|
|
386
|
-
const { colors, radius, dark, font } = useKit();
|
|
385
|
+
const { colors, radius, dark, font, shadow } = useKit();
|
|
387
386
|
const ff = font.family ? { fontFamily: font.family } : null;
|
|
388
387
|
const compact = variant === 'compact';
|
|
389
388
|
const card = variant === 'card';
|
|
@@ -399,9 +398,9 @@ function Row({
|
|
|
399
398
|
const gapBelow = compact ? 14 : card ? 12 : scroll ? 32 : 26;
|
|
400
399
|
const entrance = !still && !scroll;
|
|
401
400
|
|
|
402
|
-
const enter =
|
|
403
|
-
const fill =
|
|
404
|
-
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));
|
|
405
404
|
const wasLit = useRef(!!lit);
|
|
406
405
|
const [lineH, setLineH] = useState(0);
|
|
407
406
|
|
|
@@ -508,15 +507,8 @@ function Row({
|
|
|
508
507
|
</>
|
|
509
508
|
);
|
|
510
509
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
? { elevation: current ? 3 : 1 }
|
|
514
|
-
: {
|
|
515
|
-
shadowColor: '#000',
|
|
516
|
-
shadowOffset: { width: 0, height: current ? 6 : 3 },
|
|
517
|
-
shadowOpacity: dark ? 0.35 : current ? 0.08 : 0.04,
|
|
518
|
-
shadowRadius: current ? 16 : 10,
|
|
519
|
-
};
|
|
510
|
+
// boxShadow via the kit: the same soft card shadow on iOS, Android and web.
|
|
511
|
+
const cardShadow: ViewStyle = shadow(current ? 'md' : 'sm');
|
|
520
512
|
|
|
521
513
|
const dotWrap = scroll
|
|
522
514
|
? { transform: [{ scale: pop }] }
|
|
@@ -693,8 +685,8 @@ function Dot({
|
|
|
693
685
|
}) {
|
|
694
686
|
const { colors, radius, dark } = useKit();
|
|
695
687
|
const uid = useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
696
|
-
const draw =
|
|
697
|
-
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
|
|
698
690
|
const current = status === 'current';
|
|
699
691
|
const marked = status === 'error' || (status === 'done' && checks);
|
|
700
692
|
|
|
@@ -884,12 +876,9 @@ function Sheen({ id, size }: { id: string; size: number }) {
|
|
|
884
876
|
);
|
|
885
877
|
}
|
|
886
878
|
|
|
879
|
+
/** A soft tinted glow as boxShadow, identical on iOS, Android and web. */
|
|
887
880
|
function glow(color: string): ViewStyle {
|
|
888
|
-
return
|
|
889
|
-
? { shadowColor: color, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.3, shadowRadius: 5 }
|
|
890
|
-
: Platform.OS === 'android'
|
|
891
|
-
? { elevation: 2 }
|
|
892
|
-
: {};
|
|
881
|
+
return { boxShadow: `0px 2px 10px ${alpha(color, 0.3)}` };
|
|
893
882
|
}
|
|
894
883
|
|
|
895
884
|
function srcOf(image: TimelineImageSource) {
|
|
@@ -905,12 +894,15 @@ function TimelineGallery({ images, dim }: { images: TimelineImageSource[]; dim:
|
|
|
905
894
|
styles.gallery,
|
|
906
895
|
{ borderRadius: radius.md, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06), opacity: dim ? 0.6 : 1 },
|
|
907
896
|
];
|
|
908
|
-
const tile = (i: number, flex: ViewStyle) =>
|
|
909
|
-
|
|
910
|
-
|
|
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
|
+
};
|
|
911
903
|
|
|
912
904
|
if (shown.length === 1) {
|
|
913
|
-
return <View style={[frame, { aspectRatio: shown[0]
|
|
905
|
+
return <View style={[frame, { aspectRatio: shown[0]?.ratio }]}>{tile(0, styles.fill)}</View>;
|
|
914
906
|
}
|
|
915
907
|
if (shown.length === 2) {
|
|
916
908
|
return (
|
|
@@ -934,7 +926,7 @@ function TimelineGallery({ images, dim }: { images: TimelineImageSource[]; dim:
|
|
|
934
926
|
function Photo({ uri, delay, style, more }: { uri: string; delay: number; style: StyleProp<ViewStyle>; more: number }) {
|
|
935
927
|
const { colors, font } = useKit();
|
|
936
928
|
const ff = font.family ? { fontFamily: font.family } : null;
|
|
937
|
-
const shown =
|
|
929
|
+
const [shown] = useState(() => new Animated.Value(0));
|
|
938
930
|
return (
|
|
939
931
|
<View style={[styles.photo, { backgroundColor: colors.muted }, style]}>
|
|
940
932
|
<Animated.Image
|