@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
|
@@ -30,13 +30,13 @@ export interface CouponTheme {
|
|
|
30
30
|
/** Text on the value panel. */
|
|
31
31
|
ink: string;
|
|
32
32
|
/** Buttons, the code chip and the applied ring (default: the fill's last colour). */
|
|
33
|
-
accent?: string;
|
|
33
|
+
accent?: string | undefined;
|
|
34
34
|
/** Details panel paint (default: the app's card colour). */
|
|
35
|
-
body?: string | readonly string[];
|
|
35
|
+
body?: string | readonly string[] | undefined;
|
|
36
36
|
/** Text on the details panel (default: the app's foreground). */
|
|
37
|
-
bodyInk?: string;
|
|
37
|
+
bodyInk?: string | undefined;
|
|
38
38
|
/** Texture on the value panel. */
|
|
39
|
-
pattern?: TicketPattern;
|
|
39
|
+
pattern?: TicketPattern | undefined;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export const couponThemes: Record<CouponThemeName, CouponTheme> = {
|
|
@@ -74,52 +74,52 @@ export interface CouponProps {
|
|
|
74
74
|
/** The headline: "50%", "₹200", "FREE", "BOGO". */
|
|
75
75
|
value: string;
|
|
76
76
|
/** Under the value: "OFF", "CASHBACK". */
|
|
77
|
-
unit?: string;
|
|
77
|
+
unit?: string | undefined;
|
|
78
78
|
/** Above the value: "FLAT", "UP TO". */
|
|
79
|
-
prefix?: string;
|
|
80
|
-
title?: string;
|
|
81
|
-
description?: string;
|
|
79
|
+
prefix?: string | undefined;
|
|
80
|
+
title?: string | undefined;
|
|
81
|
+
description?: string | undefined;
|
|
82
82
|
/** Promo code, shown in a dashed chip (tap to copy when `onCopy` is set). */
|
|
83
|
-
code?: string;
|
|
84
|
-
brand?: string;
|
|
83
|
+
code?: string | undefined;
|
|
84
|
+
brand?: string | undefined;
|
|
85
85
|
/** Logo before the brand (e.g. an <Image>). */
|
|
86
|
-
logo?: React.ReactNode;
|
|
86
|
+
logo?: React.ReactNode | undefined;
|
|
87
87
|
/** Expiry line, e.g. "Valid till 31 Dec". */
|
|
88
|
-
expires?: string;
|
|
88
|
+
expires?: string | undefined;
|
|
89
89
|
/** Live countdown to this moment; the coupon turns expired when it passes. */
|
|
90
|
-
expiresAt?: Date | number;
|
|
90
|
+
expiresAt?: Date | number | undefined;
|
|
91
91
|
/** Shown on the back — the "T&C" link turns the coupon over. */
|
|
92
|
-
terms?: string[];
|
|
93
|
-
status?: CouponStatus;
|
|
92
|
+
terms?: string[] | undefined;
|
|
93
|
+
status?: CouponStatus | undefined;
|
|
94
94
|
/** Why it's locked, e.g. "Add ₹120 more to unlock". */
|
|
95
|
-
lockedHint?: string;
|
|
95
|
+
lockedHint?: string | undefined;
|
|
96
96
|
/** A preset or your own colours (default "sunset"). */
|
|
97
|
-
theme?: CouponThemeName | CouponTheme;
|
|
97
|
+
theme?: CouponThemeName | CouponTheme | undefined;
|
|
98
98
|
/** Override the theme's texture. */
|
|
99
|
-
pattern?: TicketPattern;
|
|
99
|
+
pattern?: TicketPattern | undefined;
|
|
100
100
|
/** `horizontal` (value panel on the left) or `vertical` (value panel on top). */
|
|
101
|
-
layout?: 'horizontal' | 'vertical';
|
|
102
|
-
variant?: CouponVariant;
|
|
103
|
-
size?: CouponSize;
|
|
101
|
+
layout?: 'horizontal' | 'vertical' | undefined;
|
|
102
|
+
variant?: CouponVariant | undefined;
|
|
103
|
+
size?: CouponSize | undefined;
|
|
104
104
|
/** A light sweep across the value panel every few seconds. */
|
|
105
|
-
shine?: boolean;
|
|
105
|
+
shine?: boolean | undefined;
|
|
106
106
|
/** 3D: tilts toward the finger with a moving reflection. */
|
|
107
|
-
tilt?: boolean;
|
|
107
|
+
tilt?: boolean | undefined;
|
|
108
108
|
/** With `tilt`: a rainbow foil sheen. */
|
|
109
|
-
holo?: boolean;
|
|
109
|
+
holo?: boolean | undefined;
|
|
110
110
|
/** With `tilt`: sway slowly on its own. */
|
|
111
|
-
idle?: boolean;
|
|
111
|
+
idle?: boolean | undefined;
|
|
112
112
|
/** Called with the code from the chip — wire it to expo-clipboard. */
|
|
113
|
-
onCopy?: (code: string) => void;
|
|
113
|
+
onCopy?: ((code: string) => void) | undefined;
|
|
114
114
|
/** Shows the Apply button. */
|
|
115
|
-
onApply?: () => void;
|
|
115
|
+
onApply?: (() => void) | undefined;
|
|
116
116
|
/** Tap on the coupon itself (e.g. open details or select it). */
|
|
117
|
-
onPress?: () => void;
|
|
118
|
-
flipped?: boolean;
|
|
119
|
-
defaultFlipped?: boolean;
|
|
120
|
-
onFlip?: (flipped: boolean) => void;
|
|
121
|
-
labels?: CouponLabels;
|
|
122
|
-
style?: StyleProp<ViewStyle
|
|
117
|
+
onPress?: (() => void) | undefined;
|
|
118
|
+
flipped?: boolean | undefined;
|
|
119
|
+
defaultFlipped?: boolean | undefined;
|
|
120
|
+
onFlip?: ((flipped: boolean) => void) | undefined;
|
|
121
|
+
labels?: CouponLabels | undefined;
|
|
122
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
const VARIANTS: Record<CouponVariant, { r: number; notch: number; edge?: TicketEdge; edgeSize: number; ends?: boolean }> = {
|
|
@@ -159,11 +159,11 @@ function contrast(a: string, b: string) {
|
|
|
159
159
|
const full = hex.length === 4 ? '#' + [...hex.slice(1)].map((c) => c + c).join('') : hex;
|
|
160
160
|
const n = parseInt(full.slice(1, 7), 16);
|
|
161
161
|
if (!/^#[0-9a-f]{6}/i.test(full) || Number.isNaN(n)) return -1;
|
|
162
|
-
const
|
|
162
|
+
const [red = 0, green = 0, blue = 0] = [(n >> 16) & 255, (n >> 8) & 255, n & 255].map((v) => {
|
|
163
163
|
const c = v / 255;
|
|
164
164
|
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
165
165
|
});
|
|
166
|
-
return 0.2126 *
|
|
166
|
+
return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
|
|
167
167
|
};
|
|
168
168
|
const [x, y] = [lum(a), lum(b)];
|
|
169
169
|
if (x < 0 || y < 0) return 21;
|
|
@@ -201,13 +201,13 @@ function useRemaining(at?: Date | number) {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
/** The promo code in a dashed chip; a tap copies it and flips the icon to a check for a moment. */
|
|
204
|
-
function CodeChip({ code, color, ink, fontSize, onCopy, labels, reduce }: { code: string; color: string; ink: string; fontSize: number; onCopy?: (code: string) => void; labels: Required<CouponLabels>; reduce: boolean }) {
|
|
204
|
+
function CodeChip({ code, color, ink, fontSize, onCopy, labels, reduce }: { code: string; color: string; ink: string; fontSize: number; onCopy?: ((code: string) => void) | undefined; labels: Required<CouponLabels>; reduce: boolean }) {
|
|
205
205
|
const { font } = useKit();
|
|
206
206
|
const ff = font.family ? { fontFamily: font.family } : null;
|
|
207
207
|
const [copied, setCopied] = useState(false);
|
|
208
208
|
const [box, setBox] = useState({ w: 0, h: 0 });
|
|
209
209
|
const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
210
|
-
const pop =
|
|
210
|
+
const [pop] = useState(() => new Animated.Value(1));
|
|
211
211
|
useEffect(() => () => clearTimeout(timer.current), []);
|
|
212
212
|
const copy = () => {
|
|
213
213
|
onCopy?.(code);
|
|
@@ -234,7 +234,7 @@ function CodeChip({ code, color, ink, fontSize, onCopy, labels, reduce }: { code
|
|
|
234
234
|
<Rect x={0.75} y={0.75} width={box.w - 1.5} height={box.h - 1.5} rx={8} fill="none" stroke={alpha(color, 0.75)} strokeWidth={1.5} strokeDasharray={[5, 4]} />
|
|
235
235
|
</Svg>
|
|
236
236
|
) : null}
|
|
237
|
-
<Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[{ color: ink, fontSize, fontWeight: '800', letterSpacing: 1, flexShrink: 1 }, ff]}>
|
|
237
|
+
<Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[{ color: ink, fontSize, fontWeight: '800', letterSpacing: 1, flexShrink: 1, includeFontPadding: false, textAlignVertical: 'center' }, ff]}>
|
|
238
238
|
{code}
|
|
239
239
|
</Text>
|
|
240
240
|
{onCopy ? (
|
|
@@ -248,7 +248,7 @@ function CodeChip({ code, color, ink, fontSize, onCopy, labels, reduce }: { code
|
|
|
248
248
|
|
|
249
249
|
/** A light band that sweeps across, every few seconds, through the given windows. */
|
|
250
250
|
function Shine({ windows, width, height }: { windows: TicketRect[]; width: number; height: number }) {
|
|
251
|
-
const t =
|
|
251
|
+
const [t] = useState(() => new Animated.Value(0));
|
|
252
252
|
useEffect(() => {
|
|
253
253
|
const loop = Animated.loop(
|
|
254
254
|
Animated.sequence([Animated.timing(t, { toValue: 1, duration: 1100, easing: Easing.inOut(Easing.quad), useNativeDriver: true }), Animated.delay(2400)])
|
|
@@ -338,12 +338,12 @@ export function Coupon({
|
|
|
338
338
|
const reduce = useReduceMotion();
|
|
339
339
|
const theme = typeof themeProp === 'object' ? themeProp : couponThemes[themeProp];
|
|
340
340
|
const fills = typeof theme.fill === 'string' ? [theme.fill] : theme.fill;
|
|
341
|
-
const accent = theme.accent ?? fills[fills.length - 1];
|
|
341
|
+
const accent = theme.accent ?? fills[fills.length - 1] ?? colors.primary;
|
|
342
342
|
const body = theme.body ?? (dark ? colors.popover : colors.card);
|
|
343
343
|
const bodyInk = theme.bodyInk ?? colors.foreground;
|
|
344
344
|
const bodySoft = alpha(bodyInk, 0.6);
|
|
345
345
|
// The accent as text on the details panel — or the panel's ink when they'd blend (yellow on white).
|
|
346
|
-
const textAccent = contrast(accent, typeof body === 'string' ? body : body[0]) >= 2.6 ? accent : bodyInk;
|
|
346
|
+
const textAccent = contrast(accent, typeof body === 'string' ? body : (body[0] ?? colors.card)) >= 2.6 ? accent : bodyInk;
|
|
347
347
|
const s = SIZES[size];
|
|
348
348
|
const v = VARIANTS[variant];
|
|
349
349
|
const horizontal = layout === 'horizontal';
|
|
@@ -387,8 +387,8 @@ export function Coupon({
|
|
|
387
387
|
const n = v.notch;
|
|
388
388
|
const endEdge = v.edge;
|
|
389
389
|
const sideEdge = v.ends ? undefined : v.edge;
|
|
390
|
-
const valueEdges: Partial<Record<TicketSide, TicketEdge>> = horizontal ? { left: endEdge, top: sideEdge, bottom: sideEdge } : { top: endEdge, left: sideEdge, right: sideEdge };
|
|
391
|
-
const detailEdges: Partial<Record<TicketSide, TicketEdge>> = horizontal ? { right: endEdge, top: sideEdge, bottom: sideEdge } : { bottom: endEdge, left: sideEdge, right: sideEdge };
|
|
390
|
+
const valueEdges: Partial<Record<TicketSide, TicketEdge | undefined>> = horizontal ? { left: endEdge, top: sideEdge, bottom: sideEdge } : { top: endEdge, left: sideEdge, right: sideEdge };
|
|
391
|
+
const detailEdges: Partial<Record<TicketSide, TicketEdge | undefined>> = horizontal ? { right: endEdge, top: sideEdge, bottom: sideEdge } : { bottom: endEdge, left: sideEdge, right: sideEdge };
|
|
392
392
|
const corners = (tl: number, tr: number, br: number, bl: number): TicketCorners => [tl, tr, br, bl];
|
|
393
393
|
const valueCorners = horizontal ? corners(v.r, -n, -n, v.r) : corners(v.r, v.r, -n, -n);
|
|
394
394
|
const detailCorners = horizontal ? corners(-n, v.r, v.r, -n) : corners(-n, -n, v.r, v.r);
|
|
@@ -431,7 +431,7 @@ export function Coupon({
|
|
|
431
431
|
left !== undefined && left > 0 && !spent ? (
|
|
432
432
|
<View style={[styles.timer, { backgroundColor: timerBg }]}>
|
|
433
433
|
<Icon d={ICON.clock} size={11} color={timerInk} />
|
|
434
|
-
<Text maxFontSizeMultiplier={FONT_CAP} style={[{ color: timerInk, fontSize: 10.5, fontWeight: '800', fontVariant: ['tabular-nums'] }, ff]}>
|
|
434
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[{ color: timerInk, fontSize: 10.5, fontWeight: '800', fontVariant: ['tabular-nums'], includeFontPadding: false, textAlignVertical: 'center' }, ff]}>
|
|
435
435
|
{labels.endsIn} {formatCountdown(left)}
|
|
436
436
|
</Text>
|
|
437
437
|
</View>
|
|
@@ -521,7 +521,7 @@ export function Coupon({
|
|
|
521
521
|
]}
|
|
522
522
|
>
|
|
523
523
|
{applied || locked ? <Icon d={applied ? ICON.check : ICON.lock} size={13} color={applied ? '#ffffff' : bodySoft} /> : null}
|
|
524
|
-
<Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[{ color: applied ? '#ffffff' : locked || spent ? bodySoft : readableOn(accent), fontSize: s.chip - 0.5, fontWeight: '800' }, ff]}>
|
|
524
|
+
<Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[{ color: applied ? '#ffffff' : locked || spent ? bodySoft : readableOn(accent), fontSize: s.chip - 0.5, fontWeight: '800', includeFontPadding: false, textAlignVertical: 'center' }, ff]}>
|
|
525
525
|
{applied ? labels.applied : locked ? labels.locked : labels.apply}
|
|
526
526
|
</Text>
|
|
527
527
|
</Pressable>
|
|
@@ -679,9 +679,8 @@ export function Coupon({
|
|
|
679
679
|
<View style={{ transform: [{ scale: pressed && !reduce ? 0.985 : 1 }] }}>
|
|
680
680
|
{/* The plate waits for the painted outline (no black flash on mount), and a dimmed (used /
|
|
681
681
|
expired) coupon is see-through, so it gets none. */}
|
|
682
|
-
{spent || !measured ? null : <View pointerEvents="none" style={[styles.shadow, { top: inset, bottom: inset - 4, left: inset, right: inset, borderRadius: v.r,
|
|
683
|
-
{
|
|
684
|
-
<View style={{ elevation: 7 }}>{card}</View>
|
|
682
|
+
{spent || !measured ? null : <View pointerEvents="none" style={[styles.shadow, { top: inset, bottom: inset - 4, left: inset, right: inset, borderRadius: v.r, boxShadow: `0px 8px 28px rgba(0,0,0,${dark ? 0.5 : 0.2})` }]} />}
|
|
683
|
+
<View>{card}</View>
|
|
685
684
|
</View>
|
|
686
685
|
)}
|
|
687
686
|
</Pressable>
|
|
@@ -717,9 +716,5 @@ const styles = StyleSheet.create({
|
|
|
717
716
|
shadow: {
|
|
718
717
|
position: 'absolute',
|
|
719
718
|
backgroundColor: '#000',
|
|
720
|
-
shadowColor: '#000',
|
|
721
|
-
shadowRadius: 14,
|
|
722
|
-
shadowOffset: { width: 0, height: 8 },
|
|
723
|
-
elevation: 6,
|
|
724
719
|
},
|
|
725
720
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React, { useEffect, useMemo,
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { AccessibilityInfo, Animated, Pressable, StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import { motion } from '@/lib/kit';
|
|
4
|
+
import { motion, useKit } from '@/lib/kit';
|
|
5
5
|
|
|
6
6
|
export type FlipDirection = 'horizontal' | 'vertical';
|
|
7
7
|
export type FlipTrigger = 'press' | 'none';
|
|
@@ -32,7 +32,7 @@ export interface FlipCardProps {
|
|
|
32
32
|
progress?: Animated.Value;
|
|
33
33
|
/** Accessibility label; the hint says which side is showing. */
|
|
34
34
|
accessibilityLabel?: string;
|
|
35
|
-
/** Screen-reader hints (
|
|
35
|
+
/** Screen-reader hints (default: the kit's `labels.flipToBack` / `labels.flipToFront`). */
|
|
36
36
|
labels?: { toBack?: string; toFront?: string };
|
|
37
37
|
style?: StyleProp<ViewStyle>;
|
|
38
38
|
}
|
|
@@ -52,7 +52,7 @@ const SQUEEZE = {
|
|
|
52
52
|
*/
|
|
53
53
|
export function useFlipProgress(flipped: boolean) {
|
|
54
54
|
const reduce = useReduceMotion();
|
|
55
|
-
const progress =
|
|
55
|
+
const [progress] = useState(() => new Animated.Value(flipped ? 1 : 0));
|
|
56
56
|
useEffect(() => {
|
|
57
57
|
if (reduce) progress.setValue(flipped ? 1 : 0);
|
|
58
58
|
else Animated.spring(progress, { toValue: flipped ? 1 : 0, ...motion.gentle, useNativeDriver: true }).start();
|
|
@@ -81,6 +81,7 @@ export function FlipCard({
|
|
|
81
81
|
labels,
|
|
82
82
|
style,
|
|
83
83
|
}: FlipCardProps) {
|
|
84
|
+
const kitLabels = useKit().labels;
|
|
84
85
|
const controlled = flippedProp !== undefined;
|
|
85
86
|
const [own, setOwn] = useState(defaultFlipped);
|
|
86
87
|
const flipped = controlled ? flippedProp : own;
|
|
@@ -149,9 +150,7 @@ export function FlipCard({
|
|
|
149
150
|
) : null}
|
|
150
151
|
{/* Faces live in their own (non-collapsing) layer: iOS intersects sibling layers in 3D, so a
|
|
151
152
|
turning face next to the flat shadow plate would be sliced by it. */}
|
|
152
|
-
{
|
|
153
|
-
get none, since a face turned in 3D would cast a warped shadow wedge below the card. */}
|
|
154
|
-
<View collapsable={false} style={{ elevation: shadow ? 23 : 0 }}>
|
|
153
|
+
<View collapsable={false}>
|
|
155
154
|
{/* The hidden face stays mounted (for the turn) but is hidden from screen readers. */}
|
|
156
155
|
<Animated.View
|
|
157
156
|
style={[styles.face, face(frontTurn, frontOpacity)]}
|
|
@@ -179,7 +178,7 @@ export function FlipCard({
|
|
|
179
178
|
onPress={toggle}
|
|
180
179
|
accessibilityRole="button"
|
|
181
180
|
accessibilityLabel={accessibilityLabel}
|
|
182
|
-
accessibilityHint={flipped ? (labels?.toFront ??
|
|
181
|
+
accessibilityHint={flipped ? (labels?.toFront ?? kitLabels.flipToFront) : (labels?.toBack ?? kitLabels.flipToBack)}
|
|
183
182
|
accessibilityState={{ expanded: flipped }}
|
|
184
183
|
style={style}
|
|
185
184
|
>
|
|
@@ -214,11 +213,7 @@ const styles = StyleSheet.create({
|
|
|
214
213
|
right: 0,
|
|
215
214
|
bottom: 0,
|
|
216
215
|
backgroundColor: '#000',
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
shadowRadius: 20,
|
|
220
|
-
shadowOffset: { width: 0, height: 14 },
|
|
221
|
-
// High enough that Android's shadow spreads past the inset plate (iOS uses the shadow* props).
|
|
222
|
-
elevation: 22,
|
|
216
|
+
// boxShadow, not shadow* / elevation: the same soft shadow on iOS, Android and web.
|
|
217
|
+
boxShadow: '0px 14px 40px rgba(0,0,0,0.4)',
|
|
223
218
|
},
|
|
224
219
|
});
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
Animated,
|
|
5
5
|
Easing,
|
|
6
6
|
Image,
|
|
7
|
-
PanResponder,
|
|
8
7
|
StyleSheet,
|
|
9
8
|
View,
|
|
10
9
|
type ImageSourcePropType,
|
|
@@ -14,7 +13,7 @@ import {
|
|
|
14
13
|
} from 'react-native';
|
|
15
14
|
import Svg, { Defs, LinearGradient, RadialGradient, Rect, Stop } from 'react-native-svg';
|
|
16
15
|
|
|
17
|
-
import { motion } from '@/lib/kit';
|
|
16
|
+
import { motion, usePanResponder } from '@/lib/kit';
|
|
18
17
|
|
|
19
18
|
/** `holo` adds a rainbow foil sheen that shifts as the card tilts (always faintly visible). */
|
|
20
19
|
export type TiltCardGlare = 'soft' | 'sharp' | 'holo' | 'none';
|
|
@@ -28,58 +27,58 @@ export interface TiltRect {
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
export interface TiltCardProps {
|
|
31
|
-
children?: React.ReactNode;
|
|
30
|
+
children?: React.ReactNode | undefined;
|
|
32
31
|
/** Photo that fills the card behind the content (`require()` or `{ uri }`). */
|
|
33
|
-
image?: ImageSourcePropType;
|
|
32
|
+
image?: ImageSourcePropType | undefined;
|
|
34
33
|
/** Darkens the bottom of `image` so white text on it stays readable: 0 off … 1 strong (default 0.6). */
|
|
35
|
-
shade?: number;
|
|
34
|
+
shade?: number | undefined;
|
|
36
35
|
/** Maximum tilt in degrees at the card's edge (default 12). */
|
|
37
|
-
maxTilt?: number;
|
|
36
|
+
maxTilt?: number | undefined;
|
|
38
37
|
/** Scale while touched (default 1.03). */
|
|
39
|
-
pressScale?: number;
|
|
38
|
+
pressScale?: number | undefined;
|
|
40
39
|
/** Light reflection that slides across the card as it tilts. */
|
|
41
|
-
glare?: TiltCardGlare;
|
|
40
|
+
glare?: TiltCardGlare | undefined;
|
|
42
41
|
/**
|
|
43
42
|
* Where the reflection may fall, in card points — for cards with cut-outs (ticket notches,
|
|
44
43
|
* perforated edges), so light never shows over a hole. Default: the whole card.
|
|
45
44
|
*/
|
|
46
|
-
glareClip?: TiltRect[];
|
|
45
|
+
glareClip?: TiltRect[] | undefined;
|
|
47
46
|
/** Depth of the 3D effect — smaller is more dramatic (default 800). */
|
|
48
|
-
perspective?: number;
|
|
47
|
+
perspective?: number | undefined;
|
|
49
48
|
/** Slowly sway on its own while untouched, so the card reads as 3D at rest. */
|
|
50
|
-
idle?: boolean;
|
|
49
|
+
idle?: boolean | undefined;
|
|
51
50
|
/** Corner radius (default 20). */
|
|
52
|
-
radius?: number;
|
|
51
|
+
radius?: number | undefined;
|
|
53
52
|
/** Soft shadow under the card that shifts as it tilts (default true). */
|
|
54
|
-
shadow?: boolean;
|
|
53
|
+
shadow?: boolean | undefined;
|
|
55
54
|
/**
|
|
56
55
|
* The part of the card that casts the shadow, in card points — e.g. leave out a ticket stub that's
|
|
57
56
|
* being torn off, so the plate never shows where it was. Default: the whole card.
|
|
58
57
|
*/
|
|
59
|
-
shadowRect?: TiltRect;
|
|
58
|
+
shadowRect?: TiltRect | undefined;
|
|
60
59
|
/**
|
|
61
60
|
* Narrows the shadow sideways (1 = full width, 0 = none) — for content that turns edge-on inside
|
|
62
61
|
* the card, like a FlipCard mid-flip, so the shadow plate never shows past it.
|
|
63
62
|
*/
|
|
64
|
-
shadowScaleX?: Animated.Value | Animated.AnimatedInterpolation<number
|
|
63
|
+
shadowScaleX?: Animated.Value | Animated.AnimatedInterpolation<number> | undefined;
|
|
65
64
|
/** Turn touch tilting off (the card still renders, e.g. while a parent list scrolls). */
|
|
66
|
-
disabled?: boolean;
|
|
65
|
+
disabled?: boolean | undefined;
|
|
67
66
|
/** Called with a light tap (not after a drag). */
|
|
68
|
-
onPress?: () => void;
|
|
69
|
-
accessibilityLabel?: string;
|
|
67
|
+
onPress?: (() => void) | undefined;
|
|
68
|
+
accessibilityLabel?: string | undefined;
|
|
70
69
|
/** Width, height, margins — applied to the card's outer box. */
|
|
71
|
-
style?: StyleProp<ViewStyle
|
|
70
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
export interface TiltLayerProps {
|
|
75
|
-
children?: React.ReactNode;
|
|
74
|
+
children?: React.ReactNode | undefined;
|
|
76
75
|
/**
|
|
77
76
|
* How far above the card this layer floats: 0 sits on the card, 1 floats well above it
|
|
78
77
|
* and slides furthest as the card tilts (default 0.5). Negative sinks below the surface.
|
|
79
78
|
*/
|
|
80
|
-
depth?: number;
|
|
79
|
+
depth?: number | undefined;
|
|
81
80
|
/** Layers fill the card by default; position content inside, or override here. */
|
|
82
|
-
style?: StyleProp<ViewStyle
|
|
81
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
export interface TiltState {
|
|
@@ -109,7 +108,8 @@ const PAINT = 96;
|
|
|
109
108
|
// pinks, violets and cyans stay clean when blended over dark cards (yellow and green turn olive).
|
|
110
109
|
const SMOOTH = Easing.bezier(0.32, 0.72, 0, 1);
|
|
111
110
|
|
|
112
|
-
const
|
|
111
|
+
const HOLO_CYCLE = ['#ff6bd6', '#b38cff', '#6fb6ff', '#5ff0ff', '#a7ffe0', '#ffc2ef'];
|
|
112
|
+
const HOLO = Array.from({ length: 37 }, (_, i) => HOLO_CYCLE[i % HOLO_CYCLE.length] ?? '#ff6bd6');
|
|
113
113
|
|
|
114
114
|
function GlarePaint({ variant }: { variant: 'soft' | 'sharp' }) {
|
|
115
115
|
const sharp = variant === 'sharp';
|
|
@@ -262,21 +262,15 @@ export function TiltCard({
|
|
|
262
262
|
}: TiltCardProps) {
|
|
263
263
|
const reduce = useReduceMotion();
|
|
264
264
|
const [size, setSize] = useState({ w: 0, h: 0 });
|
|
265
|
-
const sizeRef = useRef(size);
|
|
266
|
-
sizeRef.current = size;
|
|
267
265
|
|
|
268
266
|
// -1 … 1 across the card; the card leans toward the finger.
|
|
269
|
-
const tx =
|
|
270
|
-
const ty =
|
|
271
|
-
const press =
|
|
267
|
+
const [tx] = useState(() => new Animated.Value(0));
|
|
268
|
+
const [ty] = useState(() => new Animated.Value(0));
|
|
269
|
+
const [press] = useState(() => new Animated.Value(0));
|
|
272
270
|
const touching = useRef(false);
|
|
273
271
|
const origin = useRef({ x: 0, y: 0 });
|
|
274
272
|
const moved = useRef(false);
|
|
275
273
|
const viewRef = useRef<ComponentRef<typeof View>>(null);
|
|
276
|
-
const disabledRef = useRef(disabled);
|
|
277
|
-
disabledRef.current = disabled;
|
|
278
|
-
const reduceRef = useRef(reduce);
|
|
279
|
-
reduceRef.current = reduce;
|
|
280
274
|
|
|
281
275
|
// Idle sway: a slow figure-eight while nobody is touching.
|
|
282
276
|
const idleLoop = useRef<Animated.CompositeAnimation | null>(null);
|
|
@@ -310,7 +304,7 @@ export function TiltCard({
|
|
|
310
304
|
}, [idle, reduce]);
|
|
311
305
|
|
|
312
306
|
const aim = (pageX: number, pageY: number) => {
|
|
313
|
-
const { w, h } =
|
|
307
|
+
const { w, h } = size;
|
|
314
308
|
if (!w || !h) return;
|
|
315
309
|
tx.setValue(Math.max(-1, Math.min(1, ((pageX - origin.current.x) / w) * 2 - 1)));
|
|
316
310
|
ty.setValue(Math.max(-1, Math.min(1, ((pageY - origin.current.y) / h) * 2 - 1)));
|
|
@@ -318,7 +312,7 @@ export function TiltCard({
|
|
|
318
312
|
|
|
319
313
|
const release = (tapped: boolean) => {
|
|
320
314
|
touching.current = false;
|
|
321
|
-
if (tapped && !moved.current)
|
|
315
|
+
if (tapped && !moved.current) onPress?.();
|
|
322
316
|
if (reduce) {
|
|
323
317
|
// The card never leaves flat, so only the light has to settle: a short ease, no spring.
|
|
324
318
|
const settle = (v: Animated.Value) => Animated.timing(v, { toValue: 0, duration: 150, easing: SMOOTH, useNativeDriver: true });
|
|
@@ -345,57 +339,48 @@ export function TiltCard({
|
|
|
345
339
|
if (finished && !touching.current) startIdle();
|
|
346
340
|
});
|
|
347
341
|
};
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
onPanResponderRelease: () => releaseRef.current(true),
|
|
391
|
-
onPanResponderTerminate: () => releaseRef.current(false),
|
|
392
|
-
// React Native grants before asking the current responder to let go; when a child keeps its
|
|
393
|
-
// gesture (e.g. a ticket stub being torn), the grant is rejected and no release follows.
|
|
394
|
-
onPanResponderReject: () => releaseRef.current(false),
|
|
395
|
-
}),
|
|
396
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
397
|
-
[]
|
|
398
|
-
);
|
|
342
|
+
// usePanResponder always calls the latest callbacks, so they read props and state directly and
|
|
343
|
+
// the responder is never rebuilt mid-gesture.
|
|
344
|
+
const pan = usePanResponder({
|
|
345
|
+
onStartShouldSetPanResponder: () => !disabled,
|
|
346
|
+
// Only a real drag takes the touch from a button inside the card — a tap's tiny jitter must not.
|
|
347
|
+
onMoveShouldSetPanResponder: (_e, g) => !disabled && Math.abs(g.dx) + Math.abs(g.dy) > 6,
|
|
348
|
+
// Let a parent ScrollView take over scrolls that start on the card (on Android the native
|
|
349
|
+
// scroll view can only do that when the card doesn't block it).
|
|
350
|
+
onPanResponderTerminationRequest: () => true,
|
|
351
|
+
onShouldBlockNativeResponder: () => false,
|
|
352
|
+
onPanResponderGrant: (e) => {
|
|
353
|
+
touching.current = true;
|
|
354
|
+
moved.current = false;
|
|
355
|
+
idleLoop.current?.stop();
|
|
356
|
+
const { pageX, pageY, locationX, locationY } = e.nativeEvent;
|
|
357
|
+
// locationX is relative to the touched child, so measure the card itself and correct
|
|
358
|
+
// once the measurement arrives.
|
|
359
|
+
origin.current = { x: pageX - locationX, y: pageY - locationY };
|
|
360
|
+
viewRef.current?.measure((_x, _y, _w, _h, px, py) => {
|
|
361
|
+
if (typeof px === 'number' && typeof py === 'number') origin.current = { x: px, y: py };
|
|
362
|
+
});
|
|
363
|
+
aim(pageX, pageY);
|
|
364
|
+
if (reduce) {
|
|
365
|
+
Animated.timing(press, { toValue: 1, duration: 150, easing: SMOOTH, useNativeDriver: true }).start();
|
|
366
|
+
} else {
|
|
367
|
+
Animated.spring(press, {
|
|
368
|
+
toValue: 1,
|
|
369
|
+
...motion.snappy,
|
|
370
|
+
useNativeDriver: true,
|
|
371
|
+
}).start();
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
onPanResponderMove: (e, g) => {
|
|
375
|
+
if (Math.abs(g.dx) + Math.abs(g.dy) > 6) moved.current = true;
|
|
376
|
+
aim(e.nativeEvent.pageX, e.nativeEvent.pageY);
|
|
377
|
+
},
|
|
378
|
+
onPanResponderRelease: () => release(true),
|
|
379
|
+
onPanResponderTerminate: () => release(false),
|
|
380
|
+
// React Native grants before asking the current responder to let go; when a child keeps its
|
|
381
|
+
// gesture (e.g. a ticket stub being torn), the grant is rejected and no release follows.
|
|
382
|
+
onPanResponderReject: () => release(false),
|
|
383
|
+
});
|
|
399
384
|
|
|
400
385
|
const onLayout = (e: LayoutChangeEvent) => {
|
|
401
386
|
const { width, height } = e.nativeEvent.layout;
|
|
@@ -469,24 +454,21 @@ export function TiltCard({
|
|
|
469
454
|
) : null}
|
|
470
455
|
{/* The card sits in its own (non-collapsing) layer: iOS intersects sibling layers in 3D, so a
|
|
471
456
|
tilted card next to the flat shadow plate would be sliced by it along a diagonal. */}
|
|
472
|
-
{/*
|
|
473
|
-
|
|
474
|
-
<View collapsable={false} style={{ elevation: shadow ? 15 : 0 }}>
|
|
457
|
+
{/* The plate's shadow is a boxShadow (no elevation), so on Android too the card simply paints above it. */}
|
|
458
|
+
<View collapsable={false}>
|
|
475
459
|
<Animated.View
|
|
476
460
|
ref={viewRef}
|
|
477
|
-
{...
|
|
461
|
+
{...pan}
|
|
478
462
|
accessible={!!onPress || !!accessibilityLabel}
|
|
479
463
|
accessibilityRole={onPress ? 'button' : undefined}
|
|
480
464
|
accessibilityLabel={accessibilityLabel}
|
|
481
465
|
// Screen readers activate the card through an action, not synthesized touches.
|
|
482
466
|
accessibilityActions={onPress ? [{ name: 'activate' }] : undefined}
|
|
483
467
|
onAccessibilityAction={(e) => {
|
|
484
|
-
if (e.nativeEvent.actionName === 'activate')
|
|
468
|
+
if (e.nativeEvent.actionName === 'activate') onPress?.();
|
|
485
469
|
}}
|
|
486
|
-
// elevation (no background → no extra shadow) keeps the card above its shadow plate on Android.
|
|
487
470
|
style={{
|
|
488
471
|
borderRadius: radius,
|
|
489
|
-
elevation: shadow ? 15 : 0,
|
|
490
472
|
transform: [{ perspective }, { rotateX }, { rotateY }, { scale }],
|
|
491
473
|
}}
|
|
492
474
|
>
|
|
@@ -578,12 +560,12 @@ export function TiltLayer({ children, depth = 0.5, style }: TiltLayerProps) {
|
|
|
578
560
|
}
|
|
579
561
|
|
|
580
562
|
export interface TiltFoilProps {
|
|
581
|
-
children?: React.ReactNode;
|
|
563
|
+
children?: React.ReactNode | undefined;
|
|
582
564
|
/** Corner radius (default 8). */
|
|
583
|
-
radius?: number;
|
|
565
|
+
radius?: number | undefined;
|
|
584
566
|
/** Base metal under the rainbow (default silver). */
|
|
585
|
-
base?: string;
|
|
586
|
-
style?: StyleProp<ViewStyle
|
|
567
|
+
base?: string | undefined;
|
|
568
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
587
569
|
}
|
|
588
570
|
|
|
589
571
|
/**
|
|
@@ -665,11 +647,8 @@ const styles = StyleSheet.create({
|
|
|
665
647
|
right: 0,
|
|
666
648
|
bottom: 0,
|
|
667
649
|
backgroundColor: '#000',
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
shadowRadius: 24,
|
|
671
|
-
shadowOffset: { width: 0, height: 16 },
|
|
672
|
-
elevation: 14,
|
|
650
|
+
// boxShadow draws the same soft shadow on iOS, Android and web.
|
|
651
|
+
boxShadow: '0px 16px 48px rgba(0,0,0,0.45)',
|
|
673
652
|
},
|
|
674
653
|
clip: { overflow: 'hidden' },
|
|
675
654
|
window: { position: 'absolute', overflow: 'hidden' },
|