@retray-dev/ui-kit 9.0.0 → 9.2.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 (57) hide show
  1. package/COMPONENTS.md +178 -7
  2. package/CONSUMER.md +247 -0
  3. package/DESIGN.md +668 -0
  4. package/EXAMPLES.md +19 -12
  5. package/FONTS.md +107 -0
  6. package/README.md +3 -3
  7. package/dist/AlertBanner.d.mts +3 -1
  8. package/dist/AlertBanner.d.ts +3 -1
  9. package/dist/AlertBanner.js +18 -2
  10. package/dist/AlertBanner.mjs +1 -1
  11. package/dist/ConfirmDialog.d.mts +3 -1
  12. package/dist/ConfirmDialog.d.ts +3 -1
  13. package/dist/ConfirmDialog.js +3 -0
  14. package/dist/ConfirmDialog.mjs +1 -1
  15. package/dist/CurrencyInput.d.mts +3 -1
  16. package/dist/CurrencyInput.d.ts +3 -1
  17. package/dist/CurrencyInput.js +31 -4
  18. package/dist/CurrencyInput.mjs +2 -2
  19. package/dist/ImageUpload.d.mts +27 -0
  20. package/dist/ImageUpload.d.ts +27 -0
  21. package/dist/ImageUpload.js +399 -0
  22. package/dist/ImageUpload.mjs +9 -0
  23. package/dist/Input.d.mts +3 -1
  24. package/dist/Input.d.ts +3 -1
  25. package/dist/Input.js +27 -2
  26. package/dist/Input.mjs +1 -1
  27. package/dist/ListItem.d.mts +3 -1
  28. package/dist/ListItem.d.ts +3 -1
  29. package/dist/ListItem.js +2 -1
  30. package/dist/ListItem.mjs +1 -1
  31. package/dist/SheetSelect.d.mts +25 -0
  32. package/dist/SheetSelect.d.ts +25 -0
  33. package/dist/SheetSelect.js +440 -0
  34. package/dist/SheetSelect.mjs +9 -0
  35. package/dist/{chunk-M6ZXVBTK.mjs → chunk-6MKGPAR2.mjs} +21 -5
  36. package/dist/{chunk-7QHVVCB3.mjs → chunk-FZZLPJ6B.mjs} +3 -0
  37. package/dist/{chunk-MAC465BB.mjs → chunk-KNSENOV4.mjs} +5 -3
  38. package/dist/{chunk-756RAKE4.mjs → chunk-LVYEU5ZK.mjs} +27 -2
  39. package/dist/{chunk-BNP626TY.mjs → chunk-T4I5WVHA.mjs} +2 -1
  40. package/dist/chunk-URI2WBIV.mjs +147 -0
  41. package/dist/chunk-Y4GL2MHX.mjs +112 -0
  42. package/dist/index.d.mts +26 -1
  43. package/dist/index.d.ts +26 -1
  44. package/dist/index.js +327 -8
  45. package/dist/index.mjs +51 -12
  46. package/package.json +18 -5
  47. package/src/components/AlertBanner/AlertBanner.tsx +21 -3
  48. package/src/components/ConfirmDialog/ConfirmDialog.tsx +5 -0
  49. package/src/components/CurrencyInput/CurrencyInput.tsx +4 -0
  50. package/src/components/ImageUpload/ImageUpload.tsx +158 -0
  51. package/src/components/ImageUpload/index.ts +1 -0
  52. package/src/components/Input/Input.tsx +51 -23
  53. package/src/components/ListItem/ListItem.tsx +4 -1
  54. package/src/components/SheetSelect/SheetSelect.tsx +192 -0
  55. package/src/components/SheetSelect/index.ts +1 -0
  56. package/src/hooks/useConfirmDialog.ts +67 -0
  57. package/src/index.ts +6 -0
package/dist/index.js CHANGED
@@ -12,11 +12,11 @@ var Ionicons = require('@expo/vector-icons/Ionicons');
12
12
  var pressto = require('pressto');
13
13
  var Animated13 = require('react-native-reanimated');
14
14
  var expoFont = require('expo-font');
15
+ var bottomSheet = require('@gorhom/bottom-sheet');
15
16
  var vectorIcons = require('@expo/vector-icons');
16
17
  var expoLinearGradient = require('expo-linear-gradient');
17
18
  var reactNativeEase = require('react-native-ease');
18
19
  var RNSlider = require('@react-native-community/slider');
19
- var bottomSheet = require('@gorhom/bottom-sheet');
20
20
  var reactNativeSafeAreaContext = require('react-native-safe-area-context');
21
21
  var picker = require('@react-native-picker/picker');
22
22
  var sonnerNative = require('sonner-native');
@@ -1011,7 +1011,7 @@ function useColorTransition(active, options = {}) {
1011
1011
 
1012
1012
  // src/components/Input/Input.tsx
1013
1013
  var webInputResetStyle = reactNative.Platform.OS === "web" ? { outlineStyle: "none", outlineWidth: 0, outlineColor: "transparent", boxShadow: "none" } : {};
1014
- 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 }) {
1014
+ 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 }) {
1015
1015
  const { colors } = useTheme();
1016
1016
  const [focused, setFocused] = React25.useState(false);
1017
1017
  const [showPassword, setShowPassword] = React25.useState(false);
@@ -1048,7 +1048,31 @@ function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suff
1048
1048
  },
1049
1049
  /* @__PURE__ */ React25__default.default.createElement(Animated13__default.default.View, { style: [styles4.borderOverlay, borderAnimStyle], pointerEvents: "none" }),
1050
1050
  effectivePrefix ? typeof effectivePrefix === "string" ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles4.prefixText, { color: colors.foregroundMuted }, prefixStyle], allowFontScaling: true }, effectivePrefix) : /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles4.prefixContainer }, effectivePrefix) : null,
1051
- /* @__PURE__ */ React25__default.default.createElement(
1051
+ sheetMode ? /* @__PURE__ */ React25__default.default.createElement(
1052
+ bottomSheet.BottomSheetTextInput,
1053
+ {
1054
+ style: [
1055
+ styles4.input,
1056
+ { color: colors.foreground },
1057
+ webInputResetStyle,
1058
+ style
1059
+ ],
1060
+ onFocus: (e) => {
1061
+ setFocused(true);
1062
+ onFocus?.(e);
1063
+ },
1064
+ onBlur: (e) => {
1065
+ setFocused(false);
1066
+ onBlur?.(e);
1067
+ },
1068
+ placeholderTextColor: colors.foregroundMuted,
1069
+ allowFontScaling: true,
1070
+ secureTextEntry: effectiveSecure,
1071
+ editable: isDisabled ? false : editable,
1072
+ accessibilityLabel: accessibilityLabel ?? label,
1073
+ ...props
1074
+ }
1075
+ ) : /* @__PURE__ */ React25__default.default.createElement(
1052
1076
  reactNative.TextInput,
1053
1077
  {
1054
1078
  style: [
@@ -1604,7 +1628,7 @@ var styles10 = reactNative.StyleSheet.create({
1604
1628
  right: 0
1605
1629
  }
1606
1630
  });
1607
- function AlertBanner({ title, description, variant = "default", icon, iconName, iconColor, style }) {
1631
+ function AlertBanner({ title, description, variant = "default", icon, iconName, iconColor, onDismiss, style }) {
1608
1632
  const { colors, colorScheme } = useTheme();
1609
1633
  const isDark = colorScheme === "dark";
1610
1634
  const accentColor = variant === "destructive" ? colors.destructive : variant === "success" ? colors.success : variant === "warning" ? colors.warning : colors.foreground;
@@ -1630,7 +1654,18 @@ function AlertBanner({ title, description, variant = "default", icon, iconName,
1630
1654
  accessibilityLabel: a11yLabel
1631
1655
  },
1632
1656
  /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles11.iconSlot }, effectiveIcon),
1633
- /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles11.content }, /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles11.title, { color: colors.foreground }], allowFontScaling: true }, title), description ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles11.description, { color: colors.foreground, opacity: 0.85 }], allowFontScaling: true }, description) : null)
1657
+ /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles11.content }, /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles11.title, { color: colors.foreground }], allowFontScaling: true }, title), description ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles11.description, { color: colors.foreground, opacity: 0.85 }], allowFontScaling: true }, description) : null),
1658
+ onDismiss ? /* @__PURE__ */ React25__default.default.createElement(
1659
+ reactNative.TouchableOpacity,
1660
+ {
1661
+ onPress: onDismiss,
1662
+ style: styles11.dismissButton,
1663
+ activeOpacity: 0.6,
1664
+ accessibilityRole: "button",
1665
+ accessibilityLabel: "Dismiss"
1666
+ },
1667
+ /* @__PURE__ */ React25__default.default.createElement(vectorIcons.Feather, { name: "x", size: ms(16), color: colors.foregroundMuted })
1668
+ ) : null
1634
1669
  );
1635
1670
  }
1636
1671
  var styles11 = reactNative.StyleSheet.create({
@@ -1656,6 +1691,11 @@ var styles11 = reactNative.StyleSheet.create({
1656
1691
  description: {
1657
1692
  fontFamily: "Sohne-Regular",
1658
1693
  fontSize: ms(12)
1694
+ },
1695
+ dismissButton: {
1696
+ padding: s(4),
1697
+ marginTop: vs(-2),
1698
+ marginRight: -s(4)
1659
1699
  }
1660
1700
  });
1661
1701
  function Progress({ value = 0, max = 100, variant = "default", style, accessibilityLabel }) {
@@ -3249,7 +3289,8 @@ function CurrencyInput({
3249
3289
  placeholder,
3250
3290
  editable,
3251
3291
  containerStyle,
3252
- style
3292
+ style,
3293
+ sheetMode
3253
3294
  }) {
3254
3295
  const handleChange = (text) => {
3255
3296
  const withoutPrefix = prefix && text.startsWith(prefix) ? text.slice(prefix.length) : text;
@@ -3282,7 +3323,8 @@ function CurrencyInput({
3282
3323
  prefixStyle,
3283
3324
  containerStyle,
3284
3325
  inputWrapperStyle: isLarge ? { paddingVertical: vs(16), minHeight: 72 } : void 0,
3285
- style: [inputStyle, style]
3326
+ style: [inputStyle, style],
3327
+ sheetMode
3286
3328
  }
3287
3329
  );
3288
3330
  }
@@ -3359,6 +3401,7 @@ function ListItemBase({
3359
3401
  style,
3360
3402
  titleStyle,
3361
3403
  subtitleStyle,
3404
+ subtitleNumberOfLines = 2,
3362
3405
  captionStyle,
3363
3406
  accessibilityLabel
3364
3407
  }) {
@@ -3393,7 +3436,7 @@ function ListItemBase({
3393
3436
  reactNative.Text,
3394
3437
  {
3395
3438
  style: [styles25.subtitle, { color: colors.foregroundMuted }, subtitleStyle],
3396
- numberOfLines: 2,
3439
+ numberOfLines: subtitleNumberOfLines,
3397
3440
  allowFontScaling: true
3398
3441
  },
3399
3442
  subtitle
@@ -3861,6 +3904,7 @@ function ConfirmDialog({
3861
3904
  confirmLabel = "Confirm",
3862
3905
  cancelLabel = "Cancel",
3863
3906
  confirmVariant = "primary",
3907
+ loading = false,
3864
3908
  onConfirm,
3865
3909
  onCancel
3866
3910
  }) {
@@ -3900,6 +3944,8 @@ function ConfirmDialog({
3900
3944
  label: confirmLabel,
3901
3945
  variant: confirmVariant,
3902
3946
  fullWidth: true,
3947
+ loading,
3948
+ disabled: loading,
3903
3949
  onPress: () => {
3904
3950
  notificationSuccess();
3905
3951
  onConfirm();
@@ -5465,6 +5511,240 @@ var styles44 = reactNative.StyleSheet.create({
5465
5511
  alignItems: "center"
5466
5512
  }
5467
5513
  });
5514
+ function SheetSelectChip({
5515
+ option,
5516
+ selected,
5517
+ onPress
5518
+ }) {
5519
+ const { colors } = useTheme();
5520
+ const handlePress = () => {
5521
+ selectionAsync();
5522
+ onPress();
5523
+ };
5524
+ const iconColor = selected ? colors.primaryForeground : colors.foreground;
5525
+ const resolvedIcon = option.iconName ? renderIcon(option.iconName, ms(13), iconColor) : null;
5526
+ return /* @__PURE__ */ React25__default.default.createElement(
5527
+ PressableChip,
5528
+ {
5529
+ onPress: option.disabled ? void 0 : handlePress,
5530
+ rippleColor: "transparent",
5531
+ touchSoundDisabled: true,
5532
+ accessibilityRole: "button",
5533
+ accessibilityLabel: option.disabled ? `${option.label}, unavailable` : option.label,
5534
+ accessibilityState: { selected, disabled: option.disabled }
5535
+ },
5536
+ /* @__PURE__ */ React25__default.default.createElement(
5537
+ reactNativeEase.EaseView,
5538
+ {
5539
+ style: [styles45.chip, option.disabled && styles45.chipDisabled],
5540
+ animate: {
5541
+ backgroundColor: selected ? colors.primary : colors.surface,
5542
+ borderColor: selected ? colors.primary : colors.border
5543
+ },
5544
+ transition: COLOR_TRANSITION
5545
+ },
5546
+ resolvedIcon ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles45.chipIcon }, resolvedIcon) : null,
5547
+ /* @__PURE__ */ React25__default.default.createElement(
5548
+ reactNative.Text,
5549
+ {
5550
+ style: [styles45.chipLabel, { color: selected ? colors.primaryForeground : colors.foreground }],
5551
+ allowFontScaling: true
5552
+ },
5553
+ option.label
5554
+ )
5555
+ )
5556
+ );
5557
+ }
5558
+ function SheetSelect({
5559
+ options,
5560
+ value,
5561
+ onValueChange,
5562
+ multiSelect = false,
5563
+ label,
5564
+ error,
5565
+ wrap = false,
5566
+ style,
5567
+ accessibilityLabel
5568
+ }) {
5569
+ const { colors } = useTheme();
5570
+ const isSelected2 = (optionValue) => {
5571
+ if (Array.isArray(value)) return value.includes(optionValue);
5572
+ return optionValue === value;
5573
+ };
5574
+ const handlePress = (optionValue) => {
5575
+ if (!multiSelect) {
5576
+ onValueChange?.(optionValue);
5577
+ return;
5578
+ }
5579
+ const currentArray = Array.isArray(value) ? value : value != null ? [value] : [];
5580
+ const alreadySelected = currentArray.includes(optionValue);
5581
+ const newArray = alreadySelected ? currentArray.filter((v) => v !== optionValue) : [...currentArray, optionValue];
5582
+ onValueChange?.(newArray);
5583
+ };
5584
+ const chips = options.map((opt) => /* @__PURE__ */ React25__default.default.createElement(
5585
+ SheetSelectChip,
5586
+ {
5587
+ key: opt.value,
5588
+ option: opt,
5589
+ selected: isSelected2(opt.value),
5590
+ onPress: () => handlePress(opt.value)
5591
+ }
5592
+ ));
5593
+ return /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [styles45.container, style], accessibilityLabel }, label ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles45.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, wrap ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles45.wrapContainer }, chips) : /* @__PURE__ */ React25__default.default.createElement(
5594
+ reactNative.ScrollView,
5595
+ {
5596
+ horizontal: true,
5597
+ showsHorizontalScrollIndicator: false,
5598
+ contentContainerStyle: styles45.scrollContent
5599
+ },
5600
+ chips
5601
+ ), error ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles45.error, { color: colors.destructive }], allowFontScaling: true, accessibilityLiveRegion: "polite" }, error) : null);
5602
+ }
5603
+ var styles45 = reactNative.StyleSheet.create({
5604
+ container: {
5605
+ gap: vs(8)
5606
+ },
5607
+ label: {
5608
+ fontFamily: "Sohne-Medium",
5609
+ fontSize: ms(14)
5610
+ },
5611
+ scrollContent: {
5612
+ flexDirection: "row",
5613
+ gap: s(8)
5614
+ },
5615
+ wrapContainer: {
5616
+ flexDirection: "row",
5617
+ flexWrap: "wrap",
5618
+ gap: s(8)
5619
+ },
5620
+ chip: {
5621
+ borderRadius: RADIUS.full,
5622
+ paddingHorizontal: s(14),
5623
+ paddingVertical: vs(10),
5624
+ minHeight: 44,
5625
+ borderWidth: 1,
5626
+ alignItems: "center",
5627
+ justifyContent: "center",
5628
+ flexDirection: "row",
5629
+ gap: s(5)
5630
+ },
5631
+ chipDisabled: {
5632
+ opacity: 0.4
5633
+ },
5634
+ chipIcon: {
5635
+ alignItems: "center",
5636
+ justifyContent: "center"
5637
+ },
5638
+ chipLabel: {
5639
+ fontFamily: "Sohne-Medium",
5640
+ fontSize: ms(13),
5641
+ lineHeight: mvs(18)
5642
+ },
5643
+ error: {
5644
+ fontFamily: "Sohne-Regular",
5645
+ fontSize: ms(13)
5646
+ }
5647
+ });
5648
+ function ImageUpload({
5649
+ value,
5650
+ onChange,
5651
+ loading = false,
5652
+ placeholder = "Tap to add image",
5653
+ width,
5654
+ height = 200,
5655
+ borderRadius = RADIUS.lg,
5656
+ resizeMode = "cover",
5657
+ disabled = false,
5658
+ style,
5659
+ accessibilityLabel
5660
+ }) {
5661
+ const { colors } = useTheme();
5662
+ const handlePress = async () => {
5663
+ if (disabled || loading) return;
5664
+ impactLight();
5665
+ let ImagePicker;
5666
+ try {
5667
+ ImagePicker = await import('expo-image-picker');
5668
+ } catch {
5669
+ if (__DEV__) console.warn("[ImageUpload] expo-image-picker not installed. Add it as a dependency.");
5670
+ return;
5671
+ }
5672
+ if (reactNative.Platform.OS !== "web") {
5673
+ const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
5674
+ if (status !== "granted") return;
5675
+ }
5676
+ const result = await ImagePicker.launchImageLibraryAsync({
5677
+ mediaTypes: ["images"],
5678
+ allowsEditing: true,
5679
+ quality: 0.8
5680
+ });
5681
+ if (!result.canceled && result.assets[0]) {
5682
+ onChange?.(result.assets[0].uri);
5683
+ }
5684
+ };
5685
+ const containerStyle = {
5686
+ width,
5687
+ height,
5688
+ borderRadius,
5689
+ borderWidth: value ? 0 : 1,
5690
+ borderStyle: "dashed",
5691
+ borderColor: colors.border,
5692
+ backgroundColor: value ? "transparent" : colors.surface,
5693
+ overflow: "hidden"
5694
+ };
5695
+ return /* @__PURE__ */ React25__default.default.createElement(
5696
+ PressableCard,
5697
+ {
5698
+ onPress: handlePress,
5699
+ enabled: !disabled && !loading,
5700
+ rippleColor: "transparent",
5701
+ touchSoundDisabled: true,
5702
+ accessibilityRole: "button",
5703
+ accessibilityLabel: accessibilityLabel ?? (value ? "Change image" : placeholder),
5704
+ accessibilityState: { disabled: disabled || loading },
5705
+ style: [containerStyle, style]
5706
+ },
5707
+ value ? /* @__PURE__ */ React25__default.default.createElement(
5708
+ reactNative.Image,
5709
+ {
5710
+ source: { uri: value },
5711
+ style: [reactNative.StyleSheet.absoluteFillObject, { borderRadius }],
5712
+ resizeMode
5713
+ }
5714
+ ) : /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles46.placeholder }, /* @__PURE__ */ React25__default.default.createElement(vectorIcons.Feather, { name: "image", size: ms(28), color: colors.foregroundMuted }), /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles46.placeholderText, { color: colors.foregroundMuted }], allowFontScaling: true }, placeholder)),
5715
+ loading ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [styles46.loadingOverlay, { backgroundColor: colors.overlay }] }, /* @__PURE__ */ React25__default.default.createElement(Spinner, { size: "md" })) : null,
5716
+ value && !loading ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles46.editBadge, pointerEvents: "none" }, /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [styles46.editBadgeInner, { backgroundColor: colors.overlay }] }, /* @__PURE__ */ React25__default.default.createElement(vectorIcons.Feather, { name: "edit-2", size: ms(12), color: "#fff" }))) : null
5717
+ );
5718
+ }
5719
+ var styles46 = reactNative.StyleSheet.create({
5720
+ placeholder: {
5721
+ flex: 1,
5722
+ alignItems: "center",
5723
+ justifyContent: "center",
5724
+ gap: vs(8)
5725
+ },
5726
+ placeholderText: {
5727
+ fontFamily: "Sohne-Regular",
5728
+ fontSize: ms(13)
5729
+ },
5730
+ loadingOverlay: {
5731
+ ...reactNative.StyleSheet.absoluteFillObject,
5732
+ alignItems: "center",
5733
+ justifyContent: "center"
5734
+ },
5735
+ editBadge: {
5736
+ position: "absolute",
5737
+ bottom: vs(8),
5738
+ right: s(8)
5739
+ },
5740
+ editBadgeInner: {
5741
+ width: s(28),
5742
+ height: s(28),
5743
+ borderRadius: 999,
5744
+ alignItems: "center",
5745
+ justifyContent: "center"
5746
+ }
5747
+ });
5468
5748
 
5469
5749
  // src/utils/typography.ts
5470
5750
  function getResponsiveFontSize(text, maxSize, steps = [
@@ -5479,6 +5759,42 @@ function getResponsiveFontSize(text, maxSize, steps = [
5479
5759
  }
5480
5760
  return maxSize - 8;
5481
5761
  }
5762
+ function useConfirmDialog(options) {
5763
+ const [visible, setVisible] = React25.useState(false);
5764
+ const [target, setTarget] = React25.useState(null);
5765
+ const [loading, setLoading] = React25.useState(false);
5766
+ const open = React25.useCallback((t) => {
5767
+ setTarget(t ?? null);
5768
+ setVisible(true);
5769
+ }, []);
5770
+ const handleConfirm = React25.useCallback(async () => {
5771
+ setLoading(true);
5772
+ try {
5773
+ await options.onConfirm();
5774
+ } finally {
5775
+ setLoading(false);
5776
+ setVisible(false);
5777
+ setTarget(null);
5778
+ }
5779
+ }, [options]);
5780
+ const handleCancel = React25.useCallback(() => {
5781
+ setVisible(false);
5782
+ setTarget(null);
5783
+ options.onCancel?.();
5784
+ }, [options]);
5785
+ return {
5786
+ visible,
5787
+ target,
5788
+ loading,
5789
+ open,
5790
+ dialogProps: {
5791
+ visible,
5792
+ loading,
5793
+ onConfirm: handleConfirm,
5794
+ onCancel: handleCancel
5795
+ }
5796
+ };
5797
+ }
5482
5798
 
5483
5799
  Object.defineProperty(exports, "BottomSheetModalProvider", {
5484
5800
  enumerable: true,
@@ -5523,6 +5839,7 @@ exports.FormSection = FormSection;
5523
5839
  exports.ICON_SIZES = ICON_SIZES;
5524
5840
  exports.Icon = Icon;
5525
5841
  exports.IconButton = IconButton;
5842
+ exports.ImageUpload = ImageUpload;
5526
5843
  exports.ImageViewer = ImageViewer;
5527
5844
  exports.Input = Input;
5528
5845
  exports.LabelValue = LabelValue;
@@ -5549,6 +5866,7 @@ exports.Select = Select;
5549
5866
  exports.SelectableGrid = SelectableGrid;
5550
5867
  exports.Separator = Separator;
5551
5868
  exports.Sheet = Sheet;
5869
+ exports.SheetSelect = SheetSelect;
5552
5870
  exports.Skeleton = Skeleton;
5553
5871
  exports.Slider = Slider;
5554
5872
  exports.Spinner = Spinner;
@@ -5579,5 +5897,6 @@ exports.notificationWarning = notificationWarning;
5579
5897
  exports.renderIcon = renderIcon;
5580
5898
  exports.richHaptics = richHaptics;
5581
5899
  exports.selectionAsync = selectionAsync;
5900
+ exports.useConfirmDialog = useConfirmDialog;
5582
5901
  exports.useTheme = useTheme;
5583
5902
  exports.useToast = useToast;
package/dist/index.mjs CHANGED
@@ -1,13 +1,15 @@
1
+ export { Text } from './chunk-WJLKJMKR.mjs';
2
+ export { Textarea } from './chunk-EH745HE5.mjs';
1
3
  export { Toggle } from './chunk-KIHCWCWL.mjs';
2
4
  export { VirtualList } from './chunk-NC5ZTR2Y.mjs';
5
+ export { BottomSheetModalProvider, Sheet, BottomSheetTextInput as SheetTextInput } from './chunk-PFZTM6D5.mjs';
6
+ export { SheetSelect } from './chunk-URI2WBIV.mjs';
3
7
  export { Skeleton } from './chunk-AJ7ZDNBT.mjs';
4
8
  export { Slider } from './chunk-JMOZEC77.mjs';
5
- export { Spinner } from './chunk-WBOOUHSS.mjs';
6
9
  export { Switch } from './chunk-QKH5ZOD5.mjs';
7
10
  export { TabBar } from './chunk-MLF3EZFW.mjs';
8
11
  export { Tabs, TabsContent } from './chunk-GQYFLP3D.mjs';
9
- export { Text } from './chunk-WJLKJMKR.mjs';
10
- export { Textarea } from './chunk-EH745HE5.mjs';
12
+ export { Pressable } from './chunk-MBMXYJJV.mjs';
11
13
  export { PricingCard } from './chunk-4I7D47FH.mjs';
12
14
  export { Progress } from './chunk-OB4JUQ3O.mjs';
13
15
  export { RadioGroup } from './chunk-X4G6APW6.mjs';
@@ -16,33 +18,33 @@ export { ToastProvider, sonnerToast as toast, useToast } from './chunk-2UYENBLV.
16
18
  export { Select } from './chunk-A3A6KNQN.mjs';
17
19
  export { SelectableGrid } from './chunk-NA7PARID.mjs';
18
20
  export { Separator } from './chunk-MX6HRKMI.mjs';
19
- export { BottomSheetModalProvider, Sheet, BottomSheetTextInput as SheetTextInput } from './chunk-PFZTM6D5.mjs';
21
+ export { LabelValue } from './chunk-A4MDAP7G.mjs';
20
22
  export { ListGroup, ListGroupFooter, ListGroupHeader } from './chunk-SOA2Z4RB.mjs';
21
- export { ListItem } from './chunk-BNP626TY.mjs';
23
+ export { ListItem } from './chunk-T4I5WVHA.mjs';
22
24
  export { MediaCard } from './chunk-VGTDN7SW.mjs';
23
25
  export { MenuGroup, MenuGroupFooter, MenuGroupHeader } from './chunk-IRRY3CRZ.mjs';
24
26
  export { MenuItem } from './chunk-ZJKGQMYH.mjs';
25
27
  export { MonthPicker, dateToMonthPickerValue, monthPickerValueToDate } from './chunk-GD6KXMG5.mjs';
26
- export { Pressable } from './chunk-MBMXYJJV.mjs';
27
28
  export { EmptyState } from './chunk-6OAZJ577.mjs';
28
29
  export { ErrorBoundary } from './chunk-LXJIIOYQ.mjs';
29
30
  export { Form, FormField, FormFooter, FormSection } from './chunk-6Q64UFIA.mjs';
31
+ export { ImageUpload } from './chunk-Y4GL2MHX.mjs';
32
+ export { Spinner } from './chunk-WBOOUHSS.mjs';
30
33
  export { ImageViewer } from './chunk-Z4BVUWW6.mjs';
31
34
  export { PagerDots } from './chunk-4K625MVM.mjs';
32
- export { LabelValue } from './chunk-A4MDAP7G.mjs';
33
35
  export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './chunk-ID72TK46.mjs';
34
36
  export { CategoryStrip } from './chunk-VQ57HWPL.mjs';
35
37
  import './chunk-YNROWHQJ.mjs';
36
38
  export { Checkbox } from './chunk-AV4EMIRH.mjs';
37
39
  export { Chip, ChipGroup } from './chunk-UREA2GYY.mjs';
38
- export { ConfirmDialog } from './chunk-7QHVVCB3.mjs';
40
+ export { ConfirmDialog } from './chunk-FZZLPJ6B.mjs';
39
41
  export { CurrencyDisplay } from './chunk-BRKYVJVV.mjs';
40
- export { CurrencyInput } from './chunk-MAC465BB.mjs';
41
- export { Input } from './chunk-756RAKE4.mjs';
42
+ export { CurrencyInput } from './chunk-KNSENOV4.mjs';
43
+ export { Input } from './chunk-LVYEU5ZK.mjs';
42
44
  import './chunk-26BCI223.mjs';
43
45
  export { DetailRow } from './chunk-JB67UOB5.mjs';
44
46
  export { Accordion } from './chunk-O3HA6TYM.mjs';
45
- export { AlertBanner } from './chunk-M6ZXVBTK.mjs';
47
+ export { AlertBanner } from './chunk-6MKGPAR2.mjs';
46
48
  export { AppHeader } from './chunk-AZJF2BLK.mjs';
47
49
  export { IconButton } from './chunk-3U4SSNWP.mjs';
48
50
  export { Avatar } from './chunk-JT7HKXRB.mjs';
@@ -57,6 +59,7 @@ export { ThemeProvider, defaultDark, defaultLight, deriveColors, useTheme } from
57
59
  export { ButtonGroup } from './chunk-3BBOZ3OQ.mjs';
58
60
  import './chunk-2CE3TQVY.mjs';
59
61
  import './chunk-Y6FXYEAI.mjs';
62
+ import { useState, useCallback } from 'react';
60
63
 
61
64
  // src/utils/typography.ts
62
65
  function getResponsiveFontSize(text, maxSize, steps = [
@@ -71,5 +74,41 @@ function getResponsiveFontSize(text, maxSize, steps = [
71
74
  }
72
75
  return maxSize - 8;
73
76
  }
77
+ function useConfirmDialog(options) {
78
+ const [visible, setVisible] = useState(false);
79
+ const [target, setTarget] = useState(null);
80
+ const [loading, setLoading] = useState(false);
81
+ const open = useCallback((t) => {
82
+ setTarget(t ?? null);
83
+ setVisible(true);
84
+ }, []);
85
+ const handleConfirm = useCallback(async () => {
86
+ setLoading(true);
87
+ try {
88
+ await options.onConfirm();
89
+ } finally {
90
+ setLoading(false);
91
+ setVisible(false);
92
+ setTarget(null);
93
+ }
94
+ }, [options]);
95
+ const handleCancel = useCallback(() => {
96
+ setVisible(false);
97
+ setTarget(null);
98
+ options.onCancel?.();
99
+ }, [options]);
100
+ return {
101
+ visible,
102
+ target,
103
+ loading,
104
+ open,
105
+ dialogProps: {
106
+ visible,
107
+ loading,
108
+ onConfirm: handleConfirm,
109
+ onCancel: handleCancel
110
+ }
111
+ };
112
+ }
74
113
 
75
- export { getResponsiveFontSize };
114
+ export { getResponsiveFontSize, useConfirmDialog };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retray-dev/ui-kit",
3
- "version": "9.0.0",
3
+ "version": "9.2.0",
4
4
  "description": "Personal UI Kit for React Native / Expo",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -22,7 +22,10 @@
22
22
  "dist",
23
23
  "src",
24
24
  "COMPONENTS.md",
25
- "EXAMPLES.md"
25
+ "CONSUMER.md",
26
+ "EXAMPLES.md",
27
+ "FONTS.md",
28
+ "DESIGN.md"
26
29
  ],
27
30
  "scripts": {
28
31
  "build": "tsup",
@@ -55,6 +58,7 @@
55
58
  "@shopify/react-native-skia": ">=1.0.0",
56
59
  "expo-font": ">=14.0.0",
57
60
  "expo-haptics": ">=14.0.0",
61
+ "expo-image-picker": ">=15.0.0",
58
62
  "expo-linear-gradient": ">=13.0.0",
59
63
  "expo-sensors": ">=13.0.0",
60
64
  "pressto": ">=0.6.0",
@@ -63,13 +67,13 @@
63
67
  "react-native-ease": ">=0.7.0",
64
68
  "react-native-gesture-handler": ">=2.0.0",
65
69
  "react-native-pulsar": ">=1.6.0",
66
- "react-native-reanimated": ">=4.0.0",
70
+ "react-native-reanimated": ">=4.0.0 <5.0.0",
67
71
  "react-native-safe-area-context": ">=4.0.0",
68
72
  "react-native-screens": ">=3.0.0",
69
73
  "react-native-size-matters": ">=0.4.0",
70
74
  "react-native-svg": ">=15.0.0",
71
- "react-native-worklets": ">=0.5.0",
72
- "sonner-native": ">=0.20.0"
75
+ "react-native-worklets": "~0.5.1",
76
+ "sonner-native": ">=0.22.0"
73
77
  },
74
78
  "peerDependenciesMeta": {
75
79
  "@shopify/react-native-skia": {
@@ -80,6 +84,15 @@
80
84
  },
81
85
  "react-native-pulsar": {
82
86
  "optional": true
87
+ },
88
+ "expo-image-picker": {
89
+ "optional": true
90
+ },
91
+ "react-native-ease": {
92
+ "optional": false
93
+ },
94
+ "pressto": {
95
+ "optional": false
83
96
  }
84
97
  },
85
98
  "pnpm": {
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
- import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
- import { FontAwesome5, MaterialIcons, Entypo } from '@expo/vector-icons'
2
+ import { View, Text, StyleSheet, ViewStyle, TouchableOpacity } from 'react-native'
3
+ import { FontAwesome5, MaterialIcons, Entypo, Feather } 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'
@@ -15,10 +15,12 @@ export interface AlertBannerProps {
15
15
  icon?: React.ReactNode
16
16
  iconName?: string
17
17
  iconColor?: string
18
+ /** Called when the user taps the dismiss (×) button. If omitted, no button is shown. */
19
+ onDismiss?: () => void
18
20
  style?: ViewStyle
19
21
  }
20
22
 
21
- export function AlertBanner({ title, description, variant = 'default', icon, iconName, iconColor, style }: AlertBannerProps) {
23
+ export function AlertBanner({ title, description, variant = 'default', icon, iconName, iconColor, onDismiss, style }: AlertBannerProps) {
22
24
  const { colors, colorScheme } = useTheme()
23
25
 
24
26
  // Match Toast richColors appearance — saturated semantic colors
@@ -81,6 +83,17 @@ export function AlertBanner({ title, description, variant = 'default', icon, ico
81
83
  <Text style={[styles.description, { color: colors.foreground, opacity: 0.85 }]} allowFontScaling={true}>{description}</Text>
82
84
  ) : null}
83
85
  </View>
86
+ {onDismiss ? (
87
+ <TouchableOpacity
88
+ onPress={onDismiss}
89
+ style={styles.dismissButton}
90
+ activeOpacity={0.6}
91
+ accessibilityRole="button"
92
+ accessibilityLabel="Dismiss"
93
+ >
94
+ <Feather name="x" size={ms(16)} color={colors.foregroundMuted} />
95
+ </TouchableOpacity>
96
+ ) : null}
84
97
  </View>
85
98
  )
86
99
  }
@@ -109,4 +122,9 @@ const styles = StyleSheet.create({
109
122
  fontFamily: 'Sohne-Regular',
110
123
  fontSize: ms(12),
111
124
  },
125
+ dismissButton: {
126
+ padding: s(4),
127
+ marginTop: vs(-2),
128
+ marginRight: -s(4),
129
+ },
112
130
  })