@harya72/react-native-ruler 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/Ruler.d.ts +21 -0
- package/dist/Ruler.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +268 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +273 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +75 -0
- package/src/Ruler.tsx +457 -0
- package/src/index.ts +8 -0
- package/src/types.ts +182 -0
package/src/Ruler.tsx
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import {
|
|
2
|
+
View,
|
|
3
|
+
Dimensions,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Platform,
|
|
6
|
+
TextInput,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import React, {
|
|
9
|
+
useCallback,
|
|
10
|
+
useEffect,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
memo,
|
|
14
|
+
} from 'react';
|
|
15
|
+
import Animated, {
|
|
16
|
+
runOnJS,
|
|
17
|
+
useAnimatedProps,
|
|
18
|
+
useAnimatedScrollHandler,
|
|
19
|
+
useSharedValue,
|
|
20
|
+
} from 'react-native-reanimated';
|
|
21
|
+
import type {
|
|
22
|
+
RulerProps,
|
|
23
|
+
AnimatedTextInputProps,
|
|
24
|
+
AnimatedGradientTextProps,
|
|
25
|
+
BarProps,
|
|
26
|
+
} from './types';
|
|
27
|
+
|
|
28
|
+
let LinearGradient: any = null;
|
|
29
|
+
try {
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
LinearGradient = require('expo-linear-gradient').LinearGradient;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
// expo-linear-gradient not installed, fade gradients will be disabled
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
37
|
+
const { width: screenWidth } = Dimensions.get('screen');
|
|
38
|
+
|
|
39
|
+
const FADE_CONFIG = {
|
|
40
|
+
fadeWidth: 0.25,
|
|
41
|
+
fadeOpacity: 0.1,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const STEP = 1;
|
|
45
|
+
const LONG_BAR_WIDTH = 6;
|
|
46
|
+
const LONG_BAR_HEIGHT = 50;
|
|
47
|
+
const SHORT_BAR_WIDTH = 3;
|
|
48
|
+
const SHORT_BAR_HEIGHT = 30;
|
|
49
|
+
const LONG_BAR_COLOR = 'white';
|
|
50
|
+
const SHORT_BAR_COLOR = '#B5B5B5';
|
|
51
|
+
const GAP = 10;
|
|
52
|
+
const BORDER_RADIUS = 50;
|
|
53
|
+
|
|
54
|
+
const Bar = memo(
|
|
55
|
+
({
|
|
56
|
+
index,
|
|
57
|
+
longBarHeight,
|
|
58
|
+
longBarWidth,
|
|
59
|
+
shortBarHeight,
|
|
60
|
+
shortBarWidth,
|
|
61
|
+
longBarColor,
|
|
62
|
+
shortBarColor,
|
|
63
|
+
borderRadius,
|
|
64
|
+
}: BarProps) => {
|
|
65
|
+
const isLong = index % 10 === 0;
|
|
66
|
+
const barWidth = isLong ? longBarWidth : shortBarWidth;
|
|
67
|
+
const barHeight = isLong ? longBarHeight : shortBarHeight;
|
|
68
|
+
|
|
69
|
+
const margin = (longBarWidth - barWidth) / 2;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<View
|
|
73
|
+
style={{
|
|
74
|
+
height: barHeight,
|
|
75
|
+
backgroundColor: isLong ? longBarColor : shortBarColor,
|
|
76
|
+
width: barWidth,
|
|
77
|
+
borderRadius: borderRadius,
|
|
78
|
+
marginHorizontal: margin,
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
Bar.displayName = 'Bar';
|
|
86
|
+
|
|
87
|
+
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
|
|
88
|
+
|
|
89
|
+
const AnimatedText = memo(({
|
|
90
|
+
text,
|
|
91
|
+
fontSize = 96,
|
|
92
|
+
style,
|
|
93
|
+
}: AnimatedGradientTextProps) => {
|
|
94
|
+
const animatedProps = useAnimatedProps(() => {
|
|
95
|
+
return {
|
|
96
|
+
text: text.value,
|
|
97
|
+
defaultValue: text.value,
|
|
98
|
+
} as Partial<AnimatedTextInputProps>;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const textStyle = [
|
|
102
|
+
style,
|
|
103
|
+
{
|
|
104
|
+
fontSize: fontSize,
|
|
105
|
+
padding: 0,
|
|
106
|
+
margin: 0,
|
|
107
|
+
textAlign: 'center' as const,
|
|
108
|
+
textAlignVertical: 'center' as const,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<AnimatedTextInput
|
|
114
|
+
animatedProps={animatedProps}
|
|
115
|
+
editable={false}
|
|
116
|
+
underlineColorAndroid="transparent"
|
|
117
|
+
style={[textStyle, { opacity: 1 }]}
|
|
118
|
+
multiline={false}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
AnimatedText.displayName = 'AnimatedText';
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A highly customizable, performant ruler component for React Native
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```tsx
|
|
130
|
+
* <Ruler
|
|
131
|
+
* min={0}
|
|
132
|
+
* max={200}
|
|
133
|
+
* step={1}
|
|
134
|
+
* height={100}
|
|
135
|
+
* initialValue={50}
|
|
136
|
+
* onChange={(value) => console.log('Selected:', value)}
|
|
137
|
+
* supportLinearGradient={true}
|
|
138
|
+
* />
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
const Ruler = ({
|
|
142
|
+
onChange,
|
|
143
|
+
max,
|
|
144
|
+
min,
|
|
145
|
+
fadeWidth = FADE_CONFIG.fadeWidth,
|
|
146
|
+
fadeOpacity = FADE_CONFIG.fadeOpacity,
|
|
147
|
+
decelerationRate = 'normal',
|
|
148
|
+
...rest
|
|
149
|
+
}: RulerProps) => {
|
|
150
|
+
// Prop validation
|
|
151
|
+
if (__DEV__) {
|
|
152
|
+
if (min >= max) {
|
|
153
|
+
console.error('Ruler: min must be less than max');
|
|
154
|
+
}
|
|
155
|
+
if (rest?.step !== undefined && rest.step <= 0) {
|
|
156
|
+
console.error('Ruler: step must be greater than 0');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const MAX = max;
|
|
161
|
+
const MIN = min;
|
|
162
|
+
const step = rest?.step ?? STEP;
|
|
163
|
+
const longBarWidth = rest?.longBarWidth ?? LONG_BAR_WIDTH;
|
|
164
|
+
const shortBarWidth = rest?.shortBarWidth ?? SHORT_BAR_WIDTH;
|
|
165
|
+
const longBarHeight = rest?.longBarHeight ?? LONG_BAR_HEIGHT;
|
|
166
|
+
const shortBarHeight = rest?.shortBarHeight ?? SHORT_BAR_HEIGHT;
|
|
167
|
+
const longBarColor = rest?.longBarColor ?? LONG_BAR_COLOR;
|
|
168
|
+
const shortBarColor = rest?.shortBarColor ?? SHORT_BAR_COLOR;
|
|
169
|
+
const gap = rest?.gapBetweenSteps ?? GAP;
|
|
170
|
+
const borderRadius = rest?.borderRadius ?? BORDER_RADIUS;
|
|
171
|
+
|
|
172
|
+
// Calculate fraction digits based on step precision
|
|
173
|
+
const fractionDigits = useMemo(() => {
|
|
174
|
+
if (step >= 1) return 0;
|
|
175
|
+
const decimals = step.toString().split('.')[1]?.length || 0;
|
|
176
|
+
return Math.min(decimals, 10);
|
|
177
|
+
}, [step]);
|
|
178
|
+
|
|
179
|
+
const formatValue = useCallback(
|
|
180
|
+
(value: number) => value.toFixed(fractionDigits),
|
|
181
|
+
[fractionDigits]
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const getInitialValue = useCallback(() => {
|
|
185
|
+
const initialVal =
|
|
186
|
+
rest?.initialValue !== undefined ? rest.initialValue : MIN;
|
|
187
|
+
return Math.max(MIN, Math.min(initialVal, MAX));
|
|
188
|
+
}, [rest?.initialValue, MIN, MAX]);
|
|
189
|
+
|
|
190
|
+
const prevInitialValue = useRef(getInitialValue());
|
|
191
|
+
const prevMomentumValue = useSharedValue<string>(
|
|
192
|
+
formatValue(getInitialValue())
|
|
193
|
+
);
|
|
194
|
+
const isUserInteractingRef = useRef(false);
|
|
195
|
+
const isUserInteracting = useSharedValue(false);
|
|
196
|
+
|
|
197
|
+
const setIsUserInteracting = useCallback((value: boolean) => {
|
|
198
|
+
isUserInteractingRef.current = value;
|
|
199
|
+
}, []);
|
|
200
|
+
|
|
201
|
+
const currentValue = useSharedValue<string>(formatValue(getInitialValue()));
|
|
202
|
+
|
|
203
|
+
const listRef = useRef<Animated.FlatList<any>>(null);
|
|
204
|
+
const isInitialMount = useRef(true);
|
|
205
|
+
|
|
206
|
+
const data = useMemo(() => {
|
|
207
|
+
// Prevent division by zero
|
|
208
|
+
if (step <= 0) return [];
|
|
209
|
+
|
|
210
|
+
const itemAmount = (MAX - MIN) / step;
|
|
211
|
+
|
|
212
|
+
// Warn about large ranges
|
|
213
|
+
if (__DEV__ && itemAmount > 1000) {
|
|
214
|
+
console.warn(
|
|
215
|
+
`Ruler: Large range (${Math.round(itemAmount)} items). Consider increasing step size for better performance.`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return Array.from({ length: itemAmount + 1 }, (_, i) => MIN + i * step);
|
|
220
|
+
}, [MIN, MAX, step]);
|
|
221
|
+
|
|
222
|
+
const snapOffsets = useMemo(() => {
|
|
223
|
+
return data.map((_, index) => index * (longBarWidth + gap));
|
|
224
|
+
}, [data, longBarWidth, gap]);
|
|
225
|
+
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
let isMounted = true;
|
|
228
|
+
|
|
229
|
+
if (isInitialMount.current || isUserInteractingRef.current) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const clampedValue = getInitialValue();
|
|
233
|
+
const prevValue = prevInitialValue.current;
|
|
234
|
+
|
|
235
|
+
if (Math.abs(clampedValue - prevValue) <= step) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
prevInitialValue.current = clampedValue;
|
|
240
|
+
currentValue.value = formatValue(clampedValue);
|
|
241
|
+
|
|
242
|
+
const index = Math.round((clampedValue - MIN) / step);
|
|
243
|
+
|
|
244
|
+
if (isMounted) {
|
|
245
|
+
listRef.current?.scrollToOffset({
|
|
246
|
+
offset: index * (longBarWidth + gap),
|
|
247
|
+
animated: true,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return () => {
|
|
252
|
+
isMounted = false;
|
|
253
|
+
};
|
|
254
|
+
}, [rest?.initialValue, MIN, MAX, step, longBarWidth, gap, formatValue, getInitialValue]);
|
|
255
|
+
|
|
256
|
+
const scrollHandler = useAnimatedScrollHandler({
|
|
257
|
+
onBeginDrag: () => {
|
|
258
|
+
'worklet';
|
|
259
|
+
isUserInteracting.value = true;
|
|
260
|
+
runOnJS(setIsUserInteracting)(true);
|
|
261
|
+
},
|
|
262
|
+
onScroll: (event) => {
|
|
263
|
+
if (!isUserInteracting.value) return;
|
|
264
|
+
const scrollX = event.contentOffset.x;
|
|
265
|
+
const index = Math.round(scrollX / (longBarWidth + gap));
|
|
266
|
+
const value = MIN + index * step;
|
|
267
|
+
|
|
268
|
+
if (value >= MIN && value <= MAX) {
|
|
269
|
+
currentValue.value = value.toFixed(fractionDigits);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
onMomentumEnd: (event) => {
|
|
273
|
+
const scrollX = event.contentOffset.x;
|
|
274
|
+
const index = Math.round(scrollX / (longBarWidth + gap));
|
|
275
|
+
const value = MIN + index * step;
|
|
276
|
+
|
|
277
|
+
const formattedValue = value.toFixed(fractionDigits);
|
|
278
|
+
if (value >= MIN && value <= MAX) {
|
|
279
|
+
if (prevMomentumValue.value !== formattedValue) {
|
|
280
|
+
if (isUserInteracting.value) {
|
|
281
|
+
runOnJS(onChange)(parseFloat(formattedValue));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
prevMomentumValue.value = formattedValue;
|
|
287
|
+
isUserInteracting.value = false;
|
|
288
|
+
runOnJS(setIsUserInteracting)(false);
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const Spacer = useCallback(
|
|
293
|
+
() => <View style={{ width: SCREEN_WIDTH / 2 - longBarWidth / 2 }} />,
|
|
294
|
+
[longBarWidth]
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const onContentSizeChange = useCallback(() => {
|
|
298
|
+
if (isInitialMount.current) {
|
|
299
|
+
isInitialMount.current = false;
|
|
300
|
+
const initialIndex = Math.round((getInitialValue() - MIN) / step);
|
|
301
|
+
listRef.current?.scrollToOffset({
|
|
302
|
+
offset: initialIndex * (longBarWidth + gap),
|
|
303
|
+
animated: false,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}, [getInitialValue, MIN, step, longBarWidth, gap]);
|
|
307
|
+
|
|
308
|
+
const renderItem = useCallback(
|
|
309
|
+
({ index }: { index: number }) => (
|
|
310
|
+
<Bar
|
|
311
|
+
index={index}
|
|
312
|
+
longBarColor={longBarColor}
|
|
313
|
+
longBarHeight={longBarHeight}
|
|
314
|
+
longBarWidth={longBarWidth}
|
|
315
|
+
shortBarColor={shortBarColor}
|
|
316
|
+
shortBarHeight={shortBarHeight}
|
|
317
|
+
shortBarWidth={shortBarWidth}
|
|
318
|
+
borderRadius={borderRadius}
|
|
319
|
+
/>
|
|
320
|
+
),
|
|
321
|
+
[
|
|
322
|
+
longBarColor,
|
|
323
|
+
longBarHeight,
|
|
324
|
+
longBarWidth,
|
|
325
|
+
shortBarColor,
|
|
326
|
+
shortBarHeight,
|
|
327
|
+
shortBarWidth,
|
|
328
|
+
borderRadius,
|
|
329
|
+
]
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const renderSeparator = useCallback(
|
|
333
|
+
() => <View style={{ width: gap }} />,
|
|
334
|
+
[gap]
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const getItemLayout = useCallback(
|
|
338
|
+
(_data: any, index: number) => ({
|
|
339
|
+
length: longBarWidth + gap,
|
|
340
|
+
offset: (longBarWidth + gap) * index,
|
|
341
|
+
index,
|
|
342
|
+
}),
|
|
343
|
+
[longBarWidth, gap]
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Check if linear gradient is supported
|
|
347
|
+
const shouldShowGradient = rest?.supportLinearGradient && LinearGradient;
|
|
348
|
+
|
|
349
|
+
return (
|
|
350
|
+
<View style={{ width: screenWidth }}>
|
|
351
|
+
{!rest?.textComponent ? (
|
|
352
|
+
<AnimatedText
|
|
353
|
+
text={currentValue}
|
|
354
|
+
style={[styles.valueText, rest?.textStyle]}
|
|
355
|
+
/>
|
|
356
|
+
) : (
|
|
357
|
+
rest.textComponent(currentValue)
|
|
358
|
+
)}
|
|
359
|
+
|
|
360
|
+
<View style={{ justifyContent: 'center', maxHeight: rest?.height }}>
|
|
361
|
+
<Animated.FlatList
|
|
362
|
+
ref={listRef}
|
|
363
|
+
data={data}
|
|
364
|
+
renderItem={renderItem}
|
|
365
|
+
keyExtractor={(_, i) => i.toString()}
|
|
366
|
+
horizontal
|
|
367
|
+
showsHorizontalScrollIndicator={false}
|
|
368
|
+
ListHeaderComponent={Spacer}
|
|
369
|
+
ListFooterComponent={Spacer}
|
|
370
|
+
ItemSeparatorComponent={renderSeparator}
|
|
371
|
+
contentContainerStyle={{
|
|
372
|
+
alignItems: 'center',
|
|
373
|
+
}}
|
|
374
|
+
scrollEventThrottle={16}
|
|
375
|
+
onScroll={scrollHandler}
|
|
376
|
+
overScrollMode="never"
|
|
377
|
+
snapToOffsets={snapOffsets}
|
|
378
|
+
snapToAlignment="start"
|
|
379
|
+
onContentSizeChange={onContentSizeChange}
|
|
380
|
+
decelerationRate={decelerationRate}
|
|
381
|
+
maxToRenderPerBatch={10}
|
|
382
|
+
initialNumToRender={20}
|
|
383
|
+
windowSize={21}
|
|
384
|
+
getItemLayout={getItemLayout}
|
|
385
|
+
removeClippedSubviews={Platform.OS === 'android'}
|
|
386
|
+
/>
|
|
387
|
+
{shouldShowGradient && (
|
|
388
|
+
<LinearGradient
|
|
389
|
+
colors={['rgba(0,0,0,0.9)', `rgba(0,0,0,${fadeOpacity})`] as const}
|
|
390
|
+
start={{ x: 0, y: 0 }}
|
|
391
|
+
end={{ x: 1, y: 0 }}
|
|
392
|
+
style={[
|
|
393
|
+
styles.fadeOverlay,
|
|
394
|
+
{
|
|
395
|
+
left: 0,
|
|
396
|
+
width: SCREEN_WIDTH * fadeWidth,
|
|
397
|
+
},
|
|
398
|
+
]}
|
|
399
|
+
pointerEvents="none"
|
|
400
|
+
/>
|
|
401
|
+
)}
|
|
402
|
+
{shouldShowGradient && (
|
|
403
|
+
<LinearGradient
|
|
404
|
+
colors={[`rgba(0,0,0,${fadeOpacity})`, 'rgba(0,0,0,0.9)'] as const}
|
|
405
|
+
start={{ x: 0, y: 0 }}
|
|
406
|
+
end={{ x: 1, y: 0 }}
|
|
407
|
+
style={[
|
|
408
|
+
styles.fadeOverlay,
|
|
409
|
+
{
|
|
410
|
+
right: 0,
|
|
411
|
+
width: SCREEN_WIDTH * fadeWidth,
|
|
412
|
+
},
|
|
413
|
+
]}
|
|
414
|
+
pointerEvents="none"
|
|
415
|
+
/>
|
|
416
|
+
)}
|
|
417
|
+
<View
|
|
418
|
+
style={{
|
|
419
|
+
position: 'absolute',
|
|
420
|
+
alignSelf: 'center',
|
|
421
|
+
zIndex: 1,
|
|
422
|
+
}}
|
|
423
|
+
pointerEvents="none"
|
|
424
|
+
>
|
|
425
|
+
{rest?.barComponent ?? (
|
|
426
|
+
<View
|
|
427
|
+
style={{
|
|
428
|
+
height: rest?.indicatorHeight ?? 100,
|
|
429
|
+
width: rest?.indicatorWidth ?? 10,
|
|
430
|
+
backgroundColor: rest?.indicatorColor ?? 'white',
|
|
431
|
+
borderRadius: rest?.borderRadius ?? BORDER_RADIUS,
|
|
432
|
+
}}
|
|
433
|
+
/>
|
|
434
|
+
)}
|
|
435
|
+
</View>
|
|
436
|
+
</View>
|
|
437
|
+
</View>
|
|
438
|
+
);
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
export default Ruler;
|
|
442
|
+
|
|
443
|
+
const styles = StyleSheet.create({
|
|
444
|
+
container: { justifyContent: 'center', alignItems: 'center' },
|
|
445
|
+
valueText: {
|
|
446
|
+
fontSize: 28,
|
|
447
|
+
color: 'white',
|
|
448
|
+
marginBottom: 16,
|
|
449
|
+
textAlign: 'center',
|
|
450
|
+
},
|
|
451
|
+
fadeOverlay: {
|
|
452
|
+
position: 'absolute',
|
|
453
|
+
top: 0,
|
|
454
|
+
bottom: 0,
|
|
455
|
+
zIndex: 2,
|
|
456
|
+
},
|
|
457
|
+
});
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleProp, ViewStyle, TextStyle, TextInputProps } from 'react-native';
|
|
3
|
+
import { SharedValue } from 'react-native-reanimated';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Props for the Ruler component
|
|
7
|
+
*/
|
|
8
|
+
export interface RulerProps {
|
|
9
|
+
/**
|
|
10
|
+
* Callback function called when the ruler value changes
|
|
11
|
+
* @param value - The current selected value
|
|
12
|
+
*/
|
|
13
|
+
onChange: (value: number) => void;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Maximum value on the ruler
|
|
17
|
+
*/
|
|
18
|
+
max: number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Minimum value on the ruler
|
|
22
|
+
*/
|
|
23
|
+
min: number;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Step increment between values
|
|
27
|
+
* @default 1
|
|
28
|
+
*/
|
|
29
|
+
step?: number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Width of the fade gradient as a percentage of screen width (0-1)
|
|
33
|
+
* @default 0.25
|
|
34
|
+
*/
|
|
35
|
+
fadeWidth?: number;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Opacity of the fade gradient (0-1)
|
|
39
|
+
* @default 0.1
|
|
40
|
+
*/
|
|
41
|
+
fadeOpacity?: number;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Style for the ruler container
|
|
45
|
+
*/
|
|
46
|
+
style?: StyleProp<ViewStyle>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Style for the value text
|
|
50
|
+
*/
|
|
51
|
+
textStyle?: StyleProp<TextStyle>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Color of the center indicator
|
|
55
|
+
* @default "white"
|
|
56
|
+
*/
|
|
57
|
+
indicatorColor?: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Height of the center indicator
|
|
61
|
+
* @default 100
|
|
62
|
+
*/
|
|
63
|
+
indicatorHeight?: number;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Width of the center indicator
|
|
67
|
+
* @default 10
|
|
68
|
+
*/
|
|
69
|
+
indicatorWidth?: number;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Height of long bars (every 10th step)
|
|
73
|
+
* @default 50
|
|
74
|
+
*/
|
|
75
|
+
longBarHeight?: number;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Height of short bars
|
|
79
|
+
* @default 30
|
|
80
|
+
*/
|
|
81
|
+
shortBarHeight?: number;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Width of long bars
|
|
85
|
+
* @default 6
|
|
86
|
+
*/
|
|
87
|
+
longBarWidth?: number;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Width of short bars
|
|
91
|
+
* @default 3
|
|
92
|
+
*/
|
|
93
|
+
shortBarWidth?: number;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Color of long bars
|
|
97
|
+
* @default "white"
|
|
98
|
+
*/
|
|
99
|
+
longBarColor?: string;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Color of short bars
|
|
103
|
+
* @default "#B5B5B5"
|
|
104
|
+
*/
|
|
105
|
+
shortBarColor?: string;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Gap between ruler steps
|
|
109
|
+
* @default 10
|
|
110
|
+
*/
|
|
111
|
+
gapBetweenSteps?: number;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Border radius for bars and indicator
|
|
115
|
+
* @default 50
|
|
116
|
+
*/
|
|
117
|
+
borderRadius?: number;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Custom component to render as the center indicator bar
|
|
121
|
+
*/
|
|
122
|
+
barComponent?: ReactNode;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Enable linear gradient fade on edges (requires expo-linear-gradient)
|
|
126
|
+
* @default false
|
|
127
|
+
*/
|
|
128
|
+
supportLinearGradient?: boolean;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Initial value to display
|
|
132
|
+
* @default min
|
|
133
|
+
*/
|
|
134
|
+
initialValue?: number;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Scroll deceleration rate
|
|
138
|
+
* @default "normal"
|
|
139
|
+
*/
|
|
140
|
+
decelerationRate?: 'fast' | 'normal';
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Height of the ruler component
|
|
144
|
+
*/
|
|
145
|
+
height: number;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Custom text component to display the current value
|
|
149
|
+
* @param currentValue - Shared value containing the current ruler value as a string
|
|
150
|
+
*/
|
|
151
|
+
textComponent?: (currentValue: SharedValue<string>) => ReactNode;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Internal props for AnimatedTextInput
|
|
156
|
+
*/
|
|
157
|
+
export interface AnimatedTextInputProps extends TextInputProps {
|
|
158
|
+
text?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Props for the AnimatedText component
|
|
163
|
+
*/
|
|
164
|
+
export interface AnimatedGradientTextProps {
|
|
165
|
+
text: SharedValue<string>;
|
|
166
|
+
fontSize?: number;
|
|
167
|
+
style?: any;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Props for the Bar component
|
|
172
|
+
*/
|
|
173
|
+
export interface BarProps {
|
|
174
|
+
index: number;
|
|
175
|
+
longBarHeight: number;
|
|
176
|
+
longBarWidth: number;
|
|
177
|
+
shortBarHeight: number;
|
|
178
|
+
shortBarWidth: number;
|
|
179
|
+
longBarColor: string;
|
|
180
|
+
shortBarColor: string;
|
|
181
|
+
borderRadius?: number;
|
|
182
|
+
}
|