react-native-varia 0.2.3 → 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.
Files changed (43) hide show
  1. package/bin/cli.js +24 -34
  2. package/lib/components/Accordion.tsx +113 -0
  3. package/lib/components/Button.tsx +16 -3
  4. package/lib/components/Checkbox.tsx +12 -7
  5. package/lib/components/CircleProgress.tsx +30 -21
  6. package/lib/components/Divider.tsx +23 -19
  7. package/lib/components/Drawer.tsx +23 -69
  8. package/lib/components/Field.tsx +24 -39
  9. package/lib/components/GradientBackground.tsx +25 -7
  10. package/lib/components/GradientText.tsx +61 -21
  11. package/lib/components/IconWrapper.tsx +20 -14
  12. package/lib/components/Input.tsx +107 -25
  13. package/lib/components/Modal.tsx +4 -10
  14. package/lib/components/NumberInput.tsx +54 -11
  15. package/lib/components/OldSlider.tsx +327 -0
  16. package/lib/components/RadioGroup.tsx +58 -18
  17. package/lib/components/ReText.tsx +1 -1
  18. package/lib/components/Select.tsx +58 -22
  19. package/lib/components/Slider.tsx +273 -138
  20. package/lib/components/Slideshow.tsx +65 -63
  21. package/lib/components/SlidingDrawer.tsx +20 -21
  22. package/lib/components/Spinner.tsx +13 -5
  23. package/lib/components/Toast.tsx +89 -0
  24. package/lib/components/context/Field.tsx +27 -0
  25. package/lib/patterns/index.tsx +16 -5
  26. package/lib/patterns/newPatterns.tsx +285 -0
  27. package/lib/theme/Button.recipe.tsx +11 -1
  28. package/lib/theme/CircleProgress.recipe.tsx +3 -3
  29. package/lib/theme/Drawer.recipe.tsx +107 -0
  30. package/lib/theme/Field.recipe.tsx +17 -2
  31. package/lib/theme/Input.recipe.tsx +12 -3
  32. package/lib/theme/NumberInput.recipe.tsx +8 -3
  33. package/lib/theme/RadioGroup.recipe.tsx +7 -1
  34. package/lib/theme/Select.recipe.tsx +7 -7
  35. package/lib/theme/Slider.recipe.tsx +402 -27
  36. package/lib/theme/Slideshow.recipe.tsx +1 -1
  37. package/lib/theme/Toast.recipe.tsx +71 -0
  38. package/lib/varia/mixins.ts +0 -4
  39. package/lib/varia/types.ts +8 -0
  40. package/lib/varia/utils.ts +66 -0
  41. package/package.json +1 -1
  42. package/lib/theme/Button.recipe-old.tsx +0 -67
  43. package/lib/theme/SlidingDrawer.recipe.tsx +0 -53
@@ -1,5 +1,5 @@
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, {
@@ -10,8 +10,29 @@ import {runOnJS} from 'react-native-worklets'
10
10
  import {SliderStyles, SliderDefaultVariants} from '../theme/Slider.recipe'
11
11
  import {PalettesWithNestedKeys} from '../style/varia/types'
12
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
+
13
29
  type SliderVariants = UnistylesVariants<typeof SliderStyles>
14
30
 
31
+ type ThumbStyleExtended = ReturnType<typeof SliderStyles.thumb> & {
32
+ width: number
33
+ height: number
34
+ }
35
+
15
36
  type SliderProps = SliderVariants & {
16
37
  colorPalette?: PalettesWithNestedKeys
17
38
  thickness?: number
@@ -23,11 +44,12 @@ type SliderProps = SliderVariants & {
23
44
  height: number
24
45
  }
25
46
  thumbChildren?: React.ReactNode
26
- axis: 'x' | 'y'
27
47
  steps?: number
28
48
  value?: number
29
49
  onValueChange?: (value: number) => void
30
50
  onSlidingComplete?: (value: number) => void
51
+ flex?: ViewStyle['flex']
52
+ alignSelf?: ViewStyle['alignSelf']
31
53
  }
32
54
 
33
55
  const Slider = ({
@@ -41,28 +63,27 @@ const Slider = ({
41
63
  steps,
42
64
  onValueChange,
43
65
  onSlidingComplete,
66
+ flex = 0,
67
+ alignSelf = 'auto',
44
68
  }: SliderProps) => {
45
- let styles
46
- if (axis === 'x') {
47
- styles = stylesX
48
- } else {
49
- styles = stylesY
50
- }
51
69
  SliderStyles.useVariants({
52
70
  size,
53
71
  variant,
72
+ axis,
54
73
  })
55
- const thumbStyle = SliderStyles.thumb(colorPalette)
56
- const maximumTrackStyle = SliderStyles.maximumTrack(colorPalette)
57
- const minimumTrackStyle = SliderStyles.minimumTrack(colorPalette, false)
58
-
59
- const halfSize = (thumbStyle.width ?? 0) / 2
60
- const thumbSize = {
61
- width: thumbStyle.width,
62
- height: thumbStyle.height,
63
- }
64
- const maximumTrackWidth = maximumTrackStyle.height ?? 0
65
- const minimumTrackWidth = minimumTrackStyle.height ?? 0
74
+ styles.useVariants({
75
+ axis,
76
+ })
77
+
78
+ const throttledOnValueChange = React.useMemo(() => {
79
+ if (!onValueChange) return undefined
80
+ return throttle(onValueChange, 50)
81
+ }, [onValueChange])
82
+
83
+ const thumbStyle = SliderStyles.thumb(colorPalette) as ThumbStyleExtended
84
+
85
+ const halfSize =
86
+ axis === 'x' ? (thumbStyle.width ?? 0) / 2 : (thumbStyle.height ?? 0) / 2
66
87
  const context = useSharedValue({x: 0})
67
88
  const translate = useSharedValue(value)
68
89
  const trackLength = useSharedValue(0)
@@ -97,10 +118,10 @@ const Slider = ({
97
118
 
98
119
  if (steps) {
99
120
  const stepIndex = Math.round(snappedValue / stepLength)
100
- onValueChange && runOnJS(onValueChange)(stepIndex)
121
+ throttledOnValueChange && runOnJS(throttledOnValueChange)(stepIndex)
101
122
  } else {
102
- onValueChange &&
103
- runOnJS(onValueChange)(
123
+ throttledOnValueChange &&
124
+ runOnJS(throttledOnValueChange)(
104
125
  Math.round((snappedValue / trackLength.value) * 100) / 100,
105
126
  )
106
127
  }
@@ -117,6 +138,7 @@ const Slider = ({
117
138
  .onFinalize(() => {
118
139
  isInsideChild.value = false
119
140
  })
141
+
120
142
  const tapGesture = Gesture.Tap()
121
143
  .onBegin(e => {
122
144
  if (isInsideChild.value) {
@@ -152,57 +174,143 @@ const Slider = ({
152
174
  })
153
175
  .simultaneousWithExternalGesture(slideGesture)
154
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
+
155
243
  const animatedTrack = useAnimatedStyle(() => ({
156
244
  [axis === 'y' ? 'height' : 'width']: translate.value + halfSize,
157
245
  }))
158
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
+
159
260
  const opacityTrack = steps !== undefined && displayStepsOnMinimumTrack
160
261
 
161
262
  return (
162
263
  <View
163
264
  style={[
164
- styles.container(maximumTrackWidth, halfSize),
265
+ styles.container(halfSize, flex, alignSelf),
165
266
  SliderStyles.container(colorPalette),
166
267
  ]}>
167
268
  {axis === 'x' && <View style={{width: halfSize}} />}
168
- <GestureDetector gesture={tapGesture}>
269
+ <GestureDetector gesture={Gesture.Simultaneous(trackPan, tapGesture)}>
169
270
  <View
170
271
  style={[
171
- styles.maximumTrack(maximumTrackWidth),
272
+ styles.maximumTrack(),
172
273
  SliderStyles.maximumTrack(colorPalette),
173
274
  ]}
174
275
  onLayout={handleTrackLayout}>
175
- {steps && (
176
- <View style={styles.steps}>
177
- {Array.from({length: steps + 1}, (_, index) => index).map(
178
- (_, i) => (
179
- <View
180
- key={i}
181
- style={[
182
- styles.step(i, steps),
183
- SliderStyles.step(colorPalette),
184
- ]}
185
- />
186
- ),
187
- )}
188
- </View>
189
- )}
190
276
  <Animated.View
191
277
  style={[
192
- styles.minimumTrack(halfSize, minimumTrackWidth),
278
+ styles.minimumTrack(halfSize),
193
279
  SliderStyles.minimumTrack(colorPalette, opacityTrack),
194
280
  animatedTrack,
195
- ]}>
196
- <GestureDetector gesture={slideGesture}>
197
- <View
198
- style={[
199
- SliderStyles.thumb(colorPalette),
200
- styles.thumb(halfSize, thumbSize),
201
- ]}>
202
- {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
+ )}
203
298
  </View>
204
- </GestureDetector>
205
- </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>
206
314
  </View>
207
315
  </GestureDetector>
208
316
  {axis === 'y' && <View style={{height: halfSize}} />}
@@ -210,114 +318,141 @@ const Slider = ({
210
318
  )
211
319
  }
212
320
 
213
- const stylesX = StyleSheet.create(theme => ({
214
- container: (thickness, halfSize) => ({
215
- flex: thickness ? 1 : 1,
216
- maxWidth: 'auto',
217
- borderRadius: 22,
218
- width: '100%',
219
- height: '100%',
220
- paddingTop: 0,
221
- paddingRight: halfSize,
222
- flexDirection: 'row',
321
+ const styles = StyleSheet.create(theme => ({
322
+ container: (
323
+ halfSize: number,
324
+ flex: ViewStyle['flex'],
325
+ alignSelf: ViewStyle['alignSelf'],
326
+ ) => ({
327
+ flex,
328
+ alignSelf,
329
+ variants: {
330
+ axis: {
331
+ x: {
332
+ maxWidth: 'auto',
333
+ paddingTop: 0,
334
+ paddingRight: halfSize,
335
+ flexDirection: 'row',
336
+ },
337
+ y: {
338
+ maxHeight: 'auto',
339
+ height: '100%',
340
+ paddingTop: halfSize,
341
+ paddingRight: 0,
342
+ flexDirection: 'column',
343
+ },
344
+ },
345
+ },
223
346
  }),
224
- maximumTrack: thickness => ({
225
- flex: thickness ? 1 : 1,
226
- justifyContent: 'center',
347
+ maximumTrack: () => ({
348
+ flex: 1,
227
349
  position: 'relative',
228
- borderRadius: 22,
350
+ overflow: 'visible',
351
+ variants: {
352
+ axis: {
353
+ x: {
354
+ justifyContent: 'center',
355
+ },
356
+ y: {
357
+ justifyContent: 'flex-end',
358
+ },
359
+ },
360
+ },
229
361
  }),
230
- minimumTrack: (halfSize, minimumTrackThickness) => ({
231
- height: minimumTrackThickness,
232
- width: '100%',
233
- left: halfSize * -1,
234
- bottom: 'auto',
235
- paddingBottom: 0,
236
- paddingLeft: halfSize,
237
- borderBottomLeftRadius: 20,
238
- borderBottomRightRadius: 0,
239
- borderTopLeftRadius: 20,
240
- justifyContent: 'center',
362
+ minimumTrack: (halfSize: number) => ({
363
+ variants: {
364
+ axis: {
365
+ x: {
366
+ flex: 1,
367
+ marginLeft: halfSize * -1,
368
+ width: '100%',
369
+ bottom: 'auto',
370
+ paddingBottom: 0,
371
+ paddingLeft: halfSize,
372
+ borderBottomLeftRadius: 20,
373
+ borderBottomRightRadius: 0,
374
+ borderTopLeftRadius: 20,
375
+ justifyContent: 'center',
376
+ alignItems: 'flex-end',
377
+ },
378
+ y: {
379
+ height: '100%',
380
+ paddingBottom: halfSize,
381
+ marginBottom: halfSize * -1,
382
+ paddingLeft: 0,
383
+ borderBottomLeftRadius: 20,
384
+ borderBottomRightRadius: 20,
385
+ borderTopLeftRadius: 0,
386
+ alignItems: 'flex-start',
387
+ },
388
+ },
389
+ },
241
390
  }),
242
- thumb: (halfSize, thumbSize) => ({
243
- width: halfSize ? thumbSize.width : thumbSize.width,
244
- height: thumbSize.height,
245
- borderRadius: 25,
391
+ thumbContainerFull: {
246
392
  position: 'absolute',
393
+ left: 0,
247
394
  right: 0,
395
+ top: 0,
396
+ bottom: 0,
248
397
  zIndex: 2,
249
- transform: [{translateX: '50%'}],
250
- }),
251
- steps: {
252
- width: '100%',
253
- height: '100%',
254
- flexDirection: 'row',
255
- justifyContent: 'space-between',
256
- alignItems: 'center',
257
- zIndex: 0,
258
- position: 'absolute',
259
398
  },
260
- step: (index, length) => ({
261
- width: 1,
262
- height: '100%',
263
- backgroundColor:
264
- index === 0 || index === length ? 'transparent' : theme.colors.fg.default,
265
- }),
266
- }))
267
- const stylesY = StyleSheet.create(theme => ({
268
- container: (maximumTrackWidth, halfSize) => ({
269
- flex: 1,
270
- maxWidth: maximumTrackWidth,
271
- maxHeight: 'auto',
272
- borderRadius: 22,
273
- width: '100%',
274
- height: '100%',
275
- paddingTop: halfSize,
276
- paddingRight: 0,
277
- flexDirection: 'column',
278
- }),
279
- maximumTrack: maximumTrackWidth => ({
280
- flex: 1,
281
- justifyContent: 'center',
282
- alignItems: 'center',
283
- position: 'relative',
284
- maxWidth: maximumTrackWidth,
285
- }),
286
- minimumTrack: (halfSize, minimumTrackWidth) => ({
287
- position: 'absolute',
288
- height: '100%',
289
- width: minimumTrackWidth,
290
- left: 'auto',
291
- bottom: halfSize * -1,
292
- paddingBottom: halfSize,
293
- paddingLeft: 0,
294
- borderBottomLeftRadius: 20,
295
- borderBottomRightRadius: 20,
296
- borderTopLeftRadius: 0,
297
- alignItems: 'center',
298
- }),
299
- thumb: (halfSize, thumbSize) => ({
399
+ thumb: halfSize => ({
300
400
  borderRadius: 25,
401
+ variants: {
402
+ axis: {
403
+ x: {
404
+ height: '100%',
405
+ justifyContent: 'center',
406
+ },
407
+ y: {
408
+ width: '100%',
409
+ alignItems: 'center',
410
+ },
411
+ },
412
+ },
413
+ }),
414
+
415
+ stepsOverlay: {
301
416
  position: 'absolute',
417
+ left: 0,
418
+ right: 0,
302
419
  top: 0,
303
- zIndex: 2,
304
- transform: [{translateY: halfSize * -1}],
305
- width: thumbSize.height,
306
- height: thumbSize.width,
307
- }),
420
+ bottom: 0,
421
+ zIndex: 1,
422
+ },
308
423
  steps: {
309
424
  width: '100%',
310
425
  height: '100%',
311
- flexDirection: 'column',
312
426
  justifyContent: 'space-between',
313
427
  alignItems: 'center',
314
428
  zIndex: 0,
429
+ position: 'absolute',
430
+ variants: {
431
+ axis: {
432
+ x: {
433
+ flexDirection: 'row',
434
+ },
435
+ y: {
436
+ flexDirection: 'column',
437
+ },
438
+ },
439
+ },
315
440
  },
316
441
  step: (index, length) => ({
317
- width: '100%',
318
- height: 1,
319
442
  backgroundColor:
320
443
  index === 0 || index === length ? 'transparent' : theme.colors.fg.default,
444
+ variants: {
445
+ axis: {
446
+ x: {
447
+ width: 1,
448
+ height: '100%',
449
+ },
450
+ y: {
451
+ width: '100%',
452
+ height: 1,
453
+ },
454
+ },
455
+ },
321
456
  }),
322
457
  }))
323
458