@retray-dev/ui-kit 9.3.1 → 10.1.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 (39) hide show
  1. package/COMPONENTS.md +136 -22
  2. package/CONSUMER.md +48 -8
  3. package/FONTS.md +54 -13
  4. package/README.md +40 -3
  5. package/dist/Accordion.d.mts +1 -1
  6. package/dist/Accordion.d.ts +1 -1
  7. package/dist/Accordion.js +2 -2
  8. package/dist/Accordion.mjs +1 -1
  9. package/dist/ConfirmDialog.d.mts +6 -1
  10. package/dist/ConfirmDialog.d.ts +6 -1
  11. package/dist/ConfirmDialog.js +44 -14
  12. package/dist/ConfirmDialog.mjs +1 -1
  13. package/dist/ImageViewer.js +282 -141
  14. package/dist/ImageViewer.mjs +3 -1
  15. package/dist/Sheet.js +16 -13
  16. package/dist/Sheet.mjs +1 -1
  17. package/dist/Switch.js +40 -17
  18. package/dist/Switch.mjs +1 -1
  19. package/dist/{chunk-O3HA6TYM.mjs → chunk-DJ7RN37L.mjs} +2 -2
  20. package/dist/{chunk-FZZLPJ6B.mjs → chunk-KZL5VTYK.mjs} +43 -14
  21. package/dist/{chunk-QKH5ZOD5.mjs → chunk-WF2XDFRK.mjs} +40 -17
  22. package/dist/{chunk-Z4BVUWW6.mjs → chunk-WOEYDUJZ.mjs} +19 -31
  23. package/dist/{chunk-PFZTM6D5.mjs → chunk-Y2NS74WS.mjs} +9 -7
  24. package/dist/fonts.d.mts +39 -31
  25. package/dist/fonts.d.ts +39 -31
  26. package/dist/fonts.js +34 -39
  27. package/dist/fonts.mjs +35 -34
  28. package/dist/index.js +119 -76
  29. package/dist/index.mjs +5 -5
  30. package/package.json +3 -1
  31. package/scripts/build-apk.sh +84 -0
  32. package/scripts/copy-fonts.js +90 -0
  33. package/scripts/test-consumer-fonts.sh +82 -0
  34. package/src/components/Accordion/Accordion.tsx +7 -3
  35. package/src/components/ConfirmDialog/ConfirmDialog.tsx +61 -23
  36. package/src/components/ImageViewer/ImageViewer.tsx +25 -30
  37. package/src/components/Sheet/Sheet.tsx +10 -9
  38. package/src/components/Switch/Switch.tsx +30 -17
  39. package/src/fonts.ts +59 -40
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ var Ionicons = require('@expo/vector-icons/Ionicons');
12
12
  var pressto = require('pressto');
13
13
  var Animated12 = require('react-native-reanimated');
14
14
  var expoFont = require('expo-font');
15
- var bottomSheet = require('@gorhom/bottom-sheet');
15
+ var BottomSheet = require('@gorhom/bottom-sheet');
16
16
  var reactNativeEase = require('react-native-ease');
17
17
  var vectorIcons = require('@expo/vector-icons');
18
18
  var expoLinearGradient = require('expo-linear-gradient');
@@ -32,6 +32,7 @@ var FontAwesome5__default = /*#__PURE__*/_interopDefault(FontAwesome5);
32
32
  var MaterialIcons__default = /*#__PURE__*/_interopDefault(MaterialIcons);
33
33
  var Ionicons__default = /*#__PURE__*/_interopDefault(Ionicons);
34
34
  var Animated12__default = /*#__PURE__*/_interopDefault(Animated12);
35
+ var BottomSheet__default = /*#__PURE__*/_interopDefault(BottomSheet);
35
36
  var RNSlider__default = /*#__PURE__*/_interopDefault(RNSlider);
36
37
 
37
38
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -1035,7 +1036,7 @@ function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suff
1035
1036
  },
1036
1037
  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,
1037
1038
  sheetMode ? /* @__PURE__ */ React25__default.default.createElement(
1038
- bottomSheet.BottomSheetTextInput,
1039
+ BottomSheet.BottomSheetTextInput,
1039
1040
  {
1040
1041
  style: [
1041
1042
  styles4.input,
@@ -2028,9 +2029,11 @@ var THUMB_SIZE = s(24);
2028
2029
  var THUMB_OFFSET = s(3);
2029
2030
  var THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2;
2030
2031
  var ICON_SIZE = s(13);
2032
+ var DISABLED_OPACITY = 0.45;
2031
2033
  function Switch({ checked = false, onCheckedChange, disabled, style, accessibilityLabel }) {
2032
2034
  const { colors } = useTheme();
2033
- return /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [{ opacity: disabled ? 0.45 : 1, alignSelf: "flex-start" }, style] }, /* @__PURE__ */ React25__default.default.createElement(
2035
+ const isDisabled = !!disabled;
2036
+ return /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [{ alignSelf: "flex-start" }, style] }, /* @__PURE__ */ React25__default.default.createElement(
2034
2037
  reactNative.TouchableOpacity,
2035
2038
  {
2036
2039
  onPress: () => {
@@ -2042,47 +2045,68 @@ function Switch({ checked = false, onCheckedChange, disabled, style, accessibili
2042
2045
  touchSoundDisabled: true,
2043
2046
  accessibilityRole: "switch",
2044
2047
  accessibilityLabel,
2045
- accessibilityState: { checked, disabled: !!disabled },
2048
+ accessibilityState: { checked, disabled: isDisabled },
2046
2049
  style: styles16.touchable
2047
2050
  },
2048
- /* @__PURE__ */ React25__default.default.createElement(
2051
+ /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles16.trackContainer }, /* @__PURE__ */ React25__default.default.createElement(
2049
2052
  reactNativeEase.EaseView,
2050
2053
  {
2051
- style: styles16.track,
2054
+ style: [styles16.track, isDisabled && styles16.disabledTrack],
2052
2055
  animate: { backgroundColor: checked ? colors.primary : colors.surfaceStrong },
2053
2056
  transition: COLOR_TRANSITION
2057
+ }
2058
+ ), /* @__PURE__ */ React25__default.default.createElement(
2059
+ reactNativeEase.EaseView,
2060
+ {
2061
+ style: [styles16.trackBorder, { borderWidth: 1.5 }],
2062
+ pointerEvents: "none",
2063
+ animate: { borderColor: checked ? "transparent" : colors.border },
2064
+ transition: COLOR_TRANSITION
2065
+ }
2066
+ ), /* @__PURE__ */ React25__default.default.createElement(
2067
+ reactNativeEase.EaseView,
2068
+ {
2069
+ style: [styles16.thumb, { backgroundColor: colors.primaryForeground }],
2070
+ animate: { translateX: checked ? THUMB_TRAVEL : 0 },
2071
+ transition: SPRING_ELASTIC
2054
2072
  },
2055
2073
  /* @__PURE__ */ React25__default.default.createElement(
2056
2074
  reactNativeEase.EaseView,
2057
2075
  {
2058
- style: [styles16.trackBorder, { borderWidth: 1.5 }],
2059
- pointerEvents: "none",
2060
- animate: { borderColor: checked ? "transparent" : colors.border },
2061
- transition: COLOR_TRANSITION
2062
- }
2076
+ style: styles16.iconWrapper,
2077
+ animate: { opacity: checked ? isDisabled ? DISABLED_OPACITY : 1 : 0 },
2078
+ transition: OPACITY_TRANSITION
2079
+ },
2080
+ /* @__PURE__ */ React25__default.default.createElement(vectorIcons.Feather, { name: "check", size: ICON_SIZE, color: colors.primary })
2063
2081
  ),
2064
2082
  /* @__PURE__ */ React25__default.default.createElement(
2065
2083
  reactNativeEase.EaseView,
2066
2084
  {
2067
- style: [styles16.thumb, { backgroundColor: colors.primaryForeground }],
2068
- animate: { translateX: checked ? THUMB_TRAVEL : 0 },
2069
- transition: SPRING_ELASTIC
2085
+ style: styles16.iconWrapper,
2086
+ animate: { opacity: checked ? 0 : isDisabled ? DISABLED_OPACITY : 1 },
2087
+ transition: OPACITY_TRANSITION
2070
2088
  },
2071
- /* @__PURE__ */ React25__default.default.createElement(reactNativeEase.EaseView, { style: styles16.iconWrapper, animate: { opacity: checked ? 1 : 0 }, transition: OPACITY_TRANSITION }, /* @__PURE__ */ React25__default.default.createElement(vectorIcons.Feather, { name: "check", size: ICON_SIZE, color: colors.primary })),
2072
- /* @__PURE__ */ React25__default.default.createElement(reactNativeEase.EaseView, { style: styles16.iconWrapper, animate: { opacity: checked ? 0 : 1 }, transition: OPACITY_TRANSITION }, /* @__PURE__ */ React25__default.default.createElement(vectorIcons.Feather, { name: "x", size: ICON_SIZE, color: colors.foregroundMuted }))
2089
+ /* @__PURE__ */ React25__default.default.createElement(vectorIcons.Feather, { name: "x", size: ICON_SIZE, color: colors.foregroundMuted })
2073
2090
  )
2074
- )
2091
+ ))
2075
2092
  ));
2076
2093
  }
2077
2094
  var styles16 = reactNative.StyleSheet.create({
2078
2095
  touchable: {
2079
2096
  alignSelf: "flex-start"
2080
2097
  },
2081
- track: {
2098
+ trackContainer: {
2099
+ position: "relative",
2082
2100
  width: TRACK_WIDTH,
2083
- height: TRACK_HEIGHT,
2101
+ height: TRACK_HEIGHT
2102
+ },
2103
+ track: {
2104
+ ...reactNative.StyleSheet.absoluteFillObject,
2084
2105
  borderRadius: TRACK_HEIGHT / 2
2085
2106
  },
2107
+ disabledTrack: {
2108
+ opacity: DISABLED_OPACITY
2109
+ },
2086
2110
  trackBorder: {
2087
2111
  ...reactNative.StyleSheet.absoluteFillObject,
2088
2112
  borderRadius: TRACK_HEIGHT / 2
@@ -2601,9 +2625,9 @@ function AccordionItemComponent({
2601
2625
  },
2602
2626
  accessibilityRole: "button",
2603
2627
  accessibilityState: { expanded: isOpen },
2604
- accessibilityLabel: item.trigger
2628
+ accessibilityLabel: typeof item.trigger === "string" ? item.trigger : void 0
2605
2629
  },
2606
- /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles20.triggerContent }, resolvedIcon ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles20.icon }, resolvedIcon) : null, /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles20.triggerText, { color: colors.foreground }], allowFontScaling: true }, item.trigger)),
2630
+ /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles20.triggerContent }, resolvedIcon ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles20.icon }, resolvedIcon) : null, typeof item.trigger === "string" ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles20.triggerText, { color: colors.foreground }], allowFontScaling: true }, item.trigger) : item.trigger),
2607
2631
  /* @__PURE__ */ React25__default.default.createElement(Animated12__default.default.View, { style: [styles20.chevron, rotationStyle] }, /* @__PURE__ */ React25__default.default.createElement(vectorIcons.Entypo, { name: "chevron-down", size: 18, color: colors.foregroundMuted }))
2608
2632
  ), /* @__PURE__ */ React25__default.default.createElement(Animated12__default.default.View, { style: bodyStyle }, /* @__PURE__ */ React25__default.default.createElement(
2609
2633
  reactNative.View,
@@ -2811,13 +2835,13 @@ function Sheet({
2811
2835
  React25.useEffect(() => {
2812
2836
  if (open) {
2813
2837
  impactMedium();
2814
- ref.current?.present();
2838
+ ref.current?.snapToIndex(0);
2815
2839
  } else {
2816
- ref.current?.dismiss();
2840
+ ref.current?.close();
2817
2841
  }
2818
2842
  }, [open]);
2819
2843
  const renderBackdrop = React25.useCallback((props) => /* @__PURE__ */ React25__default.default.createElement(
2820
- bottomSheet.BottomSheetBackdrop,
2844
+ BottomSheet.BottomSheetBackdrop,
2821
2845
  {
2822
2846
  ...props,
2823
2847
  disappearsOnIndex: -1,
@@ -2834,7 +2858,7 @@ function Sheet({
2834
2858
  ) : children;
2835
2859
  const effectiveSubtitle = subtitle ?? description;
2836
2860
  const showHeader = !!(title || effectiveSubtitle || showCloseButton) && !customHeader;
2837
- const headerNode = customHeader ? customHeader : showHeader ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles22.header, accessibilityRole: "header" }, /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles22.headerRow }, title ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles22.title, { color: colors.foreground }], allowFontScaling: true }, title) : /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: { flex: 1 } }), showCloseButton ? /* @__PURE__ */ React25__default.default.createElement(
2861
+ const headerNode = customHeader ? customHeader : showHeader ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [styles22.header, { backgroundColor: colors.card }], accessibilityRole: "header" }, /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles22.headerRow }, title ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles22.title, { color: colors.foreground }], allowFontScaling: true }, title) : /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: { flex: 1 } }), showCloseButton ? /* @__PURE__ */ React25__default.default.createElement(
2838
2862
  reactNative.TouchableOpacity,
2839
2863
  {
2840
2864
  onPress: onClose,
@@ -2851,7 +2875,7 @@ function Sheet({
2851
2875
  const effectiveFooter = customFooter ? customFooter : footer;
2852
2876
  const renderFooter = React25.useCallback((props) => {
2853
2877
  if (!effectiveFooter) return null;
2854
- return /* @__PURE__ */ React25__default.default.createElement(bottomSheet.BottomSheetFooter, { ...props }, effectiveFooter);
2878
+ return /* @__PURE__ */ React25__default.default.createElement(BottomSheet.BottomSheetFooter, { ...props }, effectiveFooter);
2855
2879
  }, [effectiveFooter]);
2856
2880
  if (asDialog) {
2857
2881
  return /* @__PURE__ */ React25__default.default.createElement(reactNative.Modal, { visible: open, transparent: true, animationType: "fade", onRequestClose: onClose }, /* @__PURE__ */ React25__default.default.createElement(reactNative.Pressable, { style: styles22.dialogBackdrop, onPress: onClose, accessibilityRole: "button", accessibilityLabel: "Close" }, /* @__PURE__ */ React25__default.default.createElement(
@@ -2882,13 +2906,14 @@ function Sheet({
2882
2906
  const effectiveMaxHeight = maxHeight ?? DEFAULT_MAX_HEIGHT;
2883
2907
  const useDynamicSizing = !snapPoints;
2884
2908
  return /* @__PURE__ */ React25__default.default.createElement(
2885
- bottomSheet.BottomSheetModal,
2909
+ BottomSheet__default.default,
2886
2910
  {
2887
2911
  ref,
2912
+ index: -1,
2913
+ onClose,
2888
2914
  enableDynamicSizing: useDynamicSizing,
2889
2915
  snapPoints,
2890
2916
  maxDynamicContentSize: useDynamicSizing ? effectiveMaxHeight : void 0,
2891
- onDismiss: onClose,
2892
2917
  backdropComponent: renderBackdrop,
2893
2918
  footerComponent: effectiveFooter ? renderFooter : void 0,
2894
2919
  backgroundStyle: [styles22.background, { backgroundColor: colors.card }],
@@ -2901,7 +2926,7 @@ function Sheet({
2901
2926
  enableBlurKeyboardOnGesture
2902
2927
  },
2903
2928
  useScroll ? /* @__PURE__ */ React25__default.default.createElement(
2904
- bottomSheet.BottomSheetScrollView,
2929
+ BottomSheet.BottomSheetScrollView,
2905
2930
  {
2906
2931
  contentContainerStyle: [
2907
2932
  styles22.scrollContent,
@@ -2910,11 +2935,12 @@ function Sheet({
2910
2935
  style: contentStyle,
2911
2936
  showsVerticalScrollIndicator: true,
2912
2937
  indicatorStyle: "black",
2913
- persistentScrollbar: isAndroid
2938
+ persistentScrollbar: isAndroid,
2939
+ stickyHeaderIndices: headerNode ? [0] : void 0
2914
2940
  },
2915
2941
  headerNode,
2916
2942
  contentNode
2917
- ) : /* @__PURE__ */ React25__default.default.createElement(bottomSheet.BottomSheetView, { style: [styles22.content, contentStyle, style] }, headerNode, contentNode)
2943
+ ) : /* @__PURE__ */ React25__default.default.createElement(BottomSheet.BottomSheetView, { style: [styles22.content, contentStyle, style] }, headerNode, contentNode)
2918
2944
  );
2919
2945
  }
2920
2946
  Sheet.Header = SheetHeader;
@@ -3900,26 +3926,29 @@ var styles29 = reactNative.StyleSheet.create({
3900
3926
  function ConfirmDialog({
3901
3927
  visible,
3902
3928
  title,
3929
+ subtitle,
3903
3930
  description,
3904
3931
  confirmLabel = "Confirm",
3905
3932
  cancelLabel = "Cancel",
3906
3933
  confirmVariant = "primary",
3907
3934
  loading = false,
3935
+ showCloseButton = false,
3908
3936
  onConfirm,
3909
3937
  onCancel
3910
3938
  }) {
3911
3939
  const { colors } = useTheme();
3912
3940
  const ref = React25.useRef(null);
3941
+ const effectiveSubtitle = subtitle ?? description;
3913
3942
  React25.useEffect(() => {
3914
3943
  if (visible) {
3915
3944
  impactMedium();
3916
- ref.current?.present();
3945
+ ref.current?.snapToIndex(0);
3917
3946
  } else {
3918
- ref.current?.dismiss();
3947
+ ref.current?.close();
3919
3948
  }
3920
3949
  }, [visible]);
3921
3950
  const renderBackdrop = (props) => /* @__PURE__ */ React25__default.default.createElement(
3922
- bottomSheet.BottomSheetBackdrop,
3951
+ BottomSheet.BottomSheetBackdrop,
3923
3952
  {
3924
3953
  ...props,
3925
3954
  disappearsOnIndex: -1,
@@ -3928,17 +3957,30 @@ function ConfirmDialog({
3928
3957
  }
3929
3958
  );
3930
3959
  return /* @__PURE__ */ React25__default.default.createElement(
3931
- bottomSheet.BottomSheetModal,
3960
+ BottomSheet__default.default,
3932
3961
  {
3933
3962
  ref,
3963
+ index: -1,
3964
+ onClose: onCancel,
3934
3965
  enableDynamicSizing: true,
3935
- onDismiss: onCancel,
3936
3966
  backdropComponent: renderBackdrop,
3937
3967
  backgroundStyle: [styles30.background, { backgroundColor: colors.card }],
3938
3968
  handleIndicatorStyle: [styles30.handle, { backgroundColor: colors.border }],
3939
3969
  enablePanDownToClose: true
3940
3970
  },
3941
- /* @__PURE__ */ React25__default.default.createElement(bottomSheet.BottomSheetView, { style: styles30.content }, /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles30.title, { color: colors.foreground }], allowFontScaling: true }, title), description ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles30.description, { color: colors.foregroundMuted }], allowFontScaling: true }, description) : null, /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles30.actions }, /* @__PURE__ */ React25__default.default.createElement(
3971
+ /* @__PURE__ */ React25__default.default.createElement(BottomSheet.BottomSheetView, { style: styles30.content }, /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles30.header, accessibilityRole: "header" }, /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles30.headerRow }, /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles30.title, { color: colors.foreground }], allowFontScaling: true }, title), showCloseButton ? /* @__PURE__ */ React25__default.default.createElement(
3972
+ reactNative.TouchableOpacity,
3973
+ {
3974
+ onPress: onCancel,
3975
+ style: styles30.closeButton,
3976
+ activeOpacity: 0.6,
3977
+ touchSoundDisabled: true,
3978
+ accessibilityRole: "button",
3979
+ accessibilityLabel: "Close",
3980
+ hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }
3981
+ },
3982
+ /* @__PURE__ */ React25__default.default.createElement(vectorIcons.Feather, { name: "x", size: ms(18), color: colors.foregroundMuted })
3983
+ ) : null), effectiveSubtitle ? /* @__PURE__ */ React25__default.default.createElement(reactNative.Text, { style: [styles30.subtitle, { color: colors.foregroundMuted }], allowFontScaling: true }, effectiveSubtitle) : null), /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles30.actions }, /* @__PURE__ */ React25__default.default.createElement(
3942
3984
  Button,
3943
3985
  {
3944
3986
  label: confirmLabel,
@@ -3985,19 +4027,32 @@ var styles30 = reactNative.StyleSheet.create({
3985
4027
  borderRadius: ms(2)
3986
4028
  },
3987
4029
  content: {
3988
- paddingHorizontal: s(24),
3989
- paddingBottom: vs(32),
3990
- gap: vs(12)
4030
+ paddingHorizontal: s(16),
4031
+ paddingBottom: vs(32)
4032
+ },
4033
+ header: {
4034
+ paddingTop: vs(4),
4035
+ paddingBottom: vs(12),
4036
+ gap: vs(4)
4037
+ },
4038
+ headerRow: {
4039
+ flexDirection: "row",
4040
+ alignItems: "center",
4041
+ justifyContent: "space-between"
3991
4042
  },
3992
4043
  title: {
3993
4044
  fontFamily: "Sohne-SemiBold",
3994
4045
  fontSize: ms(18),
3995
- lineHeight: mvs(26)
4046
+ flex: 1
3996
4047
  },
3997
- description: {
4048
+ closeButton: {
4049
+ padding: s(4),
4050
+ marginLeft: s(8)
4051
+ },
4052
+ subtitle: {
3998
4053
  fontFamily: "Sohne-Regular",
3999
- fontSize: ms(15),
4000
- lineHeight: mvs(22)
4054
+ fontSize: ms(14),
4055
+ lineHeight: mvs(20)
4001
4056
  },
4002
4057
  actions: {
4003
4058
  gap: vs(10),
@@ -4620,7 +4675,7 @@ function VirtualListInner({ itemHeight, keyExtractor, renderItem, ...props }, re
4620
4675
  }
4621
4676
  var VirtualList = React25__default.default.forwardRef(VirtualListInner);
4622
4677
  function RetrayProvider({ children, theme, colorScheme = "system" }) {
4623
- return /* @__PURE__ */ React25__default.default.createElement(reactNativeSafeAreaContext.SafeAreaProvider, { initialMetrics: reactNativeSafeAreaContext.initialWindowMetrics }, /* @__PURE__ */ React25__default.default.createElement(reactNativeGestureHandler.GestureHandlerRootView, { style: styles37.root }, /* @__PURE__ */ React25__default.default.createElement(ThemeProvider, { theme, colorScheme }, /* @__PURE__ */ React25__default.default.createElement(bottomSheet.BottomSheetModalProvider, null, /* @__PURE__ */ React25__default.default.createElement(ToastProvider, null, children)))));
4678
+ return /* @__PURE__ */ React25__default.default.createElement(reactNativeSafeAreaContext.SafeAreaProvider, { initialMetrics: reactNativeSafeAreaContext.initialWindowMetrics }, /* @__PURE__ */ React25__default.default.createElement(reactNativeGestureHandler.GestureHandlerRootView, { style: styles37.root }, /* @__PURE__ */ React25__default.default.createElement(ThemeProvider, { theme, colorScheme }, /* @__PURE__ */ React25__default.default.createElement(BottomSheet.BottomSheetModalProvider, null, /* @__PURE__ */ React25__default.default.createElement(ToastProvider, null, children)))));
4624
4679
  }
4625
4680
  var styles37 = reactNative.StyleSheet.create({
4626
4681
  root: { flex: 1 }
@@ -5381,17 +5436,12 @@ function ZoomableImage({ source, width, height, onZoomChange }) {
5381
5436
  { scale: scale2.value }
5382
5437
  ]
5383
5438
  }));
5384
- return /* @__PURE__ */ React25__default.default.createElement(reactNativeGestureHandler.GestureDetector, { gesture: composed }, /* @__PURE__ */ React25__default.default.createElement(Animated12__default.default.View, { style: [{ width, height }, styles44.imageWrap] }, /* @__PURE__ */ React25__default.default.createElement(
5385
- Animated12__default.default.Image,
5386
- {
5387
- source,
5388
- style: [{ width, height }, animatedStyle],
5389
- resizeMode: "contain"
5390
- }
5391
- )));
5439
+ return /* @__PURE__ */ React25__default.default.createElement(reactNativeGestureHandler.GestureDetector, { gesture: composed }, /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [{ width, height }, styles44.imageWrap], collapsable: false }, /* @__PURE__ */ React25__default.default.createElement(Animated12__default.default.View, { style: [{ width, height }, animatedStyle] }, /* @__PURE__ */ React25__default.default.createElement(reactNative.Image, { source, style: { width, height }, resizeMode: "contain" }))));
5392
5440
  }
5393
5441
  function ImageViewer({ images, visible, onClose, initialIndex = 0 }) {
5394
- const { width, height } = reactNative.useWindowDimensions();
5442
+ const window = reactNative.useWindowDimensions();
5443
+ const width = window.width > 0 ? window.width : reactNative.Dimensions.get("window").width;
5444
+ const height = window.height > 0 ? window.height : reactNative.Dimensions.get("window").height;
5395
5445
  const insets = reactNativeSafeAreaContext.useSafeAreaInsets();
5396
5446
  const [index, setIndex] = React25.useState(initialIndex);
5397
5447
  const [pagingEnabled, setPagingEnabled] = React25.useState(true);
@@ -5434,7 +5484,7 @@ function ImageViewer({ images, visible, onClose, initialIndex = 0 }) {
5434
5484
  scrollRef.current?.scrollTo({ x: page * width, animated: true });
5435
5485
  setIndex(page);
5436
5486
  };
5437
- return /* @__PURE__ */ React25__default.default.createElement(reactNative.Modal, { visible, transparent: false, animationType: "fade", onRequestClose: onClose, statusBarTranslucent: true }, /* @__PURE__ */ React25__default.default.createElement(reactNativeGestureHandler.GestureHandlerRootView, { style: styles44.root }, /* @__PURE__ */ React25__default.default.createElement(Animated12__default.default.View, { style: [styles44.backdrop, backdropStyle], pointerEvents: "none" }), /* @__PURE__ */ React25__default.default.createElement(Animated12__default.default.View, { style: [styles44.container, dismissStyle] }, /* @__PURE__ */ React25__default.default.createElement(reactNativeGestureHandler.GestureDetector, { gesture: swipeDown }, /* @__PURE__ */ React25__default.default.createElement(Animated12__default.default.View, { style: styles44.root }, /* @__PURE__ */ React25__default.default.createElement(
5487
+ return /* @__PURE__ */ React25__default.default.createElement(reactNative.Modal, { visible, transparent: false, animationType: "fade", onRequestClose: onClose, statusBarTranslucent: true }, /* @__PURE__ */ React25__default.default.createElement(reactNativeGestureHandler.GestureHandlerRootView, { style: styles44.root }, /* @__PURE__ */ React25__default.default.createElement(Animated12__default.default.View, { style: [styles44.backdrop, backdropStyle], pointerEvents: "none" }), /* @__PURE__ */ React25__default.default.createElement(Animated12__default.default.View, { style: [styles44.container, dismissStyle] }, /* @__PURE__ */ React25__default.default.createElement(reactNativeGestureHandler.GestureDetector, { gesture: swipeDown }, /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: styles44.root, collapsable: false }, /* @__PURE__ */ React25__default.default.createElement(
5438
5488
  reactNative.ScrollView,
5439
5489
  {
5440
5490
  ref: scrollRef,
@@ -5455,19 +5505,18 @@ function ImageViewer({ images, visible, onClose, initialIndex = 0 }) {
5455
5505
  onZoomChange: (zoomed) => setPagingEnabled(!zoomed)
5456
5506
  }
5457
5507
  ))
5458
- ))), /* @__PURE__ */ React25__default.default.createElement(
5459
- reactNative.TouchableOpacity,
5508
+ ))), /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [styles44.closeButtonWrapper, { top: insets.top + vs(8) }] }, /* @__PURE__ */ React25__default.default.createElement(
5509
+ IconButton,
5460
5510
  {
5461
- style: [styles44.closeButton, { top: insets.top + vs(8) }],
5511
+ iconName: "x",
5512
+ size: "md",
5513
+ variant: "text",
5514
+ style: { backgroundColor: "rgba(255,255,255,0.18)" },
5515
+ iconColor: "#fff",
5462
5516
  onPress: onClose,
5463
- activeOpacity: 0.7,
5464
- touchSoundDisabled: true,
5465
- accessibilityRole: "button",
5466
- accessibilityLabel: "Close",
5467
- hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }
5468
- },
5469
- renderIcon("x", 26, "#fff")
5470
- ), images.length > 1 ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [styles44.dots, { bottom: insets.bottom + vs(16) }], pointerEvents: "box-none" }, /* @__PURE__ */ React25__default.default.createElement(
5517
+ accessibilityLabel: "Close"
5518
+ }
5519
+ )), images.length > 1 ? /* @__PURE__ */ React25__default.default.createElement(reactNative.View, { style: [styles44.dots, { bottom: insets.bottom + vs(16) }], pointerEvents: "box-none" }, /* @__PURE__ */ React25__default.default.createElement(
5471
5520
  PagerDots,
5472
5521
  {
5473
5522
  count: images.length,
@@ -5494,15 +5543,9 @@ var styles44 = reactNative.StyleSheet.create({
5494
5543
  justifyContent: "center",
5495
5544
  overflow: "hidden"
5496
5545
  },
5497
- closeButton: {
5546
+ closeButtonWrapper: {
5498
5547
  position: "absolute",
5499
- right: s(12),
5500
- width: s(40),
5501
- height: s(40),
5502
- borderRadius: s(20),
5503
- backgroundColor: "rgba(0,0,0,0.4)",
5504
- alignItems: "center",
5505
- justifyContent: "center"
5548
+ right: s(12)
5506
5549
  },
5507
5550
  dots: {
5508
5551
  position: "absolute",
@@ -5798,11 +5841,11 @@ function useConfirmDialog(options) {
5798
5841
 
5799
5842
  Object.defineProperty(exports, "BottomSheetModalProvider", {
5800
5843
  enumerable: true,
5801
- get: function () { return bottomSheet.BottomSheetModalProvider; }
5844
+ get: function () { return BottomSheet.BottomSheetModalProvider; }
5802
5845
  });
5803
5846
  Object.defineProperty(exports, "SheetTextInput", {
5804
5847
  enumerable: true,
5805
- get: function () { return bottomSheet.BottomSheetTextInput; }
5848
+ get: function () { return BottomSheet.BottomSheetTextInput; }
5806
5849
  });
5807
5850
  Object.defineProperty(exports, "toast", {
5808
5851
  enumerable: true,
package/dist/index.mjs CHANGED
@@ -4,11 +4,11 @@ export { Textarea } from './chunk-CZCQZHG6.mjs';
4
4
  export { Toggle } from './chunk-KIHCWCWL.mjs';
5
5
  export { VirtualList } from './chunk-NC5ZTR2Y.mjs';
6
6
  export { Separator } from './chunk-MX6HRKMI.mjs';
7
- export { BottomSheetModalProvider, Sheet, BottomSheetTextInput as SheetTextInput } from './chunk-PFZTM6D5.mjs';
7
+ export { BottomSheetModalProvider, Sheet, BottomSheetTextInput as SheetTextInput } from './chunk-Y2NS74WS.mjs';
8
8
  export { SheetSelect } from './chunk-URI2WBIV.mjs';
9
9
  export { Skeleton } from './chunk-AJ7ZDNBT.mjs';
10
10
  export { Slider } from './chunk-JMOZEC77.mjs';
11
- export { Switch } from './chunk-QKH5ZOD5.mjs';
11
+ export { Switch } from './chunk-WF2XDFRK.mjs';
12
12
  export { TabBar } from './chunk-MLF3EZFW.mjs';
13
13
  export { Pressable } from './chunk-MBMXYJJV.mjs';
14
14
  export { PricingCard } from './chunk-4I7D47FH.mjs';
@@ -31,7 +31,7 @@ export { ErrorBoundary } from './chunk-LXJIIOYQ.mjs';
31
31
  export { Form, FormField, FormFooter, FormSection } from './chunk-6Q64UFIA.mjs';
32
32
  export { ImageUpload } from './chunk-Y4GL2MHX.mjs';
33
33
  export { Spinner } from './chunk-WBOOUHSS.mjs';
34
- export { ImageViewer } from './chunk-Z4BVUWW6.mjs';
34
+ export { ImageViewer } from './chunk-WOEYDUJZ.mjs';
35
35
  export { PagerDots } from './chunk-4K625MVM.mjs';
36
36
  export { ButtonGroup } from './chunk-3BBOZ3OQ.mjs';
37
37
  export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './chunk-ID72TK46.mjs';
@@ -39,11 +39,11 @@ export { CategoryStrip } from './chunk-VQ57HWPL.mjs';
39
39
  import './chunk-YNROWHQJ.mjs';
40
40
  export { Checkbox } from './chunk-AV4EMIRH.mjs';
41
41
  export { Chip, ChipGroup } from './chunk-UREA2GYY.mjs';
42
- export { ConfirmDialog } from './chunk-FZZLPJ6B.mjs';
42
+ export { ConfirmDialog } from './chunk-KZL5VTYK.mjs';
43
43
  export { CurrencyDisplay } from './chunk-BRKYVJVV.mjs';
44
44
  export { CurrencyInput } from './chunk-JUXSWN54.mjs';
45
45
  export { Input } from './chunk-ZUR7AU5R.mjs';
46
- export { Accordion } from './chunk-O3HA6TYM.mjs';
46
+ export { Accordion } from './chunk-DJ7RN37L.mjs';
47
47
  export { AlertBanner } from './chunk-6MKGPAR2.mjs';
48
48
  export { AppHeader } from './chunk-AZJF2BLK.mjs';
49
49
  export { IconButton } from './chunk-3U4SSNWP.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retray-dev/ui-kit",
3
- "version": "9.3.1",
3
+ "version": "10.1.0",
4
4
  "description": "Personal UI Kit for React Native / Expo",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -25,6 +25,7 @@
25
25
  "files": [
26
26
  "dist",
27
27
  "src",
28
+ "scripts",
28
29
  "COMPONENTS.md",
29
30
  "CONSUMER.md",
30
31
  "EXAMPLES.md",
@@ -32,6 +33,7 @@
32
33
  "DESIGN.md"
33
34
  ],
34
35
  "scripts": {
36
+ "postinstall": "node scripts/copy-fonts.js",
35
37
  "build": "tsup",
36
38
  "dev": "tsup --watch",
37
39
  "typecheck": "tsc --noEmit",
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # build-apk.sh — Build a standalone release APK of the example app for phone install.
4
+ #
5
+ # Produces: build-output/retray-ui-kit-example.apk (signed with debug keystore,
6
+ # installable on any Android phone via "Install unknown apps").
7
+ #
8
+ # Requirements (already set up on this machine):
9
+ # - Android SDK at $ANDROID_HOME (default: ~/Library/Android/sdk)
10
+ # - Java 17 (JDK)
11
+ # - pnpm, node
12
+ #
13
+ # Usage:
14
+ # ./scripts/build-apk.sh # full build
15
+ # ./scripts/build-apk.sh --install # build, then adb install to connected phone
16
+ # ./scripts/build-apk.sh --clean # wipe android/ + gradle cache first (slow, fixes weird errors)
17
+ #
18
+ set -euo pipefail
19
+
20
+ # --- resolve repo root regardless of cwd ---
21
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
22
+ EXAMPLE="$ROOT/example"
23
+ OUT_DIR="$ROOT/build-output"
24
+ APK_NAME="retray-ui-kit-example.apk"
25
+
26
+ export ANDROID_HOME="${ANDROID_HOME:-$HOME/Library/Android/sdk}"
27
+
28
+ DO_INSTALL=false
29
+ DO_CLEAN=false
30
+ for arg in "$@"; do
31
+ case "$arg" in
32
+ --install) DO_INSTALL=true ;;
33
+ --clean) DO_CLEAN=true ;;
34
+ *) echo "Unknown arg: $arg"; exit 1 ;;
35
+ esac
36
+ done
37
+
38
+ echo "==> repo: $ROOT"
39
+ echo "==> sdk: $ANDROID_HOME"
40
+ [ -d "$ANDROID_HOME" ] || { echo "ERROR: Android SDK not found at $ANDROID_HOME"; exit 1; }
41
+
42
+ # --- 1. build the ui-kit (example depends on dist/) ---
43
+ echo "==> [1/4] Building @retray-dev/ui-kit ..."
44
+ ( cd "$ROOT" && pnpm build )
45
+
46
+ # --- 2. ensure deps + native android/ dir exist ---
47
+ echo "==> [2/4] Prebuild (generate android/ if missing) ..."
48
+ if $DO_CLEAN; then
49
+ echo " --clean: removing android/ and gradle build cache"
50
+ rm -rf "$EXAMPLE/android"
51
+ fi
52
+ # babel-preset-expo must be a top-level dep of example so Metro resolves it
53
+ # under pnpm hoisted layout. Declared in example/package.json devDependencies.
54
+ ( cd "$ROOT" && pnpm install --frozen-lockfile=false )
55
+ if [ ! -d "$EXAMPLE/android" ]; then
56
+ ( cd "$EXAMPLE" && CI=1 npx expo prebuild --platform android --no-install )
57
+ fi
58
+
59
+ # --- 3. assemble release APK ---
60
+ echo "==> [3/4] Gradle assembleRelease (first run ~7 min) ..."
61
+ ( cd "$EXAMPLE/android" && ANDROID_HOME="$ANDROID_HOME" ./gradlew assembleRelease )
62
+
63
+ # --- 4. copy to build-output/ ---
64
+ echo "==> [4/4] Copying APK ..."
65
+ SRC_APK="$EXAMPLE/android/app/build/outputs/apk/release/app-release.apk"
66
+ [ -f "$SRC_APK" ] || { echo "ERROR: APK not found at $SRC_APK"; exit 1; }
67
+ mkdir -p "$OUT_DIR"
68
+ cp "$SRC_APK" "$OUT_DIR/$APK_NAME"
69
+
70
+ echo ""
71
+ echo "✅ APK ready: $OUT_DIR/$APK_NAME ($(du -h "$OUT_DIR/$APK_NAME" | cut -f1))"
72
+
73
+ # --- optional install ---
74
+ if $DO_INSTALL; then
75
+ echo "==> adb install ..."
76
+ "$ANDROID_HOME/platform-tools/adb" install -r "$OUT_DIR/$APK_NAME"
77
+ echo "✅ Installed to connected device."
78
+ else
79
+ echo ""
80
+ echo "To put on your phone:"
81
+ echo " • USB: $ANDROID_HOME/platform-tools/adb install -r \"$OUT_DIR/$APK_NAME\""
82
+ echo " • or run again with --install"
83
+ echo " • or AirDrop/Drive the .apk and tap it (enable 'Install unknown apps')"
84
+ fi
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall script: copies Sohne font files to consumer project's assets/fonts/sohne/
4
+ *
5
+ * This runs automatically when a consumer installs @retray-dev/ui-kit.
6
+ * Fonts are copied from dist/assets/fonts/ to <consumer-root>/assets/fonts/sohne/
7
+ * so Metro can resolve require() calls in the consumer's App.tsx.
8
+ */
9
+ const fs = require('fs')
10
+ const path = require('path')
11
+
12
+ // npm sets INIT_CWD to consumer project root, pnpm sets PROJECT_CWD
13
+ const consumerRoot =
14
+ process.env.INIT_CWD ||
15
+ process.env.PROJECT_CWD ||
16
+ process.cwd()
17
+
18
+ // Don't run when installing inside the ui-kit repo itself
19
+ const pkgPath = path.join(consumerRoot, 'package.json')
20
+ if (!fs.existsSync(pkgPath)) {
21
+ process.exit(0)
22
+ }
23
+
24
+ let pkg
25
+ try {
26
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
27
+ } catch {
28
+ // Can't read package.json — skip silently
29
+ process.exit(0)
30
+ }
31
+
32
+ // Skip if we're in the ui-kit repo itself
33
+ if (pkg.name === '@retray-dev/ui-kit') {
34
+ process.exit(0)
35
+ }
36
+
37
+ // Source: dist/assets/fonts/ (relative to this script in scripts/)
38
+ const src = path.join(__dirname, '..', 'dist', 'assets', 'fonts')
39
+
40
+ // Destination: consumer's assets/fonts/sohne/
41
+ const dest = path.join(consumerRoot, 'assets', 'fonts', 'sohne')
42
+
43
+ // If source doesn't exist (maybe dist not built yet), skip gracefully
44
+ if (!fs.existsSync(src)) {
45
+ console.warn('[ui-kit] Font source not found:', src)
46
+ console.warn('[ui-kit] Run `pnpm build` in the ui-kit package first, or fonts will need manual setup.')
47
+ process.exit(0)
48
+ }
49
+
50
+ // Create destination directory
51
+ try {
52
+ fs.mkdirSync(dest, { recursive: true })
53
+ } catch (err) {
54
+ console.warn('[ui-kit] Could not create font directory:', dest)
55
+ console.warn('[ui-kit] Error:', err.message)
56
+ console.warn('[ui-kit] You may need to copy fonts manually. See FONTS.md for setup.')
57
+ process.exit(0)
58
+ }
59
+
60
+ // Copy .otf files (skip if already exists to avoid overwriting user modifications)
61
+ const files = fs.readdirSync(src).filter(f => f.endsWith('.otf'))
62
+ let copied = 0
63
+ let skipped = 0
64
+
65
+ for (const file of files) {
66
+ const srcFile = path.join(src, file)
67
+ const destFile = path.join(dest, file)
68
+
69
+ if (fs.existsSync(destFile)) {
70
+ skipped++
71
+ continue
72
+ }
73
+
74
+ try {
75
+ fs.copyFileSync(srcFile, destFile)
76
+ copied++
77
+ } catch (err) {
78
+ console.warn(`[ui-kit] Failed to copy ${file}:`, err.message)
79
+ }
80
+ }
81
+
82
+ if (copied > 0) {
83
+ console.log(`[ui-kit] Copied ${copied} Sohne font files → assets/fonts/sohne/`)
84
+ }
85
+ if (skipped > 0 && copied === 0) {
86
+ // All fonts already present — no message needed
87
+ }
88
+ if (copied > 0 || (skipped > 0 && process.env.VERBOSE)) {
89
+ console.log('[ui-kit] Add SohneFonts to your App.tsx — see CONSUMER.md for the boilerplate.')
90
+ }