@expokit/cli 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +32 -2
- package/bin/expo-kit.js +1204 -0
- package/bin/lib.mjs +343 -0
- package/bin/mcp.mjs +338 -0
- package/package.json +44 -4
- package/registry/_docs/accordion.json +99 -0
- package/registry/_docs/action-sheet.json +110 -0
- package/registry/_docs/alert.json +127 -0
- package/registry/_docs/avatar.json +203 -0
- package/registry/_docs/badge.json +117 -0
- package/registry/_docs/bottom-nav.json +119 -0
- package/registry/_docs/bottom-sheet.json +158 -0
- package/registry/_docs/bullet.json +24 -0
- package/registry/_docs/button-3d.json +122 -0
- package/registry/_docs/button.json +115 -0
- package/registry/_docs/calendar.json +182 -0
- package/registry/_docs/card-stack.json +108 -0
- package/registry/_docs/card.json +111 -0
- package/registry/_docs/carousel.json +102 -0
- package/registry/_docs/checkbox.json +93 -0
- package/registry/_docs/checkout.json +102 -0
- package/registry/_docs/chip-group.json +116 -0
- package/registry/_docs/color-swatch.json +66 -0
- package/registry/_docs/confetti.json +82 -0
- package/registry/_docs/coupon.json +176 -0
- package/registry/_docs/cube-pager.json +84 -0
- package/registry/_docs/dashboard.json +120 -0
- package/registry/_docs/date-picker.json +249 -0
- package/registry/_docs/dialog.json +162 -0
- package/registry/_docs/drawer.json +171 -0
- package/registry/_docs/fab.json +120 -0
- package/registry/_docs/flip-card.json +111 -0
- package/registry/_docs/floating-input.json +191 -0
- package/registry/_docs/gift-card.json +108 -0
- package/registry/_docs/glass-button.json +118 -0
- package/registry/_docs/glass-nav-bar.json +78 -0
- package/registry/_docs/glass-search-bar.json +90 -0
- package/registry/_docs/glass-segmented.json +66 -0
- package/registry/_docs/glass-sheet.json +108 -0
- package/registry/_docs/glass-tab-bar.json +76 -0
- package/registry/_docs/glass.json +78 -0
- package/registry/_docs/inbox.json +72 -0
- package/registry/_docs/isometric-bar.json +72 -0
- package/registry/_docs/kit.json +11 -0
- package/registry/_docs/kpi-card.json +11 -0
- package/registry/_docs/like-button.json +72 -0
- package/registry/_docs/liquidglass-tab.json +27 -0
- package/registry/_docs/list-item.json +200 -0
- package/registry/_docs/loyalty-card.json +108 -0
- package/registry/_docs/morph-button.json +108 -0
- package/registry/_docs/number-ticker.json +96 -0
- package/registry/_docs/offers.json +78 -0
- package/registry/_docs/onboarding.json +66 -0
- package/registry/_docs/otp-input.json +134 -0
- package/registry/_docs/pagination-dots.json +88 -0
- package/registry/_docs/parallax-layers.json +84 -0
- package/registry/_docs/parallax-scroll.json +186 -0
- package/registry/_docs/password-input.json +92 -0
- package/registry/_docs/payment-card.json +153 -0
- package/registry/_docs/pixel-bar.json +66 -0
- package/registry/_docs/polar-area.json +78 -0
- package/registry/_docs/profile.json +84 -0
- package/registry/_docs/progress-steps.json +92 -0
- package/registry/_docs/progress.json +132 -0
- package/registry/_docs/radio-group.json +92 -0
- package/registry/_docs/rating.json +90 -0
- package/registry/_docs/ring-carousel.json +102 -0
- package/registry/_docs/scratch-card.json +90 -0
- package/registry/_docs/search-bar.json +140 -0
- package/registry/_docs/search-results.json +120 -0
- package/registry/_docs/segmented-control.json +103 -0
- package/registry/_docs/select.json +252 -0
- package/registry/_docs/settings.json +72 -0
- package/registry/_docs/shimmer-button.json +78 -0
- package/registry/_docs/sign-in.json +198 -0
- package/registry/_docs/skeleton.json +142 -0
- package/registry/_docs/slide-to-confirm.json +84 -0
- package/registry/_docs/slider.json +157 -0
- package/registry/_docs/spin-wheel.json +114 -0
- package/registry/_docs/spinner.json +91 -0
- package/registry/_docs/stepper.json +168 -0
- package/registry/_docs/swipe-deck.json +102 -0
- package/registry/_docs/swipeable-row.json +72 -0
- package/registry/_docs/switch.json +104 -0
- package/registry/_docs/tabs.json +105 -0
- package/registry/_docs/tag-input.json +128 -0
- package/registry/_docs/textarea.json +175 -0
- package/registry/_docs/ticket-card.json +174 -0
- package/registry/_docs/ticket-shape.json +11 -0
- package/registry/_docs/tilt-card.json +141 -0
- package/registry/_docs/time-picker.json +253 -0
- package/registry/_docs/timeline-chart.json +30 -0
- package/registry/_docs/timeline.json +156 -0
- package/registry/_docs/toast.json +161 -0
- package/registry/_docs/treemap.json +30 -0
- package/registry/_docs/waterfall.json +36 -0
- package/registry/_docs/wheel-list.json +102 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +233 -0
- package/registry/accordion/files/src/lib/kit.tsx +219 -0
- package/registry/accordion.json +17 -0
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +580 -0
- package/registry/action-sheet/files/src/lib/kit.tsx +219 -0
- package/registry/action-sheet.json +18 -0
- package/registry/alert/files/src/components/ui/alert.tsx +654 -0
- package/registry/alert/files/src/components/ui/button.tsx +274 -0
- package/registry/alert/files/src/lib/kit.tsx +219 -0
- package/registry/alert.json +20 -0
- package/registry/avatar/files/src/components/ui/avatar.tsx +790 -0
- package/registry/avatar/files/src/lib/kit.tsx +219 -0
- package/registry/avatar.json +18 -0
- package/registry/badge/files/src/components/ui/badge.tsx +316 -0
- package/registry/badge/files/src/lib/kit.tsx +219 -0
- package/registry/badge.json +17 -0
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +1050 -0
- package/registry/bottom-nav/files/src/lib/kit.tsx +219 -0
- package/registry/bottom-nav.json +18 -0
- package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +585 -0
- package/registry/bottom-sheet/files/src/lib/kit.tsx +219 -0
- package/registry/bottom-sheet.json +17 -0
- package/registry/bullet.json +14 -0
- package/registry/button/files/src/components/ui/button.tsx +274 -0
- package/registry/button/files/src/lib/kit.tsx +219 -0
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +212 -0
- package/registry/button-3d/files/src/lib/kit.tsx +219 -0
- package/registry/button-3d.json +18 -0
- package/registry/button.json +18 -0
- package/registry/calendar/files/src/components/ui/calendar.tsx +822 -0
- package/registry/calendar/files/src/lib/kit.tsx +219 -0
- package/registry/calendar.json +17 -0
- package/registry/card/files/src/components/ui/card.tsx +300 -0
- package/registry/card/files/src/lib/kit.tsx +219 -0
- package/registry/card-stack.json +15 -0
- package/registry/card.json +17 -0
- package/registry/carousel.json +15 -0
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +150 -0
- package/registry/checkbox/files/src/lib/kit.tsx +219 -0
- package/registry/checkbox.json +17 -0
- package/registry/checkout.json +26 -0
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +269 -0
- package/registry/chip-group/files/src/lib/kit.tsx +219 -0
- package/registry/chip-group.json +17 -0
- package/registry/color-swatch.json +17 -0
- package/registry/confetti/files/src/components/ui/confetti.tsx +222 -0
- package/registry/confetti/files/src/lib/kit.tsx +219 -0
- package/registry/confetti.json +15 -0
- package/registry/coupon/files/src/components/ui/coupon.tsx +698 -0
- package/registry/coupon/files/src/components/ui/flip-card.tsx +205 -0
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +635 -0
- package/registry/coupon/files/src/lib/kit.tsx +219 -0
- package/registry/coupon/files/src/lib/ticket-shape.tsx +543 -0
- package/registry/coupon.json +23 -0
- package/registry/cube-pager.json +15 -0
- package/registry/dashboard.json +32 -0
- package/registry/date-picker/files/src/components/ui/calendar.tsx +822 -0
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +796 -0
- package/registry/date-picker/files/src/lib/kit.tsx +219 -0
- package/registry/date-picker.json +19 -0
- package/registry/dialog/files/src/components/ui/dialog.tsx +759 -0
- package/registry/dialog/files/src/lib/kit.tsx +219 -0
- package/registry/dialog.json +18 -0
- package/registry/drawer/files/src/components/ui/drawer.tsx +1135 -0
- package/registry/drawer/files/src/lib/kit.tsx +219 -0
- package/registry/drawer.json +18 -0
- package/registry/fab.json +18 -0
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +205 -0
- package/registry/flip-card/files/src/lib/kit.tsx +219 -0
- package/registry/flip-card.json +15 -0
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +528 -0
- package/registry/floating-input/files/src/lib/kit.tsx +219 -0
- package/registry/floating-input.json +17 -0
- package/registry/gift-card.json +21 -0
- package/registry/glass-button.json +22 -0
- package/registry/glass-nav-bar.json +21 -0
- package/registry/glass-search-bar.json +21 -0
- package/registry/glass-segmented.json +22 -0
- package/registry/glass-sheet.json +22 -0
- package/registry/glass-tab-bar.json +22 -0
- package/registry/glass.json +19 -0
- package/registry/inbox.json +30 -0
- package/registry/isometric-bar.json +14 -0
- package/registry/kit/files/src/lib/kit.tsx +219 -0
- package/registry/kit.json +11 -0
- package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +45 -0
- package/registry/kpi-card.json +14 -0
- package/registry/like-button.json +18 -0
- package/registry/liquidglass-tab/files/src/app/(tabs)/_layout.tsx +97 -0
- package/registry/liquidglass-tab/files/src/app/(tabs)/alerts.tsx +5 -0
- package/registry/liquidglass-tab/files/src/app/(tabs)/explore.tsx +5 -0
- package/registry/liquidglass-tab/files/src/app/(tabs)/index.tsx +5 -0
- package/registry/liquidglass-tab/files/src/app/(tabs)/profile.tsx +5 -0
- package/registry/liquidglass-tab/files/src/app/_layout.tsx +33 -0
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +259 -0
- package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +122 -0
- package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +61 -0
- package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +130 -0
- package/registry/liquidglass-tab/files/src/lib/tabs.ts +108 -0
- package/registry/liquidglass-tab.json +23 -0
- package/registry/list-item/files/src/components/ui/list-item.tsx +508 -0
- package/registry/list-item/files/src/lib/kit.tsx +219 -0
- package/registry/list-item.json +17 -0
- package/registry/loyalty-card.json +23 -0
- package/registry/morph-button.json +18 -0
- package/registry/number-ticker.json +15 -0
- package/registry/offers.json +24 -0
- package/registry/onboarding.json +17 -0
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +380 -0
- package/registry/otp-input/files/src/lib/kit.tsx +219 -0
- package/registry/otp-input.json +17 -0
- package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +262 -0
- package/registry/pagination-dots/files/src/lib/kit.tsx +219 -0
- package/registry/pagination-dots.json +15 -0
- package/registry/parallax-layers.json +15 -0
- package/registry/parallax-scroll.json +17 -0
- package/registry/password-input/files/src/components/ui/floating-input.tsx +528 -0
- package/registry/password-input/files/src/components/ui/password-input.tsx +271 -0
- package/registry/password-input/files/src/lib/kit.tsx +219 -0
- package/registry/password-input.json +19 -0
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +205 -0
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +385 -0
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +635 -0
- package/registry/payment-card/files/src/lib/kit.tsx +219 -0
- package/registry/payment-card.json +21 -0
- package/registry/pixel-bar.json +14 -0
- package/registry/polar-area.json +14 -0
- package/registry/profile.json +17 -0
- package/registry/progress/files/src/components/ui/progress.tsx +883 -0
- package/registry/progress/files/src/lib/kit.tsx +219 -0
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +559 -0
- package/registry/progress-steps/files/src/lib/kit.tsx +219 -0
- package/registry/progress-steps.json +18 -0
- package/registry/progress.json +17 -0
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +327 -0
- package/registry/radio-group/files/src/lib/kit.tsx +219 -0
- package/registry/radio-group.json +18 -0
- package/registry/rating.json +18 -0
- package/registry/ring-carousel.json +15 -0
- package/registry/scratch-card.json +18 -0
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +530 -0
- package/registry/search-bar/files/src/lib/kit.tsx +219 -0
- package/registry/search-bar.json +17 -0
- package/registry/search-results.json +30 -0
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +341 -0
- package/registry/segmented-control/files/src/lib/kit.tsx +219 -0
- package/registry/segmented-control.json +18 -0
- package/registry/select/files/src/components/ui/select.tsx +1398 -0
- package/registry/select/files/src/lib/kit.tsx +219 -0
- package/registry/select.json +17 -0
- package/registry/settings.json +26 -0
- package/registry/shimmer-button.json +17 -0
- package/registry/sign-in.json +26 -0
- package/registry/skeleton/files/src/components/ui/skeleton.tsx +799 -0
- package/registry/skeleton/files/src/lib/kit.tsx +219 -0
- package/registry/skeleton.json +17 -0
- package/registry/slide-to-confirm.json +18 -0
- package/registry/slider/files/src/components/ui/slider.tsx +716 -0
- package/registry/slider/files/src/lib/kit.tsx +219 -0
- package/registry/slider.json +17 -0
- package/registry/spin-wheel.json +18 -0
- package/registry/spinner/files/src/components/ui/spinner.tsx +335 -0
- package/registry/spinner/files/src/lib/kit.tsx +219 -0
- package/registry/spinner.json +17 -0
- package/registry/stepper/files/src/components/ui/stepper.tsx +646 -0
- package/registry/stepper/files/src/lib/kit.tsx +219 -0
- package/registry/stepper.json +17 -0
- package/registry/swipe-deck.json +15 -0
- package/registry/swipeable-row.json +15 -0
- package/registry/switch/files/src/components/ui/switch.tsx +344 -0
- package/registry/switch/files/src/lib/kit.tsx +219 -0
- package/registry/switch.json +18 -0
- package/registry/tabs/files/src/components/ui/tabs.tsx +539 -0
- package/registry/tabs/files/src/lib/kit.tsx +219 -0
- package/registry/tabs.json +17 -0
- package/registry/tag-input/files/src/components/ui/tag-input.tsx +720 -0
- package/registry/tag-input/files/src/lib/kit.tsx +219 -0
- package/registry/tag-input.json +17 -0
- package/registry/textarea/files/src/components/ui/textarea.tsx +553 -0
- package/registry/textarea/files/src/lib/kit.tsx +219 -0
- package/registry/textarea.json +17 -0
- package/registry/ticket-card.json +24 -0
- package/registry/ticket-shape/files/src/lib/kit.tsx +219 -0
- package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +543 -0
- package/registry/ticket-shape.json +17 -0
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +635 -0
- package/registry/tilt-card/files/src/lib/kit.tsx +219 -0
- package/registry/tilt-card.json +17 -0
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +1159 -0
- package/registry/time-picker/files/src/lib/kit.tsx +219 -0
- package/registry/time-picker.json +17 -0
- package/registry/timeline/files/src/components/ui/avatar.tsx +790 -0
- package/registry/timeline/files/src/components/ui/timeline.tsx +1059 -0
- package/registry/timeline/files/src/lib/kit.tsx +219 -0
- package/registry/timeline-chart.json +14 -0
- package/registry/timeline.json +20 -0
- package/registry/toast/files/src/components/ui/button.tsx +274 -0
- package/registry/toast/files/src/components/ui/toast.tsx +1284 -0
- package/registry/toast/files/src/lib/kit.tsx +219 -0
- package/registry/toast.json +20 -0
- package/registry/treemap.json +14 -0
- package/registry/waterfall.json +14 -0
- package/registry/wheel-list.json +17 -0
- package/schema/components.schema.json +13 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
type AccessibilityActionEvent,
|
|
9
|
+
type LayoutChangeEvent,
|
|
10
|
+
type StyleProp,
|
|
11
|
+
type ViewStyle,
|
|
12
|
+
} from 'react-native';
|
|
13
|
+
|
|
14
|
+
import { alpha, motion, useKit } from '@/lib/kit';
|
|
15
|
+
|
|
16
|
+
export type PaginationDotsVariant = 'dots' | 'scale' | 'line' | 'numbers';
|
|
17
|
+
export type PaginationDotsSize = 'sm' | 'md' | 'lg';
|
|
18
|
+
|
|
19
|
+
/** A page position: 0 = first page, 1.5 = halfway between the second and third. */
|
|
20
|
+
export type PaginationProgress = Animated.Value | Animated.AnimatedInterpolation<number> | number;
|
|
21
|
+
|
|
22
|
+
export interface PaginationDotsProps {
|
|
23
|
+
/** Number of pages. */
|
|
24
|
+
count: number;
|
|
25
|
+
/** Active page. Changes spring the indicator across. Ignored when `progress` is an Animated value. */
|
|
26
|
+
index?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Live page position, e.g. `Animated.divide(scrollX, pageWidth)`, so the indicator follows the
|
|
29
|
+
* finger while swiping. Drive it with `useNativeDriver: false` for the 'dots' variant (it
|
|
30
|
+
* animates widths); 'scale', 'line' and 'numbers' work with either driver.
|
|
31
|
+
*/
|
|
32
|
+
progress?: PaginationProgress;
|
|
33
|
+
/** 'dots' active dot stretches into a pill · 'scale' active dot grows · 'line' progress segments · 'numbers' "2 / 5". */
|
|
34
|
+
variant?: PaginationDotsVariant;
|
|
35
|
+
size?: PaginationDotsSize;
|
|
36
|
+
/** Makes each dot a button (with a 44pt hit area) that reports its page. */
|
|
37
|
+
onPress?: (index: number) => void;
|
|
38
|
+
/** Active colour (default: theme accent). */
|
|
39
|
+
accent?: string;
|
|
40
|
+
/** Inactive dot colour (default: a soft tint of the foreground). */
|
|
41
|
+
inactiveColor?: string;
|
|
42
|
+
accessibilityLabel?: string;
|
|
43
|
+
style?: StyleProp<ViewStyle>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const SIZES: Record<PaginationDotsSize, { dot: number; gap: number; pill: number; line: number; font: number }> = {
|
|
47
|
+
sm: { dot: 6, gap: 6, pill: 18, line: 3, font: 12 },
|
|
48
|
+
md: { dot: 8, gap: 8, pill: 24, line: 4, font: 13 },
|
|
49
|
+
lg: { dot: 10, gap: 10, pill: 32, line: 5, font: 15 },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Page indicator for carousels and onboarding. Feed it a live `progress` value and it tracks the
|
|
54
|
+
* swipe continuously: the active dot stretches into a pill that hands its width to the next dot,
|
|
55
|
+
* scales up, fills a segmented line or rolls the page number.
|
|
56
|
+
*/
|
|
57
|
+
export function PaginationDots({
|
|
58
|
+
count,
|
|
59
|
+
index = 0,
|
|
60
|
+
progress,
|
|
61
|
+
variant = 'dots',
|
|
62
|
+
size = 'md',
|
|
63
|
+
onPress,
|
|
64
|
+
accent,
|
|
65
|
+
inactiveColor,
|
|
66
|
+
accessibilityLabel = 'Page',
|
|
67
|
+
style,
|
|
68
|
+
}: PaginationDotsProps) {
|
|
69
|
+
const { colors, radius } = useKit();
|
|
70
|
+
const tint = accent ?? colors.accent;
|
|
71
|
+
const idle = inactiveColor ?? alpha(colors.foreground, 0.18);
|
|
72
|
+
const s = SIZES[size];
|
|
73
|
+
const n = Math.max(1, count);
|
|
74
|
+
|
|
75
|
+
// A spring-driven position when we aren't handed an Animated value.
|
|
76
|
+
const external = typeof progress === 'object' ? progress : null;
|
|
77
|
+
const target = typeof progress === 'number' ? progress : index;
|
|
78
|
+
const internal = useRef(new Animated.Value(target)).current;
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (external) return;
|
|
81
|
+
if (typeof progress === 'number') internal.setValue(target);
|
|
82
|
+
else Animated.spring(internal, { toValue: target, useNativeDriver: false, ...motion.snappy }).start();
|
|
83
|
+
}, [external, progress, target, internal]);
|
|
84
|
+
const pos = external ?? internal;
|
|
85
|
+
|
|
86
|
+
// Rounded page for the 'numbers' label and accessibility.
|
|
87
|
+
const [page, setPage] = useState(Math.round(target));
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!external) {
|
|
90
|
+
setPage(Math.round(target));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const id = external.addListener(({ value }: { value: number }) => setPage(Math.min(n - 1, Math.max(0, Math.round(value)))));
|
|
94
|
+
return () => external.removeListener(id);
|
|
95
|
+
}, [external, target, n]);
|
|
96
|
+
|
|
97
|
+
const pages = useMemo(() => Array.from({ length: n }, (_, i) => i), [n]);
|
|
98
|
+
const around = (i: number, outside: number, inside: number) =>
|
|
99
|
+
pos.interpolate({ inputRange: [i - 1, i, i + 1], outputRange: [outside, inside, outside], extrapolate: 'clamp' });
|
|
100
|
+
|
|
101
|
+
const onAction = (e: AccessibilityActionEvent) => {
|
|
102
|
+
if (!onPress) return;
|
|
103
|
+
if (e.nativeEvent.actionName === 'increment' && page < n - 1) onPress(page + 1);
|
|
104
|
+
if (e.nativeEvent.actionName === 'decrement' && page > 0) onPress(page - 1);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const a11y = {
|
|
108
|
+
accessible: true,
|
|
109
|
+
accessibilityRole: onPress ? ('adjustable' as const) : ('text' as const),
|
|
110
|
+
accessibilityLabel,
|
|
111
|
+
accessibilityValue: { min: 1, max: n, now: page + 1, text: `${page + 1} of ${n}` },
|
|
112
|
+
accessibilityActions: onPress ? [{ name: 'increment' as const }, { name: 'decrement' as const }] : undefined,
|
|
113
|
+
onAccessibilityAction: onAction,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const hit = (i: number, child: React.ReactNode, extra?: ViewStyle) =>
|
|
117
|
+
onPress ? (
|
|
118
|
+
<Pressable
|
|
119
|
+
key={i}
|
|
120
|
+
onPress={() => onPress(i)}
|
|
121
|
+
hitSlop={{ top: 18, bottom: 18, left: s.gap / 2, right: s.gap / 2 }}
|
|
122
|
+
accessibilityLabel={`Go to page ${i + 1}`}
|
|
123
|
+
style={extra}
|
|
124
|
+
>
|
|
125
|
+
{child}
|
|
126
|
+
</Pressable>
|
|
127
|
+
) : (
|
|
128
|
+
<View key={i} style={extra}>
|
|
129
|
+
{child}
|
|
130
|
+
</View>
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (variant === 'numbers') {
|
|
134
|
+
return (
|
|
135
|
+
<View {...a11y} style={[styles.numbers, { borderRadius: radius.capsule(Math.ceil(s.font * 1.4) + 10), backgroundColor: alpha(colors.foreground, 0.06) }, style]}>
|
|
136
|
+
<RollingNumber value={page + 1} color={tint} size={s.font} />
|
|
137
|
+
<Text style={[styles.numText, { fontSize: s.font, color: colors.mutedForeground }]}> / {n}</Text>
|
|
138
|
+
</View>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (variant === 'line') return <LineSegments {...{ pages, pos, s, tint, idle, a11y, hit, style }} />;
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<View {...a11y} style={[styles.row, { gap: s.gap, height: s.dot * (variant === 'scale' ? 1.8 : 1) }, style]}>
|
|
146
|
+
{pages.map((i) =>
|
|
147
|
+
variant === 'scale'
|
|
148
|
+
? hit(
|
|
149
|
+
i,
|
|
150
|
+
<Animated.View
|
|
151
|
+
style={{
|
|
152
|
+
width: s.dot,
|
|
153
|
+
height: s.dot,
|
|
154
|
+
borderRadius: s.dot,
|
|
155
|
+
backgroundColor: tint,
|
|
156
|
+
opacity: around(i, 0.28, 1),
|
|
157
|
+
transform: [{ scale: around(i, 1, 1.55) }],
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
)
|
|
161
|
+
: hit(
|
|
162
|
+
i,
|
|
163
|
+
// Width (JS driver) on the outer view, the accent fill fades on an inner view.
|
|
164
|
+
<Animated.View
|
|
165
|
+
style={{ width: around(i, s.dot, s.pill), height: s.dot, borderRadius: s.dot, backgroundColor: idle, overflow: 'hidden' }}
|
|
166
|
+
>
|
|
167
|
+
<Animated.View style={[StyleSheet.absoluteFill, { backgroundColor: tint, opacity: around(i, 0, 1) }]} />
|
|
168
|
+
</Animated.View>
|
|
169
|
+
)
|
|
170
|
+
)}
|
|
171
|
+
</View>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
interface LineProps {
|
|
176
|
+
pages: number[];
|
|
177
|
+
pos: Animated.Value | Animated.AnimatedInterpolation<number>;
|
|
178
|
+
s: (typeof SIZES)[PaginationDotsSize];
|
|
179
|
+
tint: string;
|
|
180
|
+
idle: string;
|
|
181
|
+
a11y: object;
|
|
182
|
+
hit: (i: number, child: React.ReactNode, extra?: ViewStyle) => React.ReactNode;
|
|
183
|
+
style?: StyleProp<ViewStyle>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Segments fill left-to-right as the position moves into each page. */
|
|
187
|
+
function LineSegments({ pages, pos, s, tint, idle, a11y, hit, style }: LineProps) {
|
|
188
|
+
const [w, setW] = useState(0);
|
|
189
|
+
const n = pages.length;
|
|
190
|
+
const segW = w ? (w - s.gap * (n - 1)) / n : 0;
|
|
191
|
+
const onLayout = (e: LayoutChangeEvent) => setW(e.nativeEvent.layout.width);
|
|
192
|
+
return (
|
|
193
|
+
<View {...a11y} onLayout={onLayout} style={[styles.line, { gap: s.gap }, style]}>
|
|
194
|
+
{pages.map((i) =>
|
|
195
|
+
hit(
|
|
196
|
+
i,
|
|
197
|
+
<View style={{ height: s.line, borderRadius: s.line, backgroundColor: idle, overflow: 'hidden' }}>
|
|
198
|
+
{segW ? (
|
|
199
|
+
<Animated.View
|
|
200
|
+
style={{
|
|
201
|
+
width: segW,
|
|
202
|
+
height: s.line,
|
|
203
|
+
borderRadius: s.line,
|
|
204
|
+
backgroundColor: tint,
|
|
205
|
+
transform: [
|
|
206
|
+
{
|
|
207
|
+
translateX: pos.interpolate({ inputRange: [i - 1, i], outputRange: [-segW, 0], extrapolate: 'clamp' }),
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
}}
|
|
211
|
+
/>
|
|
212
|
+
) : null}
|
|
213
|
+
</View>,
|
|
214
|
+
styles.segment
|
|
215
|
+
)
|
|
216
|
+
)}
|
|
217
|
+
</View>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function RollingNumber({ value, color, size }: { value: number; color: string; size: number }) {
|
|
222
|
+
const v = useRef(new Animated.Value(1)).current;
|
|
223
|
+
const last = useRef(value);
|
|
224
|
+
const dir = value >= last.current ? 1 : -1;
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
if (value === last.current) return;
|
|
227
|
+
last.current = value;
|
|
228
|
+
v.setValue(0);
|
|
229
|
+
Animated.spring(v, { toValue: 1, useNativeDriver: true, ...motion.snappy }).start();
|
|
230
|
+
}, [value, v]);
|
|
231
|
+
return (
|
|
232
|
+
<Animated.Text
|
|
233
|
+
style={[
|
|
234
|
+
styles.numText,
|
|
235
|
+
{
|
|
236
|
+
fontSize: size,
|
|
237
|
+
color,
|
|
238
|
+
fontWeight: '400',
|
|
239
|
+
opacity: v,
|
|
240
|
+
transform: [{ translateY: v.interpolate({ inputRange: [0, 1], outputRange: [dir * size * 0.7, 0] }) }],
|
|
241
|
+
},
|
|
242
|
+
]}
|
|
243
|
+
>
|
|
244
|
+
{value}
|
|
245
|
+
</Animated.Text>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const styles = StyleSheet.create({
|
|
250
|
+
row: { flexDirection: 'row', alignItems: 'center', alignSelf: 'center' },
|
|
251
|
+
line: { flexDirection: 'row', alignItems: 'center', alignSelf: 'stretch' },
|
|
252
|
+
segment: { flex: 1, justifyContent: 'center' },
|
|
253
|
+
numbers: {
|
|
254
|
+
flexDirection: 'row',
|
|
255
|
+
alignItems: 'center',
|
|
256
|
+
alignSelf: 'center',
|
|
257
|
+
paddingHorizontal: 12,
|
|
258
|
+
paddingVertical: 5,
|
|
259
|
+
overflow: 'hidden',
|
|
260
|
+
},
|
|
261
|
+
numText: { fontVariant: ['tabular-nums'], fontWeight: '400' },
|
|
262
|
+
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo } from 'react';
|
|
2
|
+
import { Platform, useColorScheme, type TextStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ExpoKit design tokens. Every component reads them through useKit(), so one
|
|
6
|
+
* <KitProvider> (optional) re-skins the whole kit: light / dark, accent colour, radius.
|
|
7
|
+
* Without a provider, components follow the system colour scheme.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Bumped whenever components start using a new kit API (2: radius.scale / radius.capsule).
|
|
12
|
+
* The CLI won't keep an edited kit.tsx older than the component being added needs.
|
|
13
|
+
*/
|
|
14
|
+
export const KIT_VERSION = 2;
|
|
15
|
+
|
|
16
|
+
export type ColorScheme = 'light' | 'dark';
|
|
17
|
+
|
|
18
|
+
export interface Colors {
|
|
19
|
+
background: string;
|
|
20
|
+
foreground: string;
|
|
21
|
+
/** Cards, sheets, inputs. */
|
|
22
|
+
card: string;
|
|
23
|
+
cardForeground: string;
|
|
24
|
+
/** Raised surfaces: popovers, toasts. */
|
|
25
|
+
popover: string;
|
|
26
|
+
muted: string;
|
|
27
|
+
mutedForeground: string;
|
|
28
|
+
border: string;
|
|
29
|
+
input: string;
|
|
30
|
+
primary: string;
|
|
31
|
+
primaryForeground: string;
|
|
32
|
+
secondary: string;
|
|
33
|
+
secondaryForeground: string;
|
|
34
|
+
accent: string;
|
|
35
|
+
accentForeground: string;
|
|
36
|
+
success: string;
|
|
37
|
+
warning: string;
|
|
38
|
+
danger: string;
|
|
39
|
+
dangerForeground: string;
|
|
40
|
+
ring: string;
|
|
41
|
+
overlay: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Theme {
|
|
45
|
+
dark: boolean;
|
|
46
|
+
colors: Colors;
|
|
47
|
+
/** Corner radius scale. `radius.full` makes pills. */
|
|
48
|
+
radius: {
|
|
49
|
+
sm: number;
|
|
50
|
+
md: number;
|
|
51
|
+
lg: number;
|
|
52
|
+
xl: number;
|
|
53
|
+
full: number;
|
|
54
|
+
/** A corner designed at the default radius (12), scaled to the current one: `radius.scale(16)` is 16 at 12, 0 at 0. */
|
|
55
|
+
scale: (n: number) => number;
|
|
56
|
+
/** Corner for a bar or chip of height `h`: a pill at radius ≥ 12, softer below, square at 0. */
|
|
57
|
+
capsule: (h: number) => number;
|
|
58
|
+
};
|
|
59
|
+
/** 4pt spacing scale: space(4) === 16. */
|
|
60
|
+
space: (n: number) => number;
|
|
61
|
+
font: { family?: string; mono: string };
|
|
62
|
+
text: Record<'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', TextStyle>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const light: Colors = {
|
|
66
|
+
background: '#ffffff',
|
|
67
|
+
foreground: '#09090b',
|
|
68
|
+
card: '#ffffff',
|
|
69
|
+
cardForeground: '#09090b',
|
|
70
|
+
popover: '#ffffff',
|
|
71
|
+
muted: '#f4f4f5',
|
|
72
|
+
mutedForeground: '#71717a',
|
|
73
|
+
border: '#e4e4e7',
|
|
74
|
+
input: '#d4d4d8',
|
|
75
|
+
primary: '#18181b',
|
|
76
|
+
primaryForeground: '#fafafa',
|
|
77
|
+
secondary: '#f4f4f5',
|
|
78
|
+
secondaryForeground: '#18181b',
|
|
79
|
+
accent: '#18181b',
|
|
80
|
+
accentForeground: '#ffffff',
|
|
81
|
+
success: '#16a34a',
|
|
82
|
+
warning: '#f59e0b',
|
|
83
|
+
danger: '#e5484d',
|
|
84
|
+
dangerForeground: '#ffffff',
|
|
85
|
+
ring: '#18181b',
|
|
86
|
+
overlay: 'rgba(0,0,0,0.4)',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const dark: Colors = {
|
|
90
|
+
background: '#09090b',
|
|
91
|
+
foreground: '#fafafa',
|
|
92
|
+
card: '#111113',
|
|
93
|
+
cardForeground: '#fafafa',
|
|
94
|
+
popover: '#18181b',
|
|
95
|
+
muted: '#1c1c20',
|
|
96
|
+
mutedForeground: '#a1a1aa',
|
|
97
|
+
border: '#27272a',
|
|
98
|
+
input: '#34343a',
|
|
99
|
+
primary: '#fafafa',
|
|
100
|
+
primaryForeground: '#18181b',
|
|
101
|
+
secondary: '#1f1f23',
|
|
102
|
+
secondaryForeground: '#fafafa',
|
|
103
|
+
accent: '#fafafa',
|
|
104
|
+
accentForeground: '#09090b',
|
|
105
|
+
success: '#3dd68c',
|
|
106
|
+
warning: '#ffb224',
|
|
107
|
+
danger: '#ff6369',
|
|
108
|
+
dangerForeground: '#ffffff',
|
|
109
|
+
ring: '#d4d4d8',
|
|
110
|
+
overlay: 'rgba(0,0,0,0.6)',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const palettes = { light, dark };
|
|
114
|
+
|
|
115
|
+
function build(scheme: ColorScheme, overrides?: ThemeOverrides): Theme {
|
|
116
|
+
const base = scheme === 'dark' ? dark : light;
|
|
117
|
+
const accent = overrides?.accent;
|
|
118
|
+
const colors: Colors = {
|
|
119
|
+
...base,
|
|
120
|
+
...(accent ? { accent, ring: accent, accentForeground: readableOn(accent) } : null),
|
|
121
|
+
...overrides?.colors?.[scheme],
|
|
122
|
+
};
|
|
123
|
+
const r = overrides?.radius ?? 12;
|
|
124
|
+
return {
|
|
125
|
+
dark: scheme === 'dark',
|
|
126
|
+
colors,
|
|
127
|
+
radius: {
|
|
128
|
+
sm: Math.round(r * 0.5),
|
|
129
|
+
md: r,
|
|
130
|
+
lg: Math.round(r * 1.35),
|
|
131
|
+
xl: Math.round(r * 1.8),
|
|
132
|
+
full: 999,
|
|
133
|
+
scale: (n) => Math.round((n * r) / 12),
|
|
134
|
+
capsule: (h) => Math.round((h / 2) * Math.min(1, r / 12)),
|
|
135
|
+
},
|
|
136
|
+
space: (n) => n * 4,
|
|
137
|
+
font: {
|
|
138
|
+
family: overrides?.fontFamily,
|
|
139
|
+
mono: Platform.select({ ios: 'Menlo', android: 'monospace', default: 'ui-monospace, SFMono-Regular, Menlo, monospace' })!,
|
|
140
|
+
},
|
|
141
|
+
text: {
|
|
142
|
+
xs: { fontSize: 12, lineHeight: 16 },
|
|
143
|
+
sm: { fontSize: 14, lineHeight: 20 },
|
|
144
|
+
md: { fontSize: 16, lineHeight: 22 },
|
|
145
|
+
lg: { fontSize: 18, lineHeight: 24, fontWeight: '400' },
|
|
146
|
+
xl: { fontSize: 22, lineHeight: 28, fontWeight: '400', letterSpacing: -0.3 },
|
|
147
|
+
xxl: { fontSize: 30, lineHeight: 36, fontWeight: '400', letterSpacing: -0.8 },
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface ThemeOverrides {
|
|
153
|
+
/** Brand colour used for focus rings, selected states and accent buttons. */
|
|
154
|
+
accent?: string;
|
|
155
|
+
/** Base corner radius in points (default 12). */
|
|
156
|
+
radius?: number;
|
|
157
|
+
fontFamily?: string;
|
|
158
|
+
/** Override any token per scheme. */
|
|
159
|
+
colors?: Partial<Record<ColorScheme, Partial<Colors>>>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const KitContext = createContext<{ scheme?: ColorScheme; overrides?: ThemeOverrides } | null>(null);
|
|
163
|
+
|
|
164
|
+
export function KitProvider({
|
|
165
|
+
scheme,
|
|
166
|
+
children,
|
|
167
|
+
...overrides
|
|
168
|
+
}: ThemeOverrides & { scheme?: ColorScheme | 'system'; children: React.ReactNode }) {
|
|
169
|
+
const value = useMemo(
|
|
170
|
+
() => ({ scheme: scheme === 'system' ? undefined : scheme, overrides }),
|
|
171
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
172
|
+
[scheme, overrides.accent, overrides.radius, overrides.fontFamily, overrides.colors]
|
|
173
|
+
);
|
|
174
|
+
return <KitContext.Provider value={value}>{children}</KitContext.Provider>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function useKit(): Theme {
|
|
178
|
+
const ctx = useContext(KitContext);
|
|
179
|
+
const system = useColorScheme();
|
|
180
|
+
const scheme: ColorScheme = ctx?.scheme ?? (system === 'dark' ? 'dark' : 'light');
|
|
181
|
+
return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Shared motion presets so the whole kit moves the same way. */
|
|
185
|
+
export const motion = {
|
|
186
|
+
/** Toggles, selection indicators. */
|
|
187
|
+
snappy: { damping: 18, stiffness: 260, mass: 0.8 },
|
|
188
|
+
/** Press feedback, pops. */
|
|
189
|
+
bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
|
|
190
|
+
/** Sheets, layout changes. */
|
|
191
|
+
gentle: { damping: 22, stiffness: 160, mass: 1 },
|
|
192
|
+
fast: 160,
|
|
193
|
+
normal: 240,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** Adds alpha to a #rrggbb colour: alpha('#6366f1', 0.2). */
|
|
197
|
+
export function alpha(hex: string, a: number) {
|
|
198
|
+
if (!hex.startsWith('#') || (hex.length !== 7 && hex.length !== 4)) return hex;
|
|
199
|
+
const full = hex.length === 4 ? '#' + [...hex.slice(1)].map((c) => c + c).join('') : hex;
|
|
200
|
+
return full + Math.round(Math.max(0, Math.min(1, a)) * 255).toString(16).padStart(2, '0');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Black or white, whichever reads better on `hex`. */
|
|
204
|
+
export function readableOn(hex: string) {
|
|
205
|
+
const full = hex.length === 4 ? '#' + [...hex.slice(1)].map((c) => c + c).join('') : hex;
|
|
206
|
+
const n = parseInt(full.slice(1, 7), 16);
|
|
207
|
+
if (Number.isNaN(n)) return '#ffffff';
|
|
208
|
+
const [r, g, b] = [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
209
|
+
return (r * 299 + g * 587 + b * 114) / 1000 > 150 ? '#09090b' : '#ffffff';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* True for children that must be wrapped in <Text>: strings, numbers and arrays of them —
|
|
214
|
+
* `{count} new` is the array [3, ' new'], and raw text outside <Text> crashes on native.
|
|
215
|
+
*/
|
|
216
|
+
export function isTextNode(node: React.ReactNode): node is string | number | (string | number)[] {
|
|
217
|
+
if (typeof node === 'string' || typeof node === 'number') return true;
|
|
218
|
+
return Array.isArray(node) && node.length > 0 && node.every((n) => typeof n === 'string' || typeof n === 'number');
|
|
219
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pagination-dots",
|
|
3
|
+
"type": "component",
|
|
4
|
+
"title": "Pagination Dots",
|
|
5
|
+
"description": "Page indicator for carousels and onboarding that follows your finger: hand it a live scroll position and the active dot stretches into a pill that pours into the next one, dots scale, segments fill, or the page number rolls. Tap a dot to jump; VoiceOver can swipe through pages.",
|
|
6
|
+
"tier": "free",
|
|
7
|
+
"dependencies": [],
|
|
8
|
+
"registryDependencies": [
|
|
9
|
+
"kit"
|
|
10
|
+
],
|
|
11
|
+
"sharedFiles": [
|
|
12
|
+
"lib/kit.tsx"
|
|
13
|
+
],
|
|
14
|
+
"filesDir": "pagination-dots/files"
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "parallax-layers",
|
|
3
|
+
"type": "component",
|
|
4
|
+
"title": "Parallax Layers",
|
|
5
|
+
"description": "Build a scene from layers at different depths — drag across it and near layers slide further than far ones while the whole scene tilts, for a convincing 3D look. Drifts on its own at rest. Native-driven, no extra libraries.",
|
|
6
|
+
"tier": "pro",
|
|
7
|
+
"dependencies": [],
|
|
8
|
+
"registryDependencies": [
|
|
9
|
+
"kit"
|
|
10
|
+
],
|
|
11
|
+
"sharedFiles": [
|
|
12
|
+
"lib/kit.tsx"
|
|
13
|
+
],
|
|
14
|
+
"filesDir": null
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "parallax-scroll",
|
|
3
|
+
"type": "component",
|
|
4
|
+
"title": "Parallax Scroll",
|
|
5
|
+
"description": "A scroll view with real depth: the header photo glides slower than the page, darkens as it leaves and stretches when you pull down, while a title bar fades in over it. Photos pan inside their frames and headlines drift at their own speed — all on the native driver, zero JS per scroll frame.",
|
|
6
|
+
"tier": "pro",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"react-native-svg"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"kit"
|
|
12
|
+
],
|
|
13
|
+
"sharedFiles": [
|
|
14
|
+
"lib/kit.tsx"
|
|
15
|
+
],
|
|
16
|
+
"filesDir": null
|
|
17
|
+
}
|