@expokit/cli 0.3.0 → 0.4.1
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 +8 -4
- package/bin/expo-kit.js +137 -24
- package/bin/lib.mjs +110 -1
- package/bin/mcp.mjs +40 -18
- package/package.json +2 -2
- package/registry/_docs/accordion.json +1 -1
- package/registry/_docs/assistant-app.json +47 -0
- package/registry/_docs/avatar.json +6 -6
- package/registry/_docs/badge.json +4 -4
- package/registry/_docs/bottom-nav.json +6 -6
- package/registry/_docs/bottom-sheet.json +3 -3
- package/registry/_docs/bullet.json +6 -0
- package/registry/_docs/button-3d.json +3 -3
- package/registry/_docs/button.json +2 -2
- package/registry/_docs/calendar.json +5 -5
- package/registry/_docs/card.json +1 -1
- package/registry/_docs/checkout.json +7 -1
- package/registry/_docs/chip-group.json +1 -1
- package/registry/_docs/confetti.json +3 -3
- package/registry/_docs/date-picker.json +4 -4
- package/registry/_docs/donut-shop.json +88 -0
- package/registry/_docs/drawer.json +4 -4
- package/registry/_docs/fintech-app.json +49 -0
- package/registry/_docs/flip-card.json +4 -4
- package/registry/_docs/floating-input.json +5 -5
- package/registry/_docs/food-app.json +47 -0
- package/registry/_docs/glass.json +1 -1
- package/registry/_docs/grain.json +80 -0
- package/registry/_docs/isometric-bar.json +6 -0
- package/registry/_docs/liquidglass-tab.json +1 -1
- package/registry/_docs/morphing-button-group.json +96 -0
- package/registry/_docs/orb-reveal.json +132 -0
- package/registry/_docs/otp-input.json +4 -4
- package/registry/_docs/password-input.json +3 -3
- package/registry/_docs/payment-card.json +3 -3
- package/registry/_docs/pixel-bar.json +6 -0
- package/registry/_docs/polar-area.json +6 -0
- package/registry/_docs/progress-steps.json +4 -4
- package/registry/_docs/progress.json +2 -2
- package/registry/_docs/qr-code-button.json +122 -0
- package/registry/_docs/radio-group.json +1 -1
- package/registry/_docs/search-bar.json +4 -4
- package/registry/_docs/segmented-control.json +1 -1
- package/registry/_docs/select.json +7 -7
- package/registry/_docs/settings.json +6 -0
- package/registry/_docs/skeleton.json +3 -3
- package/registry/_docs/slider.json +6 -6
- package/registry/_docs/spinner.json +3 -3
- package/registry/_docs/stateful-button.json +1 -1
- package/registry/_docs/stepper.json +7 -7
- package/registry/_docs/tabs.json +2 -2
- package/registry/_docs/text-animation.json +4 -4
- package/registry/_docs/textarea.json +3 -3
- package/registry/_docs/ticket-shape.json +1 -1
- package/registry/_docs/tilt-card.json +4 -4
- package/registry/_docs/time-picker.json +6 -6
- package/registry/_docs/timeline-chart.json +6 -0
- package/registry/_docs/timeline.json +6 -6
- package/registry/_docs/toast.json +8 -8
- package/registry/_docs/treemap.json +6 -0
- package/registry/_docs/waterfall.json +6 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +36 -7
- package/registry/accordion/files/src/lib/kit.tsx +8 -5
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +12 -8
- package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/alert/files/src/components/ui/alert.tsx +11 -5
- package/registry/alert/files/src/components/ui/button.tsx +35 -6
- package/registry/alert/files/src/lib/kit.tsx +8 -5
- package/registry/assistant-app.json +49 -0
- package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
- package/registry/avatar/files/src/lib/kit.tsx +8 -5
- package/registry/badge/files/src/components/ui/badge.tsx +23 -9
- package/registry/badge/files/src/lib/kit.tsx +8 -5
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +21 -8
- package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
- package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
- package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/button/files/src/components/ui/button.tsx +35 -6
- package/registry/button/files/src/lib/kit.tsx +8 -5
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +35 -7
- package/registry/button-3d/files/src/lib/kit.tsx +8 -5
- package/registry/calendar/files/src/components/ui/calendar.tsx +68 -19
- package/registry/calendar/files/src/lib/kit.tsx +8 -5
- package/registry/card/files/src/components/ui/card.tsx +32 -10
- package/registry/card/files/src/lib/kit.tsx +8 -5
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +31 -8
- package/registry/checkbox/files/src/lib/kit.tsx +8 -5
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +39 -7
- package/registry/chip-group/files/src/lib/kit.tsx +8 -5
- package/registry/confetti/files/src/lib/kit.tsx +8 -5
- package/registry/coupon/files/src/components/ui/coupon.tsx +55 -28
- package/registry/coupon/files/src/components/ui/flip-card.tsx +23 -4
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +59 -18
- package/registry/coupon/files/src/lib/kit.tsx +8 -5
- package/registry/date-picker/files/src/components/ui/calendar.tsx +68 -19
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +98 -44
- package/registry/date-picker/files/src/lib/kit.tsx +8 -5
- package/registry/dialog/files/src/components/ui/dialog.tsx +19 -9
- package/registry/dialog/files/src/lib/kit.tsx +8 -5
- package/registry/donut-shop.json +18 -0
- package/registry/drawer/files/src/components/ui/drawer.tsx +31 -21
- package/registry/drawer/files/src/lib/kit.tsx +8 -5
- package/registry/fintech-app.json +57 -0
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +23 -4
- package/registry/flip-card/files/src/lib/kit.tsx +8 -5
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +91 -32
- package/registry/floating-input/files/src/lib/kit.tsx +8 -5
- package/registry/food-app.json +55 -0
- package/registry/grain/files/src/components/ui/grain.tsx +113 -0
- package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
- package/registry/grain.json +17 -0
- package/registry/kit/files/src/lib/kit.tsx +8 -5
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +2 -2
- package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
- package/registry/list-item/files/src/components/ui/list-item.tsx +28 -17
- package/registry/list-item/files/src/lib/kit.tsx +8 -5
- package/registry/morphing-button-group.json +18 -0
- package/registry/orb-reveal.json +20 -0
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +57 -24
- package/registry/otp-input/files/src/lib/kit.tsx +8 -5
- package/registry/password-input/files/src/components/ui/floating-input.tsx +91 -32
- package/registry/password-input/files/src/components/ui/password-input.tsx +55 -21
- package/registry/password-input/files/src/lib/kit.tsx +8 -5
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +23 -4
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +35 -18
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +59 -18
- package/registry/payment-card/files/src/lib/kit.tsx +8 -5
- package/registry/progress/files/src/components/ui/progress.tsx +16 -6
- package/registry/progress/files/src/lib/kit.tsx +8 -5
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +22 -11
- package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
- package/registry/qr-code-button.json +18 -0
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +48 -17
- package/registry/radio-group/files/src/lib/kit.tsx +8 -5
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +81 -26
- package/registry/search-bar/files/src/lib/kit.tsx +8 -5
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +40 -10
- package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
- package/registry/select/files/src/components/ui/select.tsx +161 -86
- package/registry/select/files/src/lib/kit.tsx +8 -5
- package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
- package/registry/skeleton/files/src/lib/kit.tsx +8 -5
- package/registry/slider/files/src/components/ui/slider.tsx +56 -27
- package/registry/slider/files/src/lib/kit.tsx +8 -5
- package/registry/spinner/files/src/components/ui/spinner.tsx +17 -12
- package/registry/spinner/files/src/lib/kit.tsx +8 -5
- package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +12 -4
- package/registry/stateful-button/files/src/lib/kit.tsx +8 -5
- package/registry/stepper/files/src/components/ui/stepper.tsx +83 -29
- package/registry/stepper/files/src/lib/kit.tsx +8 -5
- package/registry/switch/files/src/components/ui/switch.tsx +60 -22
- package/registry/switch/files/src/lib/kit.tsx +8 -5
- package/registry/tabs/files/src/components/ui/tabs.tsx +15 -10
- package/registry/tabs/files/src/lib/kit.tsx +8 -5
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +11 -6
- package/registry/text-animation/files/src/lib/kit.tsx +8 -5
- package/registry/textarea/files/src/components/ui/textarea.tsx +115 -45
- package/registry/textarea/files/src/lib/kit.tsx +8 -5
- package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +59 -18
- package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +156 -75
- package/registry/time-picker/files/src/lib/kit.tsx +8 -5
- package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
- package/registry/timeline/files/src/components/ui/timeline.tsx +19 -12
- package/registry/timeline/files/src/lib/kit.tsx +8 -5
- package/registry/toast/files/src/components/ui/button.tsx +35 -6
- package/registry/toast/files/src/components/ui/toast.tsx +25 -14
- package/registry/toast/files/src/lib/kit.tsx +8 -5
- package/schema/components.schema.json +1 -1
- package/registry/_docs/color-swatch.json +0 -82
- package/registry/_docs/kpi-card.json +0 -14
- package/registry/_docs/like-button.json +0 -92
- package/registry/_docs/morph-button.json +0 -128
- package/registry/_docs/pagination-dots.json +0 -108
- package/registry/_docs/shimmer-button.json +0 -98
- package/registry/_docs/tag-input.json +0 -148
- package/registry/color-swatch.json +0 -17
- package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
- package/registry/kpi-card.json +0 -14
- package/registry/like-button.json +0 -18
- package/registry/morph-button.json +0 -18
- package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
- package/registry/pagination-dots/files/src/lib/kit.tsx +0 -219
- package/registry/pagination-dots.json +0 -15
- package/registry/shimmer-button.json +0 -17
- package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
- package/registry/tag-input.json +0 -17
|
@@ -53,6 +53,9 @@ const RING = {
|
|
|
53
53
|
lg: { d: 112, w: 9, font: 24 },
|
|
54
54
|
};
|
|
55
55
|
const WHITE = '#ffffff';
|
|
56
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
57
|
+
// content text scales all the way.
|
|
58
|
+
const FONT_CAP = 1.35;
|
|
56
59
|
|
|
57
60
|
const clamp = (n: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, n));
|
|
58
61
|
|
|
@@ -104,7 +107,8 @@ export function Progress({
|
|
|
104
107
|
striped = false,
|
|
105
108
|
style,
|
|
106
109
|
}: ProgressProps) {
|
|
107
|
-
const { colors, dark } = useKit();
|
|
110
|
+
const { colors, dark, font } = useKit();
|
|
111
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
108
112
|
const reduce = useReduceMotion();
|
|
109
113
|
const top = max ?? (variant === 'steps' ? steps : 100);
|
|
110
114
|
const unknown = indeterminate || value === undefined;
|
|
@@ -137,7 +141,7 @@ export function Progress({
|
|
|
137
141
|
done={done}
|
|
138
142
|
reduce={reduce}
|
|
139
143
|
/>
|
|
140
|
-
{label ? <Text style={[styles.ringLabel, { color: colors.mutedForeground }]}>{label}</Text> : null}
|
|
144
|
+
{label ? <Text style={[styles.ringLabel, ff, { color: colors.mutedForeground }]}>{label}</Text> : null}
|
|
141
145
|
</View>
|
|
142
146
|
);
|
|
143
147
|
}
|
|
@@ -147,13 +151,13 @@ export function Progress({
|
|
|
147
151
|
const header =
|
|
148
152
|
label || valueShown ? (
|
|
149
153
|
<View style={styles.header}>
|
|
150
|
-
<Text numberOfLines={1} style={[styles.label, { color: colors.foreground }]}>
|
|
154
|
+
<Text numberOfLines={1} style={[styles.label, ff, { color: colors.foreground }]}>
|
|
151
155
|
{label}
|
|
152
156
|
</Text>
|
|
153
157
|
{checkShown ? (
|
|
154
158
|
<DrawnCheck color={tint} size={18} />
|
|
155
159
|
) : valueShown ? (
|
|
156
|
-
<Text style={[styles.value, { color: colors.mutedForeground }]}>{text}</Text>
|
|
160
|
+
<Text style={[styles.value, ff, { color: colors.mutedForeground }]}>{text}</Text>
|
|
157
161
|
) : null}
|
|
158
162
|
</View>
|
|
159
163
|
) : null;
|
|
@@ -685,6 +689,8 @@ function Ring({
|
|
|
685
689
|
reduce: boolean;
|
|
686
690
|
}) {
|
|
687
691
|
const { d, w, font } = RING[size];
|
|
692
|
+
const kit = useKit();
|
|
693
|
+
const ff = kit.font.family ? { fontFamily: kit.font.family } : null;
|
|
688
694
|
const id = useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
689
695
|
const r = (d - w) / 2;
|
|
690
696
|
const c = 2 * Math.PI * r;
|
|
@@ -835,9 +841,13 @@ function Ring({
|
|
|
835
841
|
],
|
|
836
842
|
}}
|
|
837
843
|
>
|
|
838
|
-
<Text style={[styles.ringValue, { color: fg, fontSize: font }]}>
|
|
844
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.ringValue, ff, { color: fg, fontSize: font }]}>
|
|
839
845
|
{format ? format() : <RingPercent fill={fill} initial={ratio} />}
|
|
840
|
-
{format ? null :
|
|
846
|
+
{format ? null : (
|
|
847
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: font * 0.6, color: alpha(fg, 0.55) }}>
|
|
848
|
+
%
|
|
849
|
+
</Text>
|
|
850
|
+
)}
|
|
841
851
|
</Text>
|
|
842
852
|
</Animated.View>
|
|
843
853
|
{done ? (
|
|
@@ -181,14 +181,17 @@ export function useKit(): Theme {
|
|
|
181
181
|
return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Shared motion presets so the whole kit moves the same way. Every spring is critically damped
|
|
186
|
+
* (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
|
|
187
|
+
*/
|
|
185
188
|
export const motion = {
|
|
186
189
|
/** Toggles, selection indicators. */
|
|
187
|
-
snappy: { damping:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
190
|
+
snappy: { damping: 30, stiffness: 260, mass: 0.8 },
|
|
191
|
+
/** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
|
|
192
|
+
bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
|
|
190
193
|
/** Sheets, layout changes. */
|
|
191
|
-
gentle: { damping:
|
|
194
|
+
gentle: { damping: 27, stiffness: 160, mass: 1 },
|
|
192
195
|
fast: 160,
|
|
193
196
|
normal: 240,
|
|
194
197
|
};
|
|
@@ -46,6 +46,9 @@ export interface ProgressStepsProps {
|
|
|
46
46
|
style?: StyleProp<ViewStyle>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
50
|
+
// content text scales all the way.
|
|
51
|
+
const FONT_CAP = 1.35;
|
|
49
52
|
const NODE = 28;
|
|
50
53
|
const GAP = 6;
|
|
51
54
|
const LINE = 3;
|
|
@@ -70,7 +73,8 @@ export function ProgressSteps({
|
|
|
70
73
|
accent,
|
|
71
74
|
style,
|
|
72
75
|
}: ProgressStepsProps) {
|
|
73
|
-
const { colors, dark } = useKit();
|
|
76
|
+
const { colors, dark, font } = useKit();
|
|
77
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
74
78
|
const reduce = useReduceMotion();
|
|
75
79
|
const tint = accent ?? colors.accent;
|
|
76
80
|
const items = steps.map((s) => (typeof s === 'string' ? { label: s } : s));
|
|
@@ -118,7 +122,7 @@ export function ProgressSteps({
|
|
|
118
122
|
Animated.timing(pulse, { toValue: 1, duration: 1500, easing: Easing.out(Easing.quad), useNativeDriver: true }),
|
|
119
123
|
Animated.delay(250),
|
|
120
124
|
]),
|
|
121
|
-
{ resetBeforeIteration: true }
|
|
125
|
+
{ iterations: -1, resetBeforeIteration: true }
|
|
122
126
|
);
|
|
123
127
|
loop.start();
|
|
124
128
|
return () => loop.stop();
|
|
@@ -146,6 +150,7 @@ export function ProgressSteps({
|
|
|
146
150
|
numberOfLines={1}
|
|
147
151
|
style={[
|
|
148
152
|
styles.barLabel,
|
|
153
|
+
ff,
|
|
149
154
|
{ color: failed ? colors.danger : colors.foreground },
|
|
150
155
|
{
|
|
151
156
|
opacity: labelIn,
|
|
@@ -155,7 +160,7 @@ export function ProgressSteps({
|
|
|
155
160
|
>
|
|
156
161
|
{done ? 'All done' : items[shown]!.label}
|
|
157
162
|
</Animated.Text>
|
|
158
|
-
<Text style={[styles.barStep, { color: colors.mutedForeground }]}>
|
|
163
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.barStep, ff, { color: colors.mutedForeground }]}>
|
|
159
164
|
{done ? `${n} of ${n}` : `Step ${shown + 1} of ${n}`}
|
|
160
165
|
</Text>
|
|
161
166
|
</View>
|
|
@@ -255,7 +260,8 @@ function Step({
|
|
|
255
260
|
reduce: boolean;
|
|
256
261
|
onPress?: () => void;
|
|
257
262
|
}) {
|
|
258
|
-
const { colors } = useKit();
|
|
263
|
+
const { colors, font } = useKit();
|
|
264
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
259
265
|
const press = useRef(new Animated.Value(0)).current;
|
|
260
266
|
const pressTo = (v: number) =>
|
|
261
267
|
Animated.spring(press, { toValue: v, useNativeDriver: true, ...(v ? motion.snappy : motion.bouncy) }).start();
|
|
@@ -301,9 +307,9 @@ function Step({
|
|
|
301
307
|
{!last ? line(styles.vLine) : null}
|
|
302
308
|
</View>
|
|
303
309
|
<View style={[styles.vBody, !last && styles.vBodyGap]}>
|
|
304
|
-
<Text style={[styles.vLabel, { color: labelColor }]}>{step.label}</Text>
|
|
310
|
+
<Text style={[styles.vLabel, ff, { color: labelColor }]}>{step.label}</Text>
|
|
305
311
|
{step.description ? (
|
|
306
|
-
<Text style={[styles.vDesc, { color: state === 'error' ? colors.danger : colors.mutedForeground }]}>
|
|
312
|
+
<Text style={[styles.vDesc, ff, { color: state === 'error' ? colors.danger : colors.mutedForeground }]}>
|
|
307
313
|
{step.description}
|
|
308
314
|
</Text>
|
|
309
315
|
) : null}
|
|
@@ -315,11 +321,11 @@ function Step({
|
|
|
315
321
|
return (
|
|
316
322
|
<Wrapper {...a11y} {...pressProps} style={styles.hCol}>
|
|
317
323
|
<View style={styles.hTop}>{node}</View>
|
|
318
|
-
<Text numberOfLines={2} style={[styles.hLabel, { color: labelColor }]}>
|
|
324
|
+
<Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={2} style={[styles.hLabel, ff, { color: labelColor }]}>
|
|
319
325
|
{step.label}
|
|
320
326
|
</Text>
|
|
321
327
|
{step.description ? (
|
|
322
|
-
<Text numberOfLines={2} style={[styles.hDesc, { color: colors.mutedForeground }]}>
|
|
328
|
+
<Text numberOfLines={2} style={[styles.hDesc, ff, { color: colors.mutedForeground }]}>
|
|
323
329
|
{step.description}
|
|
324
330
|
</Text>
|
|
325
331
|
) : null}
|
|
@@ -344,7 +350,8 @@ function Node({
|
|
|
344
350
|
color: string;
|
|
345
351
|
reduce: boolean;
|
|
346
352
|
}) {
|
|
347
|
-
const { colors, radius, dark } = useKit();
|
|
353
|
+
const { colors, radius, dark, font } = useKit();
|
|
354
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
348
355
|
const clamp = 'clamp' as const;
|
|
349
356
|
const round = { borderRadius: radius.capsule(NODE) };
|
|
350
357
|
const activeIn = pNative.interpolate({ inputRange: [i - 0.3, i], outputRange: [0, 1], extrapolate: clamp });
|
|
@@ -370,7 +377,9 @@ function Node({
|
|
|
370
377
|
<View
|
|
371
378
|
style={[styles.node, round, { borderWidth: 1.5, borderColor: alpha(colors.foreground, dark ? 0.22 : 0.16) }]}
|
|
372
379
|
>
|
|
373
|
-
<Text style={[styles.num, { color: colors.mutedForeground }]}>
|
|
380
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.num, ff, { color: colors.mutedForeground }]}>
|
|
381
|
+
{i + 1}
|
|
382
|
+
</Text>
|
|
374
383
|
</View>
|
|
375
384
|
|
|
376
385
|
{/* Current: soft halo, a ring that breathes outwards, accent ring with a tinted core. */}
|
|
@@ -411,7 +420,9 @@ function Node({
|
|
|
411
420
|
]}
|
|
412
421
|
>
|
|
413
422
|
<View style={[StyleSheet.absoluteFill, round, { backgroundColor: alpha(color, 0.12) }]} />
|
|
414
|
-
<Text style={[styles.num, { color }]}>
|
|
423
|
+
<Text maxFontSizeMultiplier={FONT_CAP} style={[styles.num, ff, { color }]}>
|
|
424
|
+
{i + 1}
|
|
425
|
+
</Text>
|
|
415
426
|
</Animated.View>
|
|
416
427
|
|
|
417
428
|
{/* Done: solid disc that pops in, then the check draws itself. */}
|
|
@@ -181,14 +181,17 @@ export function useKit(): Theme {
|
|
|
181
181
|
return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Shared motion presets so the whole kit moves the same way. Every spring is critically damped
|
|
186
|
+
* (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
|
|
187
|
+
*/
|
|
185
188
|
export const motion = {
|
|
186
189
|
/** Toggles, selection indicators. */
|
|
187
|
-
snappy: { damping:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
190
|
+
snappy: { damping: 30, stiffness: 260, mass: 0.8 },
|
|
191
|
+
/** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
|
|
192
|
+
bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
|
|
190
193
|
/** Sheets, layout changes. */
|
|
191
|
-
gentle: { damping:
|
|
194
|
+
gentle: { damping: 27, stiffness: 160, mass: 1 },
|
|
192
195
|
fast: 160,
|
|
193
196
|
normal: 240,
|
|
194
197
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qr-code-button",
|
|
3
|
+
"type": "component",
|
|
4
|
+
"title": "QR Code Button",
|
|
5
|
+
"description": "A \"Show QR Code\" pill that swells under your finger and morphs, on a smooth ease, into a white card holding a real, scannable QR code — Copy and close below it. The code is encoded inside the component (byte mode, error correction M, every version up to 40) and drawn as one crisp SVG path, so there's no QR library to install.",
|
|
6
|
+
"tier": "pro",
|
|
7
|
+
"dependencies": [
|
|
8
|
+
"react-native-svg",
|
|
9
|
+
"expo-haptics"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"kit"
|
|
13
|
+
],
|
|
14
|
+
"sharedFiles": [
|
|
15
|
+
"lib/kit.tsx"
|
|
16
|
+
],
|
|
17
|
+
"filesDir": null
|
|
18
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Haptics from 'expo-haptics';
|
|
2
2
|
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { Animated, Pressable, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { AccessibilityInfo, Animated, Pressable, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
4
4
|
import Svg, { Path } from 'react-native-svg';
|
|
5
5
|
|
|
6
6
|
import { alpha, motion, readableOn, useKit } from '@/lib/kit';
|
|
@@ -58,6 +58,7 @@ export function RadioGroup({
|
|
|
58
58
|
style,
|
|
59
59
|
}: RadioGroupProps) {
|
|
60
60
|
const { colors, radius } = useKit();
|
|
61
|
+
const reduce = useReduceMotion();
|
|
61
62
|
const tint = accent ?? colors.accent;
|
|
62
63
|
const [inner, setInner] = useState(defaultValue);
|
|
63
64
|
const selected = value ?? inner;
|
|
@@ -95,6 +96,7 @@ export function RadioGroup({
|
|
|
95
96
|
first={i === 0}
|
|
96
97
|
tint={tint}
|
|
97
98
|
disabled={disabled || o.disabled}
|
|
99
|
+
reduce={reduce}
|
|
98
100
|
/>
|
|
99
101
|
))}
|
|
100
102
|
</View>
|
|
@@ -110,27 +112,36 @@ interface RadioItemProps {
|
|
|
110
112
|
first: boolean;
|
|
111
113
|
tint: string;
|
|
112
114
|
disabled?: boolean;
|
|
115
|
+
reduce: boolean;
|
|
113
116
|
}
|
|
114
117
|
|
|
115
|
-
function RadioItem({ option, active, onPress, variant, horizontal, first, tint, disabled }: RadioItemProps) {
|
|
116
|
-
const { colors, radius } = useKit();
|
|
118
|
+
function RadioItem({ option, active, onPress, variant, horizontal, first, tint, disabled, reduce }: RadioItemProps) {
|
|
119
|
+
const { colors, radius, font } = useKit();
|
|
120
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
117
121
|
// p: colours + svg (JS driver). dot: scale/opacity pops (native). press: press feedback (native).
|
|
118
122
|
// ripple: a ring that expands and fades when the item gets selected, 0 → 1 (native, rests at 1).
|
|
119
123
|
const p = useRef(new Animated.Value(active ? 1 : 0)).current;
|
|
120
124
|
const dot = useRef(new Animated.Value(active ? 1 : 0)).current;
|
|
121
125
|
const press = useRef(new Animated.Value(1)).current;
|
|
122
126
|
const ripple = useRef(new Animated.Value(1)).current;
|
|
123
|
-
const
|
|
127
|
+
const was = useRef(active);
|
|
124
128
|
|
|
125
129
|
useEffect(() => {
|
|
130
|
+
const picked = active && !was.current;
|
|
131
|
+
was.current = active;
|
|
132
|
+
// Reduce Motion: colours and the check still fade in, the dot appears at once, no ripple.
|
|
133
|
+
if (reduce) {
|
|
134
|
+
Animated.timing(p, { toValue: active ? 1 : 0, duration: 150, useNativeDriver: false }).start();
|
|
135
|
+
dot.setValue(active ? 1 : 0);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
126
138
|
Animated.spring(p, { toValue: active ? 1 : 0, useNativeDriver: false, ...motion.snappy }).start();
|
|
127
139
|
Animated.spring(dot, { toValue: active ? 1 : 0, useNativeDriver: true, ...motion.bouncy }).start();
|
|
128
|
-
if (
|
|
140
|
+
if (picked) {
|
|
129
141
|
ripple.setValue(0);
|
|
130
142
|
Animated.timing(ripple, { toValue: 1, duration: 560, useNativeDriver: true }).start();
|
|
131
143
|
}
|
|
132
|
-
|
|
133
|
-
}, [active, p, dot, ripple]);
|
|
144
|
+
}, [active, reduce, p, dot, ripple]);
|
|
134
145
|
|
|
135
146
|
const rippleStyle = {
|
|
136
147
|
backgroundColor: alpha(tint, 0.35),
|
|
@@ -138,8 +149,10 @@ function RadioItem({ option, active, onPress, variant, horizontal, first, tint,
|
|
|
138
149
|
transform: [{ scale: ripple.interpolate({ inputRange: [0, 1], outputRange: [1, 2.3] }) }],
|
|
139
150
|
};
|
|
140
151
|
|
|
141
|
-
const pressTo = (to: number) =>
|
|
152
|
+
const pressTo = (to: number) => {
|
|
153
|
+
if (reduce) return press.setValue(1);
|
|
142
154
|
Animated.spring(press, { toValue: to, useNativeDriver: true, ...(to < 1 ? motion.snappy : motion.bouncy) }).start();
|
|
155
|
+
};
|
|
143
156
|
|
|
144
157
|
const fg = colors.foreground;
|
|
145
158
|
const muted = colors.mutedForeground;
|
|
@@ -154,17 +167,17 @@ function RadioItem({ option, active, onPress, variant, horizontal, first, tint,
|
|
|
154
167
|
|
|
155
168
|
const text = (
|
|
156
169
|
<View style={styles.text}>
|
|
157
|
-
<Text numberOfLines={2} style={[styles.label, { color: fg }]}>
|
|
170
|
+
<Text numberOfLines={2} style={[styles.label, ff, { color: fg }]}>
|
|
158
171
|
{option.label}
|
|
159
172
|
</Text>
|
|
160
|
-
{option.description ? <Text style={[styles.description, { color: muted }]}>{option.description}</Text> : null}
|
|
173
|
+
{option.description ? <Text style={[styles.description, ff, { color: muted }]}>{option.description}</Text> : null}
|
|
161
174
|
</View>
|
|
162
175
|
);
|
|
163
176
|
|
|
164
177
|
if (variant === 'card') {
|
|
165
178
|
const lit = (from: string, to: string) => p.interpolate({ inputRange: [0, 1], outputRange: [from, to], extrapolate: 'clamp' });
|
|
166
179
|
const hint = option.hint ? (
|
|
167
|
-
<Animated.Text style={[styles.hint, horizontal && styles.hintColumn, { color: lit(fg, tint) }]}>{option.hint}</Animated.Text>
|
|
180
|
+
<Animated.Text style={[styles.hint, ff, horizontal && styles.hintColumn, { color: lit(fg, tint) }]}>{option.hint}</Animated.Text>
|
|
168
181
|
) : null;
|
|
169
182
|
return (
|
|
170
183
|
<Animated.View style={[horizontal && styles.flex, { opacity: disabled ? 0.5 : 1, transform: [{ scale: press }] }]}>
|
|
@@ -191,10 +204,10 @@ function RadioItem({ option, active, onPress, variant, horizontal, first, tint,
|
|
|
191
204
|
</Animated.View>
|
|
192
205
|
) : null}
|
|
193
206
|
<View style={[styles.text, horizontal && styles.cardTextColumn]}>
|
|
194
|
-
<Text numberOfLines={2} style={[styles.label, { color: fg }]}>
|
|
207
|
+
<Text numberOfLines={2} style={[styles.label, ff, { color: fg }]}>
|
|
195
208
|
{option.label}
|
|
196
209
|
</Text>
|
|
197
|
-
{option.description ? <Text style={[styles.description, { color: muted }]}>{option.description}</Text> : null}
|
|
210
|
+
{option.description ? <Text style={[styles.description, ff, { color: muted }]}>{option.description}</Text> : null}
|
|
198
211
|
{horizontal ? hint : null}
|
|
199
212
|
</View>
|
|
200
213
|
{horizontal ? null : hint}
|
|
@@ -241,11 +254,11 @@ function RadioItem({ option, active, onPress, variant, horizontal, first, tint,
|
|
|
241
254
|
<View style={styles.text}>
|
|
242
255
|
<Animated.Text
|
|
243
256
|
numberOfLines={1}
|
|
244
|
-
style={[styles.label, { color: p.interpolate({ inputRange: [0, 1], outputRange: [fg, tint], extrapolate: 'clamp' }) }]}
|
|
257
|
+
style={[styles.label, ff, { color: p.interpolate({ inputRange: [0, 1], outputRange: [fg, tint], extrapolate: 'clamp' }) }]}
|
|
245
258
|
>
|
|
246
259
|
{option.label}
|
|
247
260
|
</Animated.Text>
|
|
248
|
-
{option.description ? <Text style={[styles.description, { color: muted }]}>{option.description}</Text> : null}
|
|
261
|
+
{option.description ? <Text style={[styles.description, ff, { color: muted }]}>{option.description}</Text> : null}
|
|
249
262
|
</View>
|
|
250
263
|
<Svg width={20} height={20} viewBox="0 0 24 24">
|
|
251
264
|
<AnimatedPath
|
|
@@ -286,7 +299,7 @@ function RadioItem({ option, active, onPress, variant, horizontal, first, tint,
|
|
|
286
299
|
</Animated.View>
|
|
287
300
|
</Animated.View>
|
|
288
301
|
{horizontal && !option.description ? (
|
|
289
|
-
<Text style={[styles.label, { color: fg }]}>{option.label}</Text>
|
|
302
|
+
<Text style={[styles.label, styles.shrink, ff, { color: fg }]}>{option.label}</Text>
|
|
290
303
|
) : (
|
|
291
304
|
text
|
|
292
305
|
)}
|
|
@@ -294,13 +307,31 @@ function RadioItem({ option, active, onPress, variant, horizontal, first, tint,
|
|
|
294
307
|
);
|
|
295
308
|
}
|
|
296
309
|
|
|
310
|
+
function useReduceMotion() {
|
|
311
|
+
const [reduce, setReduce] = useState(false);
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
let alive = true;
|
|
314
|
+
AccessibilityInfo.isReduceMotionEnabled()
|
|
315
|
+
.then((r) => alive && setReduce(r))
|
|
316
|
+
.catch(() => {});
|
|
317
|
+
const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduce);
|
|
318
|
+
return () => {
|
|
319
|
+
alive = false;
|
|
320
|
+
sub.remove();
|
|
321
|
+
};
|
|
322
|
+
}, []);
|
|
323
|
+
return reduce;
|
|
324
|
+
}
|
|
325
|
+
|
|
297
326
|
const styles = StyleSheet.create({
|
|
298
327
|
vertical: { flexDirection: 'column' },
|
|
299
328
|
horizontal: { flexDirection: 'row', flexWrap: 'wrap' },
|
|
300
329
|
flex: { flex: 1, minWidth: 0 },
|
|
301
330
|
flexGrow: { flexGrow: 1 },
|
|
302
331
|
row: { flexDirection: 'row', alignItems: 'flex-start', gap: 12 },
|
|
303
|
-
|
|
332
|
+
// A long label wraps at a large text size instead of running past the edge.
|
|
333
|
+
rowCompact: { alignItems: 'center', flexShrink: 1 },
|
|
334
|
+
shrink: { flexShrink: 1 },
|
|
304
335
|
ring: { width: 22, height: 22, borderRadius: 11, borderWidth: 1.5, alignItems: 'center', justifyContent: 'center' },
|
|
305
336
|
dot: { width: 8, height: 8, borderRadius: 4 },
|
|
306
337
|
ringSlot: { alignItems: 'center', justifyContent: 'center' },
|
|
@@ -181,14 +181,17 @@ export function useKit(): Theme {
|
|
|
181
181
|
return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Shared motion presets so the whole kit moves the same way. Every spring is critically damped
|
|
186
|
+
* (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
|
|
187
|
+
*/
|
|
185
188
|
export const motion = {
|
|
186
189
|
/** Toggles, selection indicators. */
|
|
187
|
-
snappy: { damping:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
190
|
+
snappy: { damping: 30, stiffness: 260, mass: 0.8 },
|
|
191
|
+
/** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
|
|
192
|
+
bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
|
|
190
193
|
/** Sheets, layout changes. */
|
|
191
|
-
gentle: { damping:
|
|
194
|
+
gentle: { damping: 27, stiffness: 160, mass: 1 },
|
|
192
195
|
fast: 160,
|
|
193
196
|
normal: 240,
|
|
194
197
|
};
|