@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { createContext, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import React, { createContext, useContext, useEffect, useId, useMemo, useRef, useState, type ComponentRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
AccessibilityInfo,
|
|
4
4
|
Animated,
|
|
@@ -131,7 +131,7 @@ interface Frame {
|
|
|
131
131
|
|
|
132
132
|
const GroupContext = createContext<{ frame: Frame | null } | null>(null);
|
|
133
133
|
|
|
134
|
-
function measure(node: View | null, e: LayoutChangeEvent, done: (f: Frame) => void) {
|
|
134
|
+
function measure(node: ComponentRef<typeof View> | null, e: LayoutChangeEvent, done: (f: Frame) => void) {
|
|
135
135
|
const { x, y, width, height } = e.nativeEvent.layout;
|
|
136
136
|
const fallback = () => done({ x, y, w: width, h: height });
|
|
137
137
|
if (!node || typeof node.measureInWindow !== 'function') return fallback();
|
|
@@ -153,7 +153,7 @@ export interface SkeletonGroupProps {
|
|
|
153
153
|
*/
|
|
154
154
|
export function SkeletonGroup({ children, style }: SkeletonGroupProps) {
|
|
155
155
|
const parent = useContext(GroupContext);
|
|
156
|
-
const ref = useRef<View
|
|
156
|
+
const ref = useRef<ComponentRef<typeof View>>(null);
|
|
157
157
|
const [frame, setFrame] = useState<Frame | null>(null);
|
|
158
158
|
const value = useMemo(() => ({ frame }), [frame]);
|
|
159
159
|
if (parent) return <View style={style}>{children}</View>;
|
|
@@ -182,7 +182,7 @@ export function Skeleton({ width = '100%', height = 14, radius = 'sm', variant =
|
|
|
182
182
|
const t = useClock(moving);
|
|
183
183
|
const group = useContext(GroupContext);
|
|
184
184
|
const win = useWindowDimensions();
|
|
185
|
-
const ref = useRef<View
|
|
185
|
+
const ref = useRef<ComponentRef<typeof View>>(null);
|
|
186
186
|
const [box, setBox] = useState<Frame | null>(null);
|
|
187
187
|
const gid = 'sk' + useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
188
188
|
|
|
@@ -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
|
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
AccessibilityInfo,
|
|
3
4
|
Animated,
|
|
5
|
+
Easing,
|
|
4
6
|
PanResponder,
|
|
5
7
|
Pressable,
|
|
6
8
|
StyleSheet,
|
|
7
9
|
Text,
|
|
8
10
|
View,
|
|
11
|
+
useWindowDimensions,
|
|
9
12
|
type AccessibilityActionEvent,
|
|
10
13
|
type GestureResponderEvent,
|
|
11
14
|
type StyleProp,
|
|
@@ -110,13 +113,22 @@ const SIZES: Record<SliderSize, { track: number; stepped: number; thumb: number;
|
|
|
110
113
|
md: { track: 4, stepped: 8, thumb: 26, bar: 46, icon: 20, font: 13, hit: 44 },
|
|
111
114
|
lg: { track: 6, stepped: 10, thumb: 30, bar: 58, icon: 24, font: 15, hit: 52 },
|
|
112
115
|
};
|
|
116
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
117
|
+
// content text scales all the way.
|
|
118
|
+
const FONT_CAP = 1.35;
|
|
113
119
|
const BUBBLE_H = 28;
|
|
114
120
|
const BUBBLE_GAP = 10;
|
|
115
121
|
const BUBBLE_W = 120;
|
|
116
122
|
/** Below this many points per step the thumb follows the finger and snaps on release. */
|
|
117
123
|
const SNAP_PX = 14;
|
|
124
|
+
const SMOOTH = Easing.bezier(0.32, 0.72, 0, 1);
|
|
118
125
|
|
|
119
126
|
const clamp = (n: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, n));
|
|
127
|
+
/** Native-driven move to `toValue`: a snappy spring, or a short ease when Reduce Motion is on. */
|
|
128
|
+
const glide = (v: Animated.Value, toValue: number, reduce: boolean) =>
|
|
129
|
+
reduce
|
|
130
|
+
? Animated.timing(v, { toValue, duration: 150, easing: SMOOTH, useNativeDriver: true })
|
|
131
|
+
: Animated.spring(v, { toValue, useNativeDriver: true, ...motion.snappy });
|
|
120
132
|
const decimals = (n: number) => (String(n).split('.')[1] ?? '').length;
|
|
121
133
|
|
|
122
134
|
/**
|
|
@@ -161,10 +173,14 @@ export function Slider(props: SliderProps) {
|
|
|
161
173
|
const onSlidingStart = props.onSlidingStart as Callback;
|
|
162
174
|
const onSlidingComplete = props.onSlidingComplete as Callback;
|
|
163
175
|
|
|
164
|
-
const { colors, radius, dark } = useKit();
|
|
176
|
+
const { colors, radius, dark, font } = useKit();
|
|
177
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
178
|
+
const reduce = useReduceMotion();
|
|
165
179
|
const tint = accent ?? colors.accent;
|
|
166
180
|
const onTint = accent ? readableOn(accent) : colors.accentForeground;
|
|
167
181
|
const sz = SIZES[size];
|
|
182
|
+
// Text scale the capped labels actually render at, for the label row's height.
|
|
183
|
+
const fontScale = Math.min(useWindowDimensions().fontScale, FONT_CAP);
|
|
168
184
|
|
|
169
185
|
const T = isBar ? 0 : sz.thumb; // thumb diameter
|
|
170
186
|
const inset = T / 2; // thumb centre travels [inset, w - inset]
|
|
@@ -210,29 +226,24 @@ export function Slider(props: SliderProps) {
|
|
|
210
226
|
const held = useRef(new Animated.Value(0)).current;
|
|
211
227
|
|
|
212
228
|
// Everything the (once-created) PanResponder reads, kept fresh every render.
|
|
213
|
-
const live = useRef({ vals, w, inset, step, span, min, range, isBar, T, disabled, controlled, snap, ratioOf, fmt, out, onValueChange, onSlidingStart, onSlidingComplete });
|
|
214
|
-
live.current = { vals, w, inset, step, span, min, range, isBar, T, disabled, controlled, snap, ratioOf, fmt, out, onValueChange, onSlidingStart, onSlidingComplete };
|
|
229
|
+
const live = useRef({ vals, w, inset, step, span, min, range, isBar, T, disabled, controlled, reduce, snap, ratioOf, fmt, out, onValueChange, onSlidingStart, onSlidingComplete });
|
|
230
|
+
live.current = { vals, w, inset, step, span, min, range, isBar, T, disabled, controlled, reduce, snap, ratioOf, fmt, out, onValueChange, onSlidingStart, onSlidingComplete };
|
|
215
231
|
const drag = useRef({ dragging: false, thumb: 0, startX: 0, origin: 0, relative: false, moved: false, cur: [] as number[], target: [-1, -1] });
|
|
216
232
|
|
|
217
233
|
// Follow value changes from outside (controlled updates, a11y, icon taps) when nobody is dragging.
|
|
218
234
|
const key = vals.join(',');
|
|
219
235
|
useEffect(() => {
|
|
220
236
|
if (drag.current.dragging) return;
|
|
221
|
-
vals.forEach((v, i) =>
|
|
237
|
+
vals.forEach((v, i) => glide(ratios[i], ratioOf(v), reduce).start());
|
|
222
238
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
223
239
|
}, [key, min, max]);
|
|
224
240
|
|
|
241
|
+
// With Reduce Motion these only fade (the scale / lift outputs below are flattened).
|
|
225
242
|
useEffect(() => {
|
|
226
|
-
press.forEach((p, i) =>
|
|
227
|
-
bubble.forEach((b, i) =>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
useNativeDriver: true,
|
|
231
|
-
...motion.snappy,
|
|
232
|
-
}).start()
|
|
233
|
-
);
|
|
234
|
-
Animated.spring(held, { toValue: active !== null ? 1 : 0, useNativeDriver: true, ...motion.snappy }).start();
|
|
235
|
-
}, [active, bubbleShown, showValue, press, bubble, held]);
|
|
243
|
+
press.forEach((p, i) => glide(p, active === i ? 1 : 0, reduce).start());
|
|
244
|
+
bubble.forEach((b, i) => glide(b, bubbleShown || (showValue === 'drag' && active === i) ? 1 : 0, reduce).start());
|
|
245
|
+
glide(held, active !== null ? 1 : 0, reduce).start();
|
|
246
|
+
}, [active, bubbleShown, showValue, press, bubble, held, reduce]);
|
|
236
247
|
|
|
237
248
|
const pan = useMemo(() => {
|
|
238
249
|
const usableOf = () => Math.max(1, live.current.w - 2 * live.current.inset);
|
|
@@ -248,7 +259,7 @@ export function Slider(props: SliderProps) {
|
|
|
248
259
|
const coarse = !!L.step && (L.step / L.span) * usableOf() >= SNAP_PX;
|
|
249
260
|
const target = coarse ? L.ratioOf(snapped) : L.ratioOf(v);
|
|
250
261
|
if (animate || coarse) {
|
|
251
|
-
if (target !== d.target[i])
|
|
262
|
+
if (target !== d.target[i]) glide(ratios[i], target, L.reduce).start();
|
|
252
263
|
} else {
|
|
253
264
|
ratios[i].setValue(target);
|
|
254
265
|
}
|
|
@@ -268,7 +279,7 @@ export function Slider(props: SliderProps) {
|
|
|
268
279
|
const d = drag.current;
|
|
269
280
|
if (!d.dragging) return;
|
|
270
281
|
if (seek) moveTo(d.startX, true);
|
|
271
|
-
|
|
282
|
+
glide(ratios[d.thumb], L.ratioOf(d.cur[d.thumb]), L.reduce).start();
|
|
272
283
|
d.dragging = false;
|
|
273
284
|
d.target = [-1, -1];
|
|
274
285
|
if (!L.controlled) setInner(d.cur);
|
|
@@ -349,17 +360,18 @@ export function Slider(props: SliderProps) {
|
|
|
349
360
|
};
|
|
350
361
|
}, [w, inset, usable, range, slotW, ratios]);
|
|
351
362
|
|
|
363
|
+
// Reduce Motion: no swell, no pop-up — the halo and tooltip just fade.
|
|
352
364
|
const anim = useMemo(
|
|
353
365
|
() => ({
|
|
354
|
-
thumb: press.map((p) => [{ scale: p.interpolate({ inputRange: [0, 1], outputRange: [1, 1.14] }) }]),
|
|
355
|
-
halo: press.map((p) => [{ scale: p.interpolate({ inputRange: [0, 1], outputRange: [1, 1.75] }) }]),
|
|
366
|
+
thumb: press.map((p) => [{ scale: p.interpolate({ inputRange: [0, 1], outputRange: [1, reduce ? 1 : 1.14] }) }]),
|
|
367
|
+
halo: press.map((p) => [{ scale: p.interpolate({ inputRange: [0, 1], outputRange: [1, reduce ? 1 : 1.75] }) }]),
|
|
356
368
|
bubble: bubble.map((b) => [
|
|
357
|
-
{ translateY: b.interpolate({ inputRange: [0, 1], outputRange: [6, 0] }) },
|
|
358
|
-
{ scale: b.interpolate({ inputRange: [0, 1], outputRange: [0.6, 1] }) },
|
|
369
|
+
{ translateY: b.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 0 : 6, 0] }) },
|
|
370
|
+
{ scale: b.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 1 : 0.6, 1] }) },
|
|
359
371
|
]),
|
|
360
|
-
held: [{ scale: held.interpolate({ inputRange: [0, 1], outputRange: [1, 1.025] }) }],
|
|
372
|
+
held: [{ scale: held.interpolate({ inputRange: [0, 1], outputRange: [1, reduce ? 1 : 1.025] }) }],
|
|
361
373
|
}),
|
|
362
|
-
[press, bubble, held]
|
|
374
|
+
[press, bubble, held, reduce]
|
|
363
375
|
);
|
|
364
376
|
|
|
365
377
|
// ---- marks ----------------------------------------------------------------------------------
|
|
@@ -412,7 +424,7 @@ export function Slider(props: SliderProps) {
|
|
|
412
424
|
<View style={[styles.barContent, { width: w, height: trackH, paddingHorizontal: Math.round(trackH * 0.32) }]}>
|
|
413
425
|
<View>{icon !== undefined ? drawIcon(icon, iconColor) : null}</View>
|
|
414
426
|
{showValue === 'always' ? (
|
|
415
|
-
<Text numberOfLines={1} selectable={false} style={[styles.barValue, { color: textColor, fontSize: sz.font + 1 }, labelStyle]}>
|
|
427
|
+
<Text numberOfLines={1} selectable={false} maxFontSizeMultiplier={FONT_CAP} style={[styles.barValue, ff, { color: textColor, fontSize: sz.font + 1 }, labelStyle]}>
|
|
416
428
|
{range ? `${fmt(vals[0])} – ${fmt(vals[1])}` : fmt(vals[0])}
|
|
417
429
|
</Text>
|
|
418
430
|
) : null}
|
|
@@ -424,7 +436,8 @@ export function Slider(props: SliderProps) {
|
|
|
424
436
|
key={key}
|
|
425
437
|
numberOfLines={1}
|
|
426
438
|
selectable={false}
|
|
427
|
-
|
|
439
|
+
maxFontSizeMultiplier={FONT_CAP}
|
|
440
|
+
style={[styles.markLabel, place, ff, { fontSize: sz.font - 1, color: lit ? colors.foreground : colors.mutedForeground }, labelStyle]}
|
|
428
441
|
>
|
|
429
442
|
{text}
|
|
430
443
|
</Text>
|
|
@@ -569,7 +582,7 @@ export function Slider(props: SliderProps) {
|
|
|
569
582
|
]}
|
|
570
583
|
>
|
|
571
584
|
<View style={[styles.bubble, { backgroundColor: colors.foreground, borderRadius: radius.capsule(BUBBLE_H) }]}>
|
|
572
|
-
<Text numberOfLines={1} selectable={false} style={[styles.bubbleText, { color: colors.background, fontSize: sz.font }, labelStyle]}>
|
|
585
|
+
<Text numberOfLines={1} selectable={false} maxFontSizeMultiplier={FONT_CAP} style={[styles.bubbleText, ff, { color: colors.background, fontSize: sz.font }, labelStyle]}>
|
|
573
586
|
{fmt(v)}
|
|
574
587
|
</Text>
|
|
575
588
|
</View>
|
|
@@ -582,7 +595,7 @@ export function Slider(props: SliderProps) {
|
|
|
582
595
|
</View>
|
|
583
596
|
|
|
584
597
|
{hasMarkLabels || minMax ? (
|
|
585
|
-
<View pointerEvents="none" style={[styles.labels, { height: sz.font + 8 }]}>
|
|
598
|
+
<View pointerEvents="none" style={[styles.labels, { height: Math.round(sz.font * fontScale) + 8 }]}>
|
|
586
599
|
{hasMarkLabels
|
|
587
600
|
? markList.map((m) => {
|
|
588
601
|
if (!m.label || !w) return null;
|
|
@@ -671,6 +684,22 @@ export function SliderIcon({ name, size = 20, color }: { name: SliderIconName; s
|
|
|
671
684
|
);
|
|
672
685
|
}
|
|
673
686
|
|
|
687
|
+
function useReduceMotion() {
|
|
688
|
+
const [reduce, setReduce] = useState(false);
|
|
689
|
+
useEffect(() => {
|
|
690
|
+
let alive = true;
|
|
691
|
+
AccessibilityInfo.isReduceMotionEnabled()
|
|
692
|
+
.then((r) => alive && setReduce(r))
|
|
693
|
+
.catch(() => {});
|
|
694
|
+
const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduce);
|
|
695
|
+
return () => {
|
|
696
|
+
alive = false;
|
|
697
|
+
sub.remove();
|
|
698
|
+
};
|
|
699
|
+
}, []);
|
|
700
|
+
return reduce;
|
|
701
|
+
}
|
|
702
|
+
|
|
674
703
|
const styles = StyleSheet.create({
|
|
675
704
|
root: { alignSelf: 'stretch' },
|
|
676
705
|
row: { flexDirection: 'row', alignItems: 'flex-start', gap: 12 },
|
|
@@ -699,7 +728,7 @@ const styles = StyleSheet.create({
|
|
|
699
728
|
},
|
|
700
729
|
bubbleWrap: { position: 'absolute', width: BUBBLE_W, alignItems: 'center', transformOrigin: 'bottom' },
|
|
701
730
|
bubble: {
|
|
702
|
-
|
|
731
|
+
minHeight: BUBBLE_H,
|
|
703
732
|
paddingHorizontal: 10,
|
|
704
733
|
minWidth: 36,
|
|
705
734
|
alignItems: '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
|
};
|
|
@@ -694,25 +694,30 @@ function Ellipsis({
|
|
|
694
694
|
color: string;
|
|
695
695
|
fontSize: number;
|
|
696
696
|
}) {
|
|
697
|
+
const { font } = useKit();
|
|
698
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
697
699
|
const t = useLoop(1400 / speed, linear, true, !reduce);
|
|
698
700
|
const base = text.replace(/(…|\.{1,3})\s*$/, '');
|
|
699
701
|
return (
|
|
700
702
|
<View style={styles.ellipsis}>
|
|
701
|
-
<Text style={[styles.label, { color, fontSize }]}>{base}</Text>
|
|
703
|
+
<Text style={[styles.label, ff, { color, fontSize }]}>{base}</Text>
|
|
702
704
|
{[0, 1, 2].map((i) => (
|
|
703
705
|
<Animated.Text
|
|
704
706
|
key={i}
|
|
705
|
-
style={
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
:
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
707
|
+
style={[
|
|
708
|
+
ff,
|
|
709
|
+
{
|
|
710
|
+
color,
|
|
711
|
+
fontSize,
|
|
712
|
+
fontWeight: '400',
|
|
713
|
+
opacity: reduce
|
|
714
|
+
? 1
|
|
715
|
+
: t.interpolate({
|
|
716
|
+
inputRange: [0, 0.15 + i * 0.18, 0.3 + i * 0.18, 0.85, 1],
|
|
717
|
+
outputRange: [0, 0, 1, 1, 0],
|
|
718
|
+
}),
|
|
719
|
+
},
|
|
720
|
+
]}
|
|
716
721
|
>
|
|
717
722
|
.
|
|
718
723
|
</Animated.Text>
|
|
@@ -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
|
};
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Pressable,
|
|
7
7
|
StyleSheet,
|
|
8
8
|
View,
|
|
9
|
+
useWindowDimensions,
|
|
9
10
|
type LayoutChangeEvent,
|
|
10
11
|
type StyleProp,
|
|
11
12
|
type TextStyle,
|
|
@@ -67,6 +68,9 @@ const SIZES: Record<StatefulButtonSize, { h: number; px: number; font: number; i
|
|
|
67
68
|
lg: { h: 54, px: 22, font: 17, icon: 20, gap: 9 },
|
|
68
69
|
};
|
|
69
70
|
const STAGGER = 22;
|
|
71
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
72
|
+
// content text scales all the way.
|
|
73
|
+
const FONT_CAP = 1.35;
|
|
70
74
|
const END = 1e6;
|
|
71
75
|
// Connected scripts (Arabic, Indic, Thai…) roll as one piece instead of letter by letter.
|
|
72
76
|
const CONNECTED = /[-ࣿऀ--ༀ-က-႟ក-יִ-﷿ﹰ-]/;
|
|
@@ -131,6 +135,7 @@ function Letters({ text, style, lh, space, anims, onLayout }: LettersProps) {
|
|
|
131
135
|
<View key={i} style={{ height: lh, overflow: 'hidden' }}>
|
|
132
136
|
<Animated.Text
|
|
133
137
|
numberOfLines={1}
|
|
138
|
+
maxFontSizeMultiplier={FONT_CAP}
|
|
134
139
|
style={[style, anims?.[i] && { opacity: anims[i].opacity, transform: [{ translateY: anims[i].translateY }] }]}
|
|
135
140
|
>
|
|
136
141
|
{ch}
|
|
@@ -252,8 +257,11 @@ export function StatefulButton({
|
|
|
252
257
|
const reduce = useReduceMotion();
|
|
253
258
|
const s = SIZES[size];
|
|
254
259
|
const fontSize = StyleSheet.flatten(textStyle)?.fontSize ?? s.font;
|
|
255
|
-
|
|
256
|
-
const
|
|
260
|
+
// Letter boxes clip to one line, so they grow with the (capped) text size; the Text's own lineHeight scales itself.
|
|
261
|
+
const textScale = Math.min(Math.max(useWindowDimensions().fontScale || 1, 1), FONT_CAP);
|
|
262
|
+
const lineHeight = Math.round(fontSize * 1.3);
|
|
263
|
+
const lh = Math.ceil(lineHeight * textScale);
|
|
264
|
+
const space = fontSize * 0.28 * textScale;
|
|
257
265
|
|
|
258
266
|
const [inner, setInner] = useState<StatefulButtonState>('idle');
|
|
259
267
|
const current = state ?? inner;
|
|
@@ -421,7 +429,7 @@ export function StatefulButton({
|
|
|
421
429
|
setWidths((prev) => (prev[text] === w ? prev : { ...prev, [text]: w }));
|
|
422
430
|
};
|
|
423
431
|
|
|
424
|
-
const textBase: StyleProp<TextStyle> = [styles.label, { fontSize: s.font, fontFamily: font.family }, textStyle, { lineHeight
|
|
432
|
+
const textBase: StyleProp<TextStyle> = [styles.label, { fontSize: s.font, fontFamily: font.family }, textStyle, { lineHeight }];
|
|
425
433
|
// Press feedback: a light wash on dark fills, a faint shade on light or transparent ones.
|
|
426
434
|
const wash = variant === 'primary' && readableOn(look[0].bg) === '#ffffff' ? alpha('#ffffff', 0.14) : alpha(colors.foreground, 0.07);
|
|
427
435
|
const corner = pill ? radius.capsule(s.h) : radius.md;
|
|
@@ -444,7 +452,7 @@ export function StatefulButton({
|
|
|
444
452
|
style={[
|
|
445
453
|
styles.surface,
|
|
446
454
|
{
|
|
447
|
-
|
|
455
|
+
minHeight: s.h,
|
|
448
456
|
minWidth: s.h,
|
|
449
457
|
paddingHorizontal: s.px,
|
|
450
458
|
borderRadius: corner,
|
|
@@ -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
|
};
|