react-native-varia 0.3.0 → 0.4.1
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/components/Accordion.tsx +61 -21
- package/lib/components/Badge.tsx +9 -0
- package/lib/components/Button.tsx +19 -8
- package/lib/components/Checkbox.tsx +21 -12
- package/lib/components/CircleProgress.tsx +8 -0
- package/lib/components/Divider.tsx +6 -0
- package/lib/components/Drawer.tsx +16 -4
- package/lib/components/Field.tsx +4 -0
- package/lib/components/FloatingAction.tsx +215 -0
- package/lib/components/GradientBackground.tsx +3 -0
- package/lib/components/GradientText.tsx +27 -14
- package/lib/components/IconWrapper.tsx +4 -3
- package/lib/components/Input.tsx +47 -21
- package/lib/components/Link.tsx +4 -0
- package/lib/components/Modal.tsx +18 -5
- package/lib/components/NumberInput.tsx +27 -5
- package/lib/components/RadioGroup.tsx +16 -5
- package/lib/components/ReText.tsx +4 -1
- package/lib/components/Select.tsx +20 -0
- package/lib/components/Slider.tsx +59 -23
- package/lib/components/Slideshow.tsx +19 -3
- package/lib/components/Spinner.tsx +9 -3
- package/lib/components/Switch.tsx +57 -26
- package/lib/components/Text.tsx +3 -0
- package/lib/components/Toast.tsx +110 -36
- package/lib/patterns/index.tsx +299 -90
- package/lib/theme/Accordion.tsx +184 -0
- package/lib/theme/Button.recipe.tsx +24 -7
- package/lib/theme/Drawer.recipe.tsx +2 -4
- package/lib/theme/Field.recipe.tsx +45 -15
- package/lib/theme/FloatingAction.tsx +112 -0
- package/lib/theme/GradientText.recipe.tsx +103 -34
- package/lib/theme/Input.recipe.tsx +14 -6
- package/lib/theme/Select.recipe.tsx +3 -0
- package/lib/theme/Slider.recipe.tsx +86 -150
- package/lib/theme/Spinner.recipe.tsx +4 -0
- package/lib/theme/Switch.recipe.tsx +19 -0
- package/lib/theme/Text.recipe.tsx +63 -12
- package/lib/theme/Toast.recipe.tsx +40 -7
- package/lib/varia/types.ts +3 -0
- package/lib/varia/utils.ts +110 -18
- package/package.json +1 -1
- package/lib/components/OldSlider.tsx +0 -327
- package/lib/components/SlidingDrawer.tsx +0 -301
- package/lib/patterns/newPatterns.tsx +0 -285
|
@@ -2,10 +2,16 @@ import React, {ReactNode, useState} from 'react'
|
|
|
2
2
|
import {View, Pressable} from 'react-native'
|
|
3
3
|
import Animated from 'react-native-reanimated'
|
|
4
4
|
import {LinearTransition} from 'react-native-reanimated'
|
|
5
|
-
import {StyleSheet} from 'react-native-unistyles'
|
|
5
|
+
import {StyleSheet, UnistylesVariants} from 'react-native-unistyles'
|
|
6
6
|
import Text from './Text'
|
|
7
|
+
import {AccordionStyles, AccordionDefaultVariants} from '../theme/Accordion'
|
|
8
|
+
import {AlignSelf, Flex, PalettesWithNestedKeys} from '../style/varia/types'
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
const AnimatedView = Animated.createAnimatedComponent(View)
|
|
11
|
+
type AccordionVariants = UnistylesVariants<typeof AccordionStyles>
|
|
12
|
+
|
|
13
|
+
type AccordionItemProps = AccordionVariants & {
|
|
14
|
+
colorPalette?: PalettesWithNestedKeys
|
|
9
15
|
title: string
|
|
10
16
|
itemKey: string
|
|
11
17
|
children: ReactNode
|
|
@@ -14,10 +20,12 @@ type AccordionItemProps = {
|
|
|
14
20
|
scrollViewRef?: any
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
type AccordionGroupRootProps = {
|
|
23
|
+
type AccordionGroupRootProps = AccordionVariants & {
|
|
18
24
|
children: ReactNode
|
|
19
25
|
defaultOpenKeys?: string[]
|
|
20
26
|
allowMultiple?: boolean
|
|
27
|
+
flex?: Flex
|
|
28
|
+
alignSelf?: AlignSelf
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export const AccordionGroup = {
|
|
@@ -26,9 +34,13 @@ export const AccordionGroup = {
|
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
function AccordionGroupRoot({
|
|
37
|
+
variant = AccordionDefaultVariants.variant,
|
|
38
|
+
size = AccordionDefaultVariants.size,
|
|
29
39
|
children,
|
|
30
40
|
defaultOpenKeys = [],
|
|
31
41
|
allowMultiple = false,
|
|
42
|
+
flex = 1,
|
|
43
|
+
alignSelf = 'auto',
|
|
32
44
|
}: AccordionGroupRootProps) {
|
|
33
45
|
const [openKeys, setOpenKeys] = useState<Set<string>>(
|
|
34
46
|
() => new Set(defaultOpenKeys),
|
|
@@ -52,31 +64,43 @@ function AccordionGroupRoot({
|
|
|
52
64
|
|
|
53
65
|
const items = React.Children.map(children, child => {
|
|
54
66
|
if (!React.isValidElement(child)) return child
|
|
55
|
-
|
|
56
|
-
const
|
|
67
|
+
|
|
68
|
+
const element = child as React.ReactElement<AccordionItemProps>
|
|
69
|
+
const key = element.props.itemKey
|
|
57
70
|
const isOpen = openKeys.has(key)
|
|
58
|
-
|
|
71
|
+
|
|
72
|
+
return React.cloneElement(element, {
|
|
59
73
|
isOpen,
|
|
74
|
+
variant,
|
|
75
|
+
size,
|
|
60
76
|
onToggle: () => toggleItem(key),
|
|
61
77
|
})
|
|
62
78
|
})
|
|
63
79
|
|
|
64
|
-
return <View style={styles.groupContainer}>{items}</View>
|
|
80
|
+
return <View style={styles.groupContainer(flex, alignSelf)}>{items}</View>
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
function AccordionGroupItem({
|
|
84
|
+
variant = AccordionDefaultVariants.variant,
|
|
85
|
+
size = AccordionDefaultVariants.size,
|
|
86
|
+
colorPalette = 'accent',
|
|
68
87
|
title,
|
|
69
88
|
children,
|
|
70
89
|
isOpen = false,
|
|
71
90
|
onToggle,
|
|
72
91
|
}: AccordionItemProps) {
|
|
92
|
+
AccordionStyles.useVariants({variant, size, active: isOpen})
|
|
73
93
|
return (
|
|
74
|
-
<
|
|
75
|
-
// Aquí aplicas la transición de layout
|
|
94
|
+
<AnimatedView
|
|
76
95
|
layout={LinearTransition.duration(150)}
|
|
77
|
-
style={
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
style={[
|
|
97
|
+
styles.itemContainer,
|
|
98
|
+
AccordionStyles.itemContainer(colorPalette),
|
|
99
|
+
]}>
|
|
100
|
+
<Pressable
|
|
101
|
+
onPress={onToggle}
|
|
102
|
+
style={[styles.header, AccordionStyles.header(colorPalette)]}>
|
|
103
|
+
<Text style={AccordionStyles.headerTitle(colorPalette)}>{title}</Text>
|
|
80
104
|
</Pressable>
|
|
81
105
|
|
|
82
106
|
{isOpen && (
|
|
@@ -84,30 +108,46 @@ function AccordionGroupItem({
|
|
|
84
108
|
<View style={styles.innerContent}>{children}</View>
|
|
85
109
|
</View>
|
|
86
110
|
)}
|
|
87
|
-
</
|
|
111
|
+
</AnimatedView>
|
|
88
112
|
)
|
|
89
113
|
}
|
|
90
114
|
|
|
91
115
|
const styles = StyleSheet.create({
|
|
92
|
-
groupContainer: {
|
|
116
|
+
groupContainer: (flex: Flex, alignSelf: AlignSelf) => ({
|
|
117
|
+
flex,
|
|
118
|
+
alignSelf,
|
|
119
|
+
flexBasis: 'auto',
|
|
120
|
+
_web: {
|
|
121
|
+
_classNames: 'accordion-group-container-base',
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
93
124
|
itemContainer: {
|
|
94
125
|
borderWidth: 1,
|
|
95
|
-
borderColor: '#ddd',
|
|
96
126
|
borderRadius: 6,
|
|
97
127
|
marginVertical: 8,
|
|
98
|
-
overflow: 'hidden',
|
|
128
|
+
overflow: 'hidden',
|
|
129
|
+
_web: {
|
|
130
|
+
_classNames: 'accordion-item-container-base',
|
|
131
|
+
},
|
|
99
132
|
},
|
|
100
133
|
header: {
|
|
101
|
-
|
|
102
|
-
|
|
134
|
+
justifyContent: 'center',
|
|
135
|
+
_web: {
|
|
136
|
+
_classNames: 'accordion-header-base',
|
|
137
|
+
},
|
|
103
138
|
},
|
|
104
139
|
headerText: {
|
|
105
140
|
fontSize: 16,
|
|
141
|
+
color: 'blue',
|
|
142
|
+
_web: {
|
|
143
|
+
_classNames: 'accordion-header-text-base',
|
|
144
|
+
},
|
|
106
145
|
},
|
|
107
|
-
contentContainer: {
|
|
108
|
-
// no necesitamos animar altura manual, lo hace el layout
|
|
109
|
-
},
|
|
146
|
+
contentContainer: {},
|
|
110
147
|
innerContent: {
|
|
111
148
|
padding: 12,
|
|
149
|
+
_web: {
|
|
150
|
+
_classNames: 'accordion-item-content-base',
|
|
151
|
+
},
|
|
112
152
|
},
|
|
113
153
|
})
|
package/lib/components/Badge.tsx
CHANGED
|
@@ -80,6 +80,9 @@ const styles = StyleSheet.create({
|
|
|
80
80
|
borderWidth: 1,
|
|
81
81
|
alignItems: 'center',
|
|
82
82
|
justifyContent: 'center',
|
|
83
|
+
_web: {
|
|
84
|
+
_classNames: 'badge-base',
|
|
85
|
+
},
|
|
83
86
|
},
|
|
84
87
|
badgeAbsolute: (x, y) => ({
|
|
85
88
|
position: 'absolute',
|
|
@@ -89,9 +92,15 @@ const styles = StyleSheet.create({
|
|
|
89
92
|
{translateY: y === 'top' ? '-50%' : '50%'},
|
|
90
93
|
{translateX: x === 'left' ? '-50%' : '50%'},
|
|
91
94
|
],
|
|
95
|
+
_web: {
|
|
96
|
+
_classNames: 'badge-absolute-base',
|
|
97
|
+
},
|
|
92
98
|
}),
|
|
93
99
|
text: {
|
|
94
100
|
textAlign: 'center',
|
|
101
|
+
_web: {
|
|
102
|
+
_classNames: 'badge-text-base',
|
|
103
|
+
},
|
|
95
104
|
},
|
|
96
105
|
customStyle: style => ({
|
|
97
106
|
...style,
|
|
@@ -5,7 +5,7 @@ import {useMemo} from 'react'
|
|
|
5
5
|
import {IconWrapperProps} from './IconWrapper'
|
|
6
6
|
import Spinner, {SpinnerProps} from './Spinner'
|
|
7
7
|
import {PalettesWithNestedKeys} from '../style/varia/types'
|
|
8
|
-
import {
|
|
8
|
+
import {getVariantValue} from '../style/varia/utils'
|
|
9
9
|
|
|
10
10
|
type TextAdjustment = 'singleLine' | 'multiline' | 'adjustToFit'
|
|
11
11
|
|
|
@@ -23,6 +23,7 @@ type ButtonProps = ButtonVariants & {
|
|
|
23
23
|
onPress: () => void
|
|
24
24
|
loading?: boolean
|
|
25
25
|
disabled?: boolean
|
|
26
|
+
activeOpacity?: number
|
|
26
27
|
maxWidth?: number | string
|
|
27
28
|
flex?: number
|
|
28
29
|
textAdjustment?: TextAdjustment
|
|
@@ -41,6 +42,7 @@ const Button = ({
|
|
|
41
42
|
colorPalette = 'accent',
|
|
42
43
|
size = ButtonDefaultVariants.size,
|
|
43
44
|
variant = ButtonDefaultVariants.variant,
|
|
45
|
+
activeOpacity = 0.6,
|
|
44
46
|
onPress,
|
|
45
47
|
text,
|
|
46
48
|
loading = false,
|
|
@@ -57,7 +59,6 @@ const Button = ({
|
|
|
57
59
|
variant,
|
|
58
60
|
disabled,
|
|
59
61
|
})
|
|
60
|
-
console.log("🚀 ~ size:", size)
|
|
61
62
|
|
|
62
63
|
const getTextProps = useMemo(
|
|
63
64
|
() => (): TextAdjustmentProps => {
|
|
@@ -78,7 +79,12 @@ const Button = ({
|
|
|
78
79
|
[textAdjustment],
|
|
79
80
|
)
|
|
80
81
|
|
|
81
|
-
const resolvedColor = getVariantValue(
|
|
82
|
+
const resolvedColor = getVariantValue(
|
|
83
|
+
ButtonStyles.text(colorPalette),
|
|
84
|
+
'variant',
|
|
85
|
+
variant,
|
|
86
|
+
'color',
|
|
87
|
+
)
|
|
82
88
|
|
|
83
89
|
const IconComponent = icon?.component
|
|
84
90
|
const IconRendered = IconComponent ? (
|
|
@@ -95,6 +101,8 @@ const Button = ({
|
|
|
95
101
|
|
|
96
102
|
return (
|
|
97
103
|
<TouchableOpacity
|
|
104
|
+
role="button"
|
|
105
|
+
activeOpacity={activeOpacity}
|
|
98
106
|
style={[
|
|
99
107
|
styles.container(flex, maxWidth),
|
|
100
108
|
ButtonStyles.container(colorPalette),
|
|
@@ -104,10 +112,7 @@ const Button = ({
|
|
|
104
112
|
onPress={onPress}
|
|
105
113
|
disabled={disabled || loading}>
|
|
106
114
|
{loading ? (
|
|
107
|
-
<Spinner
|
|
108
|
-
size={size as SpinnerProps['size']}
|
|
109
|
-
color={resolvedColor}
|
|
110
|
-
/>
|
|
115
|
+
<Spinner size={size as SpinnerProps['size']} color={resolvedColor} />
|
|
111
116
|
) : (
|
|
112
117
|
<>
|
|
113
118
|
{icon?.position === 'left' && IconRendered}
|
|
@@ -133,10 +138,16 @@ const styles = StyleSheet.create({
|
|
|
133
138
|
flexDirection: 'row',
|
|
134
139
|
justifyContent: 'center',
|
|
135
140
|
alignItems: 'center',
|
|
136
|
-
|
|
141
|
+
flexBasis: 'auto',
|
|
142
|
+
_web: {
|
|
143
|
+
_classNames: 'button-container-base',
|
|
144
|
+
},
|
|
137
145
|
}),
|
|
138
146
|
text: {
|
|
139
147
|
textAlign: 'center',
|
|
148
|
+
_web: {
|
|
149
|
+
_classNames: 'button-text-base',
|
|
150
|
+
},
|
|
140
151
|
},
|
|
141
152
|
})
|
|
142
153
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React, {useCallback} from 'react'
|
|
2
|
-
import {View, Text, Pressable
|
|
2
|
+
import {View, Text, Pressable} from 'react-native'
|
|
3
|
+
import {StyleSheet} from 'react-native-unistyles'
|
|
3
4
|
import {UnistylesVariants} from 'react-native-unistyles'
|
|
4
5
|
import {CheckboxDefaultVariants, CheckboxStyles} from '../theme/Checkbox.recipe'
|
|
5
6
|
import {PalettesWithNestedKeys} from '../style/varia/types'
|
|
6
7
|
import Svg, {Line} from 'react-native-svg'
|
|
7
|
-
import {
|
|
8
|
+
import {getVariantValue} from '../style/varia/utils'
|
|
8
9
|
|
|
9
10
|
type CheckboxVariants = UnistylesVariants<typeof CheckboxStyles>
|
|
10
11
|
|
|
@@ -32,16 +33,18 @@ function Checkbox({
|
|
|
32
33
|
|
|
33
34
|
const isChecked = checked
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
const checkColor = getVariantValue(
|
|
37
|
+
CheckboxStyles.check(colorPalette),
|
|
38
|
+
'variant',
|
|
39
|
+
variant,
|
|
40
|
+
'color',
|
|
41
|
+
)
|
|
42
|
+
const checkSize = getVariantValue(
|
|
43
|
+
CheckboxStyles.check(colorPalette),
|
|
44
|
+
'size',
|
|
45
|
+
size,
|
|
46
|
+
'width',
|
|
47
|
+
)
|
|
45
48
|
|
|
46
49
|
return (
|
|
47
50
|
<Pressable
|
|
@@ -63,10 +66,16 @@ const styles = StyleSheet.create({
|
|
|
63
66
|
container: {
|
|
64
67
|
flexDirection: 'row',
|
|
65
68
|
alignItems: 'center',
|
|
69
|
+
_web: {
|
|
70
|
+
_classNames: 'checkbox-container-base',
|
|
71
|
+
},
|
|
66
72
|
},
|
|
67
73
|
checkContainer: {
|
|
68
74
|
alignItems: 'center',
|
|
69
75
|
justifyContent: 'center',
|
|
76
|
+
_web: {
|
|
77
|
+
_classNames: 'badge-check-container-base',
|
|
78
|
+
},
|
|
70
79
|
},
|
|
71
80
|
})
|
|
72
81
|
|
|
@@ -163,10 +163,18 @@ const styles = StyleSheet.create({
|
|
|
163
163
|
flex,
|
|
164
164
|
maxWidth: width,
|
|
165
165
|
maxHeight: height,
|
|
166
|
+
flexBasis: 'auto',
|
|
167
|
+
_web: {
|
|
168
|
+
_classNames: 'circle-progress-container',
|
|
169
|
+
},
|
|
166
170
|
}),
|
|
167
171
|
childContainer: {
|
|
172
|
+
flexBasis: 'auto',
|
|
168
173
|
justifyContent: 'center',
|
|
169
174
|
alignItems: 'center',
|
|
175
|
+
_web: {
|
|
176
|
+
_classNames: 'circle-progress-child-container',
|
|
177
|
+
},
|
|
170
178
|
},
|
|
171
179
|
})
|
|
172
180
|
|
|
@@ -34,6 +34,9 @@ const styles = StyleSheet.create(theme => ({
|
|
|
34
34
|
maxHeight: axis === 'x' ? size : '100%',
|
|
35
35
|
marginHorizontal: axis === 'x' ? margin : 0,
|
|
36
36
|
marginVertical: axis === 'y' ? margin : 0,
|
|
37
|
+
_web: {
|
|
38
|
+
_classNames: 'divider-container-base',
|
|
39
|
+
},
|
|
37
40
|
}),
|
|
38
41
|
divider: (color, colorPalette, size, axis) => ({
|
|
39
42
|
width: axis === 'y' ? size : '100%',
|
|
@@ -41,6 +44,9 @@ const styles = StyleSheet.create(theme => ({
|
|
|
41
44
|
backgroundColor: color
|
|
42
45
|
? resolveColor(color, theme.colors, colorPalette)
|
|
43
46
|
: theme.colors.fg.default,
|
|
47
|
+
_web: {
|
|
48
|
+
_classNames: 'divider-base',
|
|
49
|
+
},
|
|
44
50
|
}),
|
|
45
51
|
}))
|
|
46
52
|
|
|
@@ -4,7 +4,7 @@ import React, {
|
|
|
4
4
|
useImperativeHandle,
|
|
5
5
|
useMemo,
|
|
6
6
|
} from 'react'
|
|
7
|
-
import {TouchableWithoutFeedback} from 'react-native'
|
|
7
|
+
import {Platform, TouchableWithoutFeedback, View} from 'react-native'
|
|
8
8
|
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
|
|
9
9
|
import Animated, {
|
|
10
10
|
measure,
|
|
@@ -145,6 +145,7 @@ const DrawerPositioner = ({
|
|
|
145
145
|
const AnimatedTouchable = Animated.createAnimatedComponent(
|
|
146
146
|
TouchableWithoutFeedback,
|
|
147
147
|
)
|
|
148
|
+
const AnimatedView = Animated.createAnimatedComponent(View)
|
|
148
149
|
|
|
149
150
|
const DrawerOverlay = ({onPress}: {onPress?: () => void}) => {
|
|
150
151
|
const {colorPalette, overlayOpacity, variant} = useDrawer()
|
|
@@ -169,11 +170,12 @@ const DrawerOverlay = ({onPress}: {onPress?: () => void}) => {
|
|
|
169
170
|
|
|
170
171
|
return (
|
|
171
172
|
<AnimatedTouchable onPress={onPress}>
|
|
172
|
-
<
|
|
173
|
+
<AnimatedView
|
|
173
174
|
style={[
|
|
174
175
|
StyleSheet.absoluteFillObject,
|
|
175
176
|
displayOverlayStyle,
|
|
176
177
|
DrawerStyles.overlay(colorPalette),
|
|
178
|
+
{pointerEvents: 'auto'},
|
|
177
179
|
]}
|
|
178
180
|
/>
|
|
179
181
|
</AnimatedTouchable>
|
|
@@ -452,16 +454,17 @@ const DrawerSliderInternal = ({
|
|
|
452
454
|
|
|
453
455
|
return (
|
|
454
456
|
<GestureDetector gesture={slideGesture}>
|
|
455
|
-
<
|
|
457
|
+
<AnimatedView
|
|
456
458
|
ref={viewRef}
|
|
457
459
|
onLayout={onLayout}
|
|
458
460
|
style={[
|
|
459
461
|
styles.slider,
|
|
460
462
|
blockAnimatedStyle,
|
|
461
463
|
DrawerStyles.slider(colorPalette),
|
|
464
|
+
{pointerEvents: 'auto'},
|
|
462
465
|
]}>
|
|
463
466
|
{children}
|
|
464
|
-
</
|
|
467
|
+
</AnimatedView>
|
|
465
468
|
</GestureDetector>
|
|
466
469
|
)
|
|
467
470
|
}
|
|
@@ -482,6 +485,12 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
482
485
|
left: 0,
|
|
483
486
|
right: 0,
|
|
484
487
|
bottom: 0,
|
|
488
|
+
pointerEvents: 'box-none',
|
|
489
|
+
_web: {
|
|
490
|
+
pointerEvents: 'none',
|
|
491
|
+
overflow: 'hidden',
|
|
492
|
+
_classNames: 'drawer-positioner-base',
|
|
493
|
+
},
|
|
485
494
|
},
|
|
486
495
|
slider: {
|
|
487
496
|
flex: 1,
|
|
@@ -489,5 +498,8 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
489
498
|
alignItems: 'center',
|
|
490
499
|
justifyContent: 'flex-start',
|
|
491
500
|
zIndex: 100,
|
|
501
|
+
_web: {
|
|
502
|
+
_classNames: 'drawer-slider-base',
|
|
503
|
+
},
|
|
492
504
|
},
|
|
493
505
|
}))
|
package/lib/components/Field.tsx
CHANGED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import React, {createContext, useContext} from 'react'
|
|
2
|
+
import {View, Pressable} from 'react-native'
|
|
3
|
+
import Animated, {
|
|
4
|
+
withDelay,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
useSharedValue,
|
|
7
|
+
withSpring,
|
|
8
|
+
withTiming,
|
|
9
|
+
} from 'react-native-reanimated'
|
|
10
|
+
import {StyleSheet, UnistylesVariants} from 'react-native-unistyles'
|
|
11
|
+
import Plus from '../icons/Plus'
|
|
12
|
+
import {
|
|
13
|
+
FloatingActionDefaultVariants,
|
|
14
|
+
FloatingActionStyles,
|
|
15
|
+
} from '../theme/FloatingAction'
|
|
16
|
+
import {ViewStyle} from 'react-native'
|
|
17
|
+
import {PalettesWithNestedKeys} from '../style/varia/types'
|
|
18
|
+
import {getVariantValue} from '../style/varia/utils'
|
|
19
|
+
|
|
20
|
+
const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
|
|
21
|
+
|
|
22
|
+
const SPRING_CONFIG = {
|
|
23
|
+
duration: 800,
|
|
24
|
+
overshootClamping: true,
|
|
25
|
+
dampingRatio: 0.8,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const FloatingActionContext = createContext<any>(null)
|
|
29
|
+
|
|
30
|
+
const useFloatingContext = () => {
|
|
31
|
+
const ctx = useContext(FloatingActionContext)
|
|
32
|
+
if (!ctx)
|
|
33
|
+
throw new Error('FloatingAction.Item must be inside FloatingAction.Root')
|
|
34
|
+
return ctx
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type ItemProps = FloatingActionVariants & {
|
|
38
|
+
children: React.ReactNode
|
|
39
|
+
onPress: () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function Item({children, onPress, ...props}: ItemProps) {
|
|
43
|
+
const {
|
|
44
|
+
direction,
|
|
45
|
+
gap,
|
|
46
|
+
isExpanded,
|
|
47
|
+
index,
|
|
48
|
+
variant,
|
|
49
|
+
size,
|
|
50
|
+
colorPalette = 'accent',
|
|
51
|
+
} = {...useFloatingContext(), ...props}
|
|
52
|
+
FloatingActionStyles.useVariants({
|
|
53
|
+
variant,
|
|
54
|
+
size,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const iconHeight = getVariantValue(
|
|
58
|
+
FloatingActionStyles.button(colorPalette),
|
|
59
|
+
'size',
|
|
60
|
+
size,
|
|
61
|
+
'height',
|
|
62
|
+
)
|
|
63
|
+
const animatedStyles = useAnimatedStyle(() => {
|
|
64
|
+
const multiplier = direction === 'toTop' ? -1 : 1
|
|
65
|
+
const OFFSET_FROM_MAIN = iconHeight + gap
|
|
66
|
+
|
|
67
|
+
const moveValue = isExpanded.value
|
|
68
|
+
? (OFFSET_FROM_MAIN + (gap + iconHeight) * (index - 1)) * multiplier
|
|
69
|
+
: 0
|
|
70
|
+
|
|
71
|
+
const delay = index * 30
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
opacity: withDelay(
|
|
75
|
+
delay,
|
|
76
|
+
withTiming(isExpanded.value ? 1 : 0, {duration: 150}),
|
|
77
|
+
),
|
|
78
|
+
transform: [
|
|
79
|
+
{translateY: withDelay(delay, withSpring(moveValue, SPRING_CONFIG))},
|
|
80
|
+
{
|
|
81
|
+
scale: withDelay(
|
|
82
|
+
delay,
|
|
83
|
+
withTiming(isExpanded.value ? 1 : 0.95, {duration: 200}),
|
|
84
|
+
),
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<AnimatedPressable
|
|
92
|
+
style={[
|
|
93
|
+
animatedStyles,
|
|
94
|
+
styles.itemButton,
|
|
95
|
+
FloatingActionStyles.item(colorPalette),
|
|
96
|
+
]}
|
|
97
|
+
onPress={onPress}>
|
|
98
|
+
{children}
|
|
99
|
+
</AnimatedPressable>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type directions = 'toTop' | 'toBottom'
|
|
104
|
+
type FloatingActionVariants = UnistylesVariants<typeof FloatingActionStyles>
|
|
105
|
+
type FloatingActionProps = FloatingActionVariants & {
|
|
106
|
+
colorPalette?: PalettesWithNestedKeys
|
|
107
|
+
gap?: ViewStyle['gap']
|
|
108
|
+
direction?: directions
|
|
109
|
+
top?: ViewStyle['top']
|
|
110
|
+
bottom?: ViewStyle['bottom']
|
|
111
|
+
right?: ViewStyle['right']
|
|
112
|
+
left?: ViewStyle['left']
|
|
113
|
+
children: React.ReactNode
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function Root({
|
|
117
|
+
variant = FloatingActionDefaultVariants.variant,
|
|
118
|
+
size = FloatingActionDefaultVariants.size,
|
|
119
|
+
colorPalette = 'accent',
|
|
120
|
+
children,
|
|
121
|
+
gap = 10,
|
|
122
|
+
direction = 'toTop',
|
|
123
|
+
top = null,
|
|
124
|
+
bottom = null,
|
|
125
|
+
right = null,
|
|
126
|
+
left = null,
|
|
127
|
+
}: FloatingActionProps) {
|
|
128
|
+
FloatingActionStyles.useVariants({
|
|
129
|
+
variant,
|
|
130
|
+
size,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const isExpanded = useSharedValue(false)
|
|
134
|
+
|
|
135
|
+
const plusIconStyle = useAnimatedStyle(() => ({
|
|
136
|
+
transform: [{rotate: withTiming(isExpanded.value ? '45deg' : '0deg')}],
|
|
137
|
+
}))
|
|
138
|
+
|
|
139
|
+
const arrayChildren = React.Children.toArray(children)
|
|
140
|
+
|
|
141
|
+
const iconHeight = getVariantValue(
|
|
142
|
+
FloatingActionStyles.button(colorPalette),
|
|
143
|
+
'size',
|
|
144
|
+
size,
|
|
145
|
+
'height',
|
|
146
|
+
)
|
|
147
|
+
const iconColor = getVariantValue(
|
|
148
|
+
FloatingActionStyles.buttonIcon(colorPalette),
|
|
149
|
+
'variant',
|
|
150
|
+
variant,
|
|
151
|
+
'color',
|
|
152
|
+
)
|
|
153
|
+
console.log('🚀 ~ iconColor:', iconColor)
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<View
|
|
157
|
+
style={[
|
|
158
|
+
styles.buttonContainer(top, bottom, left, right),
|
|
159
|
+
FloatingActionStyles.buttonContainer(colorPalette),
|
|
160
|
+
]}>
|
|
161
|
+
<AnimatedPressable
|
|
162
|
+
onPress={() => (isExpanded.value = !isExpanded.value)}
|
|
163
|
+
style={[
|
|
164
|
+
plusIconStyle,
|
|
165
|
+
styles.button,
|
|
166
|
+
FloatingActionStyles.button(colorPalette),
|
|
167
|
+
]}>
|
|
168
|
+
<Plus size={iconHeight - 20} color={iconColor} />
|
|
169
|
+
</AnimatedPressable>
|
|
170
|
+
{arrayChildren.map((child: any, i) => (
|
|
171
|
+
<FloatingActionContext.Provider
|
|
172
|
+
key={i}
|
|
173
|
+
value={{
|
|
174
|
+
direction,
|
|
175
|
+
gap,
|
|
176
|
+
isExpanded,
|
|
177
|
+
index: i + 1,
|
|
178
|
+
variant,
|
|
179
|
+
size,
|
|
180
|
+
colorPalette,
|
|
181
|
+
}}>
|
|
182
|
+
{child}
|
|
183
|
+
</FloatingActionContext.Provider>
|
|
184
|
+
))}
|
|
185
|
+
</View>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const FloatingAction = {
|
|
190
|
+
Root,
|
|
191
|
+
Item,
|
|
192
|
+
}
|
|
193
|
+
export default FloatingAction
|
|
194
|
+
|
|
195
|
+
const styles = StyleSheet.create({
|
|
196
|
+
buttonContainer: (top, bottom, left, right) => ({
|
|
197
|
+
position: 'absolute',
|
|
198
|
+
...(top !== null && top !== undefined ? {top} : {}),
|
|
199
|
+
...(bottom !== null && bottom !== undefined ? {bottom} : {}),
|
|
200
|
+
...(left !== null && left !== undefined ? {left} : {}),
|
|
201
|
+
...(right !== null && right !== undefined ? {right} : {}),
|
|
202
|
+
zIndex: 99,
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
}),
|
|
205
|
+
button: {
|
|
206
|
+
zIndex: 1,
|
|
207
|
+
justifyContent: 'center',
|
|
208
|
+
alignItems: 'center',
|
|
209
|
+
},
|
|
210
|
+
itemButton: {
|
|
211
|
+
position: 'absolute',
|
|
212
|
+
justifyContent: 'center',
|
|
213
|
+
alignItems: 'center',
|
|
214
|
+
},
|
|
215
|
+
})
|