react-native-varia 0.2.4 → 0.3.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.
@@ -1,9 +1,8 @@
1
1
  import React from 'react'
2
- import {type LayoutChangeEvent, View} from 'react-native'
2
+ import {type LayoutChangeEvent, View, ViewStyle} from 'react-native'
3
3
  import {StyleSheet, UnistylesVariants} from 'react-native-unistyles'
4
4
  import {Gesture, GestureDetector} from 'react-native-gesture-handler'
5
5
  import Animated, {
6
- useAnimatedReaction,
7
6
  useAnimatedStyle,
8
7
  useSharedValue,
9
8
  } from 'react-native-reanimated'
@@ -11,6 +10,22 @@ import {runOnJS} from 'react-native-worklets'
11
10
  import {SliderStyles, SliderDefaultVariants} from '../theme/Slider.recipe'
12
11
  import {PalettesWithNestedKeys} from '../style/varia/types'
13
12
 
13
+ function throttle<T extends (...args: any[]) => any>(
14
+ func: T,
15
+ limit = 50,
16
+ ): (...args: Parameters<T>) => void {
17
+ let inThrottle = false
18
+ return (...args: Parameters<T>) => {
19
+ if (!inThrottle) {
20
+ func(...(args as Parameters<T>))
21
+ inThrottle = true
22
+ setTimeout(() => {
23
+ inThrottle = false
24
+ }, limit)
25
+ }
26
+ }
27
+ }
28
+
14
29
  type SliderVariants = UnistylesVariants<typeof SliderStyles>
15
30
 
16
31
  type ThumbStyleExtended = ReturnType<typeof SliderStyles.thumb> & {
@@ -33,6 +48,8 @@ type SliderProps = SliderVariants & {
33
48
  value?: number
34
49
  onValueChange?: (value: number) => void
35
50
  onSlidingComplete?: (value: number) => void
51
+ flex?: ViewStyle['flex']
52
+ alignSelf?: ViewStyle['alignSelf']
36
53
  }
37
54
 
38
55
  const Slider = ({
@@ -46,13 +63,9 @@ const Slider = ({
46
63
  steps,
47
64
  onValueChange,
48
65
  onSlidingComplete,
66
+ flex = 0,
67
+ alignSelf = 'auto',
49
68
  }: SliderProps) => {
50
- // let styles
51
- // if (axis === 'x') {
52
- // styles = stylesX
53
- // } else {
54
- // styles = stylesY
55
- // }
56
69
  SliderStyles.useVariants({
57
70
  size,
58
71
  variant,
@@ -61,6 +74,12 @@ const Slider = ({
61
74
  styles.useVariants({
62
75
  axis,
63
76
  })
77
+
78
+ const throttledOnValueChange = React.useMemo(() => {
79
+ if (!onValueChange) return undefined
80
+ return throttle(onValueChange, 50)
81
+ }, [onValueChange])
82
+
64
83
  const thumbStyle = SliderStyles.thumb(colorPalette) as ThumbStyleExtended
65
84
 
66
85
  const halfSize =
@@ -70,11 +89,10 @@ const Slider = ({
70
89
  const trackLength = useSharedValue(0)
71
90
  const isInsideChild = useSharedValue(false)
72
91
 
73
- // const handleTrackLayout = (event: LayoutChangeEvent) => {
74
- // const {width, height} = event.nativeEvent.layout
75
- // console.log('🚀 ~ handleTrackLayout ~ width:', width)
76
- // trackLength.value = axis === 'x' ? width : height
77
- // }
92
+ const handleTrackLayout = (event: LayoutChangeEvent) => {
93
+ const {width, height} = event.nativeEvent.layout
94
+ trackLength.value = axis === 'x' ? width : height
95
+ }
78
96
 
79
97
  const slideGesture = Gesture.Pan()
80
98
  .onTouchesDown(() => {
@@ -84,7 +102,6 @@ const Slider = ({
84
102
  context.value.x = translate.value
85
103
  })
86
104
  .onUpdate(e => {
87
- // console.log('e', e.translationX)
88
105
  const stepLength = steps ? trackLength.value / steps : 1
89
106
  const delta = axis === 'y' ? -e.translationY : e.translationX
90
107
 
@@ -101,10 +118,10 @@ const Slider = ({
101
118
 
102
119
  if (steps) {
103
120
  const stepIndex = Math.round(snappedValue / stepLength)
104
- onValueChange && runOnJS(onValueChange)(stepIndex)
121
+ throttledOnValueChange && runOnJS(throttledOnValueChange)(stepIndex)
105
122
  } else {
106
- onValueChange &&
107
- runOnJS(onValueChange)(
123
+ throttledOnValueChange &&
124
+ runOnJS(throttledOnValueChange)(
108
125
  Math.round((snappedValue / trackLength.value) * 100) / 100,
109
126
  )
110
127
  }
@@ -121,6 +138,7 @@ const Slider = ({
121
138
  .onFinalize(() => {
122
139
  isInsideChild.value = false
123
140
  })
141
+
124
142
  const tapGesture = Gesture.Tap()
125
143
  .onBegin(e => {
126
144
  if (isInsideChild.value) {
@@ -156,58 +174,143 @@ const Slider = ({
156
174
  })
157
175
  .simultaneousWithExternalGesture(slideGesture)
158
176
 
177
+ const trackPan = Gesture.Pan()
178
+ .onBegin(e => {
179
+ if (trackLength.value <= 0) return
180
+
181
+ const tapPosition = axis === 'x' ? e.x : e.y
182
+ const stepLength = steps ? trackLength.value / steps : 1
183
+
184
+ const rawValue =
185
+ axis === 'y'
186
+ ? Math.max(
187
+ 0,
188
+ Math.min(trackLength.value - tapPosition, trackLength.value),
189
+ )
190
+ : Math.max(0, Math.min(tapPosition, trackLength.value))
191
+
192
+ const snappedValue = steps
193
+ ? Math.round(rawValue / stepLength) * stepLength
194
+ : rawValue
195
+
196
+ context.value.x = snappedValue
197
+ translate.value = snappedValue
198
+
199
+ if (steps) {
200
+ const stepIndex = Math.round(snappedValue / stepLength)
201
+ onValueChange && runOnJS(onValueChange)(stepIndex)
202
+ } else {
203
+ const normalizedValue =
204
+ Math.round((snappedValue / trackLength.value) * 100) / 100
205
+ onValueChange && runOnJS(onValueChange)(normalizedValue)
206
+ }
207
+ })
208
+ .onUpdate(e => {
209
+ if (trackLength.value <= 0) return
210
+
211
+ const stepLength = steps ? trackLength.value / steps : 1
212
+ const delta = axis === 'y' ? -e.translationY : e.translationX
213
+
214
+ const rawValue = Math.max(
215
+ 0,
216
+ Math.min(context.value.x + delta, trackLength.value),
217
+ )
218
+
219
+ if (steps) {
220
+ const snappedValue = Math.round(rawValue / stepLength) * stepLength
221
+ translate.value = snappedValue
222
+
223
+ const stepIndex = Math.round(snappedValue / stepLength)
224
+ throttledOnValueChange && runOnJS(throttledOnValueChange)(stepIndex)
225
+ } else {
226
+ translate.value = rawValue
227
+ throttledOnValueChange &&
228
+ runOnJS(throttledOnValueChange)(
229
+ Math.round((rawValue / trackLength.value) * 100) / 100,
230
+ )
231
+ }
232
+ })
233
+ .onEnd(() => {
234
+ const stepLength = steps ? trackLength.value / steps : 1
235
+
236
+ const snappedValue = steps
237
+ ? Math.round(translate.value / stepLength)
238
+ : translate.value / trackLength.value
239
+
240
+ onSlidingComplete && runOnJS(onSlidingComplete)(snappedValue)
241
+ })
242
+
159
243
  const animatedTrack = useAnimatedStyle(() => ({
160
244
  [axis === 'y' ? 'height' : 'width']: translate.value + halfSize,
161
245
  }))
162
246
 
247
+ const animatedThumb = useAnimatedStyle(() => {
248
+ if (axis === 'x') {
249
+ return {
250
+ transform: [{translateX: translate.value - halfSize}],
251
+ }
252
+ } else {
253
+ const translateY = trackLength.value - translate.value - halfSize
254
+ return {
255
+ transform: [{translateY}],
256
+ }
257
+ }
258
+ })
259
+
163
260
  const opacityTrack = steps !== undefined && displayStepsOnMinimumTrack
164
261
 
165
262
  return (
166
263
  <View
167
264
  style={[
168
- styles.container(halfSize),
265
+ styles.container(halfSize, flex, alignSelf),
169
266
  SliderStyles.container(colorPalette),
170
267
  ]}>
171
268
  {axis === 'x' && <View style={{width: halfSize}} />}
172
- <GestureDetector gesture={tapGesture}>
269
+ <GestureDetector gesture={Gesture.Simultaneous(trackPan, tapGesture)}>
173
270
  <View
174
271
  style={[
175
272
  styles.maximumTrack(),
176
273
  SliderStyles.maximumTrack(colorPalette),
177
274
  ]}
178
- // onLayout={handleTrackLayout}
179
- >
180
- {steps && (
181
- <View style={styles.steps}>
182
- {Array.from({length: steps + 1}, (_, index) => index).map(
183
- (_, i) => (
184
- <View
185
- key={i}
186
- style={[
187
- styles.step(i, steps),
188
- SliderStyles.step(colorPalette),
189
- ]}
190
- />
191
- ),
192
- )}
193
- </View>
194
- )}
275
+ onLayout={handleTrackLayout}>
195
276
  <Animated.View
196
277
  style={[
197
278
  styles.minimumTrack(halfSize),
198
279
  SliderStyles.minimumTrack(colorPalette, opacityTrack),
199
280
  animatedTrack,
200
- ]}>
201
- <GestureDetector gesture={slideGesture}>
202
- <View
203
- style={[
204
- SliderStyles.thumb(colorPalette),
205
- styles.thumb(halfSize),
206
- ]}>
207
- {ThumbChildren || null}
281
+ ]}
282
+ />
283
+
284
+ {steps && (
285
+ <View style={[styles.stepsOverlay]} pointerEvents="none">
286
+ <View style={styles.steps}>
287
+ {Array.from({length: steps + 1}, (_, index) => index).map(
288
+ (_, i) => (
289
+ <View
290
+ key={i}
291
+ style={[
292
+ styles.step(i, steps),
293
+ SliderStyles.step(colorPalette),
294
+ ]}
295
+ />
296
+ ),
297
+ )}
208
298
  </View>
209
- </GestureDetector>
210
- </Animated.View>
299
+ </View>
300
+ )}
301
+ <GestureDetector gesture={slideGesture}>
302
+ <View style={styles.thumbContainerFull}>
303
+ <Animated.View style={[animatedThumb]}>
304
+ <View
305
+ style={[
306
+ SliderStyles.thumb(colorPalette),
307
+ styles.thumb(halfSize),
308
+ ]}>
309
+ {ThumbChildren || null}
310
+ </View>
311
+ </Animated.View>
312
+ </View>
313
+ </GestureDetector>
211
314
  </View>
212
315
  </GestureDetector>
213
316
  {axis === 'y' && <View style={{height: halfSize}} />}
@@ -216,12 +319,17 @@ const Slider = ({
216
319
  }
217
320
 
218
321
  const styles = StyleSheet.create(theme => ({
219
- container: (halfSize: number) => ({
322
+ container: (
323
+ halfSize: number,
324
+ flex: ViewStyle['flex'],
325
+ alignSelf: ViewStyle['alignSelf'],
326
+ ) => ({
327
+ flex,
328
+ alignSelf,
220
329
  variants: {
221
330
  axis: {
222
331
  x: {
223
332
  maxWidth: 'auto',
224
- width: '100%',
225
333
  paddingTop: 0,
226
334
  paddingRight: halfSize,
227
335
  flexDirection: 'row',
@@ -238,13 +346,15 @@ const styles = StyleSheet.create(theme => ({
238
346
  }),
239
347
  maximumTrack: () => ({
240
348
  flex: 1,
241
- justifyContent: 'center',
242
349
  position: 'relative',
350
+ overflow: 'visible',
243
351
  variants: {
244
352
  axis: {
245
- x: {},
353
+ x: {
354
+ justifyContent: 'center',
355
+ },
246
356
  y: {
247
- alignItems: 'center',
357
+ justifyContent: 'flex-end',
248
358
  },
249
359
  },
250
360
  },
@@ -253,8 +363,9 @@ const styles = StyleSheet.create(theme => ({
253
363
  variants: {
254
364
  axis: {
255
365
  x: {
366
+ flex: 1,
367
+ marginLeft: halfSize * -1,
256
368
  width: '100%',
257
- left: halfSize * -1,
258
369
  bottom: 'auto',
259
370
  paddingBottom: 0,
260
371
  paddingLeft: halfSize,
@@ -262,51 +373,64 @@ const styles = StyleSheet.create(theme => ({
262
373
  borderBottomRightRadius: 0,
263
374
  borderTopLeftRadius: 20,
264
375
  justifyContent: 'center',
376
+ alignItems: 'flex-end',
265
377
  },
266
378
  y: {
267
- position: 'absolute',
268
379
  height: '100%',
269
- left: 'auto',
270
- bottom: halfSize * -1,
271
380
  paddingBottom: halfSize,
381
+ marginBottom: halfSize * -1,
272
382
  paddingLeft: 0,
273
383
  borderBottomLeftRadius: 20,
274
384
  borderBottomRightRadius: 20,
275
385
  borderTopLeftRadius: 0,
276
- alignItems: 'center',
386
+ alignItems: 'flex-start',
277
387
  },
278
388
  },
279
389
  },
280
390
  }),
391
+ thumbContainerFull: {
392
+ position: 'absolute',
393
+ left: 0,
394
+ right: 0,
395
+ top: 0,
396
+ bottom: 0,
397
+ zIndex: 2,
398
+ },
281
399
  thumb: halfSize => ({
282
400
  borderRadius: 25,
283
- position: 'absolute',
284
401
  variants: {
285
402
  axis: {
286
403
  x: {
287
- right: 0,
288
- zIndex: 2,
289
- transform: [{translateX: '50%'}],
404
+ height: '100%',
405
+ justifyContent: 'center',
290
406
  },
291
407
  y: {
292
- top: 0,
293
- zIndex: 2,
294
- transform: [{translateY: halfSize * -1}],
408
+ width: '100%',
409
+ alignItems: 'center',
295
410
  },
296
411
  },
297
412
  },
298
413
  }),
414
+
415
+ stepsOverlay: {
416
+ position: 'absolute',
417
+ left: 0,
418
+ right: 0,
419
+ top: 0,
420
+ bottom: 0,
421
+ zIndex: 1,
422
+ },
299
423
  steps: {
300
424
  width: '100%',
301
425
  height: '100%',
302
426
  justifyContent: 'space-between',
303
427
  alignItems: 'center',
304
428
  zIndex: 0,
429
+ position: 'absolute',
305
430
  variants: {
306
431
  axis: {
307
432
  x: {
308
433
  flexDirection: 'row',
309
- position: 'absolute',
310
434
  },
311
435
  y: {
312
436
  flexDirection: 'column',
@@ -331,55 +455,5 @@ const styles = StyleSheet.create(theme => ({
331
455
  },
332
456
  }),
333
457
  }))
334
- // const stylesY = StyleSheet.create(theme => ({
335
- // container: halfSize => ({
336
- // maxHeight: 'auto',
337
- // height: '100%',
338
- // paddingTop: halfSize,
339
- // paddingRight: 0,
340
- // flexDirection: 'column',
341
- // }),
342
- // maximumTrack: () => ({
343
- // flex: 1,
344
- // justifyContent: 'center',
345
- // alignItems: 'center',
346
- // position: 'relative',
347
- // // borderWidth: 1,
348
- // // borderColor: 'yellow',
349
- // }),
350
- // minimumTrack: halfSize => ({
351
- // position: 'absolute',
352
- // height: '100%',
353
- // left: 'auto',
354
- // bottom: halfSize * -1,
355
- // paddingBottom: halfSize,
356
- // paddingLeft: 0,
357
- // borderBottomLeftRadius: 20,
358
- // borderBottomRightRadius: 20,
359
- // borderTopLeftRadius: 0,
360
- // alignItems: 'center',
361
- // }),
362
- // thumb: halfSize => ({
363
- // borderRadius: 25,
364
- // position: 'absolute',
365
- // top: 0,
366
- // zIndex: 2,
367
- // transform: [{translateY: halfSize * -1}],
368
- // }),
369
- // steps: {
370
- // width: '100%',
371
- // height: '100%',
372
- // justifyContent: 'space-between',
373
- // alignItems: 'center',
374
- // zIndex: 0,
375
- // flexDirection: 'column',
376
- // },
377
- // step: (index, length) => ({
378
- // width: '100%',
379
- // height: 1,
380
- // backgroundColor:
381
- // index === 0 || index === length ? 'transparent' : theme.colors.fg.default,
382
- // }),
383
- // }))
384
458
 
385
459
  export default Slider
@@ -11,7 +11,7 @@ import {
11
11
  ThemeColors,
12
12
  } from '../style/varia/types'
13
13
  import {SpinnerTokens} from '../theme/Spinner.recipe'
14
- import {resolveColor} from '../style/varia/utils'
14
+ import {getVariantValue, resolveColor} from '../style/varia/utils'
15
15
  import {SpinnerStyles} from '../theme/Spinner.recipe'
16
16
 
17
17
  type SpinnerVariants = UnistylesVariants<typeof SpinnerStyles>
@@ -34,15 +34,19 @@ const BaseSpinner = ({
34
34
  ...props
35
35
  }: SpinnerProps) => {
36
36
  const resolvedColor = resolveColor(color, colors, colorPalette)
37
-
37
+
38
38
  SpinnerStyles.useVariants({
39
39
  size,
40
40
  })
41
41
 
42
+ // console.log('color', color)
43
+
44
+ const resolvedSize = getVariantValue(SpinnerStyles.base, 'size', size, 'width')
45
+
42
46
  return (
43
47
  <ActivityIndicator
44
48
  color={resolvedColor}
45
- size={SpinnerStyles.base.width}
49
+ size={resolvedSize}
46
50
  {...props}
47
51
  style={[style && style, mixins && mixins]}
48
52
  />
@@ -49,6 +49,7 @@ export type HstackProps = {
49
49
  hCenter?: boolean
50
50
  flex?: number
51
51
  gap?: number
52
+ stretch?: boolean
52
53
  } & ViewProps
53
54
 
54
55
  const HStack: React.FC<HstackProps> = ({
@@ -59,11 +60,15 @@ const HStack: React.FC<HstackProps> = ({
59
60
  hCenter,
60
61
  flex = 0,
61
62
  gap = 0,
63
+ stretch,
62
64
  ...props
63
65
  }) => {
64
66
  return (
65
67
  <View
66
- style={[styles.hStack(center, vCenter, hCenter, gap, flex), style]}
68
+ style={[
69
+ styles.hStack(center, vCenter, hCenter, gap, flex, stretch),
70
+ style,
71
+ ]}
67
72
  {...props}>
68
73
  {children}
69
74
  </View>
@@ -77,11 +82,15 @@ const VStack: React.FC<HstackProps> = ({
77
82
  hCenter,
78
83
  flex = 0,
79
84
  gap = 0,
85
+ stretch,
80
86
  ...props
81
87
  }) => {
82
88
  return (
83
89
  <View
84
- style={[styles.vStack(center, vCenter, hCenter, gap, flex), style]}
90
+ style={[
91
+ styles.vStack(center, vCenter, hCenter, gap, flex, stretch),
92
+ style,
93
+ ]}
85
94
  {...props}>
86
95
  {children}
87
96
  </View>
@@ -118,19 +127,21 @@ export const FlexSpacer = () => {
118
127
  )
119
128
  }
120
129
  const styles = StyleSheet.create(() => ({
121
- hStack: (center, vCenter, hCenter, gap, flex) => ({
130
+ hStack: (center, vCenter, hCenter, gap, flex, stretch) => ({
122
131
  flex,
123
132
  gap,
124
133
  flexDirection: 'row',
125
134
  alignItems: center ? 'center' : vCenter ? 'center' : 'stretch',
126
135
  justifyContent: center ? 'center' : hCenter ? 'center' : 'flex-start',
136
+ ...(stretch ? {alignSelf: 'stretch'} : {}),
127
137
  }),
128
- vStack: (center, vCenter, hCenter, gap, flex) => ({
138
+ vStack: (center, vCenter, hCenter, gap, flex, stretch) => ({
129
139
  flex,
130
140
  gap,
131
141
  flexDirection: 'column',
132
- alignItems: center ? 'center' : vCenter ? 'center' : 'flex-start',
142
+ alignItems: center ? 'center' : vCenter ? 'center' : 'stretch',
133
143
  justifyContent: center ? 'center' : hCenter ? 'center' : 'flex-start',
144
+ ...(stretch ? {alignSelf: 'stretch'} : {}),
134
145
  }),
135
146
  }))
136
147