@expokit/cli 0.4.0 → 0.4.2
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 +17 -0
- package/bin/lib.mjs +1 -1
- package/bin/liquidglass.mjs +281 -0
- package/package.json +17 -3
- package/registry/_docs/accordion.json +1 -1
- package/registry/_docs/avatar.json +6 -6
- package/registry/_docs/badge.json +3 -3
- 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 +2 -2
- package/registry/_docs/button.json +2 -2
- package/registry/_docs/calendar.json +5 -5
- package/registry/_docs/card.json +1 -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/drawer.json +4 -4
- package/registry/_docs/flip-card.json +4 -4
- package/registry/_docs/floating-input.json +1 -1
- package/registry/_docs/grain.json +2 -2
- package/registry/_docs/isometric-bar.json +6 -0
- package/registry/_docs/liquidglass-tab.json +2 -1
- package/registry/_docs/otp-input.json +1 -1
- 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/radio-group.json +1 -1
- package/registry/_docs/search-bar.json +3 -3
- package/registry/_docs/segmented-control.json +1 -1
- package/registry/_docs/select.json +6 -6
- package/registry/_docs/skeleton.json +1 -1
- package/registry/_docs/slider.json +6 -6
- package/registry/_docs/spinner.json +2 -2
- package/registry/_docs/stateful-button.json +1 -1
- package/registry/_docs/stepper.json +3 -3
- package/registry/_docs/tabs.json +2 -2
- package/registry/_docs/text-animation.json +4 -4
- package/registry/_docs/textarea.json +1 -1
- package/registry/_docs/tilt-card.json +3 -3
- package/registry/_docs/time-picker.json +6 -6
- package/registry/_docs/timeline-chart.json +6 -0
- package/registry/_docs/timeline.json +4 -4
- package/registry/_docs/treemap.json +6 -0
- package/registry/_docs/waterfall.json +6 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +31 -3
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +4 -1
- package/registry/alert/files/src/components/ui/alert.tsx +5 -2
- package/registry/alert/files/src/components/ui/button.tsx +32 -4
- package/registry/badge/files/src/components/ui/badge.tsx +2 -2
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +11 -5
- package/registry/button/files/src/components/ui/button.tsx +32 -4
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +33 -6
- package/registry/calendar/files/src/components/ui/calendar.tsx +62 -17
- package/registry/card/files/src/components/ui/card.tsx +25 -5
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +26 -4
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +35 -5
- package/registry/coupon/files/src/components/ui/coupon.tsx +45 -21
- package/registry/coupon/files/src/components/ui/flip-card.tsx +22 -3
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +57 -16
- package/registry/date-picker/files/src/components/ui/calendar.tsx +62 -17
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +96 -42
- package/registry/dialog/files/src/components/ui/dialog.tsx +9 -5
- package/registry/drawer/files/src/components/ui/drawer.tsx +8 -4
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +22 -3
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +75 -26
- package/registry/liquidglass-tab/files/src/app/(tabs)/_layout.tsx +3 -94
- package/registry/liquidglass-tab/files/src/app/(tabs)/alerts.tsx +2 -1
- package/registry/liquidglass-tab/files/src/app/_layout.tsx +2 -2
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +2 -1
- package/registry/liquidglass-tab/files/src/components/LiquidGlassTabs.tsx +110 -0
- package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +31 -6
- package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +12 -4
- package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +6 -3
- package/registry/liquidglass-tab/files/src/lib/tabs.ts +4 -2
- package/registry/list-item/files/src/components/ui/list-item.tsx +10 -3
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +50 -19
- package/registry/password-input/files/src/components/ui/floating-input.tsx +75 -26
- package/registry/password-input/files/src/components/ui/password-input.tsx +53 -19
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +22 -3
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +19 -15
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +57 -16
- package/registry/progress/files/src/components/ui/progress.tsx +9 -2
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +11 -4
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +39 -9
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +70 -17
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +37 -9
- package/registry/select/files/src/components/ui/select.tsx +158 -83
- package/registry/slider/files/src/components/ui/slider.tsx +53 -25
- package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +12 -4
- package/registry/stepper/files/src/components/ui/stepper.tsx +76 -26
- package/registry/switch/files/src/components/ui/switch.tsx +56 -19
- package/registry/tabs/files/src/components/ui/tabs.tsx +7 -4
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +7 -2
- package/registry/textarea/files/src/components/ui/textarea.tsx +104 -36
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +57 -16
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +154 -73
- package/registry/timeline/files/src/components/ui/timeline.tsx +9 -4
- package/registry/toast/files/src/components/ui/button.tsx +32 -4
- package/registry/toast/files/src/components/ui/toast.tsx +15 -6
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState, type ComponentRef } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
AccessibilityInfo,
|
|
3
4
|
Animated,
|
|
4
5
|
Easing,
|
|
5
6
|
Platform,
|
|
@@ -59,6 +60,10 @@ export interface SearchBarProps extends Omit<TextInputProps, 'style'> {
|
|
|
59
60
|
style?: StyleProp<ViewStyle>;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
64
|
+
// content text scales all the way.
|
|
65
|
+
const FONT_CAP = 1.35;
|
|
66
|
+
|
|
62
67
|
/** Every dimension comes from one table so the sizes read clearly different. */
|
|
63
68
|
const SIZES: Record<SearchBarSize, { h: number; font: number; icon: number; slot: number; r: number; clear: number; end: number }> = {
|
|
64
69
|
sm: { h: 34, font: 14, icon: 14, slot: 30, r: 9, clear: 16, end: 6 },
|
|
@@ -105,6 +110,7 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
105
110
|
ref
|
|
106
111
|
) {
|
|
107
112
|
const { colors, radius, font } = useKit();
|
|
113
|
+
const reduce = useReduceMotion();
|
|
108
114
|
const ff = font.family ? { fontFamily: font.family } : null;
|
|
109
115
|
const s = SIZES[size];
|
|
110
116
|
const tint = accent ?? colors.accent;
|
|
@@ -140,28 +146,47 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
140
146
|
useEffect(() => {
|
|
141
147
|
const to = focused ? 1 : 0;
|
|
142
148
|
Animated.timing(focusJS, { toValue: to, duration: motion.fast, easing: Easing.out(Easing.quad), useNativeDriver: false }).start();
|
|
149
|
+
// Reduce Motion: the halo only fades (its scale is pinned below) and the magnifier doesn't pop.
|
|
150
|
+
if (reduce) {
|
|
151
|
+
Animated.timing(halo, { toValue: to, duration: 150, useNativeDriver: true }).start();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
143
154
|
Animated.spring(halo, { toValue: to, useNativeDriver: true, ...motion.snappy }).start();
|
|
144
155
|
if (focused) {
|
|
145
156
|
icon.setValue(0.6);
|
|
146
157
|
Animated.spring(icon, { toValue: 1, useNativeDriver: true, ...motion.bouncy }).start();
|
|
147
158
|
}
|
|
148
|
-
}, [focused, focusJS, halo, icon]);
|
|
159
|
+
}, [focused, focusJS, halo, icon, reduce]);
|
|
149
160
|
|
|
150
161
|
useEffect(() => {
|
|
151
162
|
const to = cancelShown ? 1 : 0;
|
|
163
|
+
// Reduce Motion: the field narrows at once and Cancel fades in place.
|
|
164
|
+
if (reduce) {
|
|
165
|
+
cancelJS.setValue(to);
|
|
166
|
+
Animated.timing(cancelN, { toValue: to, duration: 150, useNativeDriver: true }).start();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
152
169
|
Animated.spring(cancelJS, { toValue: to, useNativeDriver: false, ...motion.snappy }).start();
|
|
153
170
|
Animated.spring(cancelN, { toValue: to, useNativeDriver: true, ...motion.snappy }).start();
|
|
154
|
-
}, [cancelShown, cancelJS, cancelN]);
|
|
171
|
+
}, [cancelShown, cancelJS, cancelN, reduce]);
|
|
155
172
|
|
|
156
173
|
useEffect(() => {
|
|
157
174
|
const to = open ? 1 : 0;
|
|
175
|
+
// Reduce Motion: 'minimal' snaps to its new width; the contents still fade.
|
|
176
|
+
if (reduce) {
|
|
177
|
+
openJS.setValue(to);
|
|
178
|
+
Animated.timing(openN, { toValue: to, duration: 150, useNativeDriver: true }).start();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
158
181
|
Animated.spring(openJS, { toValue: to, useNativeDriver: false, ...motion.gentle }).start();
|
|
159
182
|
Animated.timing(openN, { toValue: to, duration: open ? motion.normal : motion.fast, delay: open ? 80 : 0, useNativeDriver: true }).start();
|
|
160
|
-
}, [open, openJS, openN]);
|
|
183
|
+
}, [open, openJS, openN, reduce]);
|
|
161
184
|
|
|
162
185
|
useEffect(() => {
|
|
163
|
-
|
|
164
|
-
|
|
186
|
+
const to = text && editable && open ? 1 : 0;
|
|
187
|
+
if (reduce) Animated.timing(clear, { toValue: to, duration: 150, useNativeDriver: true }).start();
|
|
188
|
+
else Animated.spring(clear, { toValue: to, useNativeDriver: true, ...motion.bouncy }).start();
|
|
189
|
+
}, [text, editable, open, clear, reduce]);
|
|
165
190
|
|
|
166
191
|
useEffect(() => {
|
|
167
192
|
Animated.timing(busy, { toValue: loading ? 1 : 0, duration: motion.fast, useNativeDriver: true }).start();
|
|
@@ -230,7 +255,7 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
230
255
|
onLayout={(e) => setRowW(e.nativeEvent.layout.width)}
|
|
231
256
|
style={[styles.row, minimal && { justifyContent: align === 'right' ? 'flex-end' : 'flex-start' }]}
|
|
232
257
|
>
|
|
233
|
-
<Animated.View style={minimal ? { width: fieldW,
|
|
258
|
+
<Animated.View style={minimal ? { width: fieldW, minHeight: s.h } : [styles.flex, { minHeight: s.h }]}>
|
|
234
259
|
<Animated.View
|
|
235
260
|
pointerEvents="none"
|
|
236
261
|
style={[
|
|
@@ -239,7 +264,7 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
239
264
|
borderRadius: r + 4,
|
|
240
265
|
backgroundColor: alpha(tint, 0.18),
|
|
241
266
|
opacity: halo,
|
|
242
|
-
transform: [{ scale: halo.interpolate({ inputRange: [0, 1], outputRange: [0.96, 1] }) }],
|
|
267
|
+
transform: [{ scale: halo.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 1 : 0.96, 1] }) }],
|
|
243
268
|
},
|
|
244
269
|
]}
|
|
245
270
|
/>
|
|
@@ -276,6 +301,7 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
276
301
|
autoCorrect={false}
|
|
277
302
|
accessibilityRole="search"
|
|
278
303
|
accessibilityLabel={props.accessibilityLabel ?? placeholder}
|
|
304
|
+
maxFontSizeMultiplier={FONT_CAP}
|
|
279
305
|
{...props}
|
|
280
306
|
autoFocus={autoFocus}
|
|
281
307
|
editable={editable}
|
|
@@ -294,12 +320,12 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
294
320
|
if (minimal && !text) setExpanded(false);
|
|
295
321
|
onBlur?.(e);
|
|
296
322
|
}}
|
|
297
|
-
style={[styles.input, ff, { color: colors.foreground, fontSize: s.font,
|
|
323
|
+
style={[styles.input, ff, { color: colors.foreground, fontSize: s.font, minHeight: s.h - 3 }, webNoOutline]}
|
|
298
324
|
/>
|
|
299
325
|
</Animated.View>
|
|
300
326
|
<Animated.View
|
|
301
327
|
pointerEvents={text && open ? 'auto' : 'none'}
|
|
302
|
-
style={[styles.clear, { width: s.clear + 8, opacity: clear, transform: [{ scale: clear }] }]}
|
|
328
|
+
style={[styles.clear, { width: s.clear + 8, opacity: clear, transform: [{ scale: reduce ? 1 : clear }] }]}
|
|
303
329
|
>
|
|
304
330
|
<Pressable
|
|
305
331
|
hitSlop={12}
|
|
@@ -342,6 +368,7 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
342
368
|
{cancelEnabled ? (
|
|
343
369
|
<>
|
|
344
370
|
<Text
|
|
371
|
+
maxFontSizeMultiplier={FONT_CAP}
|
|
345
372
|
pointerEvents="none"
|
|
346
373
|
onLayout={(e) => setCancelW(Math.ceil(e.nativeEvent.layout.width) + 14)}
|
|
347
374
|
style={[styles.cancelText, styles.measure, ff, { fontSize: s.font }]}
|
|
@@ -357,7 +384,7 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
357
384
|
{
|
|
358
385
|
width: cancelW,
|
|
359
386
|
opacity: cancelN,
|
|
360
|
-
transform: [{ translateX: cancelN.interpolate({ inputRange: [0, 1], outputRange: [24, 0] }) }],
|
|
387
|
+
transform: [{ translateX: cancelN.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 0 : 24, 0] }) }],
|
|
361
388
|
},
|
|
362
389
|
]}
|
|
363
390
|
>
|
|
@@ -367,7 +394,7 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
367
394
|
accessibilityLabel={cancelLabel}
|
|
368
395
|
onPress={cancel}
|
|
369
396
|
>
|
|
370
|
-
<Text numberOfLines={1} style={[styles.cancelText, ff, { color: tint, fontSize: s.font }]}>
|
|
397
|
+
<Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={[styles.cancelText, ff, { color: tint, fontSize: s.font }]}>
|
|
371
398
|
{cancelLabel}
|
|
372
399
|
</Text>
|
|
373
400
|
</Pressable>
|
|
@@ -378,24 +405,33 @@ export const SearchBar = forwardRef<ComponentRef<typeof TextInput>, SearchBarPro
|
|
|
378
405
|
</View>
|
|
379
406
|
|
|
380
407
|
{suggestions ? (
|
|
381
|
-
<SuggestionPanel
|
|
408
|
+
<SuggestionPanel
|
|
409
|
+
open={panelOpen && open && visible.length > 0}
|
|
410
|
+
items={visible}
|
|
411
|
+
query={query}
|
|
412
|
+
tint={tint}
|
|
413
|
+
reduce={reduce}
|
|
414
|
+
onSelect={select}
|
|
415
|
+
/>
|
|
382
416
|
) : null}
|
|
383
417
|
</View>
|
|
384
418
|
);
|
|
385
419
|
});
|
|
386
420
|
|
|
387
|
-
/** Dropdown list under the bar: drops in, rows show an icon, bolded match and a hint. */
|
|
421
|
+
/** Dropdown list under the bar: drops in (fades with Reduce Motion), rows show an icon, bolded match and a hint. */
|
|
388
422
|
function SuggestionPanel({
|
|
389
423
|
open,
|
|
390
424
|
items,
|
|
391
425
|
query,
|
|
392
426
|
tint,
|
|
427
|
+
reduce,
|
|
393
428
|
onSelect,
|
|
394
429
|
}: {
|
|
395
430
|
open: boolean;
|
|
396
431
|
items: SearchSuggestion[];
|
|
397
432
|
query: string;
|
|
398
433
|
tint: string;
|
|
434
|
+
reduce: boolean;
|
|
399
435
|
onSelect: (item: SearchSuggestion) => void;
|
|
400
436
|
}) {
|
|
401
437
|
const { colors, radius, font } = useKit();
|
|
@@ -408,13 +444,14 @@ function SuggestionPanel({
|
|
|
408
444
|
useEffect(() => {
|
|
409
445
|
if (open) {
|
|
410
446
|
setMounted(true);
|
|
411
|
-
Animated.
|
|
447
|
+
if (reduce) Animated.timing(enter, { toValue: 1, duration: 150, useNativeDriver: true }).start();
|
|
448
|
+
else Animated.spring(enter, { toValue: 1, useNativeDriver: true, ...motion.snappy }).start();
|
|
412
449
|
} else {
|
|
413
450
|
Animated.timing(enter, { toValue: 0, duration: motion.fast, useNativeDriver: true }).start(({ finished }) => {
|
|
414
451
|
if (finished) setMounted(false);
|
|
415
452
|
});
|
|
416
453
|
}
|
|
417
|
-
}, [open, enter]);
|
|
454
|
+
}, [open, enter, reduce]);
|
|
418
455
|
|
|
419
456
|
if (!mounted) return null;
|
|
420
457
|
return (
|
|
@@ -428,8 +465,8 @@ function SuggestionPanel({
|
|
|
428
465
|
borderRadius: radius.lg,
|
|
429
466
|
opacity: enter,
|
|
430
467
|
transform: [
|
|
431
|
-
{ translateY: enter.interpolate({ inputRange: [0, 1], outputRange: [-6, 0] }) },
|
|
432
|
-
{ scale: enter.interpolate({ inputRange: [0, 1], outputRange: [0.98, 1] }) },
|
|
468
|
+
{ translateY: enter.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 0 : -6, 0] }) },
|
|
469
|
+
{ scale: enter.interpolate({ inputRange: [0, 1], outputRange: [reduce ? 1 : 0.98, 1] }) },
|
|
433
470
|
],
|
|
434
471
|
},
|
|
435
472
|
]}
|
|
@@ -498,6 +535,22 @@ function Icon({ name, size, color }: { name: 'search' | 'recent' | 'trending'; s
|
|
|
498
535
|
);
|
|
499
536
|
}
|
|
500
537
|
|
|
538
|
+
function useReduceMotion() {
|
|
539
|
+
const [reduce, setReduce] = useState(false);
|
|
540
|
+
useEffect(() => {
|
|
541
|
+
let alive = true;
|
|
542
|
+
AccessibilityInfo.isReduceMotionEnabled()
|
|
543
|
+
.then((r) => alive && setReduce(r))
|
|
544
|
+
.catch(() => {});
|
|
545
|
+
const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduce);
|
|
546
|
+
return () => {
|
|
547
|
+
alive = false;
|
|
548
|
+
sub.remove();
|
|
549
|
+
};
|
|
550
|
+
}, []);
|
|
551
|
+
return reduce;
|
|
552
|
+
}
|
|
553
|
+
|
|
501
554
|
const styles = StyleSheet.create({
|
|
502
555
|
root: { alignSelf: 'stretch' },
|
|
503
556
|
disabled: { opacity: 0.5 },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as Haptics from 'expo-haptics';
|
|
2
2
|
import React, { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import {
|
|
4
|
+
AccessibilityInfo,
|
|
4
5
|
Animated,
|
|
5
6
|
PanResponder,
|
|
6
7
|
Pressable,
|
|
@@ -48,6 +49,10 @@ export interface SegmentedControlProps {
|
|
|
48
49
|
style?: StyleProp<ViewStyle>;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
53
|
+
// content text scales all the way.
|
|
54
|
+
const FONT_CAP = 1.35;
|
|
55
|
+
|
|
51
56
|
const SIZES: Record<SegmentedControlSize, { h: number; font: number; px: number; icon: number }> = {
|
|
52
57
|
sm: { h: 32, font: 13, px: 12, icon: 14 },
|
|
53
58
|
md: { h: 40, font: 14.5, px: 16, icon: 16 },
|
|
@@ -81,6 +86,7 @@ export function SegmentedControl({
|
|
|
81
86
|
}: SegmentedControlProps) {
|
|
82
87
|
const { colors, radius, dark } = useKit();
|
|
83
88
|
const gid = 'sc' + useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
89
|
+
const reduce = useReduceMotion();
|
|
84
90
|
const items: SegmentOption[] = options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o));
|
|
85
91
|
const [inner, setInner] = useState(defaultValue ?? items[0]?.value);
|
|
86
92
|
const selected = value ?? inner;
|
|
@@ -98,7 +104,8 @@ export function SegmentedControl({
|
|
|
98
104
|
useEffect(() => {
|
|
99
105
|
const l = layouts.current[index];
|
|
100
106
|
if (!l) return;
|
|
101
|
-
|
|
107
|
+
// Reduce Motion: the indicator jumps to the new segment instead of sliding.
|
|
108
|
+
if (!placed.current || reduce) {
|
|
102
109
|
lx.setValue(l.x);
|
|
103
110
|
rx.setValue(l.x + l.width);
|
|
104
111
|
placed.current = true;
|
|
@@ -111,7 +118,7 @@ export function SegmentedControl({
|
|
|
111
118
|
Animated.spring(lx, { toValue: l.x, useNativeDriver: false, ...(right ? TRAIL : LEAD) }),
|
|
112
119
|
Animated.spring(rx, { toValue: l.x + l.width, useNativeDriver: false, ...(right ? LEAD : TRAIL) }),
|
|
113
120
|
]).start();
|
|
114
|
-
}, [index, measured, lx, rx]);
|
|
121
|
+
}, [index, measured, reduce, lx, rx]);
|
|
115
122
|
|
|
116
123
|
const onSegmentLayout = (i: number) => (e: LayoutChangeEvent) => {
|
|
117
124
|
const { x: lx, width } = e.nativeEvent.layout;
|
|
@@ -253,6 +260,7 @@ export function SegmentedControl({
|
|
|
253
260
|
activeFg={activeFg}
|
|
254
261
|
idleFg={idleFg}
|
|
255
262
|
disabled={disabled || o.disabled}
|
|
263
|
+
reduce={reduce}
|
|
256
264
|
/>
|
|
257
265
|
))}
|
|
258
266
|
</View>
|
|
@@ -271,9 +279,10 @@ interface SegmentProps {
|
|
|
271
279
|
activeFg: string;
|
|
272
280
|
idleFg: string;
|
|
273
281
|
disabled?: boolean;
|
|
282
|
+
reduce: boolean;
|
|
274
283
|
}
|
|
275
284
|
|
|
276
|
-
function Segment({ option, active, onPress, onPressIn, onLayout, fullWidth, size, height, activeFg, idleFg, disabled }: SegmentProps) {
|
|
285
|
+
function Segment({ option, active, onPress, onPressIn, onLayout, fullWidth, size, height, activeFg, idleFg, disabled, reduce }: SegmentProps) {
|
|
277
286
|
const { font } = useKit();
|
|
278
287
|
const ff = font.family ? { fontFamily: font.family } : null;
|
|
279
288
|
// a: text colour (JS). press: content squish, pop: little bounce on selection (native, same view).
|
|
@@ -283,16 +292,19 @@ function Segment({ option, active, onPress, onPressIn, onLayout, fullWidth, size
|
|
|
283
292
|
const mounted = useRef(false);
|
|
284
293
|
|
|
285
294
|
useEffect(() => {
|
|
286
|
-
Animated.timing(a, { toValue: active ? 1 : 0, duration: 180, useNativeDriver: false }).start();
|
|
287
|
-
|
|
295
|
+
Animated.timing(a, { toValue: active ? 1 : 0, duration: reduce ? 150 : 180, useNativeDriver: false }).start();
|
|
296
|
+
// Reduce Motion: no selection pop and no press squish; the label colour still fades.
|
|
297
|
+
if (active && mounted.current && !reduce) {
|
|
288
298
|
pop.setValue(0.9);
|
|
289
299
|
Animated.spring(pop, { toValue: 1, useNativeDriver: true, ...motion.bouncy }).start();
|
|
290
300
|
}
|
|
291
301
|
mounted.current = true;
|
|
292
|
-
}, [active, a, pop]);
|
|
302
|
+
}, [active, reduce, a, pop]);
|
|
293
303
|
|
|
294
|
-
const pressTo = (to: number) =>
|
|
304
|
+
const pressTo = (to: number) => {
|
|
305
|
+
if (reduce) return press.setValue(1);
|
|
295
306
|
Animated.spring(press, { toValue: to, useNativeDriver: true, ...(to < 1 ? motion.snappy : motion.bouncy) }).start();
|
|
307
|
+
};
|
|
296
308
|
|
|
297
309
|
const color = a.interpolate({ inputRange: [0, 1], outputRange: [idleFg, activeFg] });
|
|
298
310
|
const icon =
|
|
@@ -312,12 +324,12 @@ function Segment({ option, active, onPress, onPressIn, onLayout, fullWidth, size
|
|
|
312
324
|
onPressOut={() => pressTo(1)}
|
|
313
325
|
onLayout={onLayout}
|
|
314
326
|
hitSlop={height < 44 ? { top: (44 - height) / 2, bottom: (44 - height) / 2 } : undefined}
|
|
315
|
-
style={[styles.segment, fullWidth && styles.flex, { height, paddingHorizontal: size.px }]}
|
|
327
|
+
style={[styles.segment, fullWidth && styles.flex, { minHeight: height, paddingHorizontal: size.px }]}
|
|
316
328
|
>
|
|
317
329
|
<Animated.View style={[styles.content, { transform: [{ scale: Animated.multiply(press, pop) }] }]}>
|
|
318
330
|
{icon ? <View style={{ minWidth: size.icon, alignItems: 'center' }}>{icon}</View> : null}
|
|
319
331
|
{option.label ? (
|
|
320
|
-
<Animated.Text numberOfLines={1} style={[styles.label, ff, { fontSize: size.font, color }]}>
|
|
332
|
+
<Animated.Text numberOfLines={1} maxFontSizeMultiplier={FONT_CAP} style={[styles.label, ff, { fontSize: size.font, color }]}>
|
|
321
333
|
{option.label}
|
|
322
334
|
</Animated.Text>
|
|
323
335
|
) : null}
|
|
@@ -326,6 +338,22 @@ function Segment({ option, active, onPress, onPressIn, onLayout, fullWidth, size
|
|
|
326
338
|
);
|
|
327
339
|
}
|
|
328
340
|
|
|
341
|
+
function useReduceMotion() {
|
|
342
|
+
const [reduce, setReduce] = useState(false);
|
|
343
|
+
useEffect(() => {
|
|
344
|
+
let alive = true;
|
|
345
|
+
AccessibilityInfo.isReduceMotionEnabled()
|
|
346
|
+
.then((r) => alive && setReduce(r))
|
|
347
|
+
.catch(() => {});
|
|
348
|
+
const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduce);
|
|
349
|
+
return () => {
|
|
350
|
+
alive = false;
|
|
351
|
+
sub.remove();
|
|
352
|
+
};
|
|
353
|
+
}, []);
|
|
354
|
+
return reduce;
|
|
355
|
+
}
|
|
356
|
+
|
|
329
357
|
const styles = StyleSheet.create({
|
|
330
358
|
track: { flexDirection: 'row', alignItems: 'center' },
|
|
331
359
|
flex: { flex: 1 },
|