@retray-dev/ui-kit 5.4.0 → 6.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retray-dev/ui-kit",
3
- "version": "5.4.0",
3
+ "version": "6.0.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,9 @@
52
52
  "react-native-reanimated": ">=4.0.0",
53
53
  "react-native-safe-area-context": ">=4.0.0",
54
54
  "react-native-size-matters": ">=0.4.0",
55
- "react-native-worklets": ">=0.5.0"
55
+ "react-native-worklets": ">=0.5.0",
56
+ "react-native-svg": ">=15.0.0",
57
+ "react-native-screens": ">=3.0.0"
56
58
  },
57
59
  "pnpm": {
58
60
  "overrides": {
@@ -87,6 +89,9 @@
87
89
  "react-native-reanimated": "~4.1.1",
88
90
  "react-native-safe-area-context": "~5.6.2",
89
91
  "react-native-worklets": "~0.5.1",
92
+ "sonner-native": "0.23.1",
93
+ "react-native-svg": "15.12.1",
94
+ "react-native-screens": "4.16.0",
90
95
  "tsup": "^8.0.0",
91
96
  "typescript": "^5.4.0",
92
97
  "typescript-eslint": "^8.0.0"
@@ -12,7 +12,11 @@ import Animated, {
12
12
  useAnimatedStyle,
13
13
  withTiming,
14
14
  Easing,
15
+ type EasingFunction,
15
16
  } from 'react-native-reanimated'
17
+
18
+ const easingExpand: EasingFunction = Easing.bezier(0.23, 1, 0.32, 1) as unknown as EasingFunction
19
+ const easingCollapse: EasingFunction = Easing.in(Easing.ease)
16
20
  import { Entypo } from '@expo/vector-icons'
17
21
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
18
22
  import { useTheme } from '../../theme'
@@ -73,14 +77,14 @@ function AccordionItemComponent({
73
77
  const derivedHeight = useDerivedValue(() =>
74
78
  withTiming(height.value * Number(isExpanded.value), {
75
79
  duration: 220,
76
- easing: isExpanded.value ? Easing.out(Easing.ease) : Easing.in(Easing.ease),
80
+ easing: isExpanded.value ? easingExpand : easingCollapse,
77
81
  })
78
82
  )
79
83
 
80
84
  const derivedRotation = useDerivedValue(() =>
81
85
  withTiming(isExpanded.value ? 1 : 0, {
82
86
  duration: 220,
83
- easing: isExpanded.value ? Easing.out(Easing.ease) : Easing.in(Easing.ease),
87
+ easing: isExpanded.value ? easingExpand : easingCollapse,
84
88
  })
85
89
  )
86
90
 
@@ -4,6 +4,7 @@ import { FontAwesome5, MaterialIcons, Entypo } from '@expo/vector-icons'
4
4
  import { useTheme } from '../../theme'
5
5
  import { s, vs, ms } from '../../utils/scaling'
6
6
  import { renderIcon } from '../../utils/icons'
7
+ import { RADIUS } from '../../tokens'
7
8
 
8
9
  export type AlertBannerVariant = 'default' | 'destructive' | 'success' | 'warning'
9
10
 
@@ -20,52 +21,36 @@ export interface AlertBannerProps {
20
21
  export function AlertBanner({ title, description, variant = 'default', icon, iconName, iconColor, style }: AlertBannerProps) {
21
22
  const { colors } = useTheme()
22
23
 
23
- const bgColor =
24
- variant === 'destructive' ? colors.destructiveTint
25
- : variant === 'success' ? colors.successTint
26
- : variant === 'warning' ? colors.warningTint
27
- : colors.card
28
-
29
- const borderColor =
30
- variant === 'destructive' ? colors.destructiveBorder
31
- : variant === 'success' ? colors.successBorder
32
- : variant === 'warning' ? colors.warningBorder
33
- : colors.border
34
-
35
24
  const accentColor =
36
25
  variant === 'destructive' ? colors.destructive
37
26
  : variant === 'success' ? colors.success
38
27
  : variant === 'warning' ? colors.warning
39
28
  : colors.primary
40
29
 
41
- const titleColor =
42
- variant === 'default' ? colors.foreground : accentColor
43
-
44
- const descColor =
45
- variant === 'default' ? colors.foregroundMuted : accentColor
46
-
47
30
  const defaultIcon =
48
31
  variant === 'success' ? (
49
- <FontAwesome5 name="check-circle" size={16} color={accentColor} />
32
+ <FontAwesome5 name="check-circle" size={ms(16)} color={accentColor} />
50
33
  ) : variant === 'destructive' ? (
51
- <MaterialIcons name="error-outline" size={17} color={accentColor} />
34
+ <MaterialIcons name="error-outline" size={ms(17)} color={accentColor} />
52
35
  ) : variant === 'warning' ? (
53
- <MaterialIcons name="warning-amber" size={17} color={accentColor} />
36
+ <MaterialIcons name="warning-amber" size={ms(17)} color={accentColor} />
54
37
  ) : (
55
- <Entypo name="info-with-circle" size={16} color={accentColor} />
38
+ <Entypo name="info-with-circle" size={ms(16)} color={accentColor} />
56
39
  )
57
40
 
58
41
  const effectiveIcon: React.ReactNode = iconName
59
- ? renderIcon(iconName, 16, iconColor ?? accentColor)
42
+ ? renderIcon(iconName, ms(16), iconColor ?? accentColor)
60
43
  : icon ?? defaultIcon
61
44
 
62
45
  return (
63
- <View style={[styles.container, { backgroundColor: bgColor, borderColor }, style]}>
46
+ <View style={[styles.container, { backgroundColor: colors.card }, style]}>
47
+ {/* Icon */}
64
48
  <View style={styles.iconSlot}>{effectiveIcon}</View>
49
+ {/* Text */}
65
50
  <View style={styles.content}>
66
- <Text style={[styles.title, { color: titleColor }]} allowFontScaling={true}>{title}</Text>
51
+ <Text style={[styles.title, { color: colors.foreground }]} allowFontScaling={true}>{title}</Text>
67
52
  {description ? (
68
- <Text style={[styles.description, { color: descColor }]} allowFontScaling={true}>{description}</Text>
53
+ <Text style={[styles.description, { color: colors.foregroundMuted }]} allowFontScaling={true}>{description}</Text>
69
54
  ) : null}
70
55
  </View>
71
56
  </View>
@@ -76,11 +61,10 @@ const styles = StyleSheet.create({
76
61
  container: {
77
62
  flexDirection: 'row',
78
63
  alignItems: 'flex-start',
79
- borderWidth: 0.5,
80
- borderRadius: 10,
81
- paddingHorizontal: s(12),
82
- paddingVertical: vs(10),
83
- gap: s(10),
64
+ borderRadius: RADIUS.lg,
65
+ gap: s(8),
66
+ paddingVertical: vs(8),
67
+ paddingHorizontal: s(10),
84
68
  },
85
69
  iconSlot: {
86
70
  marginTop: vs(1),
@@ -92,12 +76,11 @@ const styles = StyleSheet.create({
92
76
  title: {
93
77
  fontFamily: 'Poppins-Medium',
94
78
  fontSize: ms(13),
95
- lineHeight: ms(18),
79
+ lineHeight: ms(19),
96
80
  },
97
81
  description: {
98
82
  fontFamily: 'Poppins-Regular',
99
83
  fontSize: ms(12),
100
84
  lineHeight: ms(17),
101
- opacity: 0.85,
102
85
  },
103
86
  })
@@ -12,7 +12,7 @@ import {
12
12
  } from 'react-native'
13
13
 
14
14
  const nativeDriver = Platform.OS !== 'web'
15
- import { impactLight } from '../../utils/haptics'
15
+ import { impactMedium } from '../../utils/haptics'
16
16
  import { useTheme } from '../../theme'
17
17
  import { s, vs, ms, mvs } from '../../utils/scaling'
18
18
  import { renderIcon } from '../../utils/icons'
@@ -73,15 +73,15 @@ export function Button({
73
73
 
74
74
  const handlePressIn = () => {
75
75
  if (isDisabled) return
76
- Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
76
+ Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, stiffness: 600, damping: 35, mass: 0.8 }).start()
77
77
  }
78
78
 
79
79
  const handlePressOut = () => {
80
- Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
80
+ Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, stiffness: 280, damping: 22, mass: 0.8 }).start()
81
81
  }
82
82
 
83
83
  const handlePress: TouchableOpacityProps['onPress'] = (e) => {
84
- impactLight()
84
+ impactMedium()
85
85
  onPress?.(e)
86
86
  }
87
87
 
@@ -135,7 +135,16 @@ export function Button({
135
135
  {...props}
136
136
  >
137
137
  {loading ? (
138
- <ActivityIndicator size="small" color={spinnerColor} />
138
+ <>
139
+ <ActivityIndicator size="small" color={spinnerColor} style={{ marginRight: s(6) }} />
140
+ <Text
141
+ style={[styles.label, labelVariantStyle, labelSizeStyles[size], styles.labelLoading]}
142
+ allowFontScaling={true}
143
+ numberOfLines={1}
144
+ >
145
+ {label}
146
+ </Text>
147
+ </>
139
148
  ) : (
140
149
  <>
141
150
  {effectiveIcon && iconPosition === 'left' && <>{effectiveIcon}</>}
@@ -156,7 +165,7 @@ export function Button({
156
165
 
157
166
  const styles = StyleSheet.create({
158
167
  base: {
159
- borderRadius: RADIUS.xl, // 32pxpill-shaped primary CTA (Airbnb spec)
168
+ borderRadius: RADIUS.md, // 14pxAirbnb-aligned rounded rect (not pill)
160
169
  alignItems: 'center',
161
170
  justifyContent: 'center',
162
171
  flexDirection: 'row',
@@ -174,4 +183,7 @@ const styles = StyleSheet.create({
174
183
  labelWithIcon: {
175
184
  marginHorizontal: s(6),
176
185
  },
186
+ labelLoading: {
187
+ opacity: 0.6,
188
+ },
177
189
  })
@@ -3,6 +3,7 @@ import { View, Text, TouchableOpacity, Animated, StyleSheet, ViewStyle, TextStyl
3
3
  import { impactLight } from '../../utils/haptics'
4
4
  import { useTheme } from '../../theme'
5
5
  import { s, vs, ms, mvs } from '../../utils/scaling'
6
+ import { RADIUS } from '../../tokens'
6
7
 
7
8
  const nativeDriver = Platform.OS !== 'web'
8
9
 
@@ -51,8 +52,9 @@ export function Card({ children, variant = 'elevated', onPress, style }: CardPro
51
52
  Animated.spring(scale, {
52
53
  toValue: 0.98,
53
54
  useNativeDriver: nativeDriver,
54
- speed: 40,
55
- bounciness: 0,
55
+ stiffness: 400,
56
+ damping: 30,
57
+ mass: 1.0,
56
58
  }).start()
57
59
  }
58
60
 
@@ -61,8 +63,9 @@ export function Card({ children, variant = 'elevated', onPress, style }: CardPro
61
63
  Animated.spring(scale, {
62
64
  toValue: 1,
63
65
  useNativeDriver: nativeDriver,
64
- speed: 40,
65
- bounciness: 4,
66
+ stiffness: 250,
67
+ damping: 24,
68
+ mass: 1.0,
66
69
  }).start()
67
70
  }
68
71
 
@@ -77,10 +80,10 @@ export function Card({ children, variant = 'elevated', onPress, style }: CardPro
77
80
  backgroundColor: colors.card,
78
81
  borderColor: colors.border,
79
82
  shadowColor: '#000',
80
- shadowOffset: { width: 0, height: 4 },
81
- shadowOpacity: 0.06,
82
- shadowRadius: 12,
83
- elevation: 3,
83
+ shadowOffset: { width: 0, height: 6 },
84
+ shadowOpacity: 0.10,
85
+ shadowRadius: 16,
86
+ elevation: 4,
84
87
  },
85
88
  outlined: {
86
89
  backgroundColor: colors.card,
@@ -147,7 +150,7 @@ export function CardFooter({ children, style }: CardFooterProps) {
147
150
 
148
151
  const styles = StyleSheet.create({
149
152
  card: {
150
- borderRadius: 14, // RADIUS.md — Airbnb property card spec
153
+ borderRadius: RADIUS.md, // 14px — Airbnb property card spec
151
154
  borderWidth: 1,
152
155
  },
153
156
  header: {
@@ -7,7 +7,7 @@ import {
7
7
  type BottomSheetBackdropProps,
8
8
  } from '@gorhom/bottom-sheet'
9
9
  import { Feather } from '@expo/vector-icons'
10
- import { impactLight } from '../../utils/haptics'
10
+ import { impactMedium, notificationSuccess, selectionAsync as hapticSelection } from '../../utils/haptics'
11
11
  import { useTheme } from '../../theme'
12
12
  import { Button } from '../Button'
13
13
  import { s, vs, ms, mvs } from '../../utils/scaling'
@@ -38,7 +38,7 @@ export function ConfirmDialog({
38
38
 
39
39
  useEffect(() => {
40
40
  if (visible) {
41
- impactLight()
41
+ impactMedium()
42
42
  ref.current?.present()
43
43
  } else {
44
44
  ref.current?.dismiss()
@@ -78,7 +78,7 @@ export function ConfirmDialog({
78
78
  label={confirmLabel}
79
79
  variant={confirmVariant}
80
80
  fullWidth
81
- onPress={onConfirm}
81
+ onPress={() => { notificationSuccess(); onConfirm() }}
82
82
  icon={
83
83
  <Feather
84
84
  name={confirmVariant === 'destructive' ? 'trash-2' : 'check'}
@@ -95,7 +95,7 @@ export function ConfirmDialog({
95
95
  label={cancelLabel}
96
96
  variant="secondary"
97
97
  fullWidth
98
- onPress={onCancel}
98
+ onPress={() => { hapticSelection(); onCancel() }}
99
99
  icon={<Feather name="x" size={15} color={colors.foreground} />}
100
100
  />
101
101
  </View>
@@ -12,6 +12,13 @@ const variantFontSize: Record<CurrencyDisplayVariant, number> = {
12
12
  small: ms(14),
13
13
  }
14
14
 
15
+ const variantLetterSpacing: Record<CurrencyDisplayVariant, number> = {
16
+ hero: -2,
17
+ large: -1,
18
+ medium: -0.5,
19
+ small: 0,
20
+ }
21
+
15
22
  export interface CurrencyDisplayProps {
16
23
  value: number | string
17
24
  /** Symbol prepended to the formatted value. Defaults to `'$'`. */
@@ -47,11 +54,12 @@ export function CurrencyDisplay({ value, prefix = '$', showDecimals = false, tex
47
54
  const formatted = formatValue(value, prefix, showDecimals)
48
55
  const baseFontSize = variant ? variantFontSize[variant] : ms(56)
49
56
  const fontSize = maxFontSize ?? baseFontSize
57
+ const letterSpacing = variant ? variantLetterSpacing[variant] : -2
50
58
 
51
59
  return (
52
60
  <View style={[styles.container, style]}>
53
61
  <Text
54
- style={[styles.amount, { color: textColor ?? colors.foreground, fontSize }]}
62
+ style={[styles.amount, { color: textColor ?? colors.foreground, fontSize, letterSpacing }]}
55
63
  allowFontScaling={true}
56
64
  numberOfLines={autoScale ? 1 : undefined}
57
65
  adjustsFontSizeToFit={autoScale}
@@ -64,9 +72,12 @@ export function CurrencyDisplay({ value, prefix = '$', showDecimals = false, tex
64
72
  }
65
73
 
66
74
  const styles = StyleSheet.create({
67
- container: {},
75
+ container: {
76
+ alignSelf: 'flex-start',
77
+ },
68
78
  amount: {
69
79
  fontFamily: 'Poppins-Bold',
70
- letterSpacing: -2,
80
+ includeFontPadding: false,
81
+ textAlignVertical: 'top',
71
82
  },
72
83
  })
@@ -3,6 +3,7 @@ import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
4
  import { s, vs, ms, mvs } from '../../utils/scaling'
5
5
  import { renderIcon } from '../../utils/icons'
6
+ import { Button } from '../Button'
6
7
 
7
8
  export interface EmptyStateProps {
8
9
  icon?: React.ReactNode
@@ -15,13 +16,18 @@ export interface EmptyStateProps {
15
16
  iconColor?: string
16
17
  title: string
17
18
  description?: string
19
+ /** Custom action node. Use `actionLabel` + `onAction` for a pre-built primary Button. */
18
20
  action?: React.ReactNode
21
+ /** Label for a convenience primary Button rendered below description. Ignored in compact size. */
22
+ actionLabel?: string
23
+ /** Called when the convenience action Button is pressed. Required when `actionLabel` is set. */
24
+ onAction?: () => void
19
25
  /** `compact` hides description/action and uses tighter spacing and a smaller icon. */
20
26
  size?: 'default' | 'compact'
21
27
  style?: ViewStyle
22
28
  }
23
29
 
24
- export function EmptyState({ icon, iconName, iconColor, title, description, action, size = 'default', style }: EmptyStateProps) {
30
+ export function EmptyState({ icon, iconName, iconColor, title, description, action, actionLabel, onAction, size = 'default', style }: EmptyStateProps) {
25
31
  const { colors } = useTheme()
26
32
  const isCompact = size === 'compact'
27
33
 
@@ -60,7 +66,13 @@ export function EmptyState({ icon, iconName, iconColor, title, description, acti
60
66
  <Text style={[styles.description, { color: colors.foregroundMuted }]} allowFontScaling={true}>{description}</Text>
61
67
  ) : null}
62
68
  </View>
63
- {action && !isCompact ? <View style={styles.action}>{action}</View> : null}
69
+ {!isCompact && (action ? (
70
+ <View style={styles.action}>{action}</View>
71
+ ) : actionLabel && onAction ? (
72
+ <View style={styles.action}>
73
+ <Button label={actionLabel} variant="primary" onPress={onAction} />
74
+ </View>
75
+ ) : null)}
64
76
  </View>
65
77
  )
66
78
  }
@@ -72,12 +84,8 @@ const styles = StyleSheet.create({
72
84
  borderWidth: 1,
73
85
  borderStyle: 'dashed',
74
86
  borderRadius: ms(12),
75
- padding: s(32),
76
- gap: vs(16),
77
87
  },
78
88
  containerCompact: {
79
- padding: s(20),
80
- gap: vs(10),
81
89
  },
82
90
  iconWrapper: {
83
91
  width: s(80),
@@ -85,16 +93,20 @@ const styles = StyleSheet.create({
85
93
  borderRadius: ms(20),
86
94
  alignItems: 'center',
87
95
  justifyContent: 'center',
96
+ marginTop: s(32),
88
97
  },
89
98
  iconWrapperCompact: {
90
99
  width: s(56),
91
100
  height: s(56),
92
101
  borderRadius: ms(14),
102
+ marginTop: s(20),
93
103
  },
94
104
  textWrapper: {
95
105
  alignItems: 'center',
96
106
  gap: vs(8),
97
107
  maxWidth: s(320),
108
+ paddingHorizontal: s(32),
109
+ marginTop: vs(16),
98
110
  },
99
111
  title: {
100
112
  fontFamily: 'Poppins-Medium',
@@ -103,6 +115,7 @@ const styles = StyleSheet.create({
103
115
  },
104
116
  titleCompact: {
105
117
  fontSize: ms(15),
118
+ marginTop: vs(10),
106
119
  },
107
120
  description: {
108
121
  fontFamily: 'Poppins-Regular',
@@ -112,5 +125,7 @@ const styles = StyleSheet.create({
112
125
  },
113
126
  action: {
114
127
  marginTop: vs(8),
128
+ marginBottom: s(32),
129
+ paddingHorizontal: s(32),
115
130
  },
116
131
  })
@@ -1,5 +1,5 @@
1
- import React, { useState } from 'react'
2
- import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, TextStyle, TouchableOpacity, Platform } from 'react-native'
1
+ import React, { useState, useRef } from 'react'
2
+ import { TextInput, View, Text, Animated, StyleSheet, TextInputProps, ViewStyle, TextStyle, TouchableOpacity, Platform, Easing } from 'react-native'
3
3
  import { AntDesign } from '@expo/vector-icons'
4
4
  import { useTheme } from '../../theme'
5
5
  import { s, vs, ms } from '../../utils/scaling'
@@ -16,6 +16,8 @@ export interface InputProps extends TextInputProps {
16
16
  error?: string
17
17
  /** Helper text shown below the input when there is no error. */
18
18
  hint?: string
19
+ /** Disabled visual state — dimmed appearance, not editable. Also sets `editable={false}`. */
20
+ disabled?: boolean
19
21
  /** Text or component rendered before the input text. */
20
22
  prefix?: React.ReactNode
21
23
  /** Text or component rendered after the input text. */
@@ -46,11 +48,13 @@ export interface InputProps extends TextInputProps {
46
48
  inputWrapperStyle?: ViewStyle
47
49
  }
48
50
 
49
- export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixStyle, prefixIcon, suffixIcon, prefixIconColor, suffixIconColor, type = 'text', containerStyle, inputWrapperStyle, style, onFocus, onBlur, secureTextEntry, ...props }: InputProps) {
51
+ export function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suffixStyle, prefixIcon, suffixIcon, prefixIconColor, suffixIconColor, type = 'text', containerStyle, inputWrapperStyle, style, onFocus, onBlur, secureTextEntry, editable, ...props }: InputProps) {
50
52
  const { colors } = useTheme()
51
53
  const [focused, setFocused] = useState(false)
52
54
  const [showPassword, setShowPassword] = useState(false)
55
+ const focusAnim = useRef(new Animated.Value(0)).current
53
56
 
57
+ const isDisabled = disabled || editable === false
54
58
  const isPassword = type === 'password'
55
59
  const effectiveSecure = isPassword ? !showPassword : secureTextEntry
56
60
 
@@ -68,18 +72,19 @@ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixS
68
72
  : suffix
69
73
 
70
74
  return (
71
- <View style={[styles.container, containerStyle]}>
75
+ <View style={[styles.container, isDisabled && styles.containerDisabled, containerStyle]}>
72
76
  {label ? <Text style={[styles.label, { color: colors.foreground }]} allowFontScaling={true}>{label}</Text> : null}
73
- <View
77
+ <Animated.View
74
78
  style={[
75
79
  styles.inputWrapper,
76
80
  {
77
81
  borderColor: error
78
82
  ? colors.destructive
79
- : focused
80
- ? colors.primary
81
- : colors.border,
82
- backgroundColor: colors.background,
83
+ : focusAnim.interpolate({
84
+ inputRange: [0, 1],
85
+ outputRange: [colors.border, colors.primary],
86
+ }),
87
+ backgroundColor: isDisabled ? colors.surface : colors.background,
83
88
  },
84
89
  inputWrapperStyle,
85
90
  ]}
@@ -104,15 +109,18 @@ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixS
104
109
  ]}
105
110
  onFocus={(e) => {
106
111
  setFocused(true)
112
+ Animated.timing(focusAnim, { toValue: 1, duration: 120, easing: Easing.out(Easing.ease), useNativeDriver: false }).start()
107
113
  onFocus?.(e)
108
114
  }}
109
115
  onBlur={(e) => {
110
116
  setFocused(false)
117
+ Animated.timing(focusAnim, { toValue: 0, duration: 80, easing: Easing.out(Easing.ease), useNativeDriver: false }).start()
111
118
  onBlur?.(e)
112
119
  }}
113
120
  placeholderTextColor={colors.foregroundMuted}
114
121
  allowFontScaling={true}
115
122
  secureTextEntry={effectiveSecure}
123
+ editable={isDisabled ? false : editable}
116
124
  {...props}
117
125
  />
118
126
  {effectiveSuffix ? (
@@ -124,7 +132,7 @@ export function Input({ label, error, hint, prefix, suffix, prefixStyle, suffixS
124
132
  <View style={styles.suffixContainer}>{effectiveSuffix}</View>
125
133
  )
126
134
  ) : null}
127
- </View>
135
+ </Animated.View>
128
136
  {error ? (
129
137
  <Text style={[styles.helperText, { color: colors.destructive }]} allowFontScaling={true}>{error}</Text>
130
138
  ) : null}
@@ -139,6 +147,9 @@ const styles = StyleSheet.create({
139
147
  container: {
140
148
  gap: vs(8),
141
149
  },
150
+ containerDisabled: {
151
+ opacity: 0.6,
152
+ },
142
153
  label: {
143
154
  fontFamily: 'Poppins-Medium',
144
155
  fontSize: ms(14), // caption size for input labels
@@ -14,6 +14,7 @@ import { selectionAsync as hapticSelection } from '../../utils/haptics'
14
14
  import { useTheme } from '../../theme'
15
15
  import { s, vs, ms, mvs } from '../../utils/scaling'
16
16
  import { renderIcon } from '../../utils/icons'
17
+ import { RADIUS } from '../../tokens'
17
18
 
18
19
  const nativeDriver = Platform.OS !== 'web'
19
20
 
@@ -109,8 +110,9 @@ export function ListItem({
109
110
  Animated.spring(scale, {
110
111
  toValue: 0.97,
111
112
  useNativeDriver: nativeDriver,
112
- speed: 40,
113
- bounciness: 0,
113
+ stiffness: 350,
114
+ damping: 28,
115
+ mass: 0.9,
114
116
  }).start()
115
117
  }
116
118
 
@@ -118,8 +120,9 @@ export function ListItem({
118
120
  Animated.spring(scale, {
119
121
  toValue: 1,
120
122
  useNativeDriver: nativeDriver,
121
- speed: 40,
122
- bounciness: 4,
123
+ stiffness: 220,
124
+ damping: 20,
125
+ mass: 0.9,
123
126
  }).start()
124
127
  }
125
128
 
@@ -140,7 +143,7 @@ export function ListItem({
140
143
  variant === 'card'
141
144
  ? {
142
145
  backgroundColor: colors.card,
143
- borderRadius: 12,
146
+ borderRadius: RADIUS.md,
144
147
  borderWidth: 1,
145
148
  borderColor: colors.border,
146
149
  shadowColor: '#000',
@@ -216,7 +219,10 @@ export function ListItem({
216
219
  <View
217
220
  style={[
218
221
  styles.separator,
219
- { backgroundColor: colors.border, marginLeft: effectiveLeft ? s(16) + s(44) + s(12) : s(16) },
222
+ {
223
+ backgroundColor: colors.border,
224
+ marginLeft: effectiveLeft ? s(44) + s(12) : 0
225
+ },
220
226
  ]}
221
227
  />
222
228
  ) : null}
@@ -228,7 +234,7 @@ const styles = StyleSheet.create({
228
234
  container: {
229
235
  flexDirection: 'row',
230
236
  alignItems: 'center',
231
- paddingHorizontal: s(16),
237
+ paddingHorizontal: 0,
232
238
  paddingVertical: vs(10),
233
239
  gap: s(12),
234
240
  },
@@ -274,7 +280,7 @@ const styles = StyleSheet.create({
274
280
  },
275
281
  separator: {
276
282
  height: StyleSheet.hairlineWidth,
277
- marginRight: s(16),
283
+ marginRight: 0,
278
284
  },
279
285
  disabled: {
280
286
  opacity: 0.45,
@@ -229,6 +229,7 @@ const styles = StyleSheet.create({
229
229
  },
230
230
  meta: {
231
231
  paddingTop: vs(8),
232
+ paddingBottom: vs(4),
232
233
  gap: vs(2),
233
234
  },
234
235
  title: {