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
@@ -20,30 +20,21 @@ export type SlideshowRef = {
20
20
 
21
21
  type SlideshowVariants = UnistylesVariants<typeof SlideshowStyles>
22
22
 
23
+ type Axis = 'x' | 'y'
24
+
23
25
  type SlideshowProps = SlideshowVariants & {
26
+ axis?: Axis
24
27
  animation?: keyof typeof SlideshowTokens.variants.animation
25
28
  colorPalette?: PalettesWithNestedKeys
26
29
  allowGestures?: boolean
27
30
  flex?: number
28
31
  onSlideChange?: (index: number) => void
29
- slideContainerType?: any
30
32
  children: ReactNode
31
33
  ref?: React.RefObject<SlideshowRef | null>
32
34
  }
33
35
 
34
- /**
35
- * Flex Wrapper
36
- * @param colorScheme ? string: color scheme
37
- * @param animation ? string: animation props
38
- * @param allowGestures ? boolean: allow gestures
39
- * @param flex ? number: flex value
40
- * @param onSlideChange ? function: callback function
41
- * @param slideContainerType ? color scheme
42
- * @param children ? react children
43
- * @param ref ? react ref
44
- * @returns Slideshow react component
45
- */
46
36
  const Slideshow = ({
37
+ axis = 'x',
47
38
  colorPalette = 'accent',
48
39
  variant = 'solid',
49
40
  allowGestures = true,
@@ -53,6 +44,8 @@ const Slideshow = ({
53
44
  children,
54
45
  ref,
55
46
  }: SlideshowProps) => {
47
+ const isHorizontal = axis === 'x'
48
+
56
49
  const slides = Children.toArray(children)
57
50
  const NUM_PAGES = slides.length
58
51
  const scalingFactor = 1 / NUM_PAGES
@@ -62,17 +55,17 @@ const Slideshow = ({
62
55
  })
63
56
 
64
57
  const [containerWidth, setContainerWidth] = useState(0)
58
+ const [containerHeight, setContainerHeight] = useState(0)
59
+
60
+ const dimension = isHorizontal ? containerWidth : containerHeight
61
+ const slideSize = 100 / NUM_PAGES
65
62
 
66
- const slideWidth = 100 / NUM_PAGES
67
- const translateX = useSharedValue(0)
63
+ const translate = useSharedValue(0)
68
64
  const currentSlide = useSharedValue(0)
69
65
 
70
66
  type AnimationType = 'withSpring' | 'withTiming'
67
+ const animations = {withSpring, withTiming}
71
68
 
72
- const animations = {
73
- withSpring,
74
- withTiming,
75
- }
76
69
  const animationVariant =
77
70
  SlideshowTokens.variants?.animation && animation
78
71
  ? SlideshowTokens.variants.animation[animation]
@@ -88,12 +81,8 @@ const Slideshow = ({
88
81
  const animateSlide = (destination: number) => {
89
82
  'worklet'
90
83
 
91
- if (
92
- animationVariant &&
93
- animationVariant.type &&
94
- animations[animationVariant.type]
95
- ) {
96
- translateX.value = animations[animationVariant.type](
84
+ if (animationVariant.type && animations[animationVariant.type]) {
85
+ translate.value = animations[animationVariant.type](
97
86
  destination,
98
87
  animationVariant.props,
99
88
  )
@@ -107,68 +96,73 @@ const Slideshow = ({
107
96
  useAnimatedReaction(
108
97
  () => currentSlide.value,
109
98
  () => {
110
- animateSlide(-currentSlide.value * slideWidth)
99
+ animateSlide(-currentSlide.value * slideSize)
111
100
  },
112
101
  )
113
102
 
114
- const handlePrev = () => {
103
+ const nextSlide = () => {
115
104
  'worklet'
116
- if (currentSlide.value > 0) {
117
- currentSlide.value -= 1
118
- }
105
+ if (currentSlide.value > 0) currentSlide.value -= 1
119
106
  }
120
- const handleNext = () => {
107
+
108
+ const prevSlide = () => {
121
109
  'worklet'
122
- if (currentSlide.value < NUM_PAGES - 1) {
123
- currentSlide.value += 1
124
- }
110
+ if (currentSlide.value < NUM_PAGES - 1) currentSlide.value += 1
125
111
  }
126
112
 
127
- const context = useSharedValue({x: 0, snapPoint: 0})
113
+ const context = useSharedValue({x: 0})
128
114
  const velocityThreshold = 500
129
115
 
130
116
  const slideGesture = Gesture.Pan()
131
117
  .enabled(allowGestures)
132
118
  .onBegin(() => {
133
- context.value.x = translateX.value
119
+ context.value.x = translate.value
134
120
  })
135
121
  .onUpdate(e => {
136
- const pixelToPercentage =
137
- (e.translationX / containerWidth) * 100 * scalingFactor
138
- translateX.value = context.value.x + pixelToPercentage
122
+ const translation = isHorizontal ? e.translationX : e.translationY
123
+
124
+ const pixelToPercentage = (translation / dimension) * 100 * scalingFactor
125
+
126
+ translate.value = context.value.x + pixelToPercentage
139
127
  })
140
- .onEnd(({velocityX, translationX}) => {
141
- const pixelToPercentage =
142
- (translationX / containerWidth) * 100 * scalingFactor
143
- const slideWidthPercentage = 100 / NUM_PAGES
128
+ .onEnd(({velocityX, velocityY, translationX, translationY}) => {
129
+ const translation = isHorizontal ? translationX : translationY
130
+ const velocity = isHorizontal ? velocityX : velocityY
131
+
132
+ const pixelToPercentage = (translation / dimension) * 100 * scalingFactor
144
133
 
134
+ const slideWidthPercentage = 100 / NUM_PAGES
145
135
  const currentIndex = Math.round(-context.value.x / slideWidthPercentage)
146
136
 
147
137
  let targetIndex
148
- if (Math.abs(velocityX) > velocityThreshold) {
149
- targetIndex = velocityX > 0 ? currentIndex - 1 : currentIndex + 1
138
+ if (Math.abs(velocity) > velocityThreshold) {
139
+ targetIndex = velocity > 0 ? currentIndex - 1 : currentIndex + 1
150
140
  } else {
151
- const crossedThreshold =
152
- Math.abs(pixelToPercentage) > slideWidthPercentage / 2
141
+ const crossed = Math.abs(pixelToPercentage) > slideWidthPercentage / 2
142
+
153
143
  targetIndex =
154
- crossedThreshold && pixelToPercentage < 0
144
+ crossed && pixelToPercentage < 0
155
145
  ? currentIndex + 1
156
- : crossedThreshold
146
+ : crossed
157
147
  ? currentIndex - 1
158
148
  : currentIndex
159
149
  }
150
+
160
151
  targetIndex = Math.max(0, Math.min(NUM_PAGES - 1, targetIndex))
152
+
161
153
  animateSlide(-targetIndex * slideWidthPercentage)
162
154
  currentSlide.value = targetIndex
163
155
  })
164
156
 
165
- const animatedStyle = useAnimatedStyle(() => ({
166
- transform: [{translateX: `${translateX.value}%`}],
167
- }))
157
+ const animatedStyle = useAnimatedStyle(() =>
158
+ isHorizontal
159
+ ? {transform: [{translateX: `${translate.value}%`}]}
160
+ : {transform: [{translateY: `${translate.value}%`}]},
161
+ )
168
162
 
169
163
  useImperativeHandle(ref, () => ({
170
- handlePrev,
171
- handleNext,
164
+ handlePrev: nextSlide,
165
+ handleNext: prevSlide,
172
166
  }))
173
167
 
174
168
  return (
@@ -176,17 +170,20 @@ const Slideshow = ({
176
170
  <GestureDetector gesture={slideGesture}>
177
171
  <View
178
172
  style={[styles.container(), SlideshowStyles.container(colorPalette)]}
179
- onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}>
173
+ onLayout={e => {
174
+ setContainerWidth(e.nativeEvent.layout.width)
175
+ setContainerHeight(e.nativeEvent.layout.height)
176
+ }}>
180
177
  <Animated.View
181
178
  style={[
182
- styles.slidesContainer(`${NUM_PAGES * 100}%`),
179
+ styles.slidesContainer(`${NUM_PAGES * 100}%`, isHorizontal),
183
180
  animatedStyle,
184
181
  ]}>
185
182
  {slides.map((Slide, index) => (
186
183
  <View
187
184
  key={index}
188
185
  style={[
189
- styles.slide(`${slideWidth}%`),
186
+ styles.slide(`${slideSize}%`, isHorizontal),
190
187
  SlideshowStyles.slideContainer(colorPalette),
191
188
  ]}>
192
189
  {Slide}
@@ -206,6 +203,7 @@ const styles = StyleSheet.create({
206
203
  flex,
207
204
  flexDirection: 'row',
208
205
  }),
206
+
209
207
  container: () => ({
210
208
  width: '100%',
211
209
  height: '100%',
@@ -213,14 +211,18 @@ const styles = StyleSheet.create({
213
211
  justifyContent: 'flex-start',
214
212
  alignItems: 'flex-start',
215
213
  }),
216
- slidesContainer: width => ({
217
- flexDirection: 'row',
218
- width,
214
+
215
+ slidesContainer: (size, isHorizontal) => ({
216
+ flexDirection: isHorizontal ? 'row' : 'column',
217
+ width: isHorizontal ? size : '100%',
218
+ minHeight: isHorizontal ? '100%' : size,
219
219
  flex: 1,
220
220
  }),
221
- slide: width => ({
222
- width,
223
- flex: 1,
221
+
222
+ slide: (size, isHorizontal) => ({
223
+ overflow: 'hidden',
224
+ width: isHorizontal ? size : '100%',
225
+ height: isHorizontal ? '100%' : size,
224
226
  justifyContent: 'center',
225
227
  alignItems: 'center',
226
228
  }),
@@ -8,13 +8,13 @@ import Animated, {
8
8
  withTiming,
9
9
  type SharedValue,
10
10
  } from 'react-native-reanimated'
11
- import {runOnJS} from 'react-native-worklets'
11
+ import {scheduleOnRN} from 'react-native-worklets'
12
12
  import {
13
13
  SlidingDrawerTokens,
14
14
  SlidingDrawerStyles,
15
15
  } from '../theme/SlidingDrawer.recipe'
16
16
  import {PalettesWithNestedKeys} from '../style/varia/types'
17
- import {TouchableWithoutFeedback} from 'react-native'
17
+ import {TouchableWithoutFeedback, ViewStyle} from 'react-native'
18
18
 
19
19
  export type SlidingDrawerRef = {
20
20
  snapTo: (point: number) => void | null
@@ -73,7 +73,7 @@ const SlidingDrawer = ({
73
73
  const translate = useSharedValue(points[0])
74
74
 
75
75
  const context: SharedValue<{
76
- position: any
76
+ position: number
77
77
  snapPoint: number
78
78
  }> = useSharedValue({
79
79
  position: points[0],
@@ -106,7 +106,7 @@ const SlidingDrawer = ({
106
106
  animationVariant.props,
107
107
  )
108
108
  updateCurrentSnapPoint(destination)
109
- onSnap && runOnJS(onSnap)(destination)
109
+ onSnap && scheduleOnRN(onSnap, destination)
110
110
  }
111
111
  }
112
112
 
@@ -115,7 +115,7 @@ const SlidingDrawer = ({
115
115
  if (overlay) {
116
116
  showOverlay()
117
117
  }
118
- onExpand && runOnJS(onExpand)()
118
+ onExpand && scheduleOnRN(onExpand)
119
119
  }
120
120
 
121
121
  const isCollapsed = () => {
@@ -128,7 +128,7 @@ const SlidingDrawer = ({
128
128
  if (overlay) {
129
129
  hideOverlay()
130
130
  }
131
- onCollapse && runOnJS(onCollapse)()
131
+ onCollapse && scheduleOnRN(onCollapse)
132
132
  }
133
133
 
134
134
  const hideOverlay = () => {
@@ -164,10 +164,8 @@ const SlidingDrawer = ({
164
164
 
165
165
  let clamped = proposed
166
166
 
167
- // Si se pasa del rango, aplicamos resistencia
168
167
  if (proposed < minPoint) {
169
168
  const overdrag = minPoint - proposed
170
- // Cuanto mayor sea el exceso, menos se mueve (efecto pesado)
171
169
  clamped = minPoint - overdrag / (1 + overdrag / 60)
172
170
  } else if (proposed > maxPoint) {
173
171
  const overdrag = proposed - maxPoint
@@ -175,7 +173,6 @@ const SlidingDrawer = ({
175
173
  }
176
174
 
177
175
  translate.value = clamped
178
- // translate.value = delta + (context.value.position ?? 0)
179
176
  })
180
177
  .onEnd(({velocityX, velocityY, translationX, translationY}) => {
181
178
  const velocity = axis === 'y' ? velocityY : velocityX
@@ -184,7 +181,6 @@ const SlidingDrawer = ({
184
181
  const minPoint = Math.min(...points)
185
182
  const maxPoint = Math.max(...points)
186
183
 
187
- // 🧲 1. Si está fuera del rango, lo regresamos suavemente
188
184
  if (translate.value < minPoint) {
189
185
  translate.value = withSpring(minPoint, {velocity})
190
186
  return
@@ -193,7 +189,6 @@ const SlidingDrawer = ({
193
189
  return
194
190
  }
195
191
 
196
- // 🧭 2. Lógica original de snapping
197
192
  const forwardsThreshold =
198
193
  (points[context.value.snapPoint] +
199
194
  points[context.value.snapPoint + 1]) /
@@ -238,18 +233,22 @@ const SlidingDrawer = ({
238
233
  })
239
234
 
240
235
  const blockAnimatedStyle = useAnimatedStyle(() => {
241
- return {
242
- transform: [
243
- {[axis === 'y' ? 'translateY' : 'translateX']: translate.value},
244
- ],
245
- } as any
236
+ if (axis === 'y') {
237
+ return {
238
+ transform: [{translateY: translate.value}],
239
+ }
240
+ } else {
241
+ return {
242
+ transform: [{translateX: translate.value}],
243
+ }
244
+ }
246
245
  })
247
246
 
248
247
  const displayOverlayStyle = useAnimatedStyle(() => {
249
248
  return {
250
- display: changeDisplay.value,
249
+ display: changeDisplay.value as ViewStyle['display'],
251
250
  opacity: opacity.value,
252
- } as any
251
+ }
253
252
  })
254
253
 
255
254
  return (
@@ -261,7 +260,7 @@ const SlidingDrawer = ({
261
260
  style={[
262
261
  StyleSheet.absoluteFillObject,
263
262
  displayOverlayStyle,
264
- SlidingDrawerStyles.backdrop(colorPalette),
263
+ SlidingDrawerStyles.overlay(colorPalette),
265
264
  ]}
266
265
  />
267
266
  </AnimatedTouchableOpacity>
@@ -271,7 +270,7 @@ const SlidingDrawer = ({
271
270
  style={[
272
271
  styles.container(flex, direction, axis, width),
273
272
  blockAnimatedStyle,
274
- SlidingDrawerStyles.container(colorPalette),
273
+ SlidingDrawerStyles.slider(colorPalette),
275
274
  ]}>
276
275
  {children}
277
276
  </Animated.View>
@@ -282,7 +281,7 @@ const SlidingDrawer = ({
282
281
  export default SlidingDrawer
283
282
 
284
283
  const styles = StyleSheet.create({
285
- container: (flex, direction, axis, width): any => ({
284
+ container: (flex, direction, axis, width) => ({
286
285
  position: flex === 0 ? 'absolute' : 'relative',
287
286
  bottom: axis === 'y' && direction === -1 ? 0 : 'auto',
288
287
  top: axis === 'y' && direction === 1 ? 0 : 'auto',
@@ -5,9 +5,13 @@ import {
5
5
  type ViewStyle,
6
6
  } from 'react-native'
7
7
  import {withUnistyles, UnistylesVariants} from 'react-native-unistyles'
8
- import {PalettesWithNestedKeys, ThemeColors} from '../style/varia/types'
8
+ import {
9
+ NestedColorsType,
10
+ PalettesWithNestedKeys,
11
+ ThemeColors,
12
+ } from '../style/varia/types'
9
13
  import {SpinnerTokens} from '../theme/Spinner.recipe'
10
- import {resolveColor} from '../style/varia/utils'
14
+ import {getVariantValue, resolveColor} from '../style/varia/utils'
11
15
  import {SpinnerStyles} from '../theme/Spinner.recipe'
12
16
 
13
17
  type SpinnerVariants = UnistylesVariants<typeof SpinnerStyles>
@@ -16,7 +20,7 @@ export type SpinnerProps = SpinnerVariants &
16
20
  color?: ThemeColors
17
21
  style?: StyleProp<ViewStyle>
18
22
  mixins?: StyleProp<ViewStyle> | StyleProp<ViewStyle>[]
19
- colors: any
23
+ colors: NestedColorsType
20
24
  colorPalette?: PalettesWithNestedKeys
21
25
  }
22
26
 
@@ -30,15 +34,19 @@ const BaseSpinner = ({
30
34
  ...props
31
35
  }: SpinnerProps) => {
32
36
  const resolvedColor = resolveColor(color, colors, colorPalette)
33
-
37
+
34
38
  SpinnerStyles.useVariants({
35
39
  size,
36
40
  })
37
41
 
42
+ // console.log('color', color)
43
+
44
+ const resolvedSize = getVariantValue(SpinnerStyles.base, 'size', size, 'width')
45
+
38
46
  return (
39
47
  <ActivityIndicator
40
48
  color={resolvedColor}
41
- size={SpinnerStyles.base.width}
49
+ size={resolvedSize}
42
50
  {...props}
43
51
  style={[style && style, mixins && mixins]}
44
52
  />
@@ -0,0 +1,89 @@
1
+ import React, {useEffect} from 'react'
2
+ import {StyleSheet} from 'react-native'
3
+ import Animated, {
4
+ useSharedValue,
5
+ useAnimatedStyle,
6
+ withTiming,
7
+ Easing,
8
+ } from 'react-native-reanimated'
9
+ import {scheduleOnRN} from 'react-native-worklets'
10
+ import {ToastStyles, ToastDefaultVariants} from '../theme/Toast.recipe'
11
+ import {UnistylesVariants} from 'react-native-unistyles'
12
+ import {PalettesWithNestedKeys} from '../style/varia/types'
13
+ import Text from './Text'
14
+
15
+ type ToastVariants = UnistylesVariants<typeof ToastStyles>
16
+ type ToastProps = ToastVariants & {
17
+ colorPalette?: PalettesWithNestedKeys
18
+ message: string
19
+ duration?: number
20
+ onClose?: () => void
21
+ }
22
+
23
+ const Toast: React.FC<ToastProps> = ({
24
+ colorPalette = 'accent',
25
+ variant = ToastDefaultVariants.variant,
26
+ size = ToastDefaultVariants.size,
27
+ message,
28
+ duration = 5000,
29
+ onClose,
30
+ }) => {
31
+ ToastStyles.useVariants({
32
+ variant,
33
+ size,
34
+ })
35
+ const opacity = useSharedValue(0)
36
+ const translateY = useSharedValue(50) // Aparece desde abajo
37
+
38
+ const animatedStyle = useAnimatedStyle(() => ({
39
+ opacity: opacity.value,
40
+ transform: [{translateY: translateY.value}],
41
+ }))
42
+
43
+ useEffect(() => {
44
+ // Animación de entrada
45
+ opacity.value = withTiming(1, {
46
+ duration: 200,
47
+ easing: Easing.out(Easing.ease),
48
+ })
49
+ translateY.value = withTiming(0, {
50
+ duration: 200,
51
+ easing: Easing.out(Easing.ease),
52
+ })
53
+
54
+ // Animación de salida
55
+ const timeout = setTimeout(() => {
56
+ opacity.value = withTiming(0, {duration: 300})
57
+ translateY.value = withTiming(50, {duration: 300}, () => {
58
+ if (onClose) scheduleOnRN(onClose)
59
+ })
60
+ }, duration)
61
+
62
+ return () => clearTimeout(timeout)
63
+ }, [])
64
+
65
+ return (
66
+ <Animated.View
67
+ style={[
68
+ styles.container,
69
+ animatedStyle,
70
+ ToastStyles.container(colorPalette),
71
+ ]}>
72
+ <Text style={ToastStyles.text(colorPalette)}>{message}</Text>
73
+ </Animated.View>
74
+ )
75
+ }
76
+
77
+ const styles = StyleSheet.create({
78
+ container: {
79
+ position: 'absolute',
80
+ bottom: 50,
81
+ // left: 20,
82
+ // right: 20,
83
+ padding: 15,
84
+ borderRadius: 8,
85
+ alignItems: 'center',
86
+ },
87
+ })
88
+
89
+ export default Toast
@@ -0,0 +1,27 @@
1
+ import {createContext, useContext} from 'react'
2
+ import {UnistylesVariants} from 'react-native-unistyles'
3
+ import {FieldStyles} from '../../theme/Field.recipe'
4
+ import {PalettesWithNestedKeys} from '../../style/varia/types'
5
+
6
+ export type FieldVariants = UnistylesVariants<typeof FieldStyles>
7
+
8
+ export type FieldContextType = {
9
+ error?: string
10
+ variant?: FieldVariants['variant']
11
+ size?: FieldVariants['size']
12
+ colorPalette?: PalettesWithNestedKeys
13
+ }
14
+
15
+ const FieldContext = createContext<FieldContextType | undefined>(undefined)
16
+
17
+ export function useField() {
18
+ const context = useContext(FieldContext)
19
+ if (!context) {
20
+ throw new Error(
21
+ 'Field subcomponents (Label, Error) must be used inside Field.Root',
22
+ )
23
+ }
24
+ return context
25
+ }
26
+
27
+ export default FieldContext
@@ -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