@retray-dev/ui-kit 1.6.0 → 1.7.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 +108 -6
- package/README.md +3 -3
- package/dist/index.d.mts +43 -5
- package/dist/index.d.ts +43 -5
- package/dist/index.js +278 -121
- package/dist/index.mjs +281 -126
- package/package.json +8 -8
- package/src/components/Accordion/Accordion.tsx +4 -4
- package/src/components/Alert/Alert.tsx +13 -1
- package/src/components/Avatar/Avatar.tsx +8 -8
- package/src/components/Badge/Badge.tsx +4 -4
- package/src/components/Button/Button.tsx +10 -10
- package/src/components/Card/Card.tsx +9 -9
- package/src/components/Checkbox/Checkbox.tsx +8 -8
- package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +46 -0
- package/src/components/CurrencyDisplay/index.ts +1 -0
- package/src/components/CurrencyInputLarge/CurrencyInputLarge.tsx +66 -0
- package/src/components/CurrencyInputLarge/index.ts +1 -0
- package/src/components/Input/Input.tsx +8 -8
- package/src/components/Select/Select.tsx +19 -19
- package/src/components/Switch/Switch.tsx +8 -5
- package/src/components/Tabs/Tabs.tsx +34 -15
- package/src/components/Text/Text.tsx +6 -6
- package/src/components/Textarea/Textarea.tsx +9 -9
- package/src/components/Toast/Toast.tsx +25 -7
- package/src/components/Toggle/Toggle.tsx +93 -24
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retray-dev/ui-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Personal UI Kit for React Native / Expo",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"fast-xml-parser": "^5.5.7",
|
|
53
53
|
"react": "19.1.0",
|
|
54
54
|
"react-native": "0.81.5",
|
|
55
|
-
"react-native-worklets": "0.
|
|
55
|
+
"react-native-worklets": "0.5.1"
|
|
56
56
|
},
|
|
57
57
|
"onlyBuiltDependencies": [
|
|
58
58
|
"esbuild"
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
"@gorhom/bottom-sheet": "^5.0.0",
|
|
63
63
|
"@types/react": "^19.1.0",
|
|
64
64
|
"expo-haptics": "~15.0.8",
|
|
65
|
-
"expo-linear-gradient": "~
|
|
66
|
-
"react": "
|
|
67
|
-
"react-native": "0.
|
|
68
|
-
"react-native-gesture-handler": "~2.
|
|
69
|
-
"react-native-reanimated": "~4.
|
|
70
|
-
"react-native-worklets": "~0.
|
|
65
|
+
"expo-linear-gradient": "~15.0.8",
|
|
66
|
+
"react": "19.1.0",
|
|
67
|
+
"react-native": "0.81.5",
|
|
68
|
+
"react-native-gesture-handler": "~2.28.0",
|
|
69
|
+
"react-native-reanimated": "~4.1.1",
|
|
70
|
+
"react-native-worklets": "~0.5.1",
|
|
71
71
|
"react-native-safe-area-context": "~5.6.2",
|
|
72
72
|
"eslint": "^9.0.0",
|
|
73
73
|
"@eslint/js": "^9.0.0",
|
|
@@ -154,19 +154,19 @@ const styles = StyleSheet.create({
|
|
|
154
154
|
flexDirection: 'row',
|
|
155
155
|
justifyContent: 'space-between',
|
|
156
156
|
alignItems: 'center',
|
|
157
|
-
paddingVertical:
|
|
157
|
+
paddingVertical: 20,
|
|
158
158
|
},
|
|
159
159
|
triggerText: {
|
|
160
|
-
fontSize:
|
|
160
|
+
fontSize: 17,
|
|
161
161
|
fontWeight: '500',
|
|
162
162
|
flex: 1,
|
|
163
163
|
},
|
|
164
164
|
chevron: {
|
|
165
|
-
fontSize:
|
|
165
|
+
fontSize: 18,
|
|
166
166
|
marginLeft: 8,
|
|
167
167
|
},
|
|
168
168
|
content: {
|
|
169
|
-
paddingBottom:
|
|
169
|
+
paddingBottom: 20,
|
|
170
170
|
position: 'absolute',
|
|
171
171
|
width: '100%',
|
|
172
172
|
},
|
|
@@ -21,7 +21,15 @@ export function Alert({ title, description, variant = 'default', icon, style }:
|
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
23
|
<View style={[styles.container, { backgroundColor: colors.card, borderColor }, style]}>
|
|
24
|
-
{icon ?
|
|
24
|
+
{icon ? (
|
|
25
|
+
<View style={styles.icon}>{icon}</View>
|
|
26
|
+
) : (
|
|
27
|
+
<View style={styles.icon}>
|
|
28
|
+
<Text style={[styles.defaultIcon, { color: titleColor }]}>
|
|
29
|
+
{variant === 'destructive' ? '⚠' : 'ℹ'}
|
|
30
|
+
</Text>
|
|
31
|
+
</View>
|
|
32
|
+
)}
|
|
25
33
|
<View style={styles.content}>
|
|
26
34
|
{title ? <Text style={[styles.title, { color: titleColor }]}>{title}</Text> : null}
|
|
27
35
|
{description ? (
|
|
@@ -56,4 +64,8 @@ const styles = StyleSheet.create({
|
|
|
56
64
|
fontSize: 14,
|
|
57
65
|
lineHeight: 20,
|
|
58
66
|
},
|
|
67
|
+
defaultIcon: {
|
|
68
|
+
fontSize: 18,
|
|
69
|
+
fontWeight: '700',
|
|
70
|
+
},
|
|
59
71
|
})
|
|
@@ -14,17 +14,17 @@ export interface AvatarProps {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const sizeMap: Record<AvatarSize, number> = {
|
|
17
|
-
sm:
|
|
18
|
-
md:
|
|
19
|
-
lg:
|
|
20
|
-
xl:
|
|
17
|
+
sm: 28,
|
|
18
|
+
md: 40,
|
|
19
|
+
lg: 56,
|
|
20
|
+
xl: 72,
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const fontSizeMap: Record<AvatarSize, number> = {
|
|
24
|
-
sm:
|
|
25
|
-
md:
|
|
26
|
-
lg:
|
|
27
|
-
xl:
|
|
24
|
+
sm: 12,
|
|
25
|
+
md: 16,
|
|
26
|
+
lg: 22,
|
|
27
|
+
xl: 28,
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function Avatar({ src, fallback, size = 'md', style }: AvatarProps) {
|
|
@@ -36,13 +36,13 @@ export function Badge({ label, variant = 'default', style }: BadgeProps) {
|
|
|
36
36
|
|
|
37
37
|
const styles = StyleSheet.create({
|
|
38
38
|
container: {
|
|
39
|
-
borderRadius:
|
|
40
|
-
paddingHorizontal:
|
|
41
|
-
paddingVertical:
|
|
39
|
+
borderRadius: 8,
|
|
40
|
+
paddingHorizontal: 10,
|
|
41
|
+
paddingVertical: 4,
|
|
42
42
|
alignSelf: 'flex-start',
|
|
43
43
|
},
|
|
44
44
|
label: {
|
|
45
|
-
fontSize:
|
|
45
|
+
fontSize: 13,
|
|
46
46
|
fontWeight: '500',
|
|
47
47
|
},
|
|
48
48
|
})
|
|
@@ -28,21 +28,21 @@ export interface ButtonProps extends TouchableOpacityProps {
|
|
|
28
28
|
/** Replaces the label with a spinner and forces `disabled`. */
|
|
29
29
|
loading?: boolean
|
|
30
30
|
fullWidth?: boolean
|
|
31
|
-
/** Icon rendered alongside the label. */
|
|
32
|
-
icon?: React.ReactNode
|
|
31
|
+
/** Icon rendered alongside the label. Can be a ReactNode or a render function `(props) => ReactNode`. */
|
|
32
|
+
icon?: React.ReactNode | ((props: { label: string; size: ButtonSize; variant: ButtonVariant }) => React.ReactNode)
|
|
33
33
|
/** Side the icon appears on. Defaults to `'left'`. */
|
|
34
34
|
iconPosition?: 'left' | 'right'
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const containerSizeStyles: Record<ButtonSize, ViewStyle> = {
|
|
38
|
-
sm: { paddingHorizontal:
|
|
39
|
-
md: { paddingHorizontal:
|
|
40
|
-
lg: { paddingHorizontal:
|
|
38
|
+
sm: { paddingHorizontal: 20, paddingVertical: 12 },
|
|
39
|
+
md: { paddingHorizontal: 24, paddingVertical: 16 },
|
|
40
|
+
lg: { paddingHorizontal: 32, paddingVertical: 20 },
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const labelSizeStyles: Record<ButtonSize, TextStyle> = {
|
|
44
|
-
sm: { fontSize:
|
|
45
|
-
md: { fontSize:
|
|
44
|
+
sm: { fontSize: 15 },
|
|
45
|
+
md: { fontSize: 17 },
|
|
46
46
|
lg: { fontSize: 18 },
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -122,9 +122,9 @@ export function Button({
|
|
|
122
122
|
<ActivityIndicator size="small" color={spinnerColor} />
|
|
123
123
|
) : (
|
|
124
124
|
<>
|
|
125
|
-
{icon && iconPosition === 'left' && <>{icon}</>}
|
|
125
|
+
{icon && iconPosition === 'left' && <>{typeof icon === 'function' ? icon({ label, size, variant }) : icon}</>}
|
|
126
126
|
<Text style={[styles.label, labelVariantStyle, labelSizeStyles[size], icon ? styles.labelWithIcon : undefined]}>{label}</Text>
|
|
127
|
-
{icon && iconPosition === 'right' && <>{icon}</>}
|
|
127
|
+
{icon && iconPosition === 'right' && <>{typeof icon === 'function' ? icon({ label, size, variant }) : icon}</>}
|
|
128
128
|
</>
|
|
129
129
|
)}
|
|
130
130
|
</TouchableOpacity>
|
|
@@ -134,7 +134,7 @@ export function Button({
|
|
|
134
134
|
|
|
135
135
|
const styles = StyleSheet.create({
|
|
136
136
|
base: {
|
|
137
|
-
borderRadius:
|
|
137
|
+
borderRadius: 999,
|
|
138
138
|
alignItems: 'center',
|
|
139
139
|
justifyContent: 'center',
|
|
140
140
|
flexDirection: 'row',
|
|
@@ -69,7 +69,7 @@ export function CardFooter({ children, style }: CardFooterProps) {
|
|
|
69
69
|
|
|
70
70
|
const styles = StyleSheet.create({
|
|
71
71
|
card: {
|
|
72
|
-
borderRadius:
|
|
72
|
+
borderRadius: 20,
|
|
73
73
|
borderWidth: 1,
|
|
74
74
|
shadowColor: '#000',
|
|
75
75
|
shadowOffset: { width: 0, height: 1 },
|
|
@@ -78,24 +78,24 @@ const styles = StyleSheet.create({
|
|
|
78
78
|
elevation: 1,
|
|
79
79
|
},
|
|
80
80
|
header: {
|
|
81
|
-
padding:
|
|
81
|
+
padding: 28,
|
|
82
82
|
paddingBottom: 0,
|
|
83
|
-
gap:
|
|
83
|
+
gap: 8,
|
|
84
84
|
},
|
|
85
85
|
title: {
|
|
86
|
-
fontSize:
|
|
86
|
+
fontSize: 20,
|
|
87
87
|
fontWeight: '600',
|
|
88
|
-
lineHeight:
|
|
88
|
+
lineHeight: 28,
|
|
89
89
|
},
|
|
90
90
|
description: {
|
|
91
|
-
fontSize:
|
|
92
|
-
lineHeight:
|
|
91
|
+
fontSize: 15,
|
|
92
|
+
lineHeight: 22,
|
|
93
93
|
},
|
|
94
94
|
content: {
|
|
95
|
-
padding:
|
|
95
|
+
padding: 28,
|
|
96
96
|
},
|
|
97
97
|
footer: {
|
|
98
|
-
padding:
|
|
98
|
+
padding: 28,
|
|
99
99
|
paddingTop: 0,
|
|
100
100
|
flexDirection: 'row',
|
|
101
101
|
alignItems: 'center',
|
|
@@ -74,25 +74,25 @@ const styles = StyleSheet.create({
|
|
|
74
74
|
row: {
|
|
75
75
|
flexDirection: 'row',
|
|
76
76
|
alignItems: 'center',
|
|
77
|
-
gap:
|
|
77
|
+
gap: 12,
|
|
78
78
|
},
|
|
79
79
|
box: {
|
|
80
|
-
width:
|
|
81
|
-
height:
|
|
82
|
-
borderRadius:
|
|
80
|
+
width: 28,
|
|
81
|
+
height: 28,
|
|
82
|
+
borderRadius: 8,
|
|
83
83
|
borderWidth: 1.5,
|
|
84
84
|
alignItems: 'center',
|
|
85
85
|
justifyContent: 'center',
|
|
86
86
|
},
|
|
87
87
|
checkmark: {
|
|
88
|
-
width:
|
|
89
|
-
height:
|
|
88
|
+
width: 15,
|
|
89
|
+
height: 9,
|
|
90
90
|
borderLeftWidth: 2,
|
|
91
91
|
borderBottomWidth: 2,
|
|
92
92
|
transform: [{ rotate: '-45deg' }, { translateY: -1 }],
|
|
93
93
|
},
|
|
94
94
|
label: {
|
|
95
|
-
fontSize:
|
|
96
|
-
lineHeight:
|
|
95
|
+
fontSize: 15,
|
|
96
|
+
lineHeight: 22,
|
|
97
97
|
},
|
|
98
98
|
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
+
import { useTheme } from '../../theme'
|
|
4
|
+
|
|
5
|
+
export interface CurrencyDisplayProps {
|
|
6
|
+
value: number | string
|
|
7
|
+
/** Symbol prepended to the formatted value. Defaults to `'$'`. */
|
|
8
|
+
prefix?: string
|
|
9
|
+
/** When true, shows two decimal places separated by a comma (e.g. `$25.000,00`). Defaults to `false`. */
|
|
10
|
+
showDecimals?: boolean
|
|
11
|
+
style?: ViewStyle
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatValue(value: number | string, prefix: string, showDecimals: boolean): string {
|
|
15
|
+
const num = typeof value === 'string' ? parseFloat(value.replace(/[^0-9.-]/g, '')) : value
|
|
16
|
+
if (isNaN(num)) return `${prefix}0`
|
|
17
|
+
const abs = Math.abs(num)
|
|
18
|
+
const sign = num < 0 ? '-' : ''
|
|
19
|
+
const intPart = Math.floor(abs).toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')
|
|
20
|
+
if (showDecimals) {
|
|
21
|
+
const decStr = (abs % 1).toFixed(2).slice(2)
|
|
22
|
+
return `${sign}${prefix}${intPart},${decStr}`
|
|
23
|
+
}
|
|
24
|
+
return `${sign}${prefix}${intPart}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function CurrencyDisplay({ value, prefix = '$', showDecimals = false, style }: CurrencyDisplayProps) {
|
|
28
|
+
const { colors } = useTheme()
|
|
29
|
+
const formatted = formatValue(value, prefix, showDecimals)
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<View style={[styles.container, style]}>
|
|
33
|
+
<Text style={[styles.amount, { color: colors.foreground }]} allowFontScaling={true}>
|
|
34
|
+
{formatted}
|
|
35
|
+
</Text>
|
|
36
|
+
</View>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const styles = StyleSheet.create({
|
|
41
|
+
container: {},
|
|
42
|
+
amount: {
|
|
43
|
+
fontSize: 56,
|
|
44
|
+
fontWeight: '700',
|
|
45
|
+
},
|
|
46
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CurrencyDisplay'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { ViewStyle } from 'react-native'
|
|
3
|
+
import { Input } from '../Input'
|
|
4
|
+
|
|
5
|
+
export interface CurrencyInputLargeProps {
|
|
6
|
+
value?: string
|
|
7
|
+
onChangeText?: (formatted: string) => void
|
|
8
|
+
/** Called with the parsed numeric value (no separators, no prefix). */
|
|
9
|
+
onChangeValue?: (raw: number) => void
|
|
10
|
+
/** Symbol prepended to the formatted value. Defaults to `'$'`. */
|
|
11
|
+
prefix?: string
|
|
12
|
+
/** Character used to separate groups of three digits. Defaults to `'.'`. */
|
|
13
|
+
thousandsSeparator?: '.' | ','
|
|
14
|
+
label?: string
|
|
15
|
+
/** Red helper text; also changes input border to destructive color. */
|
|
16
|
+
error?: string
|
|
17
|
+
hint?: string
|
|
18
|
+
placeholder?: string
|
|
19
|
+
editable?: boolean
|
|
20
|
+
containerStyle?: ViewStyle
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function formatCurrency(raw: string, separator: '.' | ','): string {
|
|
24
|
+
const digits = raw.replace(/\D/g, '')
|
|
25
|
+
if (!digits) return ''
|
|
26
|
+
return digits.replace(/\B(?=(\d{3})+(?!\d))/g, separator)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function CurrencyInputLarge({
|
|
30
|
+
value,
|
|
31
|
+
onChangeText,
|
|
32
|
+
onChangeValue,
|
|
33
|
+
prefix = '$',
|
|
34
|
+
thousandsSeparator = '.',
|
|
35
|
+
label,
|
|
36
|
+
error,
|
|
37
|
+
hint,
|
|
38
|
+
placeholder,
|
|
39
|
+
editable,
|
|
40
|
+
containerStyle,
|
|
41
|
+
}: CurrencyInputLargeProps) {
|
|
42
|
+
const handleChange = (text: string) => {
|
|
43
|
+
const withoutPrefix = prefix && text.startsWith(prefix) ? text.slice(prefix.length) : text
|
|
44
|
+
const formatted = formatCurrency(withoutPrefix, thousandsSeparator)
|
|
45
|
+
const display = formatted ? `${prefix}${formatted}` : ''
|
|
46
|
+
onChangeText?.(display)
|
|
47
|
+
const separatorRegex = new RegExp(`\\${thousandsSeparator}`, 'g')
|
|
48
|
+
const raw = parseFloat(formatted.replace(separatorRegex, '') || '0')
|
|
49
|
+
onChangeValue?.(isNaN(raw) ? 0 : raw)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Input
|
|
54
|
+
value={value}
|
|
55
|
+
onChangeText={handleChange}
|
|
56
|
+
keyboardType="numeric"
|
|
57
|
+
label={label}
|
|
58
|
+
error={error}
|
|
59
|
+
hint={hint}
|
|
60
|
+
placeholder={placeholder ?? `${prefix}0`}
|
|
61
|
+
editable={editable}
|
|
62
|
+
containerStyle={containerStyle}
|
|
63
|
+
style={{ fontSize: 36 }}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './CurrencyInputLarge'
|
|
@@ -53,21 +53,21 @@ export function Input({ label, error, hint, containerStyle, style, onFocus, onBl
|
|
|
53
53
|
|
|
54
54
|
const styles = StyleSheet.create({
|
|
55
55
|
container: {
|
|
56
|
-
gap:
|
|
56
|
+
gap: 6,
|
|
57
57
|
},
|
|
58
58
|
label: {
|
|
59
|
-
fontSize:
|
|
59
|
+
fontSize: 15,
|
|
60
60
|
fontWeight: '500',
|
|
61
|
-
marginBottom:
|
|
61
|
+
marginBottom: 6,
|
|
62
62
|
},
|
|
63
63
|
input: {
|
|
64
64
|
borderWidth: 1.5,
|
|
65
|
-
borderRadius:
|
|
66
|
-
paddingHorizontal:
|
|
67
|
-
paddingVertical:
|
|
68
|
-
fontSize:
|
|
65
|
+
borderRadius: 14,
|
|
66
|
+
paddingHorizontal: 20,
|
|
67
|
+
paddingVertical: 16,
|
|
68
|
+
fontSize: 17,
|
|
69
69
|
},
|
|
70
70
|
helperText: {
|
|
71
|
-
fontSize:
|
|
71
|
+
fontSize: 13,
|
|
72
72
|
},
|
|
73
73
|
})
|
|
@@ -163,10 +163,10 @@ export function Select({
|
|
|
163
163
|
|
|
164
164
|
const styles = StyleSheet.create({
|
|
165
165
|
container: {
|
|
166
|
-
gap:
|
|
166
|
+
gap: 6,
|
|
167
167
|
},
|
|
168
168
|
label: {
|
|
169
|
-
fontSize:
|
|
169
|
+
fontSize: 15,
|
|
170
170
|
fontWeight: '500',
|
|
171
171
|
marginBottom: 2,
|
|
172
172
|
},
|
|
@@ -175,24 +175,24 @@ const styles = StyleSheet.create({
|
|
|
175
175
|
alignItems: 'center',
|
|
176
176
|
justifyContent: 'space-between',
|
|
177
177
|
borderWidth: 1.5,
|
|
178
|
-
borderRadius:
|
|
179
|
-
paddingHorizontal:
|
|
180
|
-
paddingVertical:
|
|
178
|
+
borderRadius: 14,
|
|
179
|
+
paddingHorizontal: 20,
|
|
180
|
+
paddingVertical: 16,
|
|
181
181
|
},
|
|
182
182
|
triggerText: {
|
|
183
|
-
fontSize:
|
|
183
|
+
fontSize: 17,
|
|
184
184
|
flex: 1,
|
|
185
185
|
},
|
|
186
186
|
chevron: {
|
|
187
|
-
fontSize:
|
|
187
|
+
fontSize: 16,
|
|
188
188
|
marginLeft: 8,
|
|
189
189
|
},
|
|
190
190
|
helperText: {
|
|
191
|
-
fontSize:
|
|
191
|
+
fontSize: 13,
|
|
192
192
|
},
|
|
193
193
|
sheetBackground: {
|
|
194
|
-
borderTopLeftRadius:
|
|
195
|
-
borderTopRightRadius:
|
|
194
|
+
borderTopLeftRadius: 24,
|
|
195
|
+
borderTopRightRadius: 24,
|
|
196
196
|
},
|
|
197
197
|
sheetHandle: {
|
|
198
198
|
width: 36,
|
|
@@ -200,32 +200,32 @@ const styles = StyleSheet.create({
|
|
|
200
200
|
borderRadius: 2,
|
|
201
201
|
},
|
|
202
202
|
sheetContent: {
|
|
203
|
-
paddingHorizontal:
|
|
204
|
-
paddingBottom:
|
|
203
|
+
paddingHorizontal: 20,
|
|
204
|
+
paddingBottom: 36,
|
|
205
205
|
},
|
|
206
206
|
sheetTitle: {
|
|
207
|
-
fontSize:
|
|
207
|
+
fontSize: 17,
|
|
208
208
|
fontWeight: '600',
|
|
209
|
-
paddingVertical:
|
|
209
|
+
paddingVertical: 16,
|
|
210
210
|
paddingHorizontal: 4,
|
|
211
211
|
},
|
|
212
212
|
option: {
|
|
213
213
|
flexDirection: 'row',
|
|
214
214
|
alignItems: 'center',
|
|
215
215
|
justifyContent: 'space-between',
|
|
216
|
-
paddingHorizontal:
|
|
217
|
-
paddingVertical:
|
|
218
|
-
borderRadius:
|
|
216
|
+
paddingHorizontal: 16,
|
|
217
|
+
paddingVertical: 16,
|
|
218
|
+
borderRadius: 12,
|
|
219
219
|
},
|
|
220
220
|
optionText: {
|
|
221
|
-
fontSize:
|
|
221
|
+
fontSize: 17,
|
|
222
222
|
flex: 1,
|
|
223
223
|
},
|
|
224
224
|
disabledOption: {
|
|
225
225
|
opacity: 0.45,
|
|
226
226
|
},
|
|
227
227
|
checkmark: {
|
|
228
|
-
fontSize:
|
|
228
|
+
fontSize: 16,
|
|
229
229
|
fontWeight: '600',
|
|
230
230
|
marginLeft: 8,
|
|
231
231
|
},
|
|
@@ -3,9 +3,9 @@ import { TouchableOpacity, Animated, StyleSheet, ViewStyle } from 'react-native'
|
|
|
3
3
|
import * as Haptics from 'expo-haptics'
|
|
4
4
|
import { useTheme } from '../../theme'
|
|
5
5
|
|
|
6
|
-
const TRACK_WIDTH =
|
|
7
|
-
const TRACK_HEIGHT =
|
|
8
|
-
const THUMB_SIZE =
|
|
6
|
+
const TRACK_WIDTH = 60
|
|
7
|
+
const TRACK_HEIGHT = 36
|
|
8
|
+
const THUMB_SIZE = 28
|
|
9
9
|
const THUMB_OFFSET = 4
|
|
10
10
|
const THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2
|
|
11
11
|
|
|
@@ -70,10 +70,13 @@ const styles = StyleSheet.create({
|
|
|
70
70
|
width: TRACK_WIDTH,
|
|
71
71
|
height: TRACK_HEIGHT,
|
|
72
72
|
borderRadius: TRACK_HEIGHT / 2,
|
|
73
|
-
justifyContent
|
|
74
|
-
|
|
73
|
+
// No justifyContent/alignItems — thumb uses absolute positioning
|
|
74
|
+
// so the track's flex layout doesn't interfere with translateX animation
|
|
75
75
|
},
|
|
76
76
|
thumb: {
|
|
77
|
+
position: 'absolute',
|
|
78
|
+
top: THUMB_OFFSET,
|
|
79
|
+
left: THUMB_OFFSET,
|
|
77
80
|
width: THUMB_SIZE,
|
|
78
81
|
height: THUMB_SIZE,
|
|
79
82
|
borderRadius: THUMB_SIZE / 2,
|
|
@@ -6,6 +6,7 @@ import { useTheme } from '../../theme'
|
|
|
6
6
|
export interface TabItem {
|
|
7
7
|
label: string
|
|
8
8
|
value: string
|
|
9
|
+
icon?: React.ReactNode
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export interface TabsProps {
|
|
@@ -60,15 +61,22 @@ function TabTrigger({
|
|
|
60
61
|
touchSoundDisabled={true}
|
|
61
62
|
>
|
|
62
63
|
<Animated.View style={{ transform: [{ scale }] }}>
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
styles.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
<View style={styles.triggerInner}>
|
|
65
|
+
{tab.icon ? (
|
|
66
|
+
<View style={styles.triggerIcon}>
|
|
67
|
+
{(typeof tab.icon === 'function' ? (tab.icon as any)(isActive) : tab.icon) as React.ReactNode}
|
|
68
|
+
</View>
|
|
69
|
+
) : null}
|
|
70
|
+
<Text
|
|
71
|
+
style={[
|
|
72
|
+
styles.triggerLabel,
|
|
73
|
+
{ color: isActive ? colors.foreground : colors.mutedForeground },
|
|
74
|
+
isActive && styles.activeTriggerLabel,
|
|
75
|
+
]}
|
|
76
|
+
>
|
|
77
|
+
{tab.label}
|
|
78
|
+
</Text>
|
|
79
|
+
</View>
|
|
72
80
|
</Animated.View>
|
|
73
81
|
</TouchableOpacity>
|
|
74
82
|
)
|
|
@@ -133,7 +141,7 @@ export function Tabs({ tabs, value, onValueChange, children, style }: TabsProps)
|
|
|
133
141
|
bottom: 4,
|
|
134
142
|
left: pillX,
|
|
135
143
|
width: pillWidth,
|
|
136
|
-
borderRadius:
|
|
144
|
+
borderRadius: 8,
|
|
137
145
|
shadowColor: '#000',
|
|
138
146
|
shadowOffset: { width: 0, height: 1 },
|
|
139
147
|
shadowOpacity: 0.1,
|
|
@@ -172,22 +180,33 @@ export function TabsContent({ value, activeValue, children, style }: TabsContent
|
|
|
172
180
|
const styles = StyleSheet.create({
|
|
173
181
|
list: {
|
|
174
182
|
flexDirection: 'row',
|
|
175
|
-
borderRadius:
|
|
183
|
+
borderRadius: 12,
|
|
176
184
|
padding: 4,
|
|
177
185
|
gap: 4,
|
|
178
186
|
},
|
|
179
187
|
pill: {},
|
|
180
188
|
trigger: {
|
|
181
189
|
flex: 1,
|
|
182
|
-
paddingVertical:
|
|
183
|
-
paddingHorizontal:
|
|
184
|
-
borderRadius:
|
|
190
|
+
paddingVertical: 12,
|
|
191
|
+
paddingHorizontal: 16,
|
|
192
|
+
borderRadius: 8,
|
|
185
193
|
alignItems: 'center',
|
|
186
194
|
justifyContent: 'center',
|
|
187
195
|
zIndex: 1,
|
|
188
196
|
},
|
|
197
|
+
triggerInner: {
|
|
198
|
+
flexDirection: 'row',
|
|
199
|
+
alignItems: 'center',
|
|
200
|
+
justifyContent: 'center',
|
|
201
|
+
gap: 8,
|
|
202
|
+
},
|
|
203
|
+
triggerIcon: {
|
|
204
|
+
marginRight: 6,
|
|
205
|
+
alignItems: 'center',
|
|
206
|
+
justifyContent: 'center',
|
|
207
|
+
},
|
|
189
208
|
triggerLabel: {
|
|
190
|
-
fontSize:
|
|
209
|
+
fontSize: 15,
|
|
191
210
|
fontWeight: '400',
|
|
192
211
|
},
|
|
193
212
|
activeTriggerLabel: {
|
|
@@ -10,12 +10,12 @@ export interface TextProps extends RNTextProps {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const variantStyles: Record<TextVariant, TextStyle> = {
|
|
13
|
-
h1: { fontSize:
|
|
14
|
-
h2: { fontSize:
|
|
15
|
-
h3: { fontSize:
|
|
16
|
-
body: { fontSize:
|
|
17
|
-
caption: { fontSize:
|
|
18
|
-
label: { fontSize:
|
|
13
|
+
h1: { fontSize: 40, fontWeight: '700', lineHeight: 52 },
|
|
14
|
+
h2: { fontSize: 28, fontWeight: '700', lineHeight: 36 },
|
|
15
|
+
h3: { fontSize: 22, fontWeight: '600', lineHeight: 30 },
|
|
16
|
+
body: { fontSize: 17, fontWeight: '400', lineHeight: 26 },
|
|
17
|
+
caption: { fontSize: 13, fontWeight: '400', lineHeight: 20 },
|
|
18
|
+
label: { fontSize: 15, fontWeight: '500', lineHeight: 22 },
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function Text({ variant = 'body', color, style, children, ...props }: TextProps) {
|
|
@@ -41,7 +41,7 @@ export function Textarea({
|
|
|
41
41
|
borderColor: error ? colors.destructive : focused ? colors.ring : colors.border,
|
|
42
42
|
color: colors.foreground,
|
|
43
43
|
backgroundColor: colors.background,
|
|
44
|
-
minHeight: rows *
|
|
44
|
+
minHeight: rows * 30,
|
|
45
45
|
},
|
|
46
46
|
style,
|
|
47
47
|
]}
|
|
@@ -69,21 +69,21 @@ export function Textarea({
|
|
|
69
69
|
|
|
70
70
|
const styles = StyleSheet.create({
|
|
71
71
|
container: {
|
|
72
|
-
gap:
|
|
72
|
+
gap: 6,
|
|
73
73
|
},
|
|
74
74
|
label: {
|
|
75
|
-
fontSize:
|
|
75
|
+
fontSize: 15,
|
|
76
76
|
fontWeight: '500',
|
|
77
|
-
marginBottom:
|
|
77
|
+
marginBottom: 6,
|
|
78
78
|
},
|
|
79
79
|
input: {
|
|
80
80
|
borderWidth: 1.5,
|
|
81
|
-
borderRadius:
|
|
82
|
-
paddingHorizontal:
|
|
83
|
-
paddingVertical:
|
|
84
|
-
fontSize:
|
|
81
|
+
borderRadius: 14,
|
|
82
|
+
paddingHorizontal: 20,
|
|
83
|
+
paddingVertical: 16,
|
|
84
|
+
fontSize: 17,
|
|
85
85
|
},
|
|
86
86
|
helperText: {
|
|
87
|
-
fontSize:
|
|
87
|
+
fontSize: 13,
|
|
88
88
|
},
|
|
89
89
|
})
|