@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,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useId, useMemo,
|
|
1
|
+
import React, { useEffect, useId, useMemo, useState } from 'react';
|
|
2
2
|
import { Animated, Image, PixelRatio, StyleSheet, Text, View, type ImageSourcePropType, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
3
|
import Svg, { Circle, ClipPath, Defs, G, Line, LinearGradient, Path, Pattern, Rect, Stop } from 'react-native-svg';
|
|
4
4
|
|
|
@@ -28,13 +28,13 @@ export interface TicketRect {
|
|
|
28
28
|
export interface TicketGeometry {
|
|
29
29
|
width: number;
|
|
30
30
|
height: number;
|
|
31
|
-
corners?: TicketCorners;
|
|
31
|
+
corners?: TicketCorners | undefined;
|
|
32
32
|
/** Round bites cut into a side — e.g. both ends of a tear line. */
|
|
33
|
-
notches?: readonly TicketNotch[];
|
|
33
|
+
notches?: readonly TicketNotch[] | undefined;
|
|
34
34
|
/** Pattern per side (default straight). */
|
|
35
|
-
edges?: Partial<Record<TicketSide, TicketEdge
|
|
35
|
+
edges?: Partial<Record<TicketSide, TicketEdge | undefined>> | undefined;
|
|
36
36
|
/** Bite radius, scallop depth or tooth height of the edge patterns (default 5). */
|
|
37
|
-
edgeSize?: number;
|
|
37
|
+
edgeSize?: number | undefined;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
const SIDES: readonly TicketSide[] = ['top', 'right', 'bottom', 'left'];
|
|
@@ -140,20 +140,25 @@ export function ticketSafeRects(g: TicketGeometry, offset: { x: number; y: numbe
|
|
|
140
140
|
// Each round hole is covered by a few stepped rects that hug its curve, so almost nothing
|
|
141
141
|
// around it is left without light.
|
|
142
142
|
const holes: [number, number, number, number][] = [];
|
|
143
|
-
|
|
143
|
+
// Consecutive pairs of [0, 0.45, 0.75, 1].
|
|
144
|
+
const STEPS: readonly (readonly [number, number])[] = [
|
|
145
|
+
[0, 0.45],
|
|
146
|
+
[0.45, 0.75],
|
|
147
|
+
[0.75, 1],
|
|
148
|
+
];
|
|
144
149
|
/** A quarter disc of radius r at corner (cx, cy), opening toward (dx, dy) = ±1. */
|
|
145
150
|
const quarter = (cx: number, cy: number, r: number, dx: number, dy: number) => {
|
|
146
|
-
for (
|
|
147
|
-
const reach = r * Math.sqrt(1 -
|
|
148
|
-
const [a, b] = [cy + dy * r *
|
|
151
|
+
for (const [s0, s1] of STEPS) {
|
|
152
|
+
const reach = r * Math.sqrt(1 - s0 * s0);
|
|
153
|
+
const [a, b] = [cy + dy * r * s0, cy + dy * r * s1];
|
|
149
154
|
holes.push([Math.min(cx, cx + dx * reach), Math.min(a, b), Math.max(cx, cx + dx * reach), Math.max(a, b)]);
|
|
150
155
|
}
|
|
151
156
|
};
|
|
152
157
|
/** The same with x and y swapped (steps run along x). */
|
|
153
158
|
const quarterX = (cx: number, cy: number, r: number, dx: number, dy: number) => {
|
|
154
|
-
for (
|
|
155
|
-
const reach = r * Math.sqrt(1 -
|
|
156
|
-
const [a, b] = [cx + dx * r *
|
|
159
|
+
for (const [s0, s1] of STEPS) {
|
|
160
|
+
const reach = r * Math.sqrt(1 - s0 * s0);
|
|
161
|
+
const [a, b] = [cx + dx * r * s0, cx + dx * r * s1];
|
|
157
162
|
holes.push([Math.min(a, b), Math.min(cy, cy + dy * reach), Math.max(a, b), Math.max(cy, cy + dy * reach)]);
|
|
158
163
|
}
|
|
159
164
|
};
|
|
@@ -191,25 +196,25 @@ export function ticketSafeRects(g: TicketGeometry, offset: { x: number; y: numbe
|
|
|
191
196
|
for (const hole of holes) [hole[0], hole[1], hole[2], hole[3]] = [down(hole[0]), down(hole[1]), up(hole[2]), up(hole[3])];
|
|
192
197
|
|
|
193
198
|
const cuts = (lo: number, hi: number, more: number[]) => [...new Set([lo, hi, ...more])].filter((v) => v >= lo && v <= hi).sort((a, b) => a - b);
|
|
199
|
+
/** Each neighbouring pair of sorted cuts: [v0, v1], [v1, v2], … */
|
|
200
|
+
const bands = (v: number[]) => v.slice(1).map((b, i): [number, number] => [v[i] ?? b, b]);
|
|
194
201
|
const xs = cuts(near(x0), near(x1), holes.flatMap(([a, , c]) => [a, c]));
|
|
195
202
|
const ys = cuts(near(y0), near(y1), holes.flatMap(([, b, , d]) => [b, d]));
|
|
196
203
|
const rows: { y: number; h: number; spans: [number, number][] }[] = [];
|
|
197
204
|
// Every cut is on the pixel grid, so anything thinner than half a pixel is a rounding twin; a
|
|
198
205
|
// one-pixel row (where two stepped holes overlap) must stay, or the reflection shows a gap there.
|
|
199
206
|
const thin = 0.5 / px;
|
|
200
|
-
for (
|
|
201
|
-
const [ya, yb] = [ys[j], ys[j + 1]];
|
|
207
|
+
for (const [ya, yb] of bands(ys)) {
|
|
202
208
|
if (yb - ya < thin) continue;
|
|
203
209
|
const spans: [number, number][] = [];
|
|
204
|
-
for (
|
|
205
|
-
const [xa, xb] = [xs[i], xs[i + 1]];
|
|
210
|
+
for (const [xa, xb] of bands(xs)) {
|
|
206
211
|
if (xb - xa < thin || holes.some(([hx0, hy0, hx1, hy1]) => xa < hx1 && xb > hx0 && ya < hy1 && yb > hy0)) continue;
|
|
207
212
|
const last = spans[spans.length - 1];
|
|
208
213
|
if (last && Math.abs(last[1] - xa) < 0.01) last[1] = xb;
|
|
209
214
|
else spans.push([xa, xb]);
|
|
210
215
|
}
|
|
211
216
|
const prev = rows[rows.length - 1];
|
|
212
|
-
const same = prev && prev.spans.length === spans.length && prev.spans.every((s, i) => s[0] === spans[i][0] && s[1] === spans[i][1]);
|
|
217
|
+
const same = prev && prev.spans.length === spans.length && prev.spans.every((s, i) => s[0] === spans[i]?.[0] && s[1] === spans[i]?.[1]);
|
|
213
218
|
if (prev && same && Math.abs(prev.y + prev.h - ya) < 0.01) prev.h += yb - ya;
|
|
214
219
|
else rows.push({ y: ya, h: yb - ya, spans });
|
|
215
220
|
}
|
|
@@ -231,8 +236,8 @@ function random(seed: string) {
|
|
|
231
236
|
const CONFETTI = ['#ffd23f', '#ff5fa2', '#3ddcff', '#7dff7a', '#ffffff'];
|
|
232
237
|
|
|
233
238
|
/** A rubber stamp ("USED", "CHECKED IN") that thumps down whenever its text changes. */
|
|
234
|
-
export function TicketStamp({ text, color, size = 17, style }: { text: string; color: string; size?: number; style?: StyleProp<ViewStyle> }) {
|
|
235
|
-
const thump =
|
|
239
|
+
export function TicketStamp({ text, color, size = 17, style }: { text: string; color: string; size?: number | undefined; style?: StyleProp<ViewStyle> | undefined }) {
|
|
240
|
+
const [thump] = useState(() => new Animated.Value(0));
|
|
236
241
|
useEffect(() => {
|
|
237
242
|
thump.setValue(0);
|
|
238
243
|
Animated.spring(thump, { toValue: 1, ...motion.bouncy, useNativeDriver: true }).start();
|
|
@@ -258,12 +263,17 @@ export function TicketStamp({ text, color, size = 17, style }: { text: string; c
|
|
|
258
263
|
}
|
|
259
264
|
|
|
260
265
|
/** A QR-style matrix (finder squares, timing lines, data) drawn as one path — a look-alike, not a real code. */
|
|
261
|
-
export function QrLike({ code, size, color = '#111111' }: { code: string; size: number; color?: string }) {
|
|
266
|
+
export function QrLike({ code, size, color = '#111111' }: { code: string; size: number; color?: string | undefined }) {
|
|
262
267
|
const d = useMemo(() => {
|
|
263
268
|
const N = 25;
|
|
264
269
|
const next = random(code + '|qr');
|
|
265
270
|
const finder = (x: number, y: number) => {
|
|
266
|
-
|
|
271
|
+
const corners: [number, number][] = [
|
|
272
|
+
[0, 0],
|
|
273
|
+
[N - 7, 0],
|
|
274
|
+
[0, N - 7],
|
|
275
|
+
];
|
|
276
|
+
for (const [fx, fy] of corners) {
|
|
267
277
|
const [u, v] = [x - fx, y - fy];
|
|
268
278
|
if (u >= -1 && u <= 7 && v >= -1 && v <= 7) {
|
|
269
279
|
if (u < 0 || v < 0 || u > 6 || v > 6) return 0; // the white ring around a finder
|
|
@@ -289,7 +299,7 @@ export function QrLike({ code, size, color = '#111111' }: { code: string; size:
|
|
|
289
299
|
}
|
|
290
300
|
|
|
291
301
|
/** A Code 128-style barcode drawn as one path — a look-alike. `vertical` turns it on its side. */
|
|
292
|
-
export function BarcodeLike({ code, width, height, color = '#111111', vertical = false }: { code: string; width: number; height: number; color?: string; vertical?: boolean }) {
|
|
302
|
+
export function BarcodeLike({ code, width, height, color = '#111111', vertical = false }: { code: string; width: number; height: number; color?: string | undefined; vertical?: boolean | undefined }) {
|
|
293
303
|
const { d, units } = useMemo(() => {
|
|
294
304
|
const next = random(code + '|bars');
|
|
295
305
|
const bars: [number, number][] = [];
|
|
@@ -314,39 +324,39 @@ export function BarcodeLike({ code, width, height, color = '#111111', vertical =
|
|
|
314
324
|
}
|
|
315
325
|
|
|
316
326
|
export interface TicketShapeProps extends Omit<TicketGeometry, 'width' | 'height'> {
|
|
317
|
-
children?: React.ReactNode;
|
|
327
|
+
children?: React.ReactNode | undefined;
|
|
318
328
|
/** A colour, or 2+ colours for a gradient (default white). */
|
|
319
|
-
fill?: string | readonly string[];
|
|
329
|
+
fill?: string | readonly string[] | undefined;
|
|
320
330
|
/** Gradient direction in degrees: 0 runs left → right, 90 top → bottom (default 125). */
|
|
321
|
-
angle?: number;
|
|
331
|
+
angle?: number | undefined;
|
|
322
332
|
/** Photo painted inside the outline (cover), over the fill (`require()` or `{ uri }`). */
|
|
323
|
-
image?: ImageSourcePropType;
|
|
324
|
-
imageOpacity?: number;
|
|
333
|
+
image?: ImageSourcePropType | undefined;
|
|
334
|
+
imageOpacity?: number | undefined;
|
|
325
335
|
/** Darkens the paint toward the bottom (0 – 1) so white text reads on a photo — inside the outline only. */
|
|
326
|
-
shade?: number;
|
|
336
|
+
shade?: number | undefined;
|
|
327
337
|
/** Texture over the fill. */
|
|
328
|
-
pattern?: TicketPattern;
|
|
338
|
+
pattern?: TicketPattern | undefined;
|
|
329
339
|
/** Pattern ink (default: white or black at low opacity, whichever suits the fill; confetti is multicolour). */
|
|
330
|
-
patternColor?: string;
|
|
340
|
+
patternColor?: string | undefined;
|
|
331
341
|
/** Keeps the confetti scatter the same on every render (e.g. the coupon code). */
|
|
332
|
-
seed?: string;
|
|
342
|
+
seed?: string | undefined;
|
|
333
343
|
/** Outline colour. */
|
|
334
|
-
stroke?: string;
|
|
335
|
-
strokeWidth?: number;
|
|
344
|
+
stroke?: string | undefined;
|
|
345
|
+
strokeWidth?: number | undefined;
|
|
336
346
|
/** Dash the outline — a "cut here" coupon. */
|
|
337
|
-
dashed?: boolean;
|
|
347
|
+
dashed?: boolean | undefined;
|
|
338
348
|
/** Dashed tear line along this side. */
|
|
339
|
-
tear?: TicketSide;
|
|
340
|
-
tearColor?: string;
|
|
349
|
+
tear?: TicketSide | undefined;
|
|
350
|
+
tearColor?: string | undefined;
|
|
341
351
|
/**
|
|
342
352
|
* Paint as one piece of a bigger ticket: that ticket's rect in this shape's points. Gradients,
|
|
343
353
|
* patterns and photos then line up across the pieces.
|
|
344
354
|
*/
|
|
345
|
-
frame?: TicketRect;
|
|
355
|
+
frame?: TicketRect | undefined;
|
|
346
356
|
/** Reports the measured size after layout. */
|
|
347
|
-
onSize?: (size: { width: number; height: number }) => void;
|
|
357
|
+
onSize?: ((size: { width: number; height: number }) => void) | undefined;
|
|
348
358
|
/** Padding, width, margins… — the shape takes its size from this box. */
|
|
349
|
-
style?: StyleProp<ViewStyle
|
|
359
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
350
360
|
}
|
|
351
361
|
|
|
352
362
|
/**
|
|
@@ -381,7 +391,7 @@ export function TicketShape({
|
|
|
381
391
|
const id = 'ts' + useId().replace(/[^A-Za-z0-9_-]/g, '');
|
|
382
392
|
const { w, h } = size;
|
|
383
393
|
const colors = typeof fill === 'string' ? [fill] : fill.length ? fill : ['#ffffff'];
|
|
384
|
-
const ink = readableOn(colors[Math.floor((colors.length - 1) / 2)]);
|
|
394
|
+
const ink = readableOn(colors[Math.floor((colors.length - 1) / 2)] ?? '#ffffff');
|
|
385
395
|
const d = ticketPath({ width: w, height: h, corners, notches, edges, edgeSize });
|
|
386
396
|
const f = frameProp ?? { x: 0, y: 0, width: w, height: h };
|
|
387
397
|
|
|
@@ -395,7 +405,7 @@ export function TicketShape({
|
|
|
395
405
|
const clip = `url(#${id}-clip)`;
|
|
396
406
|
const corner = cornerList(corners);
|
|
397
407
|
const [s0, s1] = tear === 'top' || tear === 'bottom' ? (tear === 'top' ? [corner[0], corner[1]] : [corner[3], corner[2]]) : tear === 'left' ? [corner[0], corner[3]] : [corner[1], corner[2]];
|
|
398
|
-
const inset = [Math.abs(s0) + 7, Math.abs(s1) + 7];
|
|
408
|
+
const inset: [number, number] = [Math.abs(s0) + 7, Math.abs(s1) + 7];
|
|
399
409
|
const tearLine =
|
|
400
410
|
tear === 'top' || tear === 'bottom'
|
|
401
411
|
? { x1: inset[0], x2: w - inset[1], y1: tear === 'top' ? 0.75 : h - 0.75, y2: tear === 'top' ? 0.75 : h - 0.75 }
|
|
@@ -419,7 +429,7 @@ export function TicketShape({
|
|
|
419
429
|
const x = f.x + rnd() * f.width;
|
|
420
430
|
const y = f.y + rnd() * f.height;
|
|
421
431
|
const turn = Math.round(rnd() * 180);
|
|
422
|
-
const color = patternColor ?? CONFETTI[Math.floor(rnd() * CONFETTI.length)];
|
|
432
|
+
const color = patternColor ?? CONFETTI[Math.floor(rnd() * CONFETTI.length)] ?? '#ffffff';
|
|
423
433
|
const o = 0.35 + rnd() * 0.4;
|
|
424
434
|
return rnd() < 0.55 ? (
|
|
425
435
|
<Rect key={i} x={x - 1.6} y={y - 4.5} width={3.2} height={9} rx={1} fill={color} opacity={o} transform={`rotate(${turn} ${r2(x)} ${r2(y)})`} />
|
|
@@ -446,7 +456,7 @@ export function TicketShape({
|
|
|
446
456
|
))}
|
|
447
457
|
</LinearGradient>
|
|
448
458
|
) : null;
|
|
449
|
-
const fillPath = <Path d={d} fill={colors.length > 1 ? `url(#${id}-fill)` : colors[0]} />;
|
|
459
|
+
const fillPath = <Path d={d} fill={colors.length > 1 ? `url(#${id}-fill)` : (colors[0] ?? '#ffffff')} />;
|
|
450
460
|
const overlayDefs = (
|
|
451
461
|
<>
|
|
452
462
|
<ClipPath id={`${id}-clip`}>
|
|
@@ -482,7 +492,7 @@ export function TicketShape({
|
|
|
482
492
|
<>
|
|
483
493
|
{shade > 0 ? <Rect width={w} height={h} fill={`url(#${id}-shade)`} clipPath={clip} /> : null}
|
|
484
494
|
{texture ? <G clipPath={clip}>{texture}</G> : null}
|
|
485
|
-
{stroke ? <Path d={d} fill="none" stroke={stroke} strokeWidth={strokeWidth}
|
|
495
|
+
{stroke ? <Path d={d} fill="none" stroke={stroke} strokeWidth={strokeWidth} {...(dashed ? { strokeDasharray: [7, 5] } : null)} strokeLinecap="round" /> : null}
|
|
486
496
|
{tear ? <Line {...tearLine} stroke={tearColor ?? alpha(ink, 0.28)} strokeWidth={1.6} strokeDasharray={[6, 5]} strokeLinecap="round" /> : null}
|
|
487
497
|
</>
|
|
488
498
|
);
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"title": "Ticket shape",
|
|
5
5
|
"description": "Ticket and coupon outlines with real cut-outs — tear-line notches and perforated, scalloped or zigzag edges — painted with a colour, a gradient, a pattern or a photo, plus look-alike QR codes and barcodes. Shared by Ticket, Coupon and Scratch Card.",
|
|
6
6
|
"tier": "free",
|
|
7
|
+
"aliases": [],
|
|
7
8
|
"dependencies": [
|
|
8
9
|
"react-native-svg"
|
|
9
10
|
],
|
|
@@ -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' },
|