@webority-technologies/mobile 0.0.22 → 0.0.23
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/lib/commonjs/components/Accordion/Accordion.js +4 -2
- package/lib/commonjs/components/Avatar/Avatar.js +4 -2
- package/lib/commonjs/components/Badge/Badge.js +5 -5
- package/lib/commonjs/components/Banner/Banner.js +8 -4
- package/lib/commonjs/components/BottomNavigation/BottomNavigation.js +6 -4
- package/lib/commonjs/components/BottomSheet/BottomSheet.js +8 -9
- package/lib/commonjs/components/Box/Box.js +162 -0
- package/lib/commonjs/components/Box/index.js +37 -0
- package/lib/commonjs/components/Button/Button.js +7 -7
- package/lib/commonjs/components/Carousel/Carousel.js +4 -2
- package/lib/commonjs/components/Checkbox/Checkbox.js +14 -5
- package/lib/commonjs/components/DatePicker/DatePicker.js +9 -7
- package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +5 -2
- package/lib/commonjs/components/Dialog/Dialog.js +2 -2
- package/lib/commonjs/components/FieldBase/FieldBase.js +8 -4
- package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +13 -5
- package/lib/commonjs/components/FormField/FormField.js +61 -25
- package/lib/commonjs/components/Input/Input.js +41 -29
- package/lib/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +102 -0
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js +13 -0
- package/lib/commonjs/components/KeyboardToolbar/KeyboardToolbar.js +130 -0
- package/lib/commonjs/components/KeyboardToolbar/index.js +13 -0
- package/lib/commonjs/components/Modal/Modal.js +17 -6
- package/lib/commonjs/components/NumberInput/NumberInput.js +35 -28
- package/lib/commonjs/components/OTPInput/OTPInput.js +33 -18
- package/lib/commonjs/components/Radio/Radio.js +7 -5
- package/lib/commonjs/components/Radio/RadioGroup.js +10 -3
- package/lib/commonjs/components/SearchBar/SearchBar.js +4 -2
- package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +20 -10
- package/lib/commonjs/components/Select/Select.js +33 -32
- package/lib/commonjs/components/Skeleton/SkeletonContent.js +5 -2
- package/lib/commonjs/components/Slider/Slider.js +42 -26
- package/lib/commonjs/components/Spinner/Spinner.js +5 -5
- package/lib/commonjs/components/Switch/Switch.js +29 -16
- package/lib/commonjs/components/Tabs/Tabs.js +4 -2
- package/lib/commonjs/components/Text/Text.js +142 -0
- package/lib/commonjs/components/Text/index.js +13 -0
- package/lib/commonjs/components/TimePicker/TimePicker.js +10 -7
- package/lib/commonjs/components/Toast/Toast.js +22 -10
- package/lib/commonjs/components/Tooltip/Tooltip.js +6 -2
- package/lib/commonjs/components/index.js +135 -89
- package/lib/commonjs/form/FormContext.js +40 -0
- package/lib/commonjs/form/index.js +68 -0
- package/lib/commonjs/form/path.js +79 -0
- package/lib/commonjs/form/rules.js +67 -0
- package/lib/commonjs/form/types.js +2 -0
- package/lib/commonjs/form/useField.js +54 -0
- package/lib/commonjs/form/useForm.js +316 -0
- package/lib/commonjs/hooks/index.js +14 -0
- package/lib/commonjs/hooks/useControllableState.js +30 -0
- package/lib/commonjs/hooks/useReducedMotion.js +31 -0
- package/lib/commonjs/index.js +96 -11
- package/lib/commonjs/theme/ThemeContext.js +30 -2
- package/lib/commonjs/theme/tokens.js +12 -0
- package/lib/module/components/Accordion/Accordion.js +4 -2
- package/lib/module/components/Avatar/Avatar.js +4 -2
- package/lib/module/components/Badge/Badge.js +5 -5
- package/lib/module/components/Banner/Banner.js +8 -4
- package/lib/module/components/BottomNavigation/BottomNavigation.js +6 -4
- package/lib/module/components/BottomSheet/BottomSheet.js +8 -9
- package/lib/module/components/Box/Box.js +156 -0
- package/lib/module/components/Box/index.js +4 -0
- package/lib/module/components/Button/Button.js +7 -7
- package/lib/module/components/Carousel/Carousel.js +4 -2
- package/lib/module/components/Checkbox/Checkbox.js +14 -5
- package/lib/module/components/DatePicker/DatePicker.js +9 -7
- package/lib/module/components/DateRangePicker/DateRangePicker.js +5 -2
- package/lib/module/components/Dialog/Dialog.js +2 -2
- package/lib/module/components/FieldBase/FieldBase.js +8 -4
- package/lib/module/components/FloatingActionButton/FloatingActionButton.js +13 -5
- package/lib/module/components/FormField/FormField.js +62 -26
- package/lib/module/components/Input/Input.js +41 -29
- package/lib/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +98 -0
- package/lib/module/components/KeyboardAwareScrollView/index.js +4 -0
- package/lib/module/components/KeyboardToolbar/KeyboardToolbar.js +125 -0
- package/lib/module/components/KeyboardToolbar/index.js +4 -0
- package/lib/module/components/Modal/Modal.js +17 -6
- package/lib/module/components/NumberInput/NumberInput.js +30 -23
- package/lib/module/components/OTPInput/OTPInput.js +30 -15
- package/lib/module/components/Radio/Radio.js +7 -5
- package/lib/module/components/Radio/RadioGroup.js +10 -3
- package/lib/module/components/SearchBar/SearchBar.js +4 -2
- package/lib/module/components/SegmentedControl/SegmentedControl.js +20 -10
- package/lib/module/components/Select/Select.js +33 -32
- package/lib/module/components/Skeleton/SkeletonContent.js +5 -2
- package/lib/module/components/Slider/Slider.js +42 -26
- package/lib/module/components/Spinner/Spinner.js +5 -5
- package/lib/module/components/Switch/Switch.js +29 -16
- package/lib/module/components/Tabs/Tabs.js +4 -2
- package/lib/module/components/Text/Text.js +138 -0
- package/lib/module/components/Text/index.js +4 -0
- package/lib/module/components/TimePicker/TimePicker.js +10 -7
- package/lib/module/components/Toast/Toast.js +22 -10
- package/lib/module/components/Tooltip/Tooltip.js +6 -2
- package/lib/module/components/index.js +4 -0
- package/lib/module/form/FormContext.js +32 -0
- package/lib/module/form/index.js +12 -0
- package/lib/module/form/path.js +72 -0
- package/lib/module/form/rules.js +52 -0
- package/lib/module/form/types.js +2 -0
- package/lib/module/form/useField.js +49 -0
- package/lib/module/form/useForm.js +312 -0
- package/lib/module/hooks/index.js +2 -0
- package/lib/module/hooks/useControllableState.js +26 -0
- package/lib/module/hooks/useReducedMotion.js +27 -0
- package/lib/module/index.js +3 -1
- package/lib/module/theme/ThemeContext.js +30 -2
- package/lib/module/theme/tokens.js +12 -0
- package/lib/typescript/commonjs/components/BottomNavigation/BottomNavigation.d.ts +1 -1
- package/lib/typescript/commonjs/components/Box/Box.d.ts +60 -0
- package/lib/typescript/commonjs/components/Box/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/Button/Button.d.ts +1 -1
- package/lib/typescript/commonjs/components/Checkbox/Checkbox.d.ts +3 -2
- package/lib/typescript/commonjs/components/DatePicker/DatePicker.d.ts +3 -3
- package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +2 -2
- package/lib/typescript/commonjs/components/FormField/FormField.d.ts +13 -2
- package/lib/typescript/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +20 -0
- package/lib/typescript/commonjs/components/KeyboardAwareScrollView/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/KeyboardToolbar/KeyboardToolbar.d.ts +29 -0
- package/lib/typescript/commonjs/components/KeyboardToolbar/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/NumberInput/NumberInput.d.ts +3 -2
- package/lib/typescript/commonjs/components/OTPInput/OTPInput.d.ts +3 -2
- package/lib/typescript/commonjs/components/Radio/Radio.d.ts +2 -2
- package/lib/typescript/commonjs/components/Radio/RadioGroup.d.ts +3 -2
- package/lib/typescript/commonjs/components/SegmentedControl/SegmentedControl.d.ts +3 -2
- package/lib/typescript/commonjs/components/Slider/Slider.d.ts +6 -4
- package/lib/typescript/commonjs/components/Spinner/Spinner.d.ts +1 -1
- package/lib/typescript/commonjs/components/Switch/Switch.d.ts +3 -2
- package/lib/typescript/commonjs/components/Text/Text.d.ts +25 -0
- package/lib/typescript/commonjs/components/Text/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/TimePicker/TimePicker.d.ts +3 -3
- package/lib/typescript/commonjs/components/index.d.ts +8 -0
- package/lib/typescript/commonjs/form/FormContext.d.ts +17 -0
- package/lib/typescript/commonjs/form/index.d.ts +9 -0
- package/lib/typescript/commonjs/form/path.d.ts +10 -0
- package/lib/typescript/commonjs/form/rules.d.ts +31 -0
- package/lib/typescript/commonjs/form/types.d.ts +94 -0
- package/lib/typescript/commonjs/form/useField.d.ts +27 -0
- package/lib/typescript/commonjs/form/useForm.d.ts +10 -0
- package/lib/typescript/commonjs/hooks/index.d.ts +3 -0
- package/lib/typescript/commonjs/hooks/useControllableState.d.ts +17 -0
- package/lib/typescript/commonjs/hooks/useReducedMotion.d.ts +8 -0
- package/lib/typescript/commonjs/index.d.ts +4 -2
- package/lib/typescript/commonjs/theme/types.d.ts +15 -0
- package/lib/typescript/module/components/BottomNavigation/BottomNavigation.d.ts +1 -1
- package/lib/typescript/module/components/Box/Box.d.ts +60 -0
- package/lib/typescript/module/components/Box/index.d.ts +3 -0
- package/lib/typescript/module/components/Button/Button.d.ts +1 -1
- package/lib/typescript/module/components/Checkbox/Checkbox.d.ts +3 -2
- package/lib/typescript/module/components/DatePicker/DatePicker.d.ts +3 -3
- package/lib/typescript/module/components/Dialog/Dialog.d.ts +2 -2
- package/lib/typescript/module/components/FormField/FormField.d.ts +13 -2
- package/lib/typescript/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +20 -0
- package/lib/typescript/module/components/KeyboardAwareScrollView/index.d.ts +3 -0
- package/lib/typescript/module/components/KeyboardToolbar/KeyboardToolbar.d.ts +29 -0
- package/lib/typescript/module/components/KeyboardToolbar/index.d.ts +3 -0
- package/lib/typescript/module/components/NumberInput/NumberInput.d.ts +3 -2
- package/lib/typescript/module/components/OTPInput/OTPInput.d.ts +3 -2
- package/lib/typescript/module/components/Radio/Radio.d.ts +2 -2
- package/lib/typescript/module/components/Radio/RadioGroup.d.ts +3 -2
- package/lib/typescript/module/components/SegmentedControl/SegmentedControl.d.ts +3 -2
- package/lib/typescript/module/components/Slider/Slider.d.ts +6 -4
- package/lib/typescript/module/components/Spinner/Spinner.d.ts +1 -1
- package/lib/typescript/module/components/Switch/Switch.d.ts +3 -2
- package/lib/typescript/module/components/Text/Text.d.ts +25 -0
- package/lib/typescript/module/components/Text/index.d.ts +3 -0
- package/lib/typescript/module/components/TimePicker/TimePicker.d.ts +3 -3
- package/lib/typescript/module/components/index.d.ts +8 -0
- package/lib/typescript/module/form/FormContext.d.ts +17 -0
- package/lib/typescript/module/form/index.d.ts +9 -0
- package/lib/typescript/module/form/path.d.ts +10 -0
- package/lib/typescript/module/form/rules.d.ts +31 -0
- package/lib/typescript/module/form/types.d.ts +94 -0
- package/lib/typescript/module/form/useField.d.ts +27 -0
- package/lib/typescript/module/form/useForm.d.ts +10 -0
- package/lib/typescript/module/hooks/index.d.ts +3 -0
- package/lib/typescript/module/hooks/useControllableState.d.ts +17 -0
- package/lib/typescript/module/hooks/useReducedMotion.d.ts +8 -0
- package/lib/typescript/module/index.d.ts +4 -2
- package/lib/typescript/module/theme/types.d.ts +15 -0
- package/package.json +1 -1
|
@@ -61,34 +61,32 @@ const Select = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
61
61
|
const backdropAnim = useRef(createAnimatedValue(0)).current;
|
|
62
62
|
const sheetAnim = useRef(createAnimatedValue(sheetMaxHeight)).current;
|
|
63
63
|
useEffect(() => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
})]).start();
|
|
91
|
-
}
|
|
64
|
+
const anim = open ? Animated.parallel([
|
|
65
|
+
// Backdrop opacity uses JS driver — see Modal.tsx for the Fabric reason.
|
|
66
|
+
Animated.timing(backdropAnim, {
|
|
67
|
+
toValue: 1,
|
|
68
|
+
duration: theme.motion.duration.fast,
|
|
69
|
+
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
70
|
+
useNativeDriver: false
|
|
71
|
+
}), Animated.spring(sheetAnim, {
|
|
72
|
+
toValue: 0,
|
|
73
|
+
damping: theme.motion.spring.snappy.damping,
|
|
74
|
+
stiffness: theme.motion.spring.snappy.stiffness,
|
|
75
|
+
mass: theme.motion.spring.snappy.mass,
|
|
76
|
+
useNativeDriver: true
|
|
77
|
+
})]) : Animated.parallel([Animated.timing(backdropAnim, {
|
|
78
|
+
toValue: 0,
|
|
79
|
+
duration: theme.motion.duration.fast,
|
|
80
|
+
easing: Easing.bezier(...theme.motion.easing.standard),
|
|
81
|
+
useNativeDriver: false
|
|
82
|
+
}), Animated.timing(sheetAnim, {
|
|
83
|
+
toValue: sheetMaxHeight,
|
|
84
|
+
duration: theme.motion.duration.fast,
|
|
85
|
+
easing: Easing.bezier(...theme.motion.easing.accelerate),
|
|
86
|
+
useNativeDriver: true
|
|
87
|
+
})]);
|
|
88
|
+
anim.start();
|
|
89
|
+
return () => anim.stop();
|
|
92
90
|
}, [open, backdropAnim, sheetAnim, sheetMaxHeight, theme.motion.duration.fast, theme.motion.easing.standard, theme.motion.easing.accelerate, theme.motion.spring.snappy]);
|
|
93
91
|
|
|
94
92
|
// Selection helpers — typed to keep callbacks fully type-safe.
|
|
@@ -386,14 +384,17 @@ const Chevron = ({
|
|
|
386
384
|
size,
|
|
387
385
|
open
|
|
388
386
|
}) => {
|
|
387
|
+
const theme = useTheme();
|
|
389
388
|
const rotate = useRef(createAnimatedValue(open ? 1 : 0)).current;
|
|
390
389
|
useEffect(() => {
|
|
391
|
-
Animated.timing(rotate, {
|
|
390
|
+
const anim = Animated.timing(rotate, {
|
|
392
391
|
toValue: open ? 1 : 0,
|
|
393
|
-
duration:
|
|
392
|
+
duration: theme.motion.duration.fast,
|
|
394
393
|
useNativeDriver: true
|
|
395
|
-
})
|
|
396
|
-
|
|
394
|
+
});
|
|
395
|
+
anim.start();
|
|
396
|
+
return () => anim.stop();
|
|
397
|
+
}, [open, rotate, theme.motion.duration.fast]);
|
|
397
398
|
const rotation = rotate.interpolate({
|
|
398
399
|
inputRange: [0, 1],
|
|
399
400
|
outputRange: ['0deg', '180deg']
|
|
@@ -216,19 +216,22 @@ const SkeletonContent = ({
|
|
|
216
216
|
const wasLoadingRef = useRef(loading);
|
|
217
217
|
const fadeAnim = useRef(createAnimatedValue(loading ? 0 : 1)).current;
|
|
218
218
|
useEffect(() => {
|
|
219
|
+
let anim;
|
|
219
220
|
if (wasLoadingRef.current && !loading) {
|
|
220
221
|
fadeAnim.setValue(0);
|
|
221
|
-
Animated.timing(fadeAnim, {
|
|
222
|
+
anim = Animated.timing(fadeAnim, {
|
|
222
223
|
toValue: 1,
|
|
223
224
|
duration: resolvedFadeDuration,
|
|
224
225
|
useNativeDriver: true
|
|
225
|
-
})
|
|
226
|
+
});
|
|
227
|
+
anim.start();
|
|
226
228
|
} else if (!wasLoadingRef.current && loading) {
|
|
227
229
|
// Going back into loading from loaded — snap opacity so the
|
|
228
230
|
// placeholder appears instantly when data invalidates.
|
|
229
231
|
fadeAnim.setValue(1);
|
|
230
232
|
}
|
|
231
233
|
wasLoadingRef.current = loading;
|
|
234
|
+
return () => anim?.stop();
|
|
232
235
|
}, [loading, fadeAnim, resolvedFadeDuration]);
|
|
233
236
|
if (!loading) {
|
|
234
237
|
if (resolvedFadeDuration <= 0) {
|
|
@@ -4,6 +4,7 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState }
|
|
|
4
4
|
import { Animated, Easing, PanResponder, Platform, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
5
5
|
import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
|
|
6
6
|
import { triggerHaptic } from "../../utils/index.js";
|
|
7
|
+
import { useControllableState } from "../../hooks/index.js";
|
|
7
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
9
|
const SIZE_MAP = {
|
|
9
10
|
sm: {
|
|
@@ -74,10 +75,15 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
74
75
|
const [trackWidth, setTrackWidth] = useState(0);
|
|
75
76
|
const fillColor = theme.colors[TONE_TO_COLOR_KEY[tone]];
|
|
76
77
|
const isRange = props.range === true;
|
|
78
|
+
const [currentValue, setCurrentValue] = useControllableState({
|
|
79
|
+
value: props.value,
|
|
80
|
+
defaultValue: props.defaultValue ?? (isRange ? [min, max] : min),
|
|
81
|
+
onChange: props.onChange
|
|
82
|
+
});
|
|
77
83
|
|
|
78
84
|
// Current value is mirrored in refs so PanResponder closures get fresh values.
|
|
79
|
-
const lowRef = useRef(isRange ?
|
|
80
|
-
const highRef = useRef(isRange ?
|
|
85
|
+
const lowRef = useRef(isRange ? currentValue[0] : currentValue);
|
|
86
|
+
const highRef = useRef(isRange ? currentValue[1] : currentValue);
|
|
81
87
|
const lastReportedLow = useRef(lowRef.current);
|
|
82
88
|
const lastReportedHigh = useRef(highRef.current);
|
|
83
89
|
|
|
@@ -115,7 +121,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
115
121
|
useEffect(() => {
|
|
116
122
|
if (trackWidth <= 0) return;
|
|
117
123
|
if (isRange) {
|
|
118
|
-
const [v0, v1] =
|
|
124
|
+
const [v0, v1] = currentValue;
|
|
119
125
|
const lo = Math.min(v0, v1);
|
|
120
126
|
const hi = Math.max(v0, v1);
|
|
121
127
|
lowRef.current = lo;
|
|
@@ -125,38 +131,34 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
125
131
|
setNativeValue(lowX, valueToPx(lo, trackWidth));
|
|
126
132
|
setNativeValue(highX, valueToPx(hi, trackWidth));
|
|
127
133
|
} else {
|
|
128
|
-
const v =
|
|
134
|
+
const v = currentValue;
|
|
129
135
|
lowRef.current = v;
|
|
130
136
|
highRef.current = v;
|
|
131
137
|
lastReportedLow.current = v;
|
|
132
138
|
setNativeValue(lowX, valueToPx(v, trackWidth));
|
|
133
139
|
}
|
|
134
|
-
// We intentionally listen to
|
|
140
|
+
// We intentionally listen to the value across both shapes via JSON; ESLint is fine.
|
|
135
141
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
136
|
-
}, [JSON.stringify(
|
|
142
|
+
}, [JSON.stringify(currentValue), trackWidth, isRange, valueToPx]);
|
|
137
143
|
const onTrackLayout = useCallback(e => {
|
|
138
144
|
const w = e.nativeEvent.layout.width;
|
|
139
145
|
setTrackWidth(w);
|
|
140
146
|
// Initialize positions immediately.
|
|
141
147
|
if (isRange) {
|
|
142
|
-
const [v0, v1] =
|
|
148
|
+
const [v0, v1] = currentValue;
|
|
143
149
|
const lo = Math.min(v0, v1);
|
|
144
150
|
const hi = Math.max(v0, v1);
|
|
145
151
|
setNativeValue(lowX, valueToPx(lo, w));
|
|
146
152
|
setNativeValue(highX, valueToPx(hi, w));
|
|
147
153
|
} else {
|
|
148
|
-
setNativeValue(lowX, valueToPx(
|
|
154
|
+
setNativeValue(lowX, valueToPx(currentValue, w));
|
|
149
155
|
}
|
|
150
156
|
},
|
|
151
157
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
152
158
|
[isRange, valueToPx]);
|
|
153
159
|
const fireChange = useCallback((lo, hi) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
} else {
|
|
157
|
-
props.onChange(lo);
|
|
158
|
-
}
|
|
159
|
-
}, [isRange, props]);
|
|
160
|
+
setCurrentValue(isRange ? [lo, hi] : lo);
|
|
161
|
+
}, [isRange, setCurrentValue]);
|
|
160
162
|
const animateThumbPress = useCallback((target, opacity, pressed) => {
|
|
161
163
|
if (thumbPressAnimationEnabled) {
|
|
162
164
|
Animated.spring(target, {
|
|
@@ -191,24 +193,29 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
191
193
|
if (trackWidth <= 0) return;
|
|
192
194
|
const nextPx = clamp(dragOffsetRef.current + gesture.dx, 0, trackWidth);
|
|
193
195
|
let nextValue = pxToValue(nextPx, trackWidth);
|
|
196
|
+
|
|
197
|
+
// Only touch the native driver + fire onChange when the snapped value
|
|
198
|
+
// actually changes — between step boundaries the drag is a visual
|
|
199
|
+
// no-op (px is derived from the snapped value), so this drops the
|
|
200
|
+
// per-frame native round-trip and per-frame consumer re-render.
|
|
194
201
|
if (which === 'low') {
|
|
195
202
|
if (isRange && nextValue > highRef.current) nextValue = highRef.current;
|
|
196
203
|
if (nextValue !== lastReportedLow.current) {
|
|
197
204
|
if (sliderTheme?.stepHaptic ?? false) triggerHaptic('selection');
|
|
198
205
|
lastReportedLow.current = nextValue;
|
|
206
|
+
lowRef.current = nextValue;
|
|
207
|
+
setNativeValue(lowX, valueToPx(nextValue, trackWidth));
|
|
208
|
+
fireChange(nextValue, highRef.current);
|
|
199
209
|
}
|
|
200
|
-
lowRef.current = nextValue;
|
|
201
|
-
setNativeValue(lowX, valueToPx(nextValue, trackWidth));
|
|
202
|
-
fireChange(nextValue, highRef.current);
|
|
203
210
|
} else {
|
|
204
211
|
if (isRange && nextValue < lowRef.current) nextValue = lowRef.current;
|
|
205
212
|
if (nextValue !== lastReportedHigh.current) {
|
|
206
213
|
if (sliderTheme?.stepHaptic ?? false) triggerHaptic('selection');
|
|
207
214
|
lastReportedHigh.current = nextValue;
|
|
215
|
+
highRef.current = nextValue;
|
|
216
|
+
setNativeValue(highX, valueToPx(nextValue, trackWidth));
|
|
217
|
+
fireChange(lowRef.current, nextValue);
|
|
208
218
|
}
|
|
209
|
-
highRef.current = nextValue;
|
|
210
|
-
setNativeValue(highX, valueToPx(nextValue, trackWidth));
|
|
211
|
-
fireChange(lowRef.current, nextValue);
|
|
212
219
|
}
|
|
213
220
|
},
|
|
214
221
|
onPanResponderRelease: () => {
|
|
@@ -267,12 +274,21 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
267
274
|
}
|
|
268
275
|
}
|
|
269
276
|
}, [disabled, step, isRange, min, max, valueToPx, trackWidth, lowX, fireChange, sliderTheme]);
|
|
270
|
-
|
|
271
|
-
// Computed values for fill bar.
|
|
272
|
-
const fillLeft = isRange ? lowX : new Animated.Value(0);
|
|
273
|
-
const fillWidth = isRange ? Animated.subtract(highX, lowX) : lowX;
|
|
274
277
|
const thumbRadius = sizeCfg.thumbDiameter / 2;
|
|
275
278
|
|
|
279
|
+
// Stable Animated nodes for the fill bar and thumb offsets — created once and
|
|
280
|
+
// memoised so a render doesn't rebuild `new Animated.Value(...)` /
|
|
281
|
+
// Animated.subtract nodes and re-attach the animation graph every pass.
|
|
282
|
+
const zeroX = useRef(createAnimatedValue(0)).current;
|
|
283
|
+
const thumbRadiusX = useRef(createAnimatedValue(thumbRadius)).current;
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
setNativeValue(thumbRadiusX, thumbRadius);
|
|
286
|
+
}, [thumbRadius, thumbRadiusX]);
|
|
287
|
+
const fillLeft = isRange ? lowX : zeroX;
|
|
288
|
+
const fillWidth = useMemo(() => isRange ? Animated.subtract(highX, lowX) : lowX, [isRange, highX, lowX]);
|
|
289
|
+
const lowTranslateX = useMemo(() => Animated.subtract(lowX, thumbRadiusX), [lowX, thumbRadiusX]);
|
|
290
|
+
const highTranslateX = useMemo(() => Animated.subtract(highX, thumbRadiusX), [highX, thumbRadiusX]);
|
|
291
|
+
|
|
276
292
|
// Display values for labels.
|
|
277
293
|
const lowDisplayValue = isRange ? lowRef.current : lowRef.current;
|
|
278
294
|
const highDisplayValue = highRef.current;
|
|
@@ -325,7 +341,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
325
341
|
backgroundColor: theme.colors.background.elevated,
|
|
326
342
|
borderColor: disabled ? theme.colors.surface.disabled : fillColor,
|
|
327
343
|
transform: [{
|
|
328
|
-
translateX:
|
|
344
|
+
translateX: lowTranslateX
|
|
329
345
|
}, {
|
|
330
346
|
scale: lowScale
|
|
331
347
|
}],
|
|
@@ -353,7 +369,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
353
369
|
backgroundColor: theme.colors.background.elevated,
|
|
354
370
|
borderColor: disabled ? theme.colors.surface.disabled : fillColor,
|
|
355
371
|
transform: [{
|
|
356
|
-
translateX:
|
|
372
|
+
translateX: highTranslateX
|
|
357
373
|
}, {
|
|
358
374
|
scale: highScale
|
|
359
375
|
}],
|
|
@@ -5,9 +5,9 @@ import { ActivityIndicator, Animated, Easing, StyleSheet, Text, View } from 'rea
|
|
|
5
5
|
import { useTheme, createAnimatedValue } from "../../theme/index.js";
|
|
6
6
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
7
|
const sizeMap = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
sm: 20,
|
|
9
|
+
md: 32,
|
|
10
|
+
lg: 48
|
|
11
11
|
};
|
|
12
12
|
const DOT_COUNT = 3;
|
|
13
13
|
const DOT_STAGGER_MS = 150;
|
|
@@ -50,7 +50,7 @@ const Dot = ({
|
|
|
50
50
|
};
|
|
51
51
|
const Spinner = /*#__PURE__*/forwardRef((props, ref) => {
|
|
52
52
|
const {
|
|
53
|
-
size = '
|
|
53
|
+
size = 'md',
|
|
54
54
|
color,
|
|
55
55
|
message,
|
|
56
56
|
overlay = false,
|
|
@@ -93,7 +93,7 @@ const Spinner = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
93
93
|
}
|
|
94
94
|
return /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
95
95
|
color: resolvedColor,
|
|
96
|
-
size: size === '
|
|
96
|
+
size: size === 'sm' ? 'small' : 'large',
|
|
97
97
|
style: {
|
|
98
98
|
width: dimension,
|
|
99
99
|
height: dimension
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import React, { forwardRef, useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { Animated, Easing, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
5
5
|
import { useTheme, createAnimatedValue } from "../../theme/index.js";
|
|
6
|
+
import { useControllableState } from "../../hooks/index.js";
|
|
6
7
|
import { triggerHaptic } from "../../utils/hapticUtils.js";
|
|
7
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
9
|
const sizeMap = {
|
|
@@ -41,7 +42,8 @@ const toneColor = (theme, tone) => {
|
|
|
41
42
|
const Switch = /*#__PURE__*/forwardRef((props, ref) => {
|
|
42
43
|
const {
|
|
43
44
|
value,
|
|
44
|
-
|
|
45
|
+
defaultValue,
|
|
46
|
+
onChange,
|
|
45
47
|
disabled = false,
|
|
46
48
|
size = 'md',
|
|
47
49
|
tone = 'primary',
|
|
@@ -56,6 +58,11 @@ const Switch = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
56
58
|
testID,
|
|
57
59
|
...rest
|
|
58
60
|
} = props;
|
|
61
|
+
const [current, setCurrent] = useControllableState({
|
|
62
|
+
value,
|
|
63
|
+
defaultValue: defaultValue ?? false,
|
|
64
|
+
onChange
|
|
65
|
+
});
|
|
59
66
|
const theme = useTheme();
|
|
60
67
|
const sizeStyles = {
|
|
61
68
|
...sizeMap[size],
|
|
@@ -65,25 +72,29 @@ const Switch = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
65
72
|
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
66
73
|
const offTrackColor = theme.mode === 'dark' ? theme.colors.secondary : theme.colors.surface.disabled;
|
|
67
74
|
const onTrackColor = toneColor(theme, tone);
|
|
68
|
-
const progress = useRef(createAnimatedValue(
|
|
75
|
+
const progress = useRef(createAnimatedValue(current ? 1 : 0)).current;
|
|
69
76
|
useEffect(() => {
|
|
70
|
-
Animated.spring(progress, {
|
|
71
|
-
toValue:
|
|
77
|
+
const anim = Animated.spring(progress, {
|
|
78
|
+
toValue: current ? 1 : 0,
|
|
72
79
|
damping: theme.motion.spring.snappy.damping,
|
|
73
80
|
stiffness: theme.motion.spring.snappy.stiffness,
|
|
74
81
|
mass: theme.motion.spring.snappy.mass,
|
|
75
82
|
useNativeDriver: true
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
});
|
|
84
|
+
anim.start();
|
|
85
|
+
return () => anim.stop();
|
|
86
|
+
}, [current, progress, theme.motion.spring.snappy]);
|
|
87
|
+
const trackBgAnim = useRef(createAnimatedValue(current ? 1 : 0)).current;
|
|
79
88
|
useEffect(() => {
|
|
80
|
-
Animated.timing(trackBgAnim, {
|
|
81
|
-
toValue:
|
|
89
|
+
const anim = Animated.timing(trackBgAnim, {
|
|
90
|
+
toValue: current ? 1 : 0,
|
|
82
91
|
duration: theme.motion.duration.fast,
|
|
83
92
|
easing: Easing.out(Easing.ease),
|
|
84
93
|
useNativeDriver: false
|
|
85
|
-
})
|
|
86
|
-
|
|
94
|
+
});
|
|
95
|
+
anim.start();
|
|
96
|
+
return () => anim.stop();
|
|
97
|
+
}, [current, trackBgAnim, theme.motion.duration.fast]);
|
|
87
98
|
const offX = sizeStyles.padding;
|
|
88
99
|
const onX = sizeStyles.trackWidth - sizeStyles.thumbSize - sizeStyles.padding;
|
|
89
100
|
const translateX = progress.interpolate({
|
|
@@ -102,7 +113,7 @@ const Switch = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
102
113
|
isFirstRender.current = false;
|
|
103
114
|
return;
|
|
104
115
|
}
|
|
105
|
-
Animated.sequence([Animated.spring(bounceScale, {
|
|
116
|
+
const anim = Animated.sequence([Animated.spring(bounceScale, {
|
|
106
117
|
toValue: 1.15,
|
|
107
118
|
damping: 10,
|
|
108
119
|
stiffness: 220,
|
|
@@ -114,12 +125,14 @@ const Switch = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
114
125
|
stiffness: 240,
|
|
115
126
|
mass: 1,
|
|
116
127
|
useNativeDriver: true
|
|
117
|
-
})])
|
|
118
|
-
|
|
128
|
+
})]);
|
|
129
|
+
anim.start();
|
|
130
|
+
return () => anim.stop();
|
|
131
|
+
}, [current, bounce, bounceScale]);
|
|
119
132
|
const handlePress = event => {
|
|
120
133
|
if (disabled) return;
|
|
121
134
|
if (haptic !== false) triggerHaptic(haptic);
|
|
122
|
-
|
|
135
|
+
setCurrent(!current);
|
|
123
136
|
rest.onPressOut?.(event);
|
|
124
137
|
};
|
|
125
138
|
const accessibleLabel = accessibilityLabel ?? label;
|
|
@@ -130,7 +143,7 @@ const Switch = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
130
143
|
accessibilityRole: "switch",
|
|
131
144
|
accessibilityLabel: accessibleLabel,
|
|
132
145
|
accessibilityState: {
|
|
133
|
-
checked:
|
|
146
|
+
checked: current,
|
|
134
147
|
disabled
|
|
135
148
|
},
|
|
136
149
|
testID: testID,
|
|
@@ -82,7 +82,7 @@ const Tabs = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
82
82
|
if (!activeLayout) return;
|
|
83
83
|
if (useProgress) return;
|
|
84
84
|
const spring = theme.motion.spring.snappy;
|
|
85
|
-
Animated.parallel([
|
|
85
|
+
const anim = Animated.parallel([
|
|
86
86
|
// Both must use the JS driver: width can't run on native, and mixing
|
|
87
87
|
// drivers on the same view (transform native + width JS) trips RN's
|
|
88
88
|
// "node already moved to native" guard under the new architecture.
|
|
@@ -98,7 +98,9 @@ const Tabs = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
98
98
|
stiffness: spring.stiffness,
|
|
99
99
|
mass: spring.mass,
|
|
100
100
|
useNativeDriver: false
|
|
101
|
-
})])
|
|
101
|
+
})]);
|
|
102
|
+
anim.start();
|
|
103
|
+
return () => anim.stop();
|
|
102
104
|
}, [activeLayout, activeKey, indicatorTranslateX, indicatorWidth, theme.motion.spring.snappy, useProgress]);
|
|
103
105
|
const handleLayout = useCallback(key => e => {
|
|
104
106
|
const {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { forwardRef, useMemo } from 'react';
|
|
4
|
+
import { Text as RNText } from 'react-native';
|
|
5
|
+
import { fontFor, useTheme } from "../../theme/index.js";
|
|
6
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
|
+
const VARIANTS = {
|
|
8
|
+
display: {
|
|
9
|
+
size: '4xl',
|
|
10
|
+
weight: 'bold',
|
|
11
|
+
line: 'tight',
|
|
12
|
+
color: 'primary',
|
|
13
|
+
heading: true
|
|
14
|
+
},
|
|
15
|
+
h1: {
|
|
16
|
+
size: '3xl',
|
|
17
|
+
weight: 'bold',
|
|
18
|
+
line: 'tight',
|
|
19
|
+
color: 'primary',
|
|
20
|
+
heading: true
|
|
21
|
+
},
|
|
22
|
+
h2: {
|
|
23
|
+
size: '2xl',
|
|
24
|
+
weight: 'semibold',
|
|
25
|
+
line: 'tight',
|
|
26
|
+
color: 'primary',
|
|
27
|
+
heading: true
|
|
28
|
+
},
|
|
29
|
+
h3: {
|
|
30
|
+
size: 'xl',
|
|
31
|
+
weight: 'semibold',
|
|
32
|
+
line: 'tight',
|
|
33
|
+
color: 'primary',
|
|
34
|
+
heading: true
|
|
35
|
+
},
|
|
36
|
+
title: {
|
|
37
|
+
size: 'lg',
|
|
38
|
+
weight: 'semibold',
|
|
39
|
+
line: 'normal',
|
|
40
|
+
color: 'primary'
|
|
41
|
+
},
|
|
42
|
+
body: {
|
|
43
|
+
size: 'base',
|
|
44
|
+
weight: 'normal',
|
|
45
|
+
line: 'normal',
|
|
46
|
+
color: 'primary'
|
|
47
|
+
},
|
|
48
|
+
bodySmall: {
|
|
49
|
+
size: 'sm',
|
|
50
|
+
weight: 'normal',
|
|
51
|
+
line: 'normal',
|
|
52
|
+
color: 'secondary'
|
|
53
|
+
},
|
|
54
|
+
caption: {
|
|
55
|
+
size: 'xs',
|
|
56
|
+
weight: 'normal',
|
|
57
|
+
line: 'normal',
|
|
58
|
+
color: 'tertiary'
|
|
59
|
+
},
|
|
60
|
+
label: {
|
|
61
|
+
size: 'sm',
|
|
62
|
+
weight: 'medium',
|
|
63
|
+
line: 'normal',
|
|
64
|
+
color: 'secondary'
|
|
65
|
+
},
|
|
66
|
+
overline: {
|
|
67
|
+
size: 'xs',
|
|
68
|
+
weight: 'semibold',
|
|
69
|
+
line: 'normal',
|
|
70
|
+
color: 'secondary',
|
|
71
|
+
uppercase: true,
|
|
72
|
+
letterSpacing: 'wide'
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const TEXT_ROLES = {
|
|
76
|
+
primary: true,
|
|
77
|
+
secondary: true,
|
|
78
|
+
tertiary: true,
|
|
79
|
+
inverse: true,
|
|
80
|
+
disabled: true,
|
|
81
|
+
link: true
|
|
82
|
+
};
|
|
83
|
+
const SEMANTIC = {
|
|
84
|
+
success: true,
|
|
85
|
+
warning: true,
|
|
86
|
+
error: true,
|
|
87
|
+
info: true
|
|
88
|
+
};
|
|
89
|
+
const resolveColor = (theme, color) => {
|
|
90
|
+
// Text roles win over the same-named brand colour ('primary' -> text.primary),
|
|
91
|
+
// which is the intent the vast majority of the time for text.
|
|
92
|
+
if (TEXT_ROLES[color]) return theme.colors.text[color];
|
|
93
|
+
if (SEMANTIC[color]) return theme.colors[color];
|
|
94
|
+
return color; // raw colour string
|
|
95
|
+
};
|
|
96
|
+
const Text = /*#__PURE__*/forwardRef((props, ref) => {
|
|
97
|
+
const {
|
|
98
|
+
variant = 'body',
|
|
99
|
+
color,
|
|
100
|
+
weight,
|
|
101
|
+
size,
|
|
102
|
+
align,
|
|
103
|
+
italic,
|
|
104
|
+
underline,
|
|
105
|
+
style,
|
|
106
|
+
children,
|
|
107
|
+
...rest
|
|
108
|
+
} = props;
|
|
109
|
+
const theme = useTheme();
|
|
110
|
+
const textStyle = useMemo(() => {
|
|
111
|
+
const spec = VARIANTS[variant];
|
|
112
|
+
const fontSize = typeof size === 'number' ? size : theme.typography.fontSize[size ?? spec.size];
|
|
113
|
+
const lineHeight = Math.round(fontSize * theme.typography.lineHeight[spec.line]);
|
|
114
|
+
return {
|
|
115
|
+
fontSize,
|
|
116
|
+
lineHeight,
|
|
117
|
+
color: resolveColor(theme, color ?? spec.color),
|
|
118
|
+
textAlign: align,
|
|
119
|
+
textTransform: spec.uppercase ? 'uppercase' : undefined,
|
|
120
|
+
letterSpacing: spec.letterSpacing ? theme.typography.letterSpacing[spec.letterSpacing] : undefined,
|
|
121
|
+
fontStyle: italic ? 'italic' : undefined,
|
|
122
|
+
textDecorationLine: underline ? 'underline' : undefined,
|
|
123
|
+
...fontFor(theme, weight ?? spec.weight)
|
|
124
|
+
};
|
|
125
|
+
}, [theme, variant, color, weight, size, align, italic, underline]);
|
|
126
|
+
const heading = VARIANTS[variant].heading;
|
|
127
|
+
return /*#__PURE__*/_jsx(RNText, {
|
|
128
|
+
ref: ref,
|
|
129
|
+
style: [textStyle, style],
|
|
130
|
+
accessibilityRole: heading ? 'header' : rest.accessibilityRole,
|
|
131
|
+
...rest,
|
|
132
|
+
children: children
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
Text.displayName = 'Text';
|
|
136
|
+
export { Text };
|
|
137
|
+
export default Text;
|
|
138
|
+
//# sourceMappingURL=Text.js.map
|
|
@@ -11,12 +11,12 @@ import { PickerTrigger } from "../PickerTrigger/PickerTrigger.js";
|
|
|
11
11
|
/**
|
|
12
12
|
* TimePicker supports two modes:
|
|
13
13
|
*
|
|
14
|
-
* 1. Controlled-modal mode — pass `visible`, `
|
|
14
|
+
* 1. Controlled-modal mode — pass `visible`, `onChange`, `onClose`. The
|
|
15
15
|
* component renders only the modal sheet and the caller owns open/close
|
|
16
16
|
* state plus its own trigger UI.
|
|
17
17
|
* 2. Trigger mode — omit `visible`. The component renders a PickerTrigger
|
|
18
18
|
* field (label / value / placeholder / chevron / clear / helper / error)
|
|
19
|
-
* and manages its own modal open state. `
|
|
19
|
+
* and manages its own modal open state. `onChange` is still called on
|
|
20
20
|
* confirm.
|
|
21
21
|
*/
|
|
22
22
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
@@ -217,7 +217,7 @@ const TimePicker = props => {
|
|
|
217
217
|
const {
|
|
218
218
|
visible,
|
|
219
219
|
value,
|
|
220
|
-
|
|
220
|
+
onChange,
|
|
221
221
|
onClose,
|
|
222
222
|
format = '12h',
|
|
223
223
|
minuteStep = 1,
|
|
@@ -304,8 +304,9 @@ const TimePicker = props => {
|
|
|
304
304
|
const opacity = useRef(createAnimatedValue(0)).current;
|
|
305
305
|
const translateY = useRef(createAnimatedValue(40)).current;
|
|
306
306
|
useEffect(() => {
|
|
307
|
+
let anim;
|
|
307
308
|
if (open) {
|
|
308
|
-
Animated.parallel([
|
|
309
|
+
anim = Animated.parallel([
|
|
309
310
|
// Backdrop opacity uses JS driver — see Modal.tsx for the Fabric reason.
|
|
310
311
|
Animated.timing(opacity, {
|
|
311
312
|
toValue: 1,
|
|
@@ -318,11 +319,13 @@ const TimePicker = props => {
|
|
|
318
319
|
stiffness: theme.motion.spring.snappy.stiffness,
|
|
319
320
|
mass: theme.motion.spring.snappy.mass,
|
|
320
321
|
useNativeDriver: true
|
|
321
|
-
})])
|
|
322
|
+
})]);
|
|
323
|
+
anim.start();
|
|
322
324
|
} else {
|
|
323
325
|
opacity.setValue(0);
|
|
324
326
|
setNativeValue(translateY, 40);
|
|
325
327
|
}
|
|
328
|
+
return () => anim?.stop();
|
|
326
329
|
}, [open, opacity, translateY, theme.motion]);
|
|
327
330
|
const announce = useCallback(msg => {
|
|
328
331
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
@@ -351,12 +354,12 @@ const TimePicker = props => {
|
|
|
351
354
|
const hour24 = to24h(displayHour, period, format);
|
|
352
355
|
const minute = minutes[minuteIndex] ?? 0;
|
|
353
356
|
if (theme.components.timePicker?.haptic) triggerHaptic('notificationSuccess');
|
|
354
|
-
|
|
357
|
+
onChange?.({
|
|
355
358
|
hour: hour24,
|
|
356
359
|
minute
|
|
357
360
|
});
|
|
358
361
|
handleCloseModal();
|
|
359
|
-
}, [hours, hourIndex, periods, periodIndex, minutes, minuteIndex, format,
|
|
362
|
+
}, [hours, hourIndex, periods, periodIndex, minutes, minuteIndex, format, onChange, handleCloseModal, theme.components.timePicker]);
|
|
360
363
|
const summary = useMemo(() => {
|
|
361
364
|
const displayHour = hours[hourIndex] ?? 0;
|
|
362
365
|
const minute = minutes[minuteIndex] ?? 0;
|