@retray-dev/ui-kit 2.5.1 → 2.6.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/COMPONENTS.md +153 -6
- package/dist/index.d.mts +98 -8
- package/dist/index.d.ts +98 -8
- package/dist/index.js +591 -505
- package/dist/index.mjs +533 -436
- package/package.json +23 -21
- package/src/components/Accordion/Accordion.tsx +61 -57
- package/src/components/Alert/Alert.tsx +11 -10
- package/src/components/AlertBanner/AlertBanner.tsx +23 -10
- package/src/components/Avatar/Avatar.tsx +9 -8
- package/src/components/Badge/Badge.tsx +27 -12
- package/src/components/Button/Button.tsx +30 -12
- package/src/components/Card/Card.tsx +12 -11
- package/src/components/Checkbox/Checkbox.tsx +16 -13
- package/src/components/Chip/Chip.tsx +8 -7
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +12 -11
- package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +2 -1
- package/src/components/CurrencyInput/CurrencyInput.tsx +2 -1
- package/src/components/EmptyState/EmptyState.tsx +34 -21
- package/src/components/Input/Input.tsx +44 -22
- package/src/components/LabelValue/LabelValue.tsx +6 -5
- package/src/components/ListItem/ListItem.tsx +46 -22
- package/src/components/MonthPicker/MonthPicker.tsx +9 -8
- package/src/components/Progress/Progress.tsx +2 -1
- package/src/components/RadioGroup/RadioGroup.tsx +18 -15
- package/src/components/Select/Select.tsx +25 -24
- package/src/components/Sheet/Sheet.tsx +15 -14
- package/src/components/Slider/Slider.tsx +7 -6
- package/src/components/Switch/Switch.tsx +7 -6
- package/src/components/Tabs/Tabs.tsx +17 -14
- package/src/components/Text/Text.tsx +7 -6
- package/src/components/Textarea/Textarea.tsx +9 -8
- package/src/components/Toast/Toast.tsx +30 -19
- package/src/components/Toggle/Toggle.tsx +36 -10
- package/src/index.ts +4 -0
- package/src/utils/haptics.ts +32 -0
- package/src/utils/icons.ts +73 -0
- package/src/utils/scaling.ts +26 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import React, { useRef } from 'react'
|
|
2
|
-
import { TouchableOpacity, Animated, View, Text, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
-
import
|
|
2
|
+
import { TouchableOpacity, Animated, View, Text, StyleSheet, ViewStyle, Platform } from 'react-native'
|
|
3
|
+
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
4
|
+
|
|
5
|
+
const nativeDriver = Platform.OS !== 'web'
|
|
4
6
|
import { useTheme } from '../../theme'
|
|
7
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
5
8
|
|
|
6
9
|
export interface CheckboxProps {
|
|
7
10
|
checked?: boolean
|
|
@@ -23,11 +26,11 @@ export function Checkbox({
|
|
|
23
26
|
|
|
24
27
|
const handlePressIn = () => {
|
|
25
28
|
if (disabled) return
|
|
26
|
-
Animated.spring(scale, { toValue: 0.95, useNativeDriver:
|
|
29
|
+
Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
const handlePressOut = () => {
|
|
30
|
-
Animated.spring(scale, { toValue: 1, useNativeDriver:
|
|
33
|
+
Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
return (
|
|
@@ -35,7 +38,7 @@ export function Checkbox({
|
|
|
35
38
|
<TouchableOpacity
|
|
36
39
|
style={[styles.row, style]}
|
|
37
40
|
onPress={() => {
|
|
38
|
-
|
|
41
|
+
hapticSelection()
|
|
39
42
|
onCheckedChange?.(!checked)
|
|
40
43
|
}}
|
|
41
44
|
onPressIn={handlePressIn}
|
|
@@ -74,25 +77,25 @@ const styles = StyleSheet.create({
|
|
|
74
77
|
row: {
|
|
75
78
|
flexDirection: 'row',
|
|
76
79
|
alignItems: 'center',
|
|
77
|
-
gap: 12,
|
|
80
|
+
gap: s(12),
|
|
78
81
|
},
|
|
79
82
|
box: {
|
|
80
|
-
width: 24,
|
|
81
|
-
height: 24,
|
|
82
|
-
borderRadius: 8,
|
|
83
|
+
width: s(24),
|
|
84
|
+
height: s(24),
|
|
85
|
+
borderRadius: ms(8),
|
|
83
86
|
borderWidth: 1.5,
|
|
84
87
|
alignItems: 'center',
|
|
85
88
|
justifyContent: 'center',
|
|
86
89
|
},
|
|
87
90
|
checkmark: {
|
|
88
|
-
width: 12,
|
|
89
|
-
height: 7,
|
|
91
|
+
width: s(12),
|
|
92
|
+
height: vs(7),
|
|
90
93
|
borderLeftWidth: 2,
|
|
91
94
|
borderBottomWidth: 2,
|
|
92
95
|
transform: [{ rotate: '-45deg' }, { translateY: -1 }],
|
|
93
96
|
},
|
|
94
97
|
label: {
|
|
95
|
-
fontSize: 15,
|
|
96
|
-
lineHeight: 22,
|
|
98
|
+
fontSize: ms(15),
|
|
99
|
+
lineHeight: mvs(22),
|
|
97
100
|
},
|
|
98
101
|
})
|
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
Platform,
|
|
10
10
|
Easing,
|
|
11
11
|
} from 'react-native'
|
|
12
|
-
import
|
|
12
|
+
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
13
13
|
import { useTheme } from '../../theme'
|
|
14
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
14
15
|
|
|
15
16
|
const nativeDriver = Platform.OS !== 'web'
|
|
16
17
|
|
|
@@ -68,7 +69,7 @@ export function Chip({ label, selected = false, onPress, style }: ChipProps) {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
const handlePress = () => {
|
|
71
|
-
|
|
72
|
+
hapticSelection()
|
|
72
73
|
onPress?.()
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -154,20 +155,20 @@ const styles = StyleSheet.create({
|
|
|
154
155
|
wrapper: {},
|
|
155
156
|
chip: {
|
|
156
157
|
borderRadius: 999,
|
|
157
|
-
paddingHorizontal: 14,
|
|
158
|
-
paddingVertical: 8,
|
|
158
|
+
paddingHorizontal: s(14),
|
|
159
|
+
paddingVertical: vs(8),
|
|
159
160
|
borderWidth: 1.5,
|
|
160
161
|
alignItems: 'center',
|
|
161
162
|
justifyContent: 'center',
|
|
162
163
|
},
|
|
163
164
|
label: {
|
|
164
|
-
fontSize: 14,
|
|
165
|
+
fontSize: ms(14),
|
|
165
166
|
fontWeight: '500',
|
|
166
|
-
lineHeight: 20,
|
|
167
|
+
lineHeight: mvs(20),
|
|
167
168
|
},
|
|
168
169
|
group: {
|
|
169
170
|
flexDirection: 'row',
|
|
170
171
|
flexWrap: 'wrap',
|
|
171
|
-
gap: 8,
|
|
172
|
+
gap: s(8),
|
|
172
173
|
},
|
|
173
174
|
})
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import { Modal, View, Text, StyleSheet, TouchableOpacity } from 'react-native'
|
|
3
3
|
import { useTheme } from '../../theme'
|
|
4
4
|
import { Button } from '../Button'
|
|
5
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
5
6
|
|
|
6
7
|
export interface ConfirmDialogProps {
|
|
7
8
|
visible: boolean
|
|
@@ -57,14 +58,14 @@ const styles = StyleSheet.create({
|
|
|
57
58
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
58
59
|
justifyContent: 'center',
|
|
59
60
|
alignItems: 'center',
|
|
60
|
-
padding: 24,
|
|
61
|
+
padding: s(24),
|
|
61
62
|
},
|
|
62
63
|
dialog: {
|
|
63
64
|
width: '100%',
|
|
64
|
-
maxWidth: 400,
|
|
65
|
-
borderRadius: 16,
|
|
66
|
-
padding: 24,
|
|
67
|
-
gap: 12,
|
|
65
|
+
maxWidth: s(400),
|
|
66
|
+
borderRadius: ms(16),
|
|
67
|
+
padding: s(24),
|
|
68
|
+
gap: vs(12),
|
|
68
69
|
shadowColor: '#000',
|
|
69
70
|
shadowOffset: { width: 0, height: 8 },
|
|
70
71
|
shadowOpacity: 0.15,
|
|
@@ -72,16 +73,16 @@ const styles = StyleSheet.create({
|
|
|
72
73
|
elevation: 8,
|
|
73
74
|
},
|
|
74
75
|
title: {
|
|
75
|
-
fontSize: 18,
|
|
76
|
+
fontSize: ms(18),
|
|
76
77
|
fontWeight: '600',
|
|
77
|
-
lineHeight: 26,
|
|
78
|
+
lineHeight: mvs(26),
|
|
78
79
|
},
|
|
79
80
|
description: {
|
|
80
|
-
fontSize: 15,
|
|
81
|
-
lineHeight: 22,
|
|
81
|
+
fontSize: ms(15),
|
|
82
|
+
lineHeight: mvs(22),
|
|
82
83
|
},
|
|
83
84
|
actions: {
|
|
84
|
-
gap: 10,
|
|
85
|
-
marginTop: 8,
|
|
85
|
+
gap: vs(10),
|
|
86
|
+
marginTop: vs(8),
|
|
86
87
|
},
|
|
87
88
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { View, Text, StyleSheet, ViewStyle } from 'react-native'
|
|
3
3
|
import { useTheme } from '../../theme'
|
|
4
|
+
import { ms } from '../../utils/scaling'
|
|
4
5
|
|
|
5
6
|
export interface CurrencyDisplayProps {
|
|
6
7
|
value: number | string
|
|
@@ -42,7 +43,7 @@ export function CurrencyDisplay({ value, prefix = '$', showDecimals = false, tex
|
|
|
42
43
|
const styles = StyleSheet.create({
|
|
43
44
|
container: {},
|
|
44
45
|
amount: {
|
|
45
|
-
fontSize: 56,
|
|
46
|
+
fontSize: ms(56),
|
|
46
47
|
fontWeight: '700',
|
|
47
48
|
},
|
|
48
49
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { ViewStyle, TextStyle } from 'react-native'
|
|
3
3
|
import { Input } from '../Input'
|
|
4
|
+
import { ms } from '../../utils/scaling'
|
|
4
5
|
|
|
5
6
|
export interface CurrencyInputProps {
|
|
6
7
|
value?: string
|
|
@@ -54,7 +55,7 @@ export function CurrencyInput({
|
|
|
54
55
|
onChangeValue?.(isNaN(raw) ? 0 : raw)
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
const inputStyle: TextStyle = size === 'large' ? { fontSize: 36 } : {}
|
|
58
|
+
const inputStyle: TextStyle = size === 'large' ? { fontSize: ms(36) } : {}
|
|
58
59
|
|
|
59
60
|
return (
|
|
60
61
|
<Input
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { View, Text, StyleSheet, ViewStyle } from 'react-native'
|
|
3
3
|
import { useTheme } from '../../theme'
|
|
4
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
5
|
+
import { renderIcon } from '../../utils/icons'
|
|
4
6
|
|
|
5
7
|
export interface EmptyStateProps {
|
|
6
8
|
icon?: React.ReactNode
|
|
9
|
+
/**
|
|
10
|
+
* Icon name from `@expo/vector-icons`. See https://icons.expo.fyi.
|
|
11
|
+
* Takes precedence over `icon`. Sized automatically to fit the slot (48 default, 32 compact).
|
|
12
|
+
*/
|
|
13
|
+
iconName?: string
|
|
14
|
+
/** Override the resolved icon color. Defaults to `mutedForeground`. */
|
|
15
|
+
iconColor?: string
|
|
7
16
|
title: string
|
|
8
17
|
description?: string
|
|
9
18
|
action?: React.ReactNode
|
|
@@ -12,10 +21,14 @@ export interface EmptyStateProps {
|
|
|
12
21
|
style?: ViewStyle
|
|
13
22
|
}
|
|
14
23
|
|
|
15
|
-
export function EmptyState({ icon, title, description, action, size = 'default', style }: EmptyStateProps) {
|
|
24
|
+
export function EmptyState({ icon, iconName, iconColor, title, description, action, size = 'default', style }: EmptyStateProps) {
|
|
16
25
|
const { colors } = useTheme()
|
|
17
26
|
const isCompact = size === 'compact'
|
|
18
27
|
|
|
28
|
+
const effectiveIcon: React.ReactNode = iconName
|
|
29
|
+
? renderIcon(iconName, isCompact ? 32 : 48, iconColor ?? colors.mutedForeground)
|
|
30
|
+
: icon
|
|
31
|
+
|
|
19
32
|
return (
|
|
20
33
|
<View
|
|
21
34
|
style={[
|
|
@@ -25,7 +38,7 @@ export function EmptyState({ icon, title, description, action, size = 'default',
|
|
|
25
38
|
style,
|
|
26
39
|
]}
|
|
27
40
|
>
|
|
28
|
-
{
|
|
41
|
+
{effectiveIcon ? (
|
|
29
42
|
<View
|
|
30
43
|
style={[
|
|
31
44
|
styles.iconWrapper,
|
|
@@ -33,7 +46,7 @@ export function EmptyState({ icon, title, description, action, size = 'default',
|
|
|
33
46
|
{ backgroundColor: colors.muted },
|
|
34
47
|
]}
|
|
35
48
|
>
|
|
36
|
-
{
|
|
49
|
+
{effectiveIcon}
|
|
37
50
|
</View>
|
|
38
51
|
) : null}
|
|
39
52
|
<View style={styles.textWrapper}>
|
|
@@ -58,45 +71,45 @@ const styles = StyleSheet.create({
|
|
|
58
71
|
justifyContent: 'center',
|
|
59
72
|
borderWidth: 1,
|
|
60
73
|
borderStyle: 'dashed',
|
|
61
|
-
borderRadius: 12,
|
|
62
|
-
padding: 32,
|
|
63
|
-
gap: 16,
|
|
74
|
+
borderRadius: ms(12),
|
|
75
|
+
padding: s(32),
|
|
76
|
+
gap: vs(16),
|
|
64
77
|
},
|
|
65
78
|
containerCompact: {
|
|
66
|
-
padding: 20,
|
|
67
|
-
gap: 10,
|
|
79
|
+
padding: s(20),
|
|
80
|
+
gap: vs(10),
|
|
68
81
|
},
|
|
69
82
|
iconWrapper: {
|
|
70
|
-
width: 48,
|
|
71
|
-
height: 48,
|
|
72
|
-
borderRadius: 12,
|
|
83
|
+
width: s(48),
|
|
84
|
+
height: s(48),
|
|
85
|
+
borderRadius: ms(12),
|
|
73
86
|
alignItems: 'center',
|
|
74
87
|
justifyContent: 'center',
|
|
75
88
|
},
|
|
76
89
|
iconWrapperCompact: {
|
|
77
|
-
width: 36,
|
|
78
|
-
height: 36,
|
|
79
|
-
borderRadius: 8,
|
|
90
|
+
width: s(36),
|
|
91
|
+
height: s(36),
|
|
92
|
+
borderRadius: ms(8),
|
|
80
93
|
},
|
|
81
94
|
textWrapper: {
|
|
82
95
|
alignItems: 'center',
|
|
83
|
-
gap: 8,
|
|
84
|
-
maxWidth: 320,
|
|
96
|
+
gap: vs(8),
|
|
97
|
+
maxWidth: s(320),
|
|
85
98
|
},
|
|
86
99
|
title: {
|
|
87
|
-
fontSize: 18,
|
|
100
|
+
fontSize: ms(18),
|
|
88
101
|
fontWeight: '500',
|
|
89
102
|
textAlign: 'center',
|
|
90
103
|
},
|
|
91
104
|
titleCompact: {
|
|
92
|
-
fontSize: 15,
|
|
105
|
+
fontSize: ms(15),
|
|
93
106
|
},
|
|
94
107
|
description: {
|
|
95
|
-
fontSize: 14,
|
|
96
|
-
lineHeight: 20,
|
|
108
|
+
fontSize: ms(14),
|
|
109
|
+
lineHeight: mvs(20),
|
|
97
110
|
textAlign: 'center',
|
|
98
111
|
},
|
|
99
112
|
action: {
|
|
100
|
-
marginTop: 8,
|
|
113
|
+
marginTop: vs(8),
|
|
101
114
|
},
|
|
102
115
|
})
|
|
@@ -2,6 +2,8 @@ import React, { useState } from 'react'
|
|
|
2
2
|
import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, TextStyle, TouchableOpacity, Platform } from 'react-native'
|
|
3
3
|
import { AntDesign } from '@expo/vector-icons'
|
|
4
4
|
import { useTheme } from '../../theme'
|
|
5
|
+
import { s, vs, ms } from '../../utils/scaling'
|
|
6
|
+
import { renderIcon } from '../../utils/icons'
|
|
5
7
|
|
|
6
8
|
const webInputResetStyle: any =
|
|
7
9
|
Platform.OS === 'web'
|
|
@@ -22,13 +24,27 @@ export interface InputProps extends TextInputProps {
|
|
|
22
24
|
prefixStyle?: TextStyle
|
|
23
25
|
/** Style applied to suffix text if suffix is a string. */
|
|
24
26
|
suffixStyle?: TextStyle
|
|
27
|
+
/**
|
|
28
|
+
* Icon name from `@expo/vector-icons` rendered before the input text.
|
|
29
|
+
* See https://icons.expo.fyi. Takes precedence over `prefix`.
|
|
30
|
+
*/
|
|
31
|
+
prefixIcon?: string
|
|
32
|
+
/**
|
|
33
|
+
* Icon name from `@expo/vector-icons` rendered after the input text.
|
|
34
|
+
* See https://icons.expo.fyi. Takes precedence over `suffix` (unless `type="password"`).
|
|
35
|
+
*/
|
|
36
|
+
suffixIcon?: string
|
|
37
|
+
/** Override the resolved prefix icon color. Defaults to `mutedForeground`. */
|
|
38
|
+
prefixIconColor?: string
|
|
39
|
+
/** Override the resolved suffix icon color. Defaults to `mutedForeground`. */
|
|
40
|
+
suffixIconColor?: string
|
|
25
41
|
/** Input type. When set to \`'password'\`, shows a toggle button to reveal/hide text. */
|
|
26
42
|
type?: 'text' | 'password'
|
|
27
43
|
/** Style for the outer container \`View\`. Use \`style\` (from \`TextInputProps\`) to style the \`TextInput\` itself. */
|
|
28
44
|
containerStyle?: ViewStyle
|
|
29
45
|
}
|
|
30
46
|
|
|
31
|
-
export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixStyle, type = 'text', containerStyle, style, onFocus, onBlur, secureTextEntry, ...props }: InputProps) {
|
|
47
|
+
export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixStyle, prefixIcon, suffixIcon, prefixIconColor, suffixIconColor, type = 'text', containerStyle, style, onFocus, onBlur, secureTextEntry, ...props }: InputProps) {
|
|
32
48
|
const { colors } = useTheme()
|
|
33
49
|
const [focused, setFocused] = useState(false)
|
|
34
50
|
const [showPassword, setShowPassword] = useState(false)
|
|
@@ -36,12 +52,18 @@ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixS
|
|
|
36
52
|
const isPassword = type === 'password'
|
|
37
53
|
const effectiveSecure = isPassword ? !showPassword : secureTextEntry
|
|
38
54
|
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
const effectivePrefix: React.ReactNode = prefixIcon
|
|
56
|
+
? renderIcon(prefixIcon, 20, prefixIconColor ?? colors.mutedForeground)
|
|
57
|
+
: prefix
|
|
58
|
+
|
|
59
|
+
// If type is password and no suffix override is provided, add the toggle button
|
|
60
|
+
const effectiveSuffix: React.ReactNode = isPassword && !suffix && !suffixIcon ? (
|
|
41
61
|
<TouchableOpacity onPress={() => setShowPassword(!showPassword)} style={styles.passwordToggle} activeOpacity={0.6}>
|
|
42
62
|
<AntDesign name={showPassword ? 'eye' : 'eye-invisible'} size={20} color={colors.mutedForeground} />
|
|
43
63
|
</TouchableOpacity>
|
|
44
|
-
) :
|
|
64
|
+
) : suffixIcon
|
|
65
|
+
? renderIcon(suffixIcon, 20, suffixIconColor ?? colors.mutedForeground)
|
|
66
|
+
: suffix
|
|
45
67
|
|
|
46
68
|
return (
|
|
47
69
|
<View style={[styles.container, containerStyle]}>
|
|
@@ -59,13 +81,13 @@ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixS
|
|
|
59
81
|
},
|
|
60
82
|
]}
|
|
61
83
|
>
|
|
62
|
-
{
|
|
63
|
-
typeof
|
|
84
|
+
{effectivePrefix ? (
|
|
85
|
+
typeof effectivePrefix === 'string' ? (
|
|
64
86
|
<Text style={[styles.prefixText, { color: colors.mutedForeground }, prefixStyle]} allowFontScaling={true}>
|
|
65
|
-
{
|
|
87
|
+
{effectivePrefix}
|
|
66
88
|
</Text>
|
|
67
89
|
) : (
|
|
68
|
-
<View style={styles.prefixContainer}>{
|
|
90
|
+
<View style={styles.prefixContainer}>{effectivePrefix}</View>
|
|
69
91
|
)
|
|
70
92
|
) : null}
|
|
71
93
|
<TextInput
|
|
@@ -112,43 +134,43 @@ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixS
|
|
|
112
134
|
|
|
113
135
|
const styles = StyleSheet.create({
|
|
114
136
|
container: {
|
|
115
|
-
gap: 8,
|
|
137
|
+
gap: vs(8),
|
|
116
138
|
},
|
|
117
139
|
label: {
|
|
118
|
-
fontSize: 15,
|
|
140
|
+
fontSize: ms(15),
|
|
119
141
|
fontWeight: '500',
|
|
120
142
|
},
|
|
121
143
|
inputWrapper: {
|
|
122
144
|
flexDirection: 'row',
|
|
123
145
|
alignItems: 'center',
|
|
124
146
|
borderWidth: 1.5,
|
|
125
|
-
borderRadius: 8,
|
|
126
|
-
paddingHorizontal: 16,
|
|
127
|
-
paddingVertical: 14,
|
|
147
|
+
borderRadius: ms(8),
|
|
148
|
+
paddingHorizontal: s(16),
|
|
149
|
+
paddingVertical: vs(14),
|
|
128
150
|
},
|
|
129
151
|
input: {
|
|
130
152
|
flex: 1,
|
|
131
|
-
fontSize: 17,
|
|
153
|
+
fontSize: ms(17),
|
|
132
154
|
paddingVertical: 0,
|
|
133
155
|
},
|
|
134
156
|
prefixContainer: {
|
|
135
|
-
marginRight: 8,
|
|
157
|
+
marginRight: s(8),
|
|
136
158
|
},
|
|
137
159
|
prefixText: {
|
|
138
|
-
fontSize: 17,
|
|
139
|
-
marginRight: 8,
|
|
160
|
+
fontSize: ms(17),
|
|
161
|
+
marginRight: s(8),
|
|
140
162
|
},
|
|
141
163
|
suffixContainer: {
|
|
142
|
-
marginLeft: 8,
|
|
164
|
+
marginLeft: s(8),
|
|
143
165
|
},
|
|
144
166
|
suffixText: {
|
|
145
|
-
fontSize: 17,
|
|
146
|
-
marginLeft: 8,
|
|
167
|
+
fontSize: ms(17),
|
|
168
|
+
marginLeft: s(8),
|
|
147
169
|
},
|
|
148
170
|
passwordToggle: {
|
|
149
|
-
padding: 4,
|
|
171
|
+
padding: s(4),
|
|
150
172
|
},
|
|
151
173
|
helperText: {
|
|
152
|
-
fontSize: 13,
|
|
174
|
+
fontSize: ms(13),
|
|
153
175
|
},
|
|
154
176
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { View, Text, StyleSheet, ViewStyle } from 'react-native'
|
|
3
3
|
import { useTheme } from '../../theme'
|
|
4
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
4
5
|
|
|
5
6
|
export interface LabelValueProps {
|
|
6
7
|
label: string
|
|
@@ -32,16 +33,16 @@ const styles = StyleSheet.create({
|
|
|
32
33
|
flexDirection: 'row',
|
|
33
34
|
justifyContent: 'space-between',
|
|
34
35
|
alignItems: 'center',
|
|
35
|
-
gap: 12,
|
|
36
|
+
gap: s(12),
|
|
36
37
|
},
|
|
37
38
|
label: {
|
|
38
|
-
fontSize: 13,
|
|
39
|
-
lineHeight: 18,
|
|
39
|
+
fontSize: ms(13),
|
|
40
|
+
lineHeight: mvs(18),
|
|
40
41
|
},
|
|
41
42
|
value: {
|
|
42
|
-
fontSize: 15,
|
|
43
|
+
fontSize: ms(15),
|
|
43
44
|
fontWeight: '500',
|
|
44
|
-
lineHeight: 22,
|
|
45
|
+
lineHeight: mvs(22),
|
|
45
46
|
textAlign: 'right',
|
|
46
47
|
},
|
|
47
48
|
})
|
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
Platform,
|
|
11
11
|
} from 'react-native'
|
|
12
12
|
import { Entypo } from '@expo/vector-icons'
|
|
13
|
-
import
|
|
13
|
+
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
14
14
|
import { useTheme } from '../../theme'
|
|
15
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
16
|
+
import { renderIcon } from '../../utils/icons'
|
|
15
17
|
|
|
16
18
|
const nativeDriver = Platform.OS !== 'web'
|
|
17
19
|
|
|
@@ -32,6 +34,20 @@ export interface ListItemProps {
|
|
|
32
34
|
trailing?: React.ReactNode | string
|
|
33
35
|
/** @deprecated Use `leftRender` instead. */
|
|
34
36
|
icon?: React.ReactNode
|
|
37
|
+
/**
|
|
38
|
+
* Icon name from `@expo/vector-icons` rendered in the left slot.
|
|
39
|
+
* See https://icons.expo.fyi. Takes precedence over `leftRender`.
|
|
40
|
+
*/
|
|
41
|
+
leftIcon?: string
|
|
42
|
+
/**
|
|
43
|
+
* Icon name from `@expo/vector-icons` rendered in the right slot.
|
|
44
|
+
* See https://icons.expo.fyi. Takes precedence over `rightRender`.
|
|
45
|
+
*/
|
|
46
|
+
rightIcon?: string
|
|
47
|
+
/** Override the resolved left icon color. Defaults to `foreground`. */
|
|
48
|
+
leftIconColor?: string
|
|
49
|
+
/** Override the resolved right icon color. Defaults to `mutedForeground`. */
|
|
50
|
+
rightIconColor?: string
|
|
35
51
|
|
|
36
52
|
title: string
|
|
37
53
|
/** Secondary line below the title. */
|
|
@@ -68,6 +84,10 @@ export function ListItem({
|
|
|
68
84
|
rightRender,
|
|
69
85
|
trailing,
|
|
70
86
|
icon,
|
|
87
|
+
leftIcon,
|
|
88
|
+
rightIcon,
|
|
89
|
+
leftIconColor,
|
|
90
|
+
rightIconColor,
|
|
71
91
|
title,
|
|
72
92
|
subtitle,
|
|
73
93
|
caption,
|
|
@@ -104,13 +124,17 @@ export function ListItem({
|
|
|
104
124
|
}
|
|
105
125
|
|
|
106
126
|
const handlePress = () => {
|
|
107
|
-
|
|
127
|
+
hapticSelection()
|
|
108
128
|
onPress?.()
|
|
109
129
|
}
|
|
110
130
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
const effectiveLeft: React.ReactNode = leftIcon
|
|
132
|
+
? renderIcon(leftIcon, 24, leftIconColor ?? colors.foreground)
|
|
133
|
+
: leftRender ?? icon
|
|
134
|
+
|
|
135
|
+
const effectiveRight: React.ReactNode | string | undefined = rightIcon
|
|
136
|
+
? renderIcon(rightIcon, 24, rightIconColor ?? colors.mutedForeground)
|
|
137
|
+
: rightRender ?? trailing
|
|
114
138
|
|
|
115
139
|
const cardStyle: ViewStyle =
|
|
116
140
|
variant === 'card'
|
|
@@ -192,7 +216,7 @@ export function ListItem({
|
|
|
192
216
|
<View
|
|
193
217
|
style={[
|
|
194
218
|
styles.separator,
|
|
195
|
-
{ backgroundColor: colors.border, marginLeft: effectiveLeft ? 16 + 44 + 12 : 16 },
|
|
219
|
+
{ backgroundColor: colors.border, marginLeft: effectiveLeft ? s(16) + s(44) + s(12) : s(16) },
|
|
196
220
|
]}
|
|
197
221
|
/>
|
|
198
222
|
) : null}
|
|
@@ -204,52 +228,52 @@ const styles = StyleSheet.create({
|
|
|
204
228
|
container: {
|
|
205
229
|
flexDirection: 'row',
|
|
206
230
|
alignItems: 'center',
|
|
207
|
-
paddingHorizontal: 16,
|
|
208
|
-
paddingVertical: 14,
|
|
209
|
-
gap: 12,
|
|
231
|
+
paddingHorizontal: s(16),
|
|
232
|
+
paddingVertical: vs(14),
|
|
233
|
+
gap: s(12),
|
|
210
234
|
},
|
|
211
235
|
leftContainer: {
|
|
212
|
-
width: 44,
|
|
213
|
-
height: 44,
|
|
236
|
+
width: s(44),
|
|
237
|
+
height: s(44),
|
|
214
238
|
alignItems: 'center',
|
|
215
239
|
justifyContent: 'center',
|
|
216
240
|
flexShrink: 0,
|
|
217
241
|
},
|
|
218
242
|
content: {
|
|
219
243
|
flex: 1,
|
|
220
|
-
gap: 4,
|
|
244
|
+
gap: vs(4),
|
|
221
245
|
},
|
|
222
246
|
title: {
|
|
223
|
-
fontSize: 17,
|
|
247
|
+
fontSize: ms(17),
|
|
224
248
|
fontWeight: '500',
|
|
225
|
-
lineHeight: 24,
|
|
249
|
+
lineHeight: mvs(24),
|
|
226
250
|
},
|
|
227
251
|
subtitle: {
|
|
228
|
-
fontSize: 14,
|
|
252
|
+
fontSize: ms(14),
|
|
229
253
|
fontWeight: '400',
|
|
230
|
-
lineHeight: 20,
|
|
254
|
+
lineHeight: mvs(20),
|
|
231
255
|
},
|
|
232
256
|
caption: {
|
|
233
|
-
fontSize: 12,
|
|
257
|
+
fontSize: ms(12),
|
|
234
258
|
fontWeight: '400',
|
|
235
|
-
lineHeight: 16,
|
|
259
|
+
lineHeight: mvs(16),
|
|
236
260
|
opacity: 0.7,
|
|
237
261
|
},
|
|
238
262
|
rightContainer: {
|
|
239
263
|
alignItems: 'flex-end',
|
|
240
264
|
justifyContent: 'center',
|
|
241
265
|
flexShrink: 0,
|
|
242
|
-
maxWidth: 160,
|
|
266
|
+
maxWidth: s(160),
|
|
243
267
|
},
|
|
244
268
|
rightText: {
|
|
245
|
-
fontSize: 15,
|
|
269
|
+
fontSize: ms(15),
|
|
246
270
|
},
|
|
247
271
|
chevron: {
|
|
248
|
-
marginLeft: 4,
|
|
272
|
+
marginLeft: s(4),
|
|
249
273
|
},
|
|
250
274
|
separator: {
|
|
251
275
|
height: StyleSheet.hairlineWidth,
|
|
252
|
-
marginRight: 16,
|
|
276
|
+
marginRight: s(16),
|
|
253
277
|
},
|
|
254
278
|
disabled: {
|
|
255
279
|
opacity: 0.45,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle } from 'react-native'
|
|
3
3
|
import { Entypo } from '@expo/vector-icons'
|
|
4
|
-
import
|
|
4
|
+
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
5
5
|
import { useTheme } from '../../theme'
|
|
6
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
6
7
|
|
|
7
8
|
const MONTH_NAMES = [
|
|
8
9
|
'January', 'February', 'March', 'April', 'May', 'June',
|
|
@@ -25,7 +26,7 @@ export function MonthPicker({ value, onChange, style }: MonthPickerProps) {
|
|
|
25
26
|
const { colors } = useTheme()
|
|
26
27
|
|
|
27
28
|
const handlePrev = () => {
|
|
28
|
-
|
|
29
|
+
hapticSelection()
|
|
29
30
|
if (value.month === 1) {
|
|
30
31
|
onChange({ month: 12, year: value.year - 1 })
|
|
31
32
|
} else {
|
|
@@ -34,7 +35,7 @@ export function MonthPicker({ value, onChange, style }: MonthPickerProps) {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
const handleNext = () => {
|
|
37
|
-
|
|
38
|
+
hapticSelection()
|
|
38
39
|
if (value.month === 12) {
|
|
39
40
|
onChange({ month: 1, year: value.year + 1 })
|
|
40
41
|
} else {
|
|
@@ -74,16 +75,16 @@ const styles = StyleSheet.create({
|
|
|
74
75
|
justifyContent: 'space-between',
|
|
75
76
|
},
|
|
76
77
|
arrow: {
|
|
77
|
-
width: 44,
|
|
78
|
-
height: 44,
|
|
78
|
+
width: s(44),
|
|
79
|
+
height: s(44),
|
|
79
80
|
alignItems: 'center',
|
|
80
81
|
justifyContent: 'center',
|
|
81
82
|
},
|
|
82
83
|
label: {
|
|
83
|
-
fontSize: 17,
|
|
84
|
+
fontSize: ms(17),
|
|
84
85
|
fontWeight: '500',
|
|
85
|
-
lineHeight: 24,
|
|
86
|
+
lineHeight: mvs(24),
|
|
86
87
|
textAlign: 'center',
|
|
87
|
-
minWidth: 160,
|
|
88
|
+
minWidth: s(160),
|
|
88
89
|
},
|
|
89
90
|
})
|