@retray-dev/ui-kit 9.1.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/COMPONENTS.md +166 -4
  2. package/CONSUMER.md +247 -0
  3. package/DESIGN.md +668 -0
  4. package/FONTS.md +107 -0
  5. package/README.md +3 -3
  6. package/dist/AlertBanner.d.mts +3 -1
  7. package/dist/AlertBanner.d.ts +3 -1
  8. package/dist/AlertBanner.js +18 -2
  9. package/dist/AlertBanner.mjs +1 -1
  10. package/dist/ConfirmDialog.d.mts +3 -1
  11. package/dist/ConfirmDialog.d.ts +3 -1
  12. package/dist/ConfirmDialog.js +3 -0
  13. package/dist/ConfirmDialog.mjs +1 -1
  14. package/dist/CurrencyInput.d.mts +3 -1
  15. package/dist/CurrencyInput.d.ts +3 -1
  16. package/dist/CurrencyInput.js +52 -39
  17. package/dist/CurrencyInput.mjs +2 -3
  18. package/dist/ImageUpload.d.mts +27 -0
  19. package/dist/ImageUpload.d.ts +27 -0
  20. package/dist/ImageUpload.js +399 -0
  21. package/dist/ImageUpload.mjs +9 -0
  22. package/dist/Input.d.mts +3 -1
  23. package/dist/Input.d.ts +3 -1
  24. package/dist/Input.js +48 -37
  25. package/dist/Input.mjs +1 -2
  26. package/dist/ListItem.d.mts +9 -2
  27. package/dist/ListItem.d.ts +9 -2
  28. package/dist/ListItem.js +9 -2
  29. package/dist/ListItem.mjs +1 -1
  30. package/dist/SheetSelect.d.mts +25 -0
  31. package/dist/SheetSelect.d.ts +25 -0
  32. package/dist/SheetSelect.js +440 -0
  33. package/dist/SheetSelect.mjs +9 -0
  34. package/dist/Textarea.mjs +1 -2
  35. package/dist/{chunk-M6ZXVBTK.mjs → chunk-6MKGPAR2.mjs} +21 -5
  36. package/dist/{chunk-EH745HE5.mjs → chunk-CZCQZHG6.mjs} +13 -4
  37. package/dist/{chunk-7QHVVCB3.mjs → chunk-FZZLPJ6B.mjs} +3 -0
  38. package/dist/{chunk-MAC465BB.mjs → chunk-JUXSWN54.mjs} +5 -3
  39. package/dist/{chunk-BNP626TY.mjs → chunk-OHBNABL5.mjs} +10 -3
  40. package/dist/chunk-URI2WBIV.mjs +147 -0
  41. package/dist/chunk-Y4GL2MHX.mjs +112 -0
  42. package/dist/{chunk-756RAKE4.mjs → chunk-ZUR7AU5R.mjs} +38 -20
  43. package/dist/fonts.d.mts +32 -0
  44. package/dist/fonts.d.ts +32 -0
  45. package/dist/fonts.js +44 -0
  46. package/dist/fonts.mjs +37 -0
  47. package/dist/index.d.mts +26 -1
  48. package/dist/index.d.ts +26 -1
  49. package/dist/index.js +425 -106
  50. package/dist/index.mjs +55 -17
  51. package/package.json +23 -6
  52. package/src/components/AlertBanner/AlertBanner.tsx +21 -3
  53. package/src/components/ConfirmDialog/ConfirmDialog.tsx +5 -0
  54. package/src/components/CurrencyInput/CurrencyInput.tsx +4 -0
  55. package/src/components/ImageUpload/ImageUpload.tsx +158 -0
  56. package/src/components/ImageUpload/index.ts +1 -0
  57. package/src/components/Input/Input.tsx +64 -53
  58. package/src/components/ListItem/ListItem.tsx +23 -4
  59. package/src/components/SheetSelect/SheetSelect.tsx +192 -0
  60. package/src/components/SheetSelect/index.ts +1 -0
  61. package/src/fonts.ts +30 -29
  62. package/src/hooks/useConfirmDialog.ts +67 -0
  63. package/src/index.ts +6 -0
  64. package/dist/chunk-26BCI223.mjs +0 -14
package/FONTS.md ADDED
@@ -0,0 +1,107 @@
1
+ # Font Setup Guide
2
+
3
+ ## Using Sohne Fonts in Your App
4
+
5
+ All components in `@retray-dev/ui-kit` use the **Sohne** font family. Consumer apps must load these fonts at the app root using `expo-font`.
6
+
7
+ ### Installation
8
+
9
+ The library includes Sohne font files as raw `.otf` assets. Import and load them:
10
+
11
+ ```tsx
12
+ import { useFonts } from 'expo-font'
13
+ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
14
+
15
+ export default function App() {
16
+ const [fontsLoaded] = useFonts(SohneFonts)
17
+
18
+ if (!fontsLoaded) {
19
+ return null // or <SplashScreen />
20
+ }
21
+
22
+ return (
23
+ <ThemeProvider>
24
+ {/* your app */}
25
+ </ThemeProvider>
26
+ )
27
+ }
28
+ ```
29
+
30
+ ### With Splash Screen
31
+
32
+ ```tsx
33
+ import * as SplashScreen from 'expo-splash-screen'
34
+ import { useFonts } from 'expo-font'
35
+ import { SohneFonts } from '@retray-dev/ui-kit/fonts'
36
+
37
+ SplashScreen.preventAutoHideAsync()
38
+
39
+ export default function App() {
40
+ const [fontsLoaded] = useFonts(SohneFonts)
41
+
42
+ useEffect(() => {
43
+ if (fontsLoaded) {
44
+ SplashScreen.hideAsync()
45
+ }
46
+ }, [fontsLoaded])
47
+
48
+ if (!fontsLoaded) {
49
+ return null
50
+ }
51
+
52
+ return (
53
+ <ThemeProvider>
54
+ {/* your app */}
55
+ </ThemeProvider>
56
+ )
57
+ }
58
+ ```
59
+
60
+ ### Font Weights Included
61
+
62
+ `SohneFonts` exports 28 `.otf` files total.
63
+
64
+ **Sohne (14 files):**
65
+ - `Sohne-ExtraLight`
66
+ - `Sohne-ExtraLightItalic`
67
+ - `Sohne-Light`
68
+ - `Sohne-LightItalic`
69
+ - `Sohne-Regular`
70
+ - `Sohne-Italic`
71
+ - `Sohne-Medium`
72
+ - `Sohne-MediumItalic`
73
+ - `Sohne-SemiBold`
74
+ - `Sohne-SemiBoldItalic`
75
+ - `Sohne-Bold`
76
+ - `Sohne-BoldItalic`
77
+ - `Sohne-ExtraBold`
78
+ - `Sohne-ExtraBoldItalic`
79
+
80
+ **SohneMono (14 files):**
81
+ - `SohneMono-ExtraLight`
82
+ - `SohneMono-ExtraLightItalic`
83
+ - `SohneMono-Light`
84
+ - `SohneMono-LightItalic`
85
+ - `SohneMono-Regular`
86
+ - `SohneMono-Italic`
87
+ - `SohneMono-Medium`
88
+ - `SohneMono-MediumItalic`
89
+ - `SohneMono-SemiBold`
90
+ - `SohneMono-SemiBoldItalic`
91
+ - `SohneMono-Bold`
92
+ - `SohneMono-BoldItalic`
93
+ - `SohneMono-ExtraBold`
94
+ - `SohneMono-ExtraBoldItalic`
95
+
96
+ ### How It Works
97
+
98
+ 1. **Library components** reference fonts by family name (e.g., `fontFamily: 'Sohne-SemiBold'`)
99
+ 2. **Consumer app** loads font files at startup via `useFonts(SohneFonts)`
100
+ 3. **Metro bundler** resolves `require()` calls to `.otf` files in `node_modules/@retray-dev/ui-kit/src/assets/fonts/`
101
+ 4. **React Native** maps family names to loaded font files
102
+
103
+ Font files ship as raw assets — **not bundled into `dist/`**. Metro handles asset resolution natively.
104
+
105
+ ### Peer Dependency
106
+
107
+ `expo-font` ≥14.0.0 must be installed in the consuming app.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A personal React Native / Expo UI component library with a built-in design system, dark mode support, haptic feedback, and smooth animations.
4
4
 
5
- - 49 components across 9 categories (plus the deep-import `HolographicCard`)
5
+ - 51 components across 9 categories (plus the deep-import `HolographicCard`)
6
6
  - Light/dark theme with 12 public tokens (25 resolved) and full customization
7
7
  - Apple HIG–compliant touch targets and haptic feedback
8
8
  - Animated interactions: spring press, sliding tabs, accordion easing, animated progress
@@ -23,7 +23,7 @@ pnpm add @retray-dev/ui-kit
23
23
  Install these in your app if not already present:
24
24
 
25
25
  ```bash
26
- pnpm add expo-font expo-haptics expo-linear-gradient react-native-safe-area-context @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler react-native-worklets @react-native-picker/picker @react-native-community/slider @expo/vector-icons react-native-size-matters react-native-svg react-native-screens sonner-native pressto
26
+ pnpm add expo-font expo-haptics expo-linear-gradient react-native-safe-area-context @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler react-native-worklets @react-native-picker/picker @react-native-community/slider @expo/vector-icons react-native-size-matters react-native-svg react-native-screens sonner-native pressto react-native-ease
27
27
  ```
28
28
 
29
29
  For Expo projects, run `npx expo install` instead to get SDK-compatible versions.
@@ -146,7 +146,7 @@ import { SPACING, ICON_SIZES, RADIUS, SHADOWS, BREAKPOINTS, TYPOGRAPHY } from '@
146
146
  | ----------- | ----------------------------------------------------------------------------------------------- |
147
147
  | Display | `Text`, `Badge`, `Avatar`, `Separator`, `Spinner`, `Skeleton`, `Progress`, `CurrencyDisplay` |
148
148
  | Surfaces | `Card`, `AlertBanner`, `EmptyState`, `MediaCard`, `PricingCard` |
149
- | Form | `Form` (+ `Form.Field` / `Form.Section` / `Form.Footer`), `Button`, `ButtonGroup`, `IconButton`, `Input`, `CurrencyInput`, `Textarea`, `Checkbox`, `Switch`, `Toggle`, `RadioGroup`, `Select`, `Slider`, `SelectableGrid` |
149
+ | Form | `Form` (+ `Form.Field` / `Form.Section` / `Form.Footer`), `Button`, `ButtonGroup`, `IconButton`, `Input`, `CurrencyInput`, `Textarea`, `Checkbox`, `Switch`, `Toggle`, `RadioGroup`, `Select`, `Slider`, `SelectableGrid`, `SheetSelect`, `ImageUpload` |
150
150
  | Composition | `Tabs`, `Accordion` |
151
151
  | Navigation | `AppHeader`, `TabBar`, `PagerDots` |
152
152
  | Overlays | `Sheet`, `ConfirmDialog`, `ImageViewer` |
@@ -9,8 +9,10 @@ interface AlertBannerProps {
9
9
  icon?: React.ReactNode;
10
10
  iconName?: string;
11
11
  iconColor?: string;
12
+ /** Called when the user taps the dismiss (×) button. If omitted, no button is shown. */
13
+ onDismiss?: () => void;
12
14
  style?: ViewStyle;
13
15
  }
14
- declare function AlertBanner({ title, description, variant, icon, iconName, iconColor, style }: AlertBannerProps): React.JSX.Element;
16
+ declare function AlertBanner({ title, description, variant, icon, iconName, iconColor, onDismiss, style }: AlertBannerProps): React.JSX.Element;
15
17
 
16
18
  export { AlertBanner, type AlertBannerProps, type AlertBannerVariant };
@@ -9,8 +9,10 @@ interface AlertBannerProps {
9
9
  icon?: React.ReactNode;
10
10
  iconName?: string;
11
11
  iconColor?: string;
12
+ /** Called when the user taps the dismiss (×) button. If omitted, no button is shown. */
13
+ onDismiss?: () => void;
12
14
  style?: ViewStyle;
13
15
  }
14
- declare function AlertBanner({ title, description, variant, icon, iconName, iconColor, style }: AlertBannerProps): React.JSX.Element;
16
+ declare function AlertBanner({ title, description, variant, icon, iconName, iconColor, onDismiss, style }: AlertBannerProps): React.JSX.Element;
15
17
 
16
18
  export { AlertBanner, type AlertBannerProps, type AlertBannerVariant };
@@ -192,7 +192,7 @@ var RADIUS = {
192
192
  lg: 20};
193
193
 
194
194
  // src/components/AlertBanner/AlertBanner.tsx
195
- function AlertBanner({ title, description, variant = "default", icon, iconName, iconColor, style }) {
195
+ function AlertBanner({ title, description, variant = "default", icon, iconName, iconColor, onDismiss, style }) {
196
196
  const { colors, colorScheme } = useTheme();
197
197
  const isDark = colorScheme === "dark";
198
198
  const accentColor = variant === "destructive" ? colors.destructive : variant === "success" ? colors.success : variant === "warning" ? colors.warning : colors.foreground;
@@ -218,7 +218,18 @@ function AlertBanner({ title, description, variant = "default", icon, iconName,
218
218
  accessibilityLabel: a11yLabel
219
219
  },
220
220
  /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.iconSlot }, effectiveIcon),
221
- /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.content }, /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles.title, { color: colors.foreground }], allowFontScaling: true }, title), description ? /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles.description, { color: colors.foreground, opacity: 0.85 }], allowFontScaling: true }, description) : null)
221
+ /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.content }, /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles.title, { color: colors.foreground }], allowFontScaling: true }, title), description ? /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles.description, { color: colors.foreground, opacity: 0.85 }], allowFontScaling: true }, description) : null),
222
+ onDismiss ? /* @__PURE__ */ React3__default.default.createElement(
223
+ reactNative.TouchableOpacity,
224
+ {
225
+ onPress: onDismiss,
226
+ style: styles.dismissButton,
227
+ activeOpacity: 0.6,
228
+ accessibilityRole: "button",
229
+ accessibilityLabel: "Dismiss"
230
+ },
231
+ /* @__PURE__ */ React3__default.default.createElement(vectorIcons.Feather, { name: "x", size: ms(16), color: colors.foregroundMuted })
232
+ ) : null
222
233
  );
223
234
  }
224
235
  var styles = reactNative.StyleSheet.create({
@@ -244,6 +255,11 @@ var styles = reactNative.StyleSheet.create({
244
255
  description: {
245
256
  fontFamily: "Sohne-Regular",
246
257
  fontSize: ms(12)
258
+ },
259
+ dismissButton: {
260
+ padding: s(4),
261
+ marginTop: vs(-2),
262
+ marginRight: -s(4)
247
263
  }
248
264
  });
249
265
 
@@ -1,4 +1,4 @@
1
- export { AlertBanner } from './chunk-M6ZXVBTK.mjs';
1
+ export { AlertBanner } from './chunk-6MKGPAR2.mjs';
2
2
  import './chunk-QY3X2UYR.mjs';
3
3
  import './chunk-T7XZ7H7Y.mjs';
4
4
  import './chunk-SOYNZDVY.mjs';
@@ -7,9 +7,11 @@ interface ConfirmDialogProps {
7
7
  confirmLabel?: string;
8
8
  cancelLabel?: string;
9
9
  confirmVariant?: 'primary' | 'destructive';
10
+ /** Show a loading spinner in the confirm button (e.g. while async action completes). */
11
+ loading?: boolean;
10
12
  onConfirm: () => void;
11
13
  onCancel: () => void;
12
14
  }
13
- declare function ConfirmDialog({ visible, title, description, confirmLabel, cancelLabel, confirmVariant, onConfirm, onCancel, }: ConfirmDialogProps): React.JSX.Element;
15
+ declare function ConfirmDialog({ visible, title, description, confirmLabel, cancelLabel, confirmVariant, loading, onConfirm, onCancel, }: ConfirmDialogProps): React.JSX.Element;
14
16
 
15
17
  export { ConfirmDialog, type ConfirmDialogProps };
@@ -7,9 +7,11 @@ interface ConfirmDialogProps {
7
7
  confirmLabel?: string;
8
8
  cancelLabel?: string;
9
9
  confirmVariant?: 'primary' | 'destructive';
10
+ /** Show a loading spinner in the confirm button (e.g. while async action completes). */
11
+ loading?: boolean;
10
12
  onConfirm: () => void;
11
13
  onCancel: () => void;
12
14
  }
13
- declare function ConfirmDialog({ visible, title, description, confirmLabel, cancelLabel, confirmVariant, onConfirm, onCancel, }: ConfirmDialogProps): React.JSX.Element;
15
+ declare function ConfirmDialog({ visible, title, description, confirmLabel, cancelLabel, confirmVariant, loading, onConfirm, onCancel, }: ConfirmDialogProps): React.JSX.Element;
14
16
 
15
17
  export { ConfirmDialog, type ConfirmDialogProps };
@@ -459,6 +459,7 @@ function ConfirmDialog({
459
459
  confirmLabel = "Confirm",
460
460
  cancelLabel = "Cancel",
461
461
  confirmVariant = "primary",
462
+ loading = false,
462
463
  onConfirm,
463
464
  onCancel
464
465
  }) {
@@ -498,6 +499,8 @@ function ConfirmDialog({
498
499
  label: confirmLabel,
499
500
  variant: confirmVariant,
500
501
  fullWidth: true,
502
+ loading,
503
+ disabled: loading,
501
504
  onPress: () => {
502
505
  notificationSuccess();
503
506
  onConfirm();
@@ -1,4 +1,4 @@
1
- export { ConfirmDialog } from './chunk-7QHVVCB3.mjs';
1
+ export { ConfirmDialog } from './chunk-FZZLPJ6B.mjs';
2
2
  import './chunk-2TFTAWVJ.mjs';
3
3
  import './chunk-3DKJ2GIC.mjs';
4
4
  import './chunk-EJ7ZPXOH.mjs';
@@ -20,7 +20,9 @@ interface CurrencyInputProps {
20
20
  editable?: boolean;
21
21
  containerStyle?: ViewStyle;
22
22
  style?: TextStyle;
23
+ /** Use inside a Sheet/BottomSheet — passes sheetMode to the underlying Input. */
24
+ sheetMode?: boolean;
23
25
  }
24
- declare function CurrencyInput({ value, onChangeText, onChangeValue, prefix, thousandsSeparator, size, label, error, hint, placeholder, editable, containerStyle, style, }: CurrencyInputProps): React.JSX.Element;
26
+ declare function CurrencyInput({ value, onChangeText, onChangeValue, prefix, thousandsSeparator, size, label, error, hint, placeholder, editable, containerStyle, style, sheetMode, }: CurrencyInputProps): React.JSX.Element;
25
27
 
26
28
  export { CurrencyInput, type CurrencyInputProps };
@@ -20,7 +20,9 @@ interface CurrencyInputProps {
20
20
  editable?: boolean;
21
21
  containerStyle?: ViewStyle;
22
22
  style?: TextStyle;
23
+ /** Use inside a Sheet/BottomSheet — passes sheetMode to the underlying Input. */
24
+ sheetMode?: boolean;
23
25
  }
24
- declare function CurrencyInput({ value, onChangeText, onChangeValue, prefix, thousandsSeparator, size, label, error, hint, placeholder, editable, containerStyle, style, }: CurrencyInputProps): React.JSX.Element;
26
+ declare function CurrencyInput({ value, onChangeText, onChangeValue, prefix, thousandsSeparator, size, label, error, hint, placeholder, editable, containerStyle, style, sheetMode, }: CurrencyInputProps): React.JSX.Element;
25
27
 
26
28
  export { CurrencyInput, type CurrencyInputProps };
@@ -2,7 +2,8 @@
2
2
 
3
3
  var React3 = require('react');
4
4
  var reactNative = require('react-native');
5
- var Animated = require('react-native-reanimated');
5
+ var bottomSheet = require('@gorhom/bottom-sheet');
6
+ var reactNativeEase = require('react-native-ease');
6
7
  var vectorIcons = require('@expo/vector-icons');
7
8
  var reactNativeSizeMatters = require('react-native-size-matters');
8
9
  var AntDesign = require('@expo/vector-icons/AntDesign');
@@ -11,11 +12,11 @@ var Feather = require('@expo/vector-icons/Feather');
11
12
  var FontAwesome5 = require('@expo/vector-icons/FontAwesome5');
12
13
  var MaterialIcons = require('@expo/vector-icons/MaterialIcons');
13
14
  var Ionicons = require('@expo/vector-icons/Ionicons');
15
+ var reactNativeReanimated = require('react-native-reanimated');
14
16
 
15
17
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
18
 
17
19
  var React3__default = /*#__PURE__*/_interopDefault(React3);
18
- var Animated__default = /*#__PURE__*/_interopDefault(Animated);
19
20
  var AntDesign__default = /*#__PURE__*/_interopDefault(AntDesign);
20
21
  var Entypo__default = /*#__PURE__*/_interopDefault(Entypo);
21
22
  var Feather__default = /*#__PURE__*/_interopDefault(Feather);
@@ -190,38 +191,27 @@ function renderIcon(name, size, color) {
190
191
  }
191
192
  var TIMINGS = {
192
193
  /** Color/opacity transitions on toggles, checkboxes, switches. */
193
- state: { duration: 160 },
194
- /** Focus ring on inputs. */
195
- focusIn: { duration: 140 },
196
- focusOut: { duration: 100 }};
197
- var EASINGS = {
194
+ state: { duration: 160 }};
195
+ ({
198
196
  /** Material-style ease-out — natural deceleration for state changes. */
199
- standard: Animated.Easing.bezier(0.2, 0, 0, 1),
197
+ standard: reactNativeReanimated.Easing.bezier(0.2, 0, 0, 1),
200
198
  /** Strong ease-out for expanding surfaces (Accordion open). */
201
- expand: Animated.Easing.bezier(0.23, 1, 0.32, 1),
199
+ expand: reactNativeReanimated.Easing.bezier(0.23, 1, 0.32, 1),
202
200
  /** Quick ease-in for collapsing. */
203
- collapse: Animated.Easing.in(Animated.Easing.ease)
201
+ collapse: reactNativeReanimated.Easing.in(reactNativeReanimated.Easing.ease)
202
+ });
203
+ var COLOR_TRANSITION = {
204
+ type: "timing",
205
+ duration: TIMINGS.state.duration,
206
+ easing: [0.2, 0, 0, 1]
204
207
  };
205
208
 
206
- // src/utils/useColorTransition.ts
207
- function useColorTransition(active, options = {}) {
208
- const { duration = TIMINGS.state.duration } = options;
209
- const progress = Animated.useSharedValue(active ? 1 : 0);
210
- React3.useEffect(() => {
211
- progress.value = Animated.withTiming(active ? 1 : 0, { duration, easing: EASINGS.standard });
212
- }, [active, duration, progress]);
213
- return progress;
214
- }
215
-
216
209
  // src/components/Input/Input.tsx
217
210
  var webInputResetStyle = reactNative.Platform.OS === "web" ? { outlineStyle: "none", outlineWidth: 0, outlineColor: "transparent", boxShadow: "none" } : {};
218
- function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suffixStyle, prefixIcon, suffixIcon, prefixIconColor, suffixIconColor, type = "text", containerStyle, inputWrapperStyle, style, onFocus, onBlur, secureTextEntry, editable, accessibilityLabel, ...props }) {
211
+ function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suffixStyle, prefixIcon, suffixIcon, prefixIconColor, suffixIconColor, type = "text", containerStyle, inputWrapperStyle, sheetMode = false, style, onFocus, onBlur, secureTextEntry, editable, accessibilityLabel, ...props }) {
219
212
  const { colors } = useTheme();
220
213
  const [focused, setFocused] = React3.useState(false);
221
214
  const [showPassword, setShowPassword] = React3.useState(false);
222
- const focusProgress = useColorTransition(focused, {
223
- duration: focused ? TIMINGS.focusIn.duration : TIMINGS.focusOut.duration
224
- });
225
215
  const isDisabled = disabled || editable === false;
226
216
  const isPassword = type === "password";
227
217
  const effectiveSecure = isPassword ? !showPassword : secureTextEntry;
@@ -237,22 +227,45 @@ function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suff
237
227
  },
238
228
  /* @__PURE__ */ React3__default.default.createElement(vectorIcons.AntDesign, { name: showPassword ? "eye" : "eye-invisible", size: 20, color: colors.foregroundMuted })
239
229
  ) : suffixIcon ? renderIcon(suffixIcon, 20, suffixIconColor ?? colors.foregroundMuted) : suffix;
240
- const borderAnimStyle = Animated.useAnimatedStyle(() => ({
241
- borderColor: error ? colors.destructive : Animated.interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary]),
242
- borderWidth: error ? 2 : Animated.interpolate(focusProgress.value, [0, 1], [1, 2])
243
- }));
230
+ const borderColor = error ? colors.destructive : focused ? colors.primary : colors.border;
244
231
  return /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: [styles.container, isDisabled && styles.containerDisabled, containerStyle] }, label ? /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, /* @__PURE__ */ React3__default.default.createElement(
245
- Animated__default.default.View,
232
+ reactNativeEase.EaseView,
246
233
  {
247
234
  style: [
248
235
  styles.inputWrapper,
249
236
  { backgroundColor: isDisabled ? colors.surface : colors.background },
237
+ error && styles.inputWrapperError,
250
238
  inputWrapperStyle
251
- ]
239
+ ],
240
+ animate: { borderColor },
241
+ transition: COLOR_TRANSITION
252
242
  },
253
- /* @__PURE__ */ React3__default.default.createElement(Animated__default.default.View, { style: [styles.borderOverlay, borderAnimStyle], pointerEvents: "none" }),
254
243
  effectivePrefix ? typeof effectivePrefix === "string" ? /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles.prefixText, { color: colors.foregroundMuted }, prefixStyle], allowFontScaling: true }, effectivePrefix) : /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.prefixContainer }, effectivePrefix) : null,
255
- /* @__PURE__ */ React3__default.default.createElement(
244
+ sheetMode ? /* @__PURE__ */ React3__default.default.createElement(
245
+ bottomSheet.BottomSheetTextInput,
246
+ {
247
+ style: [
248
+ styles.input,
249
+ { color: colors.foreground },
250
+ webInputResetStyle,
251
+ style
252
+ ],
253
+ onFocus: (e) => {
254
+ setFocused(true);
255
+ onFocus?.(e);
256
+ },
257
+ onBlur: (e) => {
258
+ setFocused(false);
259
+ onBlur?.(e);
260
+ },
261
+ placeholderTextColor: colors.foregroundMuted,
262
+ allowFontScaling: true,
263
+ secureTextEntry: effectiveSecure,
264
+ editable: isDisabled ? false : editable,
265
+ accessibilityLabel: accessibilityLabel ?? label,
266
+ ...props
267
+ }
268
+ ) : /* @__PURE__ */ React3__default.default.createElement(
256
269
  reactNative.TextInput,
257
270
  {
258
271
  style: [
@@ -302,16 +315,14 @@ var styles = reactNative.StyleSheet.create({
302
315
  inputWrapper: {
303
316
  flexDirection: "row",
304
317
  alignItems: "center",
305
- // Border lives on borderOverlay (absolute) so its 1px→2px focus change
306
- // never resizes this box. Wrapper itself carries no border.
307
318
  borderRadius: 8,
319
+ borderWidth: 1,
308
320
  paddingHorizontal: s(14),
309
321
  paddingVertical: vs(11),
310
322
  minHeight: 48
311
323
  },
312
- borderOverlay: {
313
- ...reactNative.StyleSheet.absoluteFillObject,
314
- borderRadius: 8
324
+ inputWrapperError: {
325
+ borderWidth: 2
315
326
  },
316
327
  input: {
317
328
  fontFamily: "Sohne-Regular",
@@ -367,7 +378,8 @@ function CurrencyInput({
367
378
  placeholder,
368
379
  editable,
369
380
  containerStyle,
370
- style
381
+ style,
382
+ sheetMode
371
383
  }) {
372
384
  const handleChange = (text) => {
373
385
  const withoutPrefix = prefix && text.startsWith(prefix) ? text.slice(prefix.length) : text;
@@ -400,7 +412,8 @@ function CurrencyInput({
400
412
  prefixStyle,
401
413
  containerStyle,
402
414
  inputWrapperStyle: isLarge ? { paddingVertical: vs(16), minHeight: 72 } : void 0,
403
- style: [inputStyle, style]
415
+ style: [inputStyle, style],
416
+ sheetMode
404
417
  }
405
418
  );
406
419
  }
@@ -1,6 +1,5 @@
1
- export { CurrencyInput } from './chunk-MAC465BB.mjs';
2
- import './chunk-756RAKE4.mjs';
3
- import './chunk-26BCI223.mjs';
1
+ export { CurrencyInput } from './chunk-JUXSWN54.mjs';
2
+ import './chunk-ZUR7AU5R.mjs';
4
3
  import './chunk-DVK4G2GT.mjs';
5
4
  import './chunk-T7XZ7H7Y.mjs';
6
5
  import './chunk-SOYNZDVY.mjs';
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { ViewStyle } from 'react-native';
3
+
4
+ interface ImageUploadProps {
5
+ /** Current image URI. Pass null/undefined to show placeholder. */
6
+ value?: string | null;
7
+ /** Called with the selected image URI. Receives null when image is cleared. */
8
+ onChange?: (uri: string | null) => void;
9
+ /** Show a loading spinner over the image (e.g. while uploading). */
10
+ loading?: boolean;
11
+ /** Text shown when no image is selected. */
12
+ placeholder?: string;
13
+ /** Width of the upload area. Defaults to full width (undefined). */
14
+ width?: number;
15
+ /** Height of the upload area. Defaults to 200. */
16
+ height?: number;
17
+ /** Border radius of the upload area. Defaults to RADIUS.lg. */
18
+ borderRadius?: number;
19
+ /** Aspect ratio for the selected image. Defaults to 'cover'. */
20
+ resizeMode?: 'cover' | 'contain' | 'stretch';
21
+ disabled?: boolean;
22
+ style?: ViewStyle;
23
+ accessibilityLabel?: string;
24
+ }
25
+ declare function ImageUpload({ value, onChange, loading, placeholder, width, height, borderRadius, resizeMode, disabled, style, accessibilityLabel, }: ImageUploadProps): React.JSX.Element;
26
+
27
+ export { ImageUpload, type ImageUploadProps };
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { ViewStyle } from 'react-native';
3
+
4
+ interface ImageUploadProps {
5
+ /** Current image URI. Pass null/undefined to show placeholder. */
6
+ value?: string | null;
7
+ /** Called with the selected image URI. Receives null when image is cleared. */
8
+ onChange?: (uri: string | null) => void;
9
+ /** Show a loading spinner over the image (e.g. while uploading). */
10
+ loading?: boolean;
11
+ /** Text shown when no image is selected. */
12
+ placeholder?: string;
13
+ /** Width of the upload area. Defaults to full width (undefined). */
14
+ width?: number;
15
+ /** Height of the upload area. Defaults to 200. */
16
+ height?: number;
17
+ /** Border radius of the upload area. Defaults to RADIUS.lg. */
18
+ borderRadius?: number;
19
+ /** Aspect ratio for the selected image. Defaults to 'cover'. */
20
+ resizeMode?: 'cover' | 'contain' | 'stretch';
21
+ disabled?: boolean;
22
+ style?: ViewStyle;
23
+ accessibilityLabel?: string;
24
+ }
25
+ declare function ImageUpload({ value, onChange, loading, placeholder, width, height, borderRadius, resizeMode, disabled, style, accessibilityLabel, }: ImageUploadProps): React.JSX.Element;
26
+
27
+ export { ImageUpload, type ImageUploadProps };