@retray-dev/ui-kit 6.0.0 → 6.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 (33) hide show
  1. package/COMPONENTS.md +8 -7
  2. package/dist/index.d.mts +47 -23
  3. package/dist/index.d.ts +47 -23
  4. package/dist/index.js +702 -634
  5. package/dist/index.mjs +695 -627
  6. package/package.json +1 -1
  7. package/src/components/Accordion/Accordion.tsx +10 -12
  8. package/src/components/Button/Button.tsx +20 -18
  9. package/src/components/Card/Card.tsx +21 -33
  10. package/src/components/CategoryStrip/CategoryStrip.tsx +45 -38
  11. package/src/components/Checkbox/Checkbox.tsx +31 -50
  12. package/src/components/Chip/Chip.tsx +34 -71
  13. package/src/components/DetailRow/DetailRow.tsx +13 -8
  14. package/src/components/IconButton/IconButton.tsx +20 -18
  15. package/src/components/Input/Input.tsx +39 -22
  16. package/src/components/ListItem/ListItem.tsx +22 -34
  17. package/src/components/MediaCard/MediaCard.tsx +24 -24
  18. package/src/components/MenuItem/MenuItem.tsx +52 -39
  19. package/src/components/MonthPicker/MonthPicker.tsx +12 -2
  20. package/src/components/Pressable/Pressable.tsx +27 -46
  21. package/src/components/Progress/Progress.tsx +21 -12
  22. package/src/components/RadioGroup/RadioGroup.tsx +52 -26
  23. package/src/components/Select/Select.tsx +17 -15
  24. package/src/components/Sheet/Sheet.tsx +4 -1
  25. package/src/components/Skeleton/Skeleton.tsx +24 -13
  26. package/src/components/Slider/Slider.tsx +11 -1
  27. package/src/components/Switch/Switch.tsx +44 -49
  28. package/src/components/Tabs/Tabs.tsx +39 -31
  29. package/src/components/Textarea/Textarea.tsx +29 -12
  30. package/src/components/Toggle/Toggle.tsx +39 -45
  31. package/src/utils/animations.ts +58 -0
  32. package/src/utils/useColorTransition.ts +40 -0
  33. package/src/utils/usePressScale.ts +73 -0
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var React26 = require('react');
4
4
  var reactNative = require('react-native');
5
+ var Animated9 = require('react-native-reanimated');
5
6
  var reactNativeSizeMatters = require('react-native-size-matters');
6
7
  var AntDesign = require('@expo/vector-icons/AntDesign');
7
8
  var Entypo = require('@expo/vector-icons/Entypo');
@@ -11,7 +12,6 @@ var MaterialIcons = require('@expo/vector-icons/MaterialIcons');
11
12
  var Ionicons = require('@expo/vector-icons/Ionicons');
12
13
  var vectorIcons = require('@expo/vector-icons');
13
14
  var expoLinearGradient = require('expo-linear-gradient');
14
- var Animated12 = require('react-native-reanimated');
15
15
  var RNSlider = require('@react-native-community/slider');
16
16
  var bottomSheet = require('@gorhom/bottom-sheet');
17
17
  var reactNativeSafeAreaContext = require('react-native-safe-area-context');
@@ -21,13 +21,13 @@ var sonnerNative = require('sonner-native');
21
21
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
22
22
 
23
23
  var React26__default = /*#__PURE__*/_interopDefault(React26);
24
+ var Animated9__default = /*#__PURE__*/_interopDefault(Animated9);
24
25
  var AntDesign__default = /*#__PURE__*/_interopDefault(AntDesign);
25
26
  var Entypo__default = /*#__PURE__*/_interopDefault(Entypo);
26
27
  var Feather__default = /*#__PURE__*/_interopDefault(Feather);
27
28
  var FontAwesome5__default = /*#__PURE__*/_interopDefault(FontAwesome5);
28
29
  var MaterialIcons__default = /*#__PURE__*/_interopDefault(MaterialIcons);
29
30
  var Ionicons__default = /*#__PURE__*/_interopDefault(Ionicons);
30
- var Animated12__default = /*#__PURE__*/_interopDefault(Animated12);
31
31
  var RNSlider__default = /*#__PURE__*/_interopDefault(RNSlider);
32
32
 
33
33
  // src/theme/ThemeProvider.tsx
@@ -419,9 +419,87 @@ var TYPOGRAPHY = {
419
419
  letterSpacing: 0
420
420
  }
421
421
  };
422
+ var SPRINGS = {
423
+ /** Tight, premium press feel — Buttons, Toggle, Tabs triggers. */
424
+ pressIn: { stiffness: 600, damping: 35, mass: 0.8 },
425
+ pressOut: { stiffness: 280, damping: 22, mass: 0.8 },
426
+ /** Slightly softer for larger surfaces — Card, ListItem, MenuItem. */
427
+ surfacePressIn: { stiffness: 380, damping: 30, mass: 0.95 },
428
+ surfacePressOut: { stiffness: 220, damping: 20, mass: 0.95 },
429
+ /** Settled transitions for moving indicators — Tabs pill, Switch thumb. */
430
+ glide: { stiffness: 380, damping: 38, mass: 1 },
431
+ /** Elastic indicator — Switch thumb, RadioGroup dot. */
432
+ elastic: { stiffness: 320, damping: 22, mass: 0.7 }
433
+ };
434
+ var TIMINGS = {
435
+ /** Color/opacity transitions on toggles, checkboxes, switches. */
436
+ state: { duration: 160 },
437
+ /** Focus ring on inputs. */
438
+ focusIn: { duration: 140 },
439
+ focusOut: { duration: 100 },
440
+ /** Accordion / collapsible content. */
441
+ expand: { duration: 240 },
442
+ collapse: { duration: 200 },
443
+ /** Skeleton shimmer cycle (full pass). */
444
+ shimmer: { duration: 1400 }
445
+ };
446
+ var EASINGS = {
447
+ /** Material-style ease-out — natural deceleration for state changes. */
448
+ standard: Animated9.Easing.bezier(0.2, 0, 0, 1),
449
+ /** Strong ease-out for expanding surfaces (Accordion open). */
450
+ expand: Animated9.Easing.bezier(0.23, 1, 0.32, 1),
451
+ /** Quick ease-in for collapsing. */
452
+ collapse: Animated9.Easing.in(Animated9.Easing.ease)
453
+ };
454
+ var PRESS_SCALE = {
455
+ button: 0.95,
456
+ card: 0.98,
457
+ row: 0.97,
458
+ chip: 0.94
459
+ };
460
+ function useHover() {
461
+ const [hovered, setHovered] = React26.useState(false);
462
+ const onMouseEnter = React26.useCallback(() => setHovered(true), []);
463
+ const onMouseLeave = React26.useCallback(() => setHovered(false), []);
464
+ if (reactNative.Platform.OS !== "web") {
465
+ return { hovered: false, hoverHandlers: {} };
466
+ }
467
+ return { hovered, hoverHandlers: { onMouseEnter, onMouseLeave } };
468
+ }
469
+
470
+ // src/utils/usePressScale.ts
471
+ function usePressScale({
472
+ pressScale = PRESS_SCALE.button,
473
+ hoverScale = 1.02,
474
+ pressInSpring = SPRINGS.pressIn,
475
+ pressOutSpring = SPRINGS.pressOut,
476
+ disabled = false
477
+ } = {}) {
478
+ const scale2 = Animated9.useSharedValue(1);
479
+ const { hovered, hoverHandlers } = useHover();
480
+ const onPressIn = React26.useCallback(() => {
481
+ if (disabled) return;
482
+ scale2.value = Animated9.withSpring(pressScale, pressInSpring);
483
+ }, [disabled, pressScale, pressInSpring, scale2]);
484
+ const onPressOut = React26.useCallback(() => {
485
+ if (disabled) return;
486
+ scale2.value = Animated9.withSpring(1, pressOutSpring);
487
+ }, [disabled, pressOutSpring, scale2]);
488
+ const hoverActive = reactNative.Platform.OS === "web" && hovered && hoverScale !== 1 && !disabled;
489
+ const animatedStyle = Animated9.useAnimatedStyle(() => ({
490
+ transform: [
491
+ { scale: scale2.value * (hoverActive ? hoverScale : 1) }
492
+ ]
493
+ }));
494
+ return {
495
+ animatedStyle,
496
+ onPressIn,
497
+ onPressOut,
498
+ hoverHandlers
499
+ };
500
+ }
422
501
 
423
502
  // src/components/Button/Button.tsx
424
- var nativeDriver = reactNative.Platform.OS !== "web";
425
503
  var containerSizeStyles = {
426
504
  sm: { paddingHorizontal: s(16), paddingVertical: vs(10), minHeight: 40 },
427
505
  md: { paddingHorizontal: s(24), paddingVertical: vs(14), minHeight: 48 },
@@ -446,18 +524,16 @@ function Button({
446
524
  disabled,
447
525
  style,
448
526
  onPress,
527
+ accessibilityLabel,
528
+ accessibilityHint,
449
529
  ...props
450
530
  }) {
451
531
  const { colors } = useTheme();
452
532
  const isDisabled = disabled || loading;
453
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
454
- const handlePressIn = () => {
455
- if (isDisabled) return;
456
- reactNative.Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver, stiffness: 600, damping: 35, mass: 0.8 }).start();
457
- };
458
- const handlePressOut = () => {
459
- reactNative.Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver, stiffness: 280, damping: 22, mass: 0.8 }).start();
460
- };
533
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
534
+ pressScale: PRESS_SCALE.button,
535
+ disabled: isDisabled
536
+ });
461
537
  const handlePress = (e) => {
462
538
  impactMedium();
463
539
  onPress?.(e);
@@ -480,43 +556,54 @@ function Button({
480
556
  const styleArray = Array.isArray(style) ? style : style ? [style] : [];
481
557
  const flatStyle = reactNative.StyleSheet.flatten(styleArray);
482
558
  const { flex, ...restStyle } = flatStyle || {};
483
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [fullWidth && styles.fullWidth, flex !== void 0 && { flex }, { transform: [{ scale: scale2 }] }] }, /* @__PURE__ */ React26__default.default.createElement(
484
- reactNative.TouchableOpacity,
559
+ return /* @__PURE__ */ React26__default.default.createElement(
560
+ Animated9__default.default.View,
485
561
  {
486
- style: [
487
- styles.base,
488
- containerVariantStyle,
489
- containerSizeStyles[size],
490
- fullWidth && styles.fullWidth,
491
- isDisabled && styles.disabled,
492
- restStyle
493
- ],
494
- disabled: isDisabled,
495
- activeOpacity: 1,
496
- touchSoundDisabled: true,
497
- onPress: handlePress,
498
- onPressIn: handlePressIn,
499
- onPressOut: handlePressOut,
500
- ...props
562
+ style: [fullWidth && styles.fullWidth, flex !== void 0 && { flex }, animatedStyle],
563
+ ...hoverHandlers
501
564
  },
502
- loading ? /* @__PURE__ */ React26__default.default.createElement(React26__default.default.Fragment, null, /* @__PURE__ */ React26__default.default.createElement(reactNative.ActivityIndicator, { size: "small", color: spinnerColor, style: { marginRight: s(6) } }), /* @__PURE__ */ React26__default.default.createElement(
503
- reactNative.Text,
504
- {
505
- style: [styles.label, labelVariantStyle, labelSizeStyles[size], styles.labelLoading],
506
- allowFontScaling: true,
507
- numberOfLines: 1
508
- },
509
- label
510
- )) : /* @__PURE__ */ React26__default.default.createElement(React26__default.default.Fragment, null, effectiveIcon && iconPosition === "left" && /* @__PURE__ */ React26__default.default.createElement(React26__default.default.Fragment, null, effectiveIcon), /* @__PURE__ */ React26__default.default.createElement(
511
- reactNative.Text,
565
+ /* @__PURE__ */ React26__default.default.createElement(
566
+ reactNative.TouchableOpacity,
512
567
  {
513
- style: [styles.label, labelVariantStyle, labelSizeStyles[size], effectiveIcon ? styles.labelWithIcon : void 0],
514
- allowFontScaling: true,
515
- numberOfLines: 1
568
+ style: [
569
+ styles.base,
570
+ containerVariantStyle,
571
+ containerSizeStyles[size],
572
+ fullWidth && styles.fullWidth,
573
+ isDisabled && styles.disabled,
574
+ restStyle
575
+ ],
576
+ disabled: isDisabled,
577
+ activeOpacity: 1,
578
+ touchSoundDisabled: true,
579
+ onPress: handlePress,
580
+ onPressIn,
581
+ onPressOut,
582
+ accessibilityRole: "button",
583
+ accessibilityLabel: accessibilityLabel ?? label,
584
+ accessibilityHint,
585
+ accessibilityState: { disabled: isDisabled, busy: loading },
586
+ ...props
516
587
  },
517
- label
518
- ), effectiveIcon && iconPosition === "right" && /* @__PURE__ */ React26__default.default.createElement(React26__default.default.Fragment, null, effectiveIcon))
519
- ));
588
+ loading ? /* @__PURE__ */ React26__default.default.createElement(React26__default.default.Fragment, null, /* @__PURE__ */ React26__default.default.createElement(reactNative.ActivityIndicator, { size: "small", color: spinnerColor, style: { marginRight: s(6) } }), /* @__PURE__ */ React26__default.default.createElement(
589
+ reactNative.Text,
590
+ {
591
+ style: [styles.label, labelVariantStyle, labelSizeStyles[size], styles.labelLoading],
592
+ allowFontScaling: true,
593
+ numberOfLines: 1
594
+ },
595
+ label
596
+ )) : /* @__PURE__ */ React26__default.default.createElement(React26__default.default.Fragment, null, effectiveIcon && iconPosition === "left" && /* @__PURE__ */ React26__default.default.createElement(React26__default.default.Fragment, null, effectiveIcon), /* @__PURE__ */ React26__default.default.createElement(
597
+ reactNative.Text,
598
+ {
599
+ style: [styles.label, labelVariantStyle, labelSizeStyles[size], effectiveIcon ? styles.labelWithIcon : void 0],
600
+ allowFontScaling: true,
601
+ numberOfLines: 1
602
+ },
603
+ label
604
+ ), effectiveIcon && iconPosition === "right" && /* @__PURE__ */ React26__default.default.createElement(React26__default.default.Fragment, null, effectiveIcon))
605
+ )
606
+ );
520
607
  }
521
608
  var styles = reactNative.StyleSheet.create({
522
609
  base: {
@@ -576,7 +663,6 @@ var styles2 = reactNative.StyleSheet.create({
576
663
  flexDirection: "column"
577
664
  }
578
665
  });
579
- var nativeDriver2 = reactNative.Platform.OS !== "web";
580
666
  var sizeMap = {
581
667
  sm: { container: s(32), icon: 16 },
582
668
  md: { container: s(44), icon: 20 },
@@ -593,18 +679,16 @@ function IconButton({
593
679
  disabled,
594
680
  style,
595
681
  onPress,
682
+ accessibilityLabel,
683
+ accessibilityHint,
596
684
  ...props
597
685
  }) {
598
686
  const { colors } = useTheme();
599
687
  const isDisabled = disabled || loading;
600
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
601
- const handlePressIn = () => {
602
- if (isDisabled) return;
603
- reactNative.Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver2, speed: 40, bounciness: 0 }).start();
604
- };
605
- const handlePressOut = () => {
606
- reactNative.Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver2, speed: 40, bounciness: 4 }).start();
607
- };
688
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
689
+ pressScale: PRESS_SCALE.button,
690
+ disabled: isDisabled
691
+ });
608
692
  const handlePress = (e) => {
609
693
  impactLight();
610
694
  onPress?.(e);
@@ -629,30 +713,42 @@ function IconButton({
629
713
  const showBadge = badge !== void 0 && badge !== false && badge !== 0;
630
714
  const badgeCount = typeof badge === "number" ? Math.min(badge, 99) : null;
631
715
  const showCount = typeof badge === "number" && badge > 0;
632
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [styles3.wrapper, { transform: [{ scale: scale2 }] }] }, /* @__PURE__ */ React26__default.default.createElement(
633
- reactNative.TouchableOpacity,
716
+ return /* @__PURE__ */ React26__default.default.createElement(
717
+ Animated9__default.default.View,
634
718
  {
635
- style: [
636
- styles3.base,
637
- containerVariantStyle,
638
- { width: containerSize, height: containerSize },
639
- isDisabled && styles3.disabled,
640
- style
641
- ],
642
- disabled: isDisabled,
643
- activeOpacity: 1,
644
- touchSoundDisabled: true,
645
- onPress: handlePress,
646
- onPressIn: handlePressIn,
647
- onPressOut: handlePressOut,
648
- ...props
719
+ style: [styles3.wrapper, animatedStyle],
720
+ ...hoverHandlers
649
721
  },
650
- loading ? /* @__PURE__ */ React26__default.default.createElement(reactNative.ActivityIndicator, { size: "small", color: spinnerColor }) : resolvedIcon
651
- ), showBadge && /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [
652
- styles3.badge,
653
- { backgroundColor: colors.primary },
654
- showCount ? styles3.badgeCount : styles3.badgeDot
655
- ] }, showCount && /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles3.badgeText, { color: colors.primaryForeground }] }, badgeCount)));
722
+ /* @__PURE__ */ React26__default.default.createElement(
723
+ reactNative.TouchableOpacity,
724
+ {
725
+ style: [
726
+ styles3.base,
727
+ containerVariantStyle,
728
+ { width: containerSize, height: containerSize },
729
+ isDisabled && styles3.disabled,
730
+ style
731
+ ],
732
+ disabled: isDisabled,
733
+ activeOpacity: 1,
734
+ touchSoundDisabled: true,
735
+ onPress: handlePress,
736
+ onPressIn,
737
+ onPressOut,
738
+ accessibilityRole: "button",
739
+ accessibilityLabel: accessibilityLabel ?? iconName ?? "icon button",
740
+ accessibilityHint,
741
+ accessibilityState: { disabled: isDisabled, busy: loading },
742
+ ...props
743
+ },
744
+ loading ? /* @__PURE__ */ React26__default.default.createElement(reactNative.ActivityIndicator, { size: "small", color: spinnerColor }) : resolvedIcon
745
+ ),
746
+ showBadge && /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [
747
+ styles3.badge,
748
+ { backgroundColor: colors.primary },
749
+ showCount ? styles3.badgeCount : styles3.badgeDot
750
+ ] }, showCount && /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles3.badgeText, { color: colors.primaryForeground }] }, badgeCount))
751
+ );
656
752
  }
657
753
  var styles3 = reactNative.StyleSheet.create({
658
754
  wrapper: {
@@ -741,29 +837,49 @@ function Text3({ variant = "body-md", color, style, children, ...props }) {
741
837
  children
742
838
  );
743
839
  }
840
+ function useColorTransition(active, options = {}) {
841
+ const { duration = TIMINGS.state.duration } = options;
842
+ const progress = Animated9.useSharedValue(active ? 1 : 0);
843
+ React26.useEffect(() => {
844
+ progress.value = Animated9.withTiming(active ? 1 : 0, { duration, easing: EASINGS.standard });
845
+ }, [active, duration, progress]);
846
+ return progress;
847
+ }
848
+
849
+ // src/components/Input/Input.tsx
744
850
  var webInputResetStyle = reactNative.Platform.OS === "web" ? { outlineStyle: "none", outlineWidth: 0, outlineColor: "transparent", boxShadow: "none" } : {};
745
- function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suffixStyle, prefixIcon, suffixIcon, prefixIconColor, suffixIconColor, type = "text", containerStyle, inputWrapperStyle, style, onFocus, onBlur, secureTextEntry, editable, ...props }) {
851
+ 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 }) {
746
852
  const { colors } = useTheme();
747
853
  const [focused, setFocused] = React26.useState(false);
748
854
  const [showPassword, setShowPassword] = React26.useState(false);
749
- const focusAnim = React26.useRef(new reactNative.Animated.Value(0)).current;
855
+ const focusProgress = useColorTransition(focused, {
856
+ duration: focused ? TIMINGS.focusIn.duration : TIMINGS.focusOut.duration
857
+ });
750
858
  const isDisabled = disabled || editable === false;
751
859
  const isPassword = type === "password";
752
860
  const effectiveSecure = isPassword ? !showPassword : secureTextEntry;
753
861
  const effectivePrefix = prefixIcon ? renderIcon(prefixIcon, 20, prefixIconColor ?? colors.foregroundMuted) : prefix;
754
- const effectiveSuffix = isPassword && !suffix && !suffixIcon ? /* @__PURE__ */ React26__default.default.createElement(reactNative.TouchableOpacity, { onPress: () => setShowPassword(!showPassword), style: styles4.passwordToggle, activeOpacity: 0.6 }, /* @__PURE__ */ React26__default.default.createElement(vectorIcons.AntDesign, { name: showPassword ? "eye" : "eye-invisible", size: 20, color: colors.foregroundMuted })) : suffixIcon ? renderIcon(suffixIcon, 20, suffixIconColor ?? colors.foregroundMuted) : suffix;
862
+ const effectiveSuffix = isPassword && !suffix && !suffixIcon ? /* @__PURE__ */ React26__default.default.createElement(
863
+ reactNative.TouchableOpacity,
864
+ {
865
+ onPress: () => setShowPassword(!showPassword),
866
+ style: styles4.passwordToggle,
867
+ activeOpacity: 0.6,
868
+ accessibilityRole: "button",
869
+ accessibilityLabel: showPassword ? "Hide password" : "Show password"
870
+ },
871
+ /* @__PURE__ */ React26__default.default.createElement(vectorIcons.AntDesign, { name: showPassword ? "eye" : "eye-invisible", size: 20, color: colors.foregroundMuted })
872
+ ) : suffixIcon ? renderIcon(suffixIcon, 20, suffixIconColor ?? colors.foregroundMuted) : suffix;
873
+ const borderColorStyle = Animated9.useAnimatedStyle(() => ({
874
+ borderColor: error ? colors.destructive : Animated9.interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary])
875
+ }));
755
876
  return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles4.container, isDisabled && styles4.containerDisabled, containerStyle] }, label ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles4.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, /* @__PURE__ */ React26__default.default.createElement(
756
- reactNative.Animated.View,
877
+ Animated9__default.default.View,
757
878
  {
758
879
  style: [
759
880
  styles4.inputWrapper,
760
- {
761
- borderColor: error ? colors.destructive : focusAnim.interpolate({
762
- inputRange: [0, 1],
763
- outputRange: [colors.border, colors.primary]
764
- }),
765
- backgroundColor: isDisabled ? colors.surface : colors.background
766
- },
881
+ { backgroundColor: isDisabled ? colors.surface : colors.background },
882
+ borderColorStyle,
767
883
  inputWrapperStyle
768
884
  ]
769
885
  },
@@ -773,31 +889,36 @@ function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suff
773
889
  {
774
890
  style: [
775
891
  styles4.input,
776
- {
777
- color: colors.foreground
778
- },
892
+ { color: colors.foreground },
779
893
  webInputResetStyle,
780
894
  style
781
895
  ],
782
896
  onFocus: (e) => {
783
897
  setFocused(true);
784
- reactNative.Animated.timing(focusAnim, { toValue: 1, duration: 120, easing: reactNative.Easing.out(reactNative.Easing.ease), useNativeDriver: false }).start();
785
898
  onFocus?.(e);
786
899
  },
787
900
  onBlur: (e) => {
788
901
  setFocused(false);
789
- reactNative.Animated.timing(focusAnim, { toValue: 0, duration: 80, easing: reactNative.Easing.out(reactNative.Easing.ease), useNativeDriver: false }).start();
790
902
  onBlur?.(e);
791
903
  },
792
904
  placeholderTextColor: colors.foregroundMuted,
793
905
  allowFontScaling: true,
794
906
  secureTextEntry: effectiveSecure,
795
907
  editable: isDisabled ? false : editable,
908
+ accessibilityLabel: accessibilityLabel ?? label,
796
909
  ...props
797
910
  }
798
911
  ),
799
912
  effectiveSuffix ? typeof effectiveSuffix === "string" ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles4.suffixText, { color: colors.foregroundMuted }, suffixStyle], allowFontScaling: true }, effectiveSuffix) : /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles4.suffixContainer }, effectiveSuffix) : null
800
- ), error ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles4.helperText, { color: colors.destructive }], allowFontScaling: true }, error) : null, !error && hint ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles4.helperText, { color: colors.foregroundMuted }], allowFontScaling: true }, hint) : null);
913
+ ), error ? /* @__PURE__ */ React26__default.default.createElement(
914
+ reactNative.Text,
915
+ {
916
+ style: [styles4.helperText, { color: colors.destructive }],
917
+ allowFontScaling: true,
918
+ accessibilityLiveRegion: "polite"
919
+ },
920
+ error
921
+ ) : null, !error && hint ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles4.helperText, { color: colors.foregroundMuted }], allowFontScaling: true }, hint) : null);
801
922
  }
802
923
  var styles4 = reactNative.StyleSheet.create({
803
924
  container: {
@@ -809,7 +930,6 @@ var styles4 = reactNative.StyleSheet.create({
809
930
  label: {
810
931
  fontFamily: "Poppins-Medium",
811
932
  fontSize: ms(14)
812
- // caption size for input labels
813
933
  },
814
934
  inputWrapper: {
815
935
  flexDirection: "row",
@@ -906,30 +1026,14 @@ var styles5 = reactNative.StyleSheet.create({
906
1026
  fontFamily: "Poppins-Medium"
907
1027
  }
908
1028
  });
909
- var nativeDriver3 = reactNative.Platform.OS !== "web";
910
- function Card({ children, variant = "elevated", onPress, style }) {
1029
+ function Card({ children, variant = "elevated", onPress, style, accessibilityLabel }) {
911
1030
  const { colors } = useTheme();
912
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
913
- const handlePressIn = () => {
914
- if (!onPress) return;
915
- reactNative.Animated.spring(scale2, {
916
- toValue: 0.98,
917
- useNativeDriver: nativeDriver3,
918
- stiffness: 400,
919
- damping: 30,
920
- mass: 1
921
- }).start();
922
- };
923
- const handlePressOut = () => {
924
- if (!onPress) return;
925
- reactNative.Animated.spring(scale2, {
926
- toValue: 1,
927
- useNativeDriver: nativeDriver3,
928
- stiffness: 250,
929
- damping: 24,
930
- mass: 1
931
- }).start();
932
- };
1031
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
1032
+ pressScale: PRESS_SCALE.card,
1033
+ pressInSpring: SPRINGS.surfacePressIn,
1034
+ pressOutSpring: SPRINGS.surfacePressOut,
1035
+ disabled: !onPress
1036
+ });
933
1037
  const handlePress = () => {
934
1038
  if (!onPress) return;
935
1039
  impactLight();
@@ -960,14 +1064,16 @@ function Card({ children, variant = "elevated", onPress, style }) {
960
1064
  }[variant];
961
1065
  const cardContent = /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles6.card, variantStyle, style] }, children);
962
1066
  if (onPress) {
963
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26__default.default.createElement(
1067
+ return /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: animatedStyle, ...hoverHandlers }, /* @__PURE__ */ React26__default.default.createElement(
964
1068
  reactNative.TouchableOpacity,
965
1069
  {
966
1070
  onPress: handlePress,
967
- onPressIn: handlePressIn,
968
- onPressOut: handlePressOut,
1071
+ onPressIn,
1072
+ onPressOut,
969
1073
  activeOpacity: 1,
970
- touchSoundDisabled: true
1074
+ touchSoundDisabled: true,
1075
+ accessibilityRole: "button",
1076
+ accessibilityLabel
971
1077
  },
972
1078
  cardContent
973
1079
  ));
@@ -994,7 +1100,6 @@ function CardFooter({ children, style }) {
994
1100
  var styles6 = reactNative.StyleSheet.create({
995
1101
  card: {
996
1102
  borderRadius: RADIUS.md,
997
- // 14px — Airbnb property card spec
998
1103
  borderWidth: 1
999
1104
  },
1000
1105
  header: {
@@ -1092,20 +1197,19 @@ function Skeleton({
1092
1197
  style
1093
1198
  }) {
1094
1199
  const { colors, colorScheme } = useTheme();
1095
- const shimmerAnim = React26.useRef(new reactNative.Animated.Value(0)).current;
1200
+ const shimmer = Animated9.useSharedValue(0);
1096
1201
  const [containerWidth, setContainerWidth] = React26.useState(300);
1097
1202
  const shimmerHighlight = colorScheme === "dark" ? "rgba(255,255,255,0.08)" : "rgba(255,255,255,0.7)";
1098
1203
  React26.useEffect(() => {
1099
- const animation = reactNative.Animated.loop(
1100
- reactNative.Animated.timing(shimmerAnim, { toValue: 1, duration: 1200, useNativeDriver: true })
1204
+ shimmer.value = Animated9.withRepeat(
1205
+ Animated9.withTiming(1, { duration: TIMINGS.shimmer.duration, easing: Animated9.Easing.linear }),
1206
+ -1,
1207
+ false
1101
1208
  );
1102
- animation.start();
1103
- return () => animation.stop();
1104
- }, [shimmerAnim]);
1105
- const translateX = shimmerAnim.interpolate({
1106
- inputRange: [0, 1],
1107
- outputRange: [-containerWidth, containerWidth]
1108
- });
1209
+ }, [shimmer]);
1210
+ const shimmerStyle = Animated9.useAnimatedStyle(() => ({
1211
+ transform: [{ translateX: -containerWidth + shimmer.value * (containerWidth * 2) }]
1212
+ }));
1109
1213
  const resolvedWidth = preset === "circle" ? s(diameter) : preset === "text" ? "60%" : width;
1110
1214
  const resolvedHeight = preset === "circle" ? s(diameter) : preset === "text" ? 14 : height;
1111
1215
  const resolvedRadius = preset === "circle" ? 9999 : preset === "text" ? 4 : borderRadius;
@@ -1117,9 +1221,12 @@ function Skeleton({
1117
1221
  { width: resolvedWidth, height: resolvedHeight, borderRadius: resolvedRadius, backgroundColor: colors.surface },
1118
1222
  style
1119
1223
  ],
1120
- onLayout: (e) => setContainerWidth(e.nativeEvent.layout.width)
1224
+ onLayout: (e) => setContainerWidth(e.nativeEvent.layout.width),
1225
+ accessibilityRole: "progressbar",
1226
+ accessibilityLabel: "Loading",
1227
+ accessibilityState: { busy: true }
1121
1228
  },
1122
- /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [reactNative.StyleSheet.absoluteFill, { transform: [{ translateX }] }] }, /* @__PURE__ */ React26__default.default.createElement(
1229
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [reactNative.StyleSheet.absoluteFill, shimmerStyle] }, /* @__PURE__ */ React26__default.default.createElement(
1123
1230
  expoLinearGradient.LinearGradient,
1124
1231
  {
1125
1232
  colors: ["transparent", shimmerHighlight, "transparent"],
@@ -1265,31 +1372,32 @@ var styles11 = reactNative.StyleSheet.create({
1265
1372
  lineHeight: ms(17)
1266
1373
  }
1267
1374
  });
1268
- function Progress({ value = 0, max = 100, variant = "default", style }) {
1375
+ function Progress({ value = 0, max = 100, variant = "default", style, accessibilityLabel }) {
1269
1376
  const { colors } = useTheme();
1270
1377
  const percent = Math.min(Math.max(value / max * 100, 0), 100);
1271
1378
  const [trackWidth, setTrackWidth] = React26.useState(0);
1272
- const animatedWidth = React26.useRef(new reactNative.Animated.Value(0)).current;
1379
+ const animatedWidth = Animated9.useSharedValue(0);
1273
1380
  React26.useEffect(() => {
1274
1381
  if (trackWidth === 0) return;
1275
- reactNative.Animated.spring(animatedWidth, {
1276
- toValue: percent / 100 * trackWidth,
1277
- useNativeDriver: false,
1278
- speed: 20,
1279
- bounciness: 0
1280
- }).start();
1281
- }, [percent, trackWidth]);
1382
+ animatedWidth.value = Animated9.withSpring(percent / 100 * trackWidth, SPRINGS.glide);
1383
+ }, [percent, trackWidth, animatedWidth]);
1384
+ const indicatorAnimatedStyle = Animated9.useAnimatedStyle(() => ({
1385
+ width: animatedWidth.value
1386
+ }));
1282
1387
  const indicatorColor = variant === "success" ? colors.success : variant === "warning" ? colors.warning : variant === "destructive" ? colors.destructive : colors.primary;
1283
1388
  return /* @__PURE__ */ React26__default.default.createElement(
1284
1389
  reactNative.View,
1285
1390
  {
1286
1391
  style: [styles12.track, { backgroundColor: colors.surface }, style],
1287
- onLayout: (e) => setTrackWidth(e.nativeEvent.layout.width)
1392
+ onLayout: (e) => setTrackWidth(e.nativeEvent.layout.width),
1393
+ accessibilityRole: "progressbar",
1394
+ accessibilityLabel,
1395
+ accessibilityValue: { min: 0, max: 100, now: Math.round(percent) }
1288
1396
  },
1289
1397
  /* @__PURE__ */ React26__default.default.createElement(
1290
- reactNative.Animated.View,
1398
+ Animated9__default.default.View,
1291
1399
  {
1292
- style: [styles12.indicator, { width: animatedWidth, backgroundColor: indicatorColor }]
1400
+ style: [styles12.indicator, { backgroundColor: indicatorColor }, indicatorAnimatedStyle]
1293
1401
  }
1294
1402
  )
1295
1403
  );
@@ -1406,20 +1514,25 @@ function Textarea({
1406
1514
  style,
1407
1515
  onFocus,
1408
1516
  onBlur,
1517
+ accessibilityLabel,
1409
1518
  ...props
1410
1519
  }) {
1411
1520
  const { colors } = useTheme();
1412
1521
  const [focused, setFocused] = React26.useState(false);
1522
+ const focusProgress = useColorTransition(focused, {
1523
+ duration: focused ? TIMINGS.focusIn.duration : TIMINGS.focusOut.duration
1524
+ });
1413
1525
  const resolvedPrefixIcon = prefixIcon ? renderIcon(prefixIcon, ms(16), prefixIconColor ?? colors.foregroundMuted) : prefixIconNode;
1526
+ const borderColorStyle = Animated9.useAnimatedStyle(() => ({
1527
+ borderColor: error ? colors.destructive : Animated9.interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary])
1528
+ }));
1414
1529
  return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles14.container, containerStyle] }, label ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles14.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, /* @__PURE__ */ React26__default.default.createElement(
1415
- reactNative.View,
1530
+ Animated9__default.default.View,
1416
1531
  {
1417
1532
  style: [
1418
1533
  styles14.inputWrapper,
1419
- {
1420
- borderColor: error ? colors.destructive : focused ? colors.ring ?? colors.primary : colors.border,
1421
- backgroundColor: colors.background
1422
- }
1534
+ { backgroundColor: colors.background },
1535
+ borderColorStyle
1423
1536
  ]
1424
1537
  },
1425
1538
  resolvedPrefixIcon ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles14.prefixIcon }, resolvedPrefixIcon) : null,
@@ -1448,10 +1561,19 @@ function Textarea({
1448
1561
  },
1449
1562
  placeholderTextColor: colors.foregroundMuted,
1450
1563
  allowFontScaling: true,
1564
+ accessibilityLabel: accessibilityLabel ?? label,
1451
1565
  ...props
1452
1566
  }
1453
1567
  )
1454
- ), error ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles14.helperText, { color: colors.destructive }], allowFontScaling: true }, error) : null, !error && hint ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles14.helperText, { color: colors.foregroundMuted }], allowFontScaling: true }, hint) : null);
1568
+ ), error ? /* @__PURE__ */ React26__default.default.createElement(
1569
+ reactNative.Text,
1570
+ {
1571
+ style: [styles14.helperText, { color: colors.destructive }],
1572
+ allowFontScaling: true,
1573
+ accessibilityLiveRegion: "polite"
1574
+ },
1575
+ error
1576
+ ) : null, !error && hint ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles14.helperText, { color: colors.foregroundMuted }], allowFontScaling: true }, hint) : null);
1455
1577
  }
1456
1578
  var styles14 = reactNative.StyleSheet.create({
1457
1579
  container: {
@@ -1464,7 +1586,7 @@ var styles14 = reactNative.StyleSheet.create({
1464
1586
  marginBottom: vs(2)
1465
1587
  },
1466
1588
  inputWrapper: {
1467
- borderWidth: 1,
1589
+ borderWidth: 2,
1468
1590
  borderRadius: 8,
1469
1591
  paddingHorizontal: s(14),
1470
1592
  paddingVertical: vs(11),
@@ -1489,47 +1611,27 @@ var styles14 = reactNative.StyleSheet.create({
1489
1611
  marginTop: vs(4)
1490
1612
  }
1491
1613
  });
1492
- var nativeDriver4 = reactNative.Platform.OS !== "web";
1493
1614
  function Checkbox({
1494
1615
  checked = false,
1495
1616
  onCheckedChange,
1496
1617
  label,
1497
1618
  disabled,
1498
- style
1619
+ style,
1620
+ accessibilityLabel
1499
1621
  }) {
1500
1622
  const { colors } = useTheme();
1501
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
1502
- const bgOpacity = React26.useRef(new reactNative.Animated.Value(checked ? 1 : 0)).current;
1503
- const checkOpacity = React26.useRef(new reactNative.Animated.Value(checked ? 1 : 0)).current;
1504
- React26.useEffect(() => {
1505
- reactNative.Animated.parallel([
1506
- reactNative.Animated.timing(bgOpacity, {
1507
- toValue: checked ? 1 : 0,
1508
- duration: 150,
1509
- useNativeDriver: false
1510
- }),
1511
- reactNative.Animated.timing(checkOpacity, {
1512
- toValue: checked ? 1 : 0,
1513
- duration: 120,
1514
- useNativeDriver: false
1515
- })
1516
- ]).start();
1517
- }, [checked, bgOpacity, checkOpacity]);
1518
- const borderColor = bgOpacity.interpolate({
1519
- inputRange: [0, 1],
1520
- outputRange: [colors.border, colors.primary]
1521
- });
1522
- const backgroundColor = bgOpacity.interpolate({
1523
- inputRange: [0, 1],
1524
- outputRange: ["transparent", colors.primary]
1623
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut } = usePressScale({
1624
+ pressScale: PRESS_SCALE.button,
1625
+ disabled
1525
1626
  });
1526
- const handlePressIn = () => {
1527
- if (disabled) return;
1528
- reactNative.Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver4, speed: 40, bounciness: 0 }).start();
1529
- };
1530
- const handlePressOut = () => {
1531
- reactNative.Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver4, speed: 40, bounciness: 0 }).start();
1532
- };
1627
+ const progress = useColorTransition(checked);
1628
+ const boxStyle = Animated9.useAnimatedStyle(() => ({
1629
+ borderColor: Animated9.interpolateColor(progress.value, [0, 1], [colors.border, colors.primary]),
1630
+ backgroundColor: Animated9.interpolateColor(progress.value, [0, 1], ["transparent", colors.primary])
1631
+ }));
1632
+ const checkStyle = Animated9.useAnimatedStyle(() => ({
1633
+ opacity: Animated9.withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard })
1634
+ }));
1533
1635
  return /* @__PURE__ */ React26__default.default.createElement(
1534
1636
  reactNative.TouchableOpacity,
1535
1637
  {
@@ -1538,25 +1640,21 @@ function Checkbox({
1538
1640
  selectionAsync();
1539
1641
  onCheckedChange?.(!checked);
1540
1642
  },
1541
- onPressIn: handlePressIn,
1542
- onPressOut: handlePressOut,
1643
+ onPressIn,
1644
+ onPressOut,
1543
1645
  disabled,
1544
1646
  activeOpacity: 1,
1545
- touchSoundDisabled: true
1647
+ touchSoundDisabled: true,
1648
+ accessibilityRole: "checkbox",
1649
+ accessibilityLabel: accessibilityLabel ?? label,
1650
+ accessibilityState: { checked, disabled: !!disabled }
1546
1651
  },
1547
- /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26__default.default.createElement(
1548
- reactNative.Animated.View,
1652
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: scaleStyle }, /* @__PURE__ */ React26__default.default.createElement(
1653
+ Animated9__default.default.View,
1549
1654
  {
1550
- style: [
1551
- styles15.box,
1552
- {
1553
- borderColor,
1554
- backgroundColor,
1555
- opacity: disabled ? 0.45 : 1
1556
- }
1557
- ]
1655
+ style: [styles15.box, { opacity: disabled ? 0.45 : 1 }, boxStyle]
1558
1656
  },
1559
- /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: { opacity: checkOpacity } }, /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles15.checkmark, { borderColor: colors.primaryForeground }] }))
1657
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: checkStyle }, /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles15.checkmark, { borderColor: colors.primaryForeground }] }))
1560
1658
  )),
1561
1659
  label ? /* @__PURE__ */ React26__default.default.createElement(
1562
1660
  reactNative.Text,
@@ -1595,47 +1693,34 @@ var styles15 = reactNative.StyleSheet.create({
1595
1693
  lineHeight: mvs(20)
1596
1694
  }
1597
1695
  });
1598
- var nativeDriver5 = reactNative.Platform.OS !== "web";
1599
1696
  var TRACK_WIDTH = s(52);
1600
1697
  var TRACK_HEIGHT = s(30);
1601
1698
  var THUMB_SIZE = s(24);
1602
1699
  var THUMB_OFFSET = s(3);
1603
1700
  var THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2;
1604
1701
  var ICON_SIZE = s(13);
1605
- function Switch({ checked = false, onCheckedChange, disabled, style }) {
1702
+ function Switch({ checked = false, onCheckedChange, disabled, style, accessibilityLabel }) {
1606
1703
  const { colors } = useTheme();
1607
- const translateX = React26.useRef(new reactNative.Animated.Value(checked ? THUMB_TRAVEL : 0)).current;
1608
- const trackOpacity = React26.useRef(new reactNative.Animated.Value(checked ? 1 : 0)).current;
1609
- const checkOpacity = React26.useRef(new reactNative.Animated.Value(checked ? 1 : 0)).current;
1610
- const crossOpacity = React26.useRef(new reactNative.Animated.Value(checked ? 0 : 1)).current;
1704
+ const progress = Animated9.useSharedValue(checked ? 1 : 0);
1611
1705
  React26.useEffect(() => {
1612
- reactNative.Animated.parallel([
1613
- reactNative.Animated.spring(translateX, {
1614
- toValue: checked ? THUMB_TRAVEL : 0,
1615
- useNativeDriver: nativeDriver5,
1616
- bounciness: 4
1617
- }),
1618
- reactNative.Animated.timing(trackOpacity, {
1619
- toValue: checked ? 1 : 0,
1620
- duration: 150,
1621
- useNativeDriver: false
1622
- }),
1623
- reactNative.Animated.timing(checkOpacity, {
1624
- toValue: checked ? 1 : 0,
1625
- duration: 120,
1626
- useNativeDriver: true
1627
- }),
1628
- reactNative.Animated.timing(crossOpacity, {
1629
- toValue: checked ? 0 : 1,
1630
- duration: 120,
1631
- useNativeDriver: true
1632
- })
1633
- ]).start();
1634
- }, [checked, translateX, trackOpacity, checkOpacity, crossOpacity]);
1635
- const trackColor = trackOpacity.interpolate({
1636
- inputRange: [0, 1],
1637
- outputRange: [colors.surface, colors.primary]
1638
- });
1706
+ progress.value = Animated9.withSpring(checked ? 1 : 0, SPRINGS.elastic);
1707
+ }, [checked, progress]);
1708
+ const thumbStyle = Animated9.useAnimatedStyle(() => ({
1709
+ transform: [{ translateX: progress.value * THUMB_TRAVEL }]
1710
+ }));
1711
+ const trackStyle = Animated9.useAnimatedStyle(() => ({
1712
+ backgroundColor: Animated9.interpolateColor(
1713
+ progress.value,
1714
+ [0, 1],
1715
+ [colors.surfaceStrong, colors.primary]
1716
+ )
1717
+ }));
1718
+ const checkIconStyle = Animated9.useAnimatedStyle(() => ({
1719
+ opacity: Animated9.withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard })
1720
+ }));
1721
+ const crossIconStyle = Animated9.useAnimatedStyle(() => ({
1722
+ opacity: Animated9.withTiming(checked ? 0 : 1, { duration: TIMINGS.state.duration, easing: EASINGS.standard })
1723
+ }));
1639
1724
  return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [{ opacity: disabled ? 0.45 : 1 }, style] }, /* @__PURE__ */ React26__default.default.createElement(
1640
1725
  reactNative.TouchableOpacity,
1641
1726
  {
@@ -1646,29 +1731,25 @@ function Switch({ checked = false, onCheckedChange, disabled, style }) {
1646
1731
  disabled,
1647
1732
  activeOpacity: 0.8,
1648
1733
  touchSoundDisabled: true,
1649
- style: styles16.wrapper
1734
+ accessibilityRole: "switch",
1735
+ accessibilityLabel,
1736
+ accessibilityState: { checked, disabled: !!disabled }
1650
1737
  },
1651
- /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [styles16.track, { backgroundColor: trackColor }] }, /* @__PURE__ */ React26__default.default.createElement(
1652
- reactNative.Animated.View,
1738
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [styles16.track, trackStyle] }, /* @__PURE__ */ React26__default.default.createElement(
1739
+ Animated9__default.default.View,
1653
1740
  {
1654
- style: [
1655
- styles16.thumb,
1656
- { backgroundColor: colors.primaryForeground, transform: [{ translateX }] }
1657
- ]
1741
+ style: [styles16.thumb, { backgroundColor: colors.primaryForeground }, thumbStyle]
1658
1742
  },
1659
- /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [styles16.iconWrapper, { opacity: checkOpacity }] }, /* @__PURE__ */ React26__default.default.createElement(vectorIcons.Feather, { name: "check", size: ICON_SIZE, color: colors.primary })),
1660
- /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [styles16.iconWrapper, { opacity: crossOpacity }] }, /* @__PURE__ */ React26__default.default.createElement(vectorIcons.Feather, { name: "x", size: ICON_SIZE, color: colors.foregroundMuted }))
1743
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [styles16.iconWrapper, checkIconStyle] }, /* @__PURE__ */ React26__default.default.createElement(vectorIcons.Feather, { name: "check", size: ICON_SIZE, color: colors.primary })),
1744
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [styles16.iconWrapper, crossIconStyle] }, /* @__PURE__ */ React26__default.default.createElement(vectorIcons.Feather, { name: "x", size: ICON_SIZE, color: colors.foregroundMuted }))
1661
1745
  ))
1662
1746
  ));
1663
1747
  }
1664
1748
  var styles16 = reactNative.StyleSheet.create({
1665
- wrapper: {},
1666
1749
  track: {
1667
1750
  width: TRACK_WIDTH,
1668
1751
  height: TRACK_HEIGHT,
1669
1752
  borderRadius: TRACK_HEIGHT / 2
1670
- // No justifyContent/alignItems — thumb uses absolute positioning
1671
- // so the track's flex layout doesn't interfere with translateX animation
1672
1753
  },
1673
1754
  thumb: {
1674
1755
  position: "absolute",
@@ -1689,7 +1770,6 @@ var styles16 = reactNative.StyleSheet.create({
1689
1770
  position: "absolute"
1690
1771
  }
1691
1772
  });
1692
- var nativeDriver6 = reactNative.Platform.OS !== "web";
1693
1773
  var sizeStyles = {
1694
1774
  sm: { paddingHorizontal: s(12), paddingVertical: vs(8), minWidth: s(40), minHeight: vs(40) },
1695
1775
  md: { paddingHorizontal: s(16), paddingVertical: vs(12), minWidth: s(44), minHeight: vs(44) },
@@ -1710,38 +1790,23 @@ function Toggle({
1710
1790
  activeIconColor,
1711
1791
  disabled,
1712
1792
  style,
1793
+ accessibilityLabel,
1713
1794
  ...props
1714
1795
  }) {
1715
1796
  const { colors } = useTheme();
1716
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
1717
- const pressAnim = React26.useRef(new reactNative.Animated.Value(pressed ? 1 : 0)).current;
1718
- React26.useEffect(() => {
1719
- reactNative.Animated.timing(pressAnim, {
1720
- toValue: pressed ? 1 : 0,
1721
- duration: 150,
1722
- easing: reactNative.Easing.out(reactNative.Easing.ease),
1723
- useNativeDriver: false
1724
- }).start();
1725
- }, [pressed, pressAnim]);
1726
- const handlePressIn = () => {
1727
- if (disabled) return;
1728
- reactNative.Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver6, stiffness: 600, damping: 35, mass: 0.8 }).start();
1729
- };
1730
- const handlePressOut = () => {
1731
- reactNative.Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver6, stiffness: 280, damping: 22, mass: 0.8 }).start();
1732
- };
1733
- const borderColor = pressAnim.interpolate({
1734
- inputRange: [0, 1],
1735
- outputRange: [variant === "outline" ? colors.border : "transparent", colors.primary]
1736
- });
1737
- const backgroundColor = pressAnim.interpolate({
1738
- inputRange: [0, 1],
1739
- outputRange: ["transparent", colors.surfaceStrong]
1740
- });
1741
- const textColor = pressAnim.interpolate({
1742
- inputRange: [0, 1],
1743
- outputRange: [colors.foreground, colors.primary]
1797
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
1798
+ pressScale: PRESS_SCALE.button,
1799
+ disabled
1744
1800
  });
1801
+ const progress = useColorTransition(pressed);
1802
+ const inactiveBorder = variant === "outline" ? colors.border : "transparent";
1803
+ const surfaceStyle = Animated9.useAnimatedStyle(() => ({
1804
+ borderColor: Animated9.interpolateColor(progress.value, [0, 1], [inactiveBorder, colors.primary]),
1805
+ backgroundColor: Animated9.interpolateColor(progress.value, [0, 1], ["transparent", colors.surfaceStrong])
1806
+ }));
1807
+ const textStyle = Animated9.useAnimatedStyle(() => ({
1808
+ color: Animated9.interpolateColor(progress.value, [0, 1], [colors.foreground, colors.primary])
1809
+ }));
1745
1810
  const iconSize = iconSizeMap2[size];
1746
1811
  const LeftIcon = () => {
1747
1812
  const renderProp = (prop) => {
@@ -1760,32 +1825,43 @@ function Toggle({
1760
1825
  if (custom) return /* @__PURE__ */ React26__default.default.createElement(React26__default.default.Fragment, null, custom);
1761
1826
  return /* @__PURE__ */ React26__default.default.createElement(vectorIcons.FontAwesome5, { name: "circle", size: iconSize, color: colors.foregroundMuted });
1762
1827
  };
1763
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [{ transform: [{ scale: scale2 }] }, disabled && styles17.disabled, style] }, /* @__PURE__ */ React26__default.default.createElement(
1764
- reactNative.TouchableOpacity,
1828
+ return /* @__PURE__ */ React26__default.default.createElement(
1829
+ Animated9__default.default.View,
1765
1830
  {
1766
- onPress: () => {
1767
- selectionAsync();
1768
- onPressedChange?.(!pressed);
1769
- },
1770
- onPressIn: handlePressIn,
1771
- onPressOut: handlePressOut,
1772
- disabled,
1773
- activeOpacity: 1,
1774
- touchSoundDisabled: true,
1775
- ...props
1831
+ style: [scaleStyle, disabled && styles17.disabled, style],
1832
+ ...hoverHandlers
1776
1833
  },
1777
1834
  /* @__PURE__ */ React26__default.default.createElement(
1778
- reactNative.Animated.View,
1835
+ reactNative.TouchableOpacity,
1779
1836
  {
1780
- style: [
1781
- styles17.base,
1782
- sizeStyles[size],
1783
- { borderColor, backgroundColor, borderWidth: 2 }
1784
- ]
1837
+ onPress: () => {
1838
+ selectionAsync();
1839
+ onPressedChange?.(!pressed);
1840
+ },
1841
+ onPressIn,
1842
+ onPressOut,
1843
+ disabled,
1844
+ activeOpacity: 1,
1845
+ touchSoundDisabled: true,
1846
+ accessibilityRole: "button",
1847
+ accessibilityLabel: accessibilityLabel ?? label,
1848
+ accessibilityState: { selected: pressed, disabled: !!disabled },
1849
+ ...props
1785
1850
  },
1786
- /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles17.inner }, /* @__PURE__ */ React26__default.default.createElement(LeftIcon, null), label ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.Text, { style: [styles17.label, { color: textColor }], allowFontScaling: true }, label) : null)
1851
+ /* @__PURE__ */ React26__default.default.createElement(
1852
+ Animated9__default.default.View,
1853
+ {
1854
+ style: [
1855
+ styles17.base,
1856
+ sizeStyles[size],
1857
+ { borderWidth: 2 },
1858
+ surfaceStyle
1859
+ ]
1860
+ },
1861
+ /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles17.inner }, /* @__PURE__ */ React26__default.default.createElement(LeftIcon, null), label ? /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.Text, { style: [styles17.label, textStyle], allowFontScaling: true }, label) : null)
1862
+ )
1787
1863
  )
1788
- ));
1864
+ );
1789
1865
  }
1790
1866
  var styles17 = reactNative.StyleSheet.create({
1791
1867
  base: {
@@ -1805,21 +1881,28 @@ var styles17 = reactNative.StyleSheet.create({
1805
1881
  fontSize: ms(14)
1806
1882
  }
1807
1883
  });
1808
- var nativeDriver7 = reactNative.Platform.OS !== "web";
1809
1884
  function RadioItem({
1810
1885
  option,
1811
1886
  selected,
1812
1887
  onSelect
1813
1888
  }) {
1814
1889
  const { colors } = useTheme();
1815
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
1816
- const handlePressIn = () => {
1817
- if (option.disabled) return;
1818
- reactNative.Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver7, speed: 40, bounciness: 0 }).start();
1819
- };
1820
- const handlePressOut = () => {
1821
- reactNative.Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver7, speed: 40, bounciness: 4 }).start();
1822
- };
1890
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut } = usePressScale({
1891
+ pressScale: PRESS_SCALE.button,
1892
+ disabled: option.disabled
1893
+ });
1894
+ const colorProgress = useColorTransition(selected);
1895
+ const dotScale = Animated9.useSharedValue(selected ? 1 : 0);
1896
+ React26.useEffect(() => {
1897
+ dotScale.value = Animated9.withSpring(selected ? 1 : 0, SPRINGS.elastic);
1898
+ }, [selected, dotScale]);
1899
+ const radioStyle = Animated9.useAnimatedStyle(() => ({
1900
+ borderColor: Animated9.interpolateColor(colorProgress.value, [0, 1], [colors.border, colors.primary])
1901
+ }));
1902
+ const dotStyle = Animated9.useAnimatedStyle(() => ({
1903
+ transform: [{ scale: dotScale.value }],
1904
+ opacity: dotScale.value
1905
+ }));
1823
1906
  return /* @__PURE__ */ React26__default.default.createElement(
1824
1907
  reactNative.TouchableOpacity,
1825
1908
  {
@@ -1830,26 +1913,26 @@ function RadioItem({
1830
1913
  onSelect();
1831
1914
  }
1832
1915
  },
1833
- onPressIn: handlePressIn,
1834
- onPressOut: handlePressOut,
1916
+ onPressIn,
1917
+ onPressOut,
1835
1918
  activeOpacity: 1,
1836
1919
  touchSoundDisabled: true,
1837
- disabled: option.disabled
1920
+ disabled: option.disabled,
1921
+ accessibilityRole: "radio",
1922
+ accessibilityLabel: option.label,
1923
+ accessibilityState: { checked: selected, disabled: !!option.disabled }
1838
1924
  },
1839
- /* @__PURE__ */ React26__default.default.createElement(
1840
- reactNative.Animated.View,
1925
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: scaleStyle }, /* @__PURE__ */ React26__default.default.createElement(
1926
+ Animated9__default.default.View,
1841
1927
  {
1842
1928
  style: [
1843
1929
  styles18.radio,
1844
- {
1845
- borderColor: selected ? colors.primary : colors.border,
1846
- opacity: option.disabled ? 0.45 : 1,
1847
- transform: [{ scale: scale2 }]
1848
- }
1930
+ { opacity: option.disabled ? 0.45 : 1 },
1931
+ radioStyle
1849
1932
  ]
1850
1933
  },
1851
- selected ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles18.dot, { backgroundColor: colors.primary }] }) : null
1852
- ),
1934
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [styles18.dot, { backgroundColor: colors.primary }, dotStyle] })
1935
+ )),
1853
1936
  /* @__PURE__ */ React26__default.default.createElement(
1854
1937
  reactNative.Text,
1855
1938
  {
@@ -1868,17 +1951,26 @@ function RadioGroup({
1868
1951
  value,
1869
1952
  onValueChange,
1870
1953
  orientation = "vertical",
1871
- style
1954
+ style,
1955
+ accessibilityLabel
1872
1956
  }) {
1873
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles18.container, orientation === "horizontal" && styles18.horizontal, style] }, options.map((option) => /* @__PURE__ */ React26__default.default.createElement(
1874
- RadioItem,
1957
+ return /* @__PURE__ */ React26__default.default.createElement(
1958
+ reactNative.View,
1875
1959
  {
1876
- key: option.value,
1877
- option,
1878
- selected: option.value === value,
1879
- onSelect: () => onValueChange?.(option.value)
1880
- }
1881
- )));
1960
+ style: [styles18.container, orientation === "horizontal" && styles18.horizontal, style],
1961
+ accessibilityRole: "radiogroup",
1962
+ accessibilityLabel
1963
+ },
1964
+ options.map((option) => /* @__PURE__ */ React26__default.default.createElement(
1965
+ RadioItem,
1966
+ {
1967
+ key: option.value,
1968
+ option,
1969
+ selected: option.value === value,
1970
+ onSelect: () => onValueChange?.(option.value)
1971
+ }
1972
+ ))
1973
+ );
1882
1974
  }
1883
1975
  var styles18 = reactNative.StyleSheet.create({
1884
1976
  container: {
@@ -1912,7 +2004,6 @@ var styles18 = reactNative.StyleSheet.create({
1912
2004
  lineHeight: mvs(20)
1913
2005
  }
1914
2006
  });
1915
- var nativeDriver8 = reactNative.Platform.OS !== "web";
1916
2007
  function TabTrigger({
1917
2008
  tab,
1918
2009
  isActive,
@@ -1921,13 +2012,9 @@ function TabTrigger({
1921
2012
  variant
1922
2013
  }) {
1923
2014
  const { colors } = useTheme();
1924
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
1925
- const handlePressIn = () => {
1926
- reactNative.Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver8, stiffness: 600, damping: 35, mass: 0.8 }).start();
1927
- };
1928
- const handlePressOut = () => {
1929
- reactNative.Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver8, stiffness: 280, damping: 22, mass: 0.8 }).start();
1930
- };
2015
+ const { animatedStyle, onPressIn, onPressOut } = usePressScale({
2016
+ pressScale: PRESS_SCALE.button
2017
+ });
1931
2018
  const isUnderline = variant === "underline";
1932
2019
  return /* @__PURE__ */ React26__default.default.createElement(
1933
2020
  reactNative.TouchableOpacity,
@@ -1938,13 +2025,16 @@ function TabTrigger({
1938
2025
  isUnderline && isActive && { borderBottomColor: colors.primary }
1939
2026
  ],
1940
2027
  onPress,
1941
- onPressIn: handlePressIn,
1942
- onPressOut: handlePressOut,
2028
+ onPressIn,
2029
+ onPressOut,
1943
2030
  onLayout,
1944
2031
  activeOpacity: 1,
1945
- touchSoundDisabled: true
2032
+ touchSoundDisabled: true,
2033
+ accessibilityRole: "tab",
2034
+ accessibilityState: { selected: isActive },
2035
+ accessibilityLabel: tab.label
1946
2036
  },
1947
- /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles19.triggerInner }, tab.icon ? typeof tab.icon === "function" ? tab.icon(isActive) : tab.icon : null, /* @__PURE__ */ React26__default.default.createElement(
2037
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: animatedStyle }, /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles19.triggerInner }, tab.icon ? typeof tab.icon === "function" ? tab.icon(isActive) : tab.icon : null, /* @__PURE__ */ React26__default.default.createElement(
1948
2038
  reactNative.Text,
1949
2039
  {
1950
2040
  style: [
@@ -1963,20 +2053,18 @@ function Tabs({ tabs, variant = "pill", value, onValueChange, children, style })
1963
2053
  const { colors } = useTheme();
1964
2054
  const active = value ?? internal;
1965
2055
  const tabLayouts = React26.useRef({});
1966
- const pillX = React26.useRef(new reactNative.Animated.Value(0)).current;
1967
- const pillWidth = React26.useRef(new reactNative.Animated.Value(0)).current;
2056
+ const pillX = Animated9.useSharedValue(0);
2057
+ const pillWidth = Animated9.useSharedValue(0);
1968
2058
  const initialised = React26.useRef(false);
1969
2059
  const animatePill = (tabValue, animate) => {
1970
2060
  const layout = tabLayouts.current[tabValue];
1971
2061
  if (!layout) return;
1972
2062
  if (animate) {
1973
- reactNative.Animated.parallel([
1974
- reactNative.Animated.spring(pillX, { toValue: layout.x, useNativeDriver: false, stiffness: 380, damping: 38, mass: 1 }),
1975
- reactNative.Animated.spring(pillWidth, { toValue: layout.width, useNativeDriver: false, stiffness: 380, damping: 38, mass: 1 })
1976
- ]).start();
2063
+ pillX.value = Animated9.withSpring(layout.x, SPRINGS.glide);
2064
+ pillWidth.value = Animated9.withSpring(layout.width, SPRINGS.glide);
1977
2065
  } else {
1978
- pillX.setValue(layout.x);
1979
- pillWidth.setValue(layout.width);
2066
+ pillX.value = layout.x;
2067
+ pillWidth.value = layout.width;
1980
2068
  }
1981
2069
  };
1982
2070
  React26.useEffect(() => {
@@ -1987,51 +2075,63 @@ function Tabs({ tabs, variant = "pill", value, onValueChange, children, style })
1987
2075
  if (!value) setInternal(v);
1988
2076
  onValueChange?.(v);
1989
2077
  };
1990
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style }, /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [
1991
- variant === "pill" ? [styles19.list, { backgroundColor: colors.surface }] : styles19.listUnderline
1992
- ] }, variant === "pill" && /* @__PURE__ */ React26__default.default.createElement(
1993
- reactNative.Animated.View,
2078
+ const pillAnimatedStyle = Animated9.useAnimatedStyle(() => ({
2079
+ transform: [{ translateX: pillX.value }],
2080
+ width: pillWidth.value
2081
+ }));
2082
+ return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style }, /* @__PURE__ */ React26__default.default.createElement(
2083
+ reactNative.View,
1994
2084
  {
1995
2085
  style: [
1996
- styles19.pill,
1997
- {
1998
- backgroundColor: colors.background,
1999
- position: "absolute",
2000
- top: 4,
2001
- bottom: 4,
2002
- left: pillX,
2003
- width: pillWidth,
2004
- borderRadius: 8,
2005
- shadowColor: "#000",
2006
- shadowOffset: { width: 0, height: 1 },
2007
- shadowOpacity: 0.08,
2008
- shadowRadius: 2,
2009
- elevation: 2
2010
- }
2011
- ]
2012
- }
2013
- ), tabs.map((tab) => /* @__PURE__ */ React26__default.default.createElement(
2014
- TabTrigger,
2015
- {
2016
- key: tab.value,
2017
- tab,
2018
- isActive: tab.value === active,
2019
- onPress: () => handlePress(tab.value),
2020
- variant,
2021
- onLayout: (e) => {
2022
- const { x, width } = e.nativeEvent.layout;
2023
- tabLayouts.current[tab.value] = { x, width };
2024
- if (tab.value === active) {
2025
- animatePill(tab.value, false);
2026
- initialised.current = true;
2086
+ variant === "pill" ? [styles19.list, { backgroundColor: colors.surface }] : styles19.listUnderline
2087
+ ],
2088
+ accessibilityRole: "tablist"
2089
+ },
2090
+ variant === "pill" && /* @__PURE__ */ React26__default.default.createElement(
2091
+ Animated9__default.default.View,
2092
+ {
2093
+ style: [
2094
+ styles19.pill,
2095
+ {
2096
+ backgroundColor: colors.background,
2097
+ position: "absolute",
2098
+ top: 4,
2099
+ bottom: 4,
2100
+ left: 0,
2101
+ borderRadius: 8,
2102
+ shadowColor: "#000",
2103
+ shadowOffset: { width: 0, height: 1 },
2104
+ shadowOpacity: 0.08,
2105
+ shadowRadius: 2,
2106
+ elevation: 2
2107
+ },
2108
+ pillAnimatedStyle
2109
+ ]
2110
+ }
2111
+ ),
2112
+ tabs.map((tab) => /* @__PURE__ */ React26__default.default.createElement(
2113
+ TabTrigger,
2114
+ {
2115
+ key: tab.value,
2116
+ tab,
2117
+ isActive: tab.value === active,
2118
+ onPress: () => handlePress(tab.value),
2119
+ variant,
2120
+ onLayout: (e) => {
2121
+ const { x, width } = e.nativeEvent.layout;
2122
+ tabLayouts.current[tab.value] = { x, width };
2123
+ if (tab.value === active) {
2124
+ animatePill(tab.value, false);
2125
+ initialised.current = true;
2126
+ }
2027
2127
  }
2028
2128
  }
2029
- }
2030
- ))), children);
2129
+ ))
2130
+ ), children);
2031
2131
  }
2032
2132
  function TabsContent({ value, activeValue, children, style }) {
2033
2133
  if (value !== activeValue) return null;
2034
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style }, children);
2134
+ return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style, accessibilityRole: "none" }, children);
2035
2135
  }
2036
2136
  var styles19 = reactNative.StyleSheet.create({
2037
2137
  list: {
@@ -2080,8 +2180,6 @@ var styles19 = reactNative.StyleSheet.create({
2080
2180
  fontSize: ms(14)
2081
2181
  }
2082
2182
  });
2083
- var easingExpand = Animated12.Easing.bezier(0.23, 1, 0.32, 1);
2084
- var easingCollapse = Animated12.Easing.in(Animated12.Easing.ease);
2085
2183
  function AccordionItemComponent({
2086
2184
  item,
2087
2185
  isOpen,
@@ -2089,28 +2187,28 @@ function AccordionItemComponent({
2089
2187
  }) {
2090
2188
  const { colors } = useTheme();
2091
2189
  const resolvedIcon = item.iconName ? renderIcon(item.iconName, ms(16), item.iconColor ?? colors.foregroundMuted) : item.icon;
2092
- const isExpanded = Animated12.useSharedValue(isOpen);
2093
- const height = Animated12.useSharedValue(0);
2190
+ const isExpanded = Animated9.useSharedValue(isOpen);
2191
+ const height = Animated9.useSharedValue(0);
2094
2192
  React26__default.default.useEffect(() => {
2095
2193
  isExpanded.value = isOpen;
2096
2194
  }, [isOpen]);
2097
- const derivedHeight = Animated12.useDerivedValue(
2098
- () => Animated12.withTiming(height.value * Number(isExpanded.value), {
2099
- duration: 220,
2100
- easing: isExpanded.value ? easingExpand : easingCollapse
2195
+ const derivedHeight = Animated9.useDerivedValue(
2196
+ () => Animated9.withTiming(height.value * Number(isExpanded.value), {
2197
+ duration: isExpanded.value ? TIMINGS.expand.duration : TIMINGS.collapse.duration,
2198
+ easing: isExpanded.value ? EASINGS.expand : EASINGS.collapse
2101
2199
  })
2102
2200
  );
2103
- const derivedRotation = Animated12.useDerivedValue(
2104
- () => Animated12.withTiming(isExpanded.value ? 1 : 0, {
2105
- duration: 220,
2106
- easing: isExpanded.value ? easingExpand : easingCollapse
2201
+ const derivedRotation = Animated9.useDerivedValue(
2202
+ () => Animated9.withTiming(isExpanded.value ? 1 : 0, {
2203
+ duration: isExpanded.value ? TIMINGS.expand.duration : TIMINGS.collapse.duration,
2204
+ easing: isExpanded.value ? EASINGS.expand : EASINGS.collapse
2107
2205
  })
2108
2206
  );
2109
- const bodyStyle = Animated12.useAnimatedStyle(() => ({
2207
+ const bodyStyle = Animated9.useAnimatedStyle(() => ({
2110
2208
  height: derivedHeight.value,
2111
2209
  overflow: "hidden"
2112
2210
  }));
2113
- const rotationStyle = Animated12.useAnimatedStyle(() => ({
2211
+ const rotationStyle = Animated9.useAnimatedStyle(() => ({
2114
2212
  transform: [{ rotate: `${derivedRotation.value * 180}deg` }]
2115
2213
  }));
2116
2214
  return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles20.item, { backgroundColor: colors.card, borderColor: colors.border }] }, /* @__PURE__ */ React26__default.default.createElement(
@@ -2120,11 +2218,14 @@ function AccordionItemComponent({
2120
2218
  onPress: () => {
2121
2219
  selectionAsync();
2122
2220
  onToggle();
2123
- }
2221
+ },
2222
+ accessibilityRole: "button",
2223
+ accessibilityState: { expanded: isOpen },
2224
+ accessibilityLabel: item.trigger
2124
2225
  },
2125
2226
  /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles20.triggerContent }, resolvedIcon ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles20.icon }, resolvedIcon) : null, /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles20.triggerText, { color: colors.foreground }], allowFontScaling: true }, item.trigger)),
2126
- /* @__PURE__ */ React26__default.default.createElement(Animated12__default.default.View, { style: [styles20.chevron, rotationStyle] }, /* @__PURE__ */ React26__default.default.createElement(vectorIcons.Entypo, { name: "chevron-down", size: 18, color: colors.foregroundMuted }))
2127
- ), /* @__PURE__ */ React26__default.default.createElement(Animated12__default.default.View, { style: bodyStyle }, /* @__PURE__ */ React26__default.default.createElement(
2227
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [styles20.chevron, rotationStyle] }, /* @__PURE__ */ React26__default.default.createElement(vectorIcons.Entypo, { name: "chevron-down", size: 18, color: colors.foregroundMuted }))
2228
+ ), /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: bodyStyle }, /* @__PURE__ */ React26__default.default.createElement(
2128
2229
  reactNative.View,
2129
2230
  {
2130
2231
  style: styles20.content,
@@ -2224,23 +2325,38 @@ function Slider({
2224
2325
  }
2225
2326
  onValueChange?.(v);
2226
2327
  };
2227
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles21.wrapper, style], accessibilityLabel }, label || showValue ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles21.header }, label ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles21.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, showValue ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles21.valueText, { color: colors.foregroundMuted }], allowFontScaling: true }, formatValue2(value)) : null) : null, /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: disabled ? styles21.disabled : void 0 }, /* @__PURE__ */ React26__default.default.createElement(
2228
- RNSlider__default.default,
2328
+ return /* @__PURE__ */ React26__default.default.createElement(
2329
+ reactNative.View,
2229
2330
  {
2230
- value,
2231
- minimumValue,
2232
- maximumValue,
2233
- step: step || 0,
2234
- disabled,
2235
- onValueChange: handleValueChange,
2236
- onSlidingComplete,
2237
- minimumTrackTintColor: colors.primary,
2238
- maximumTrackTintColor: colors.surface,
2239
- thumbTintColor: colors.primary,
2240
- style: styles21.slider,
2241
- accessibilityLabel
2242
- }
2243
- )));
2331
+ style: [styles21.wrapper, style],
2332
+ accessibilityRole: "adjustable",
2333
+ accessibilityLabel: accessibilityLabel ?? label,
2334
+ accessibilityValue: {
2335
+ min: minimumValue,
2336
+ max: maximumValue,
2337
+ now: value,
2338
+ text: formatValue2(value)
2339
+ }
2340
+ },
2341
+ label || showValue ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles21.header }, label ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles21.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, showValue ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles21.valueText, { color: colors.foregroundMuted }], allowFontScaling: true }, formatValue2(value)) : null) : null,
2342
+ /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: disabled ? styles21.disabled : void 0 }, /* @__PURE__ */ React26__default.default.createElement(
2343
+ RNSlider__default.default,
2344
+ {
2345
+ value,
2346
+ minimumValue,
2347
+ maximumValue,
2348
+ step: step || 0,
2349
+ disabled,
2350
+ onValueChange: handleValueChange,
2351
+ onSlidingComplete,
2352
+ minimumTrackTintColor: colors.primary,
2353
+ maximumTrackTintColor: colors.surface,
2354
+ thumbTintColor: colors.primary,
2355
+ style: styles21.slider,
2356
+ accessibilityLabel
2357
+ }
2358
+ ))
2359
+ );
2244
2360
  }
2245
2361
  var styles21 = reactNative.StyleSheet.create({
2246
2362
  wrapper: {
@@ -2316,13 +2432,16 @@ function Sheet({
2316
2432
  }, [footer]);
2317
2433
  const effectiveSubtitle = subtitle ?? description;
2318
2434
  const showHeader = !!(title || effectiveSubtitle || showCloseButton);
2319
- const headerNode = showHeader ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles22.header }, /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles22.headerRow }, title ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles22.title, { color: colors.foreground }], allowFontScaling: true }, title) : /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: { flex: 1 } }), showCloseButton ? /* @__PURE__ */ React26__default.default.createElement(
2435
+ const headerNode = showHeader ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles22.header, accessibilityRole: "header" }, /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles22.headerRow }, title ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles22.title, { color: colors.foreground }], allowFontScaling: true }, title) : /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: { flex: 1 } }), showCloseButton ? /* @__PURE__ */ React26__default.default.createElement(
2320
2436
  reactNative.TouchableOpacity,
2321
2437
  {
2322
2438
  onPress: onClose,
2323
2439
  style: styles22.closeButton,
2324
2440
  activeOpacity: 0.6,
2325
- touchSoundDisabled: true
2441
+ touchSoundDisabled: true,
2442
+ accessibilityRole: "button",
2443
+ accessibilityLabel: "Close",
2444
+ hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }
2326
2445
  },
2327
2446
  /* @__PURE__ */ React26__default.default.createElement(vectorIcons.AntDesign, { name: "close", size: ms(18), color: colors.foregroundMuted })
2328
2447
  ) : null), effectiveSubtitle ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles22.subtitle, { color: colors.foregroundMuted }], allowFontScaling: true }, effectiveSubtitle) : null) : null;
@@ -2413,7 +2532,6 @@ var styles22 = reactNative.StyleSheet.create({
2413
2532
  var isIOS = reactNative.Platform.OS === "ios";
2414
2533
  var isAndroid2 = reactNative.Platform.OS === "android";
2415
2534
  var isWeb2 = reactNative.Platform.OS === "web";
2416
- var nativeDriver9 = reactNative.Platform.OS !== "web";
2417
2535
  function Select({
2418
2536
  options,
2419
2537
  value,
@@ -2422,21 +2540,18 @@ function Select({
2422
2540
  label,
2423
2541
  error,
2424
2542
  disabled,
2425
- style
2543
+ style,
2544
+ accessibilityLabel
2426
2545
  }) {
2427
2546
  const { colors } = useTheme();
2428
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
2547
+ const { animatedStyle, onPressIn, onPressOut } = usePressScale({
2548
+ pressScale: PRESS_SCALE.button,
2549
+ disabled
2550
+ });
2429
2551
  const [pickerVisible, setPickerVisible] = React26.useState(false);
2430
2552
  const [pendingValue, setPendingValue] = React26.useState(value);
2431
2553
  const pickerRef = React26.useRef(null);
2432
2554
  const selected = options.find((o) => o.value === value);
2433
- const handlePressIn = () => {
2434
- if (disabled) return;
2435
- reactNative.Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver9, speed: 40, bounciness: 0 }).start();
2436
- };
2437
- const handlePressOut = () => {
2438
- reactNative.Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver9, speed: 40, bounciness: 4 }).start();
2439
- };
2440
2555
  const handleOpen = () => {
2441
2556
  if (disabled) return;
2442
2557
  selectionAsync();
@@ -2457,7 +2572,7 @@ function Select({
2457
2572
  }
2458
2573
  setPickerVisible(false);
2459
2574
  };
2460
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles23.container, style] }, label ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles23.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, !isWeb2 ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: { transform: [{ scale: scale2 }], opacity: disabled ? 0.45 : 1 } }, /* @__PURE__ */ React26__default.default.createElement(
2575
+ return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles23.container, style] }, label ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles23.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, !isWeb2 ? /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [animatedStyle, { opacity: disabled ? 0.45 : 1 }] }, /* @__PURE__ */ React26__default.default.createElement(
2461
2576
  reactNative.TouchableOpacity,
2462
2577
  {
2463
2578
  style: [
@@ -2468,10 +2583,14 @@ function Select({
2468
2583
  }
2469
2584
  ],
2470
2585
  onPress: handleOpen,
2471
- onPressIn: handlePressIn,
2472
- onPressOut: handlePressOut,
2586
+ onPressIn,
2587
+ onPressOut,
2473
2588
  activeOpacity: 1,
2474
- touchSoundDisabled: true
2589
+ touchSoundDisabled: true,
2590
+ accessibilityRole: "combobox",
2591
+ accessibilityLabel: accessibilityLabel ?? label,
2592
+ accessibilityValue: { text: selected?.label ?? placeholder },
2593
+ accessibilityState: { disabled: !!disabled, expanded: pickerVisible }
2475
2594
  },
2476
2595
  /* @__PURE__ */ React26__default.default.createElement(
2477
2596
  reactNative.Text,
@@ -2785,7 +2904,6 @@ var styles24 = reactNative.StyleSheet.create({
2785
2904
  textAlignVertical: "top"
2786
2905
  }
2787
2906
  });
2788
- var nativeDriver10 = reactNative.Platform.OS !== "web";
2789
2907
  function ListItem({
2790
2908
  leftRender,
2791
2909
  rightRender,
@@ -2806,29 +2924,16 @@ function ListItem({
2806
2924
  style,
2807
2925
  titleStyle,
2808
2926
  subtitleStyle,
2809
- captionStyle
2927
+ captionStyle,
2928
+ accessibilityLabel
2810
2929
  }) {
2811
2930
  const { colors } = useTheme();
2812
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
2813
- const handlePressIn = () => {
2814
- if (!onPress || disabled) return;
2815
- reactNative.Animated.spring(scale2, {
2816
- toValue: 0.97,
2817
- useNativeDriver: nativeDriver10,
2818
- stiffness: 350,
2819
- damping: 28,
2820
- mass: 0.9
2821
- }).start();
2822
- };
2823
- const handlePressOut = () => {
2824
- reactNative.Animated.spring(scale2, {
2825
- toValue: 1,
2826
- useNativeDriver: nativeDriver10,
2827
- stiffness: 220,
2828
- damping: 20,
2829
- mass: 0.9
2830
- }).start();
2831
- };
2931
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
2932
+ pressScale: PRESS_SCALE.row,
2933
+ pressInSpring: SPRINGS.surfacePressIn,
2934
+ pressOutSpring: SPRINGS.surfacePressOut,
2935
+ disabled: !onPress || disabled
2936
+ });
2832
2937
  const handlePress = () => {
2833
2938
  selectionAsync();
2834
2939
  onPress?.();
@@ -2846,16 +2951,20 @@ function ListItem({
2846
2951
  shadowRadius: 6,
2847
2952
  elevation: 2
2848
2953
  } : {};
2849
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [{ transform: [{ scale: scale2 }] }, disabled && styles25.disabled] }, /* @__PURE__ */ React26__default.default.createElement(
2954
+ const a11yLabel = accessibilityLabel ?? [title, subtitle, caption].filter(Boolean).join(". ");
2955
+ return /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [animatedStyle, disabled && styles25.disabled], ...hoverHandlers }, /* @__PURE__ */ React26__default.default.createElement(
2850
2956
  reactNative.TouchableOpacity,
2851
2957
  {
2852
2958
  style: [styles25.container, cardStyle, style],
2853
2959
  onPress: onPress ? handlePress : void 0,
2854
- onPressIn: handlePressIn,
2855
- onPressOut: handlePressOut,
2960
+ onPressIn,
2961
+ onPressOut,
2856
2962
  disabled,
2857
2963
  activeOpacity: 1,
2858
- touchSoundDisabled: true
2964
+ touchSoundDisabled: true,
2965
+ accessibilityRole: onPress ? "button" : void 0,
2966
+ accessibilityLabel: onPress ? a11yLabel : void 0,
2967
+ accessibilityState: onPress ? { disabled: !!disabled } : void 0
2859
2968
  },
2860
2969
  effectiveLeft ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles25.leftContainer }, effectiveLeft) : null,
2861
2970
  /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles25.content }, /* @__PURE__ */ React26__default.default.createElement(
@@ -2949,9 +3058,6 @@ var styles25 = reactNative.StyleSheet.create({
2949
3058
  fontFamily: "Poppins-Regular",
2950
3059
  fontSize: ms(14)
2951
3060
  },
2952
- chevron: {
2953
- marginLeft: s(4)
2954
- },
2955
3061
  separator: {
2956
3062
  height: reactNative.StyleSheet.hairlineWidth,
2957
3063
  marginRight: 0
@@ -2960,9 +3066,9 @@ var styles25 = reactNative.StyleSheet.create({
2960
3066
  opacity: 0.45
2961
3067
  }
2962
3068
  });
2963
- var nativeDriver11 = reactNative.Platform.OS !== "web";
2964
3069
  function MenuItem({
2965
3070
  label,
3071
+ subtitle,
2966
3072
  iconName,
2967
3073
  icon,
2968
3074
  iconColor,
@@ -2973,29 +3079,16 @@ function MenuItem({
2973
3079
  variant = "plain",
2974
3080
  showSeparator = false,
2975
3081
  style,
2976
- labelStyle
3082
+ labelStyle,
3083
+ accessibilityLabel
2977
3084
  }) {
2978
3085
  const { colors } = useTheme();
2979
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
2980
- const handlePressIn = () => {
2981
- if (disabled) return;
2982
- reactNative.Animated.spring(scale2, {
2983
- toValue: 0.97,
2984
- useNativeDriver: nativeDriver11,
2985
- stiffness: 350,
2986
- damping: 28,
2987
- mass: 0.9
2988
- }).start();
2989
- };
2990
- const handlePressOut = () => {
2991
- reactNative.Animated.spring(scale2, {
2992
- toValue: 1,
2993
- useNativeDriver: nativeDriver11,
2994
- stiffness: 220,
2995
- damping: 20,
2996
- mass: 0.9
2997
- }).start();
2998
- };
3086
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3087
+ pressScale: PRESS_SCALE.row,
3088
+ pressInSpring: SPRINGS.surfacePressIn,
3089
+ pressOutSpring: SPRINGS.surfacePressOut,
3090
+ disabled
3091
+ });
2999
3092
  const handlePress = () => {
3000
3093
  selectionAsync();
3001
3094
  onPress();
@@ -3012,19 +3105,23 @@ function MenuItem({
3012
3105
  shadowRadius: 6,
3013
3106
  elevation: 2
3014
3107
  } : {};
3015
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [{ transform: [{ scale: scale2 }] }, disabled && styles26.disabled] }, /* @__PURE__ */ React26__default.default.createElement(
3108
+ const a11yLabel = accessibilityLabel ?? (subtitle ? `${label}. ${subtitle}` : label);
3109
+ return /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [animatedStyle, disabled && styles26.disabled], ...hoverHandlers }, /* @__PURE__ */ React26__default.default.createElement(
3016
3110
  reactNative.TouchableOpacity,
3017
3111
  {
3018
3112
  style: [styles26.container, cardStyle, style],
3019
3113
  onPress: handlePress,
3020
- onPressIn: handlePressIn,
3021
- onPressOut: handlePressOut,
3114
+ onPressIn,
3115
+ onPressOut,
3022
3116
  disabled,
3023
3117
  activeOpacity: 1,
3024
- touchSoundDisabled: true
3118
+ touchSoundDisabled: true,
3119
+ accessibilityRole: "button",
3120
+ accessibilityLabel: a11yLabel,
3121
+ accessibilityState: { disabled }
3025
3122
  },
3026
3123
  resolvedIcon ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles26.iconContainer }, resolvedIcon) : null,
3027
- /* @__PURE__ */ React26__default.default.createElement(
3124
+ /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles26.labelContainer }, /* @__PURE__ */ React26__default.default.createElement(
3028
3125
  reactNative.Text,
3029
3126
  {
3030
3127
  style: [styles26.label, { color: colors.foreground }, labelStyle],
@@ -3032,7 +3129,15 @@ function MenuItem({
3032
3129
  allowFontScaling: true
3033
3130
  },
3034
3131
  label
3035
- ),
3132
+ ), subtitle ? /* @__PURE__ */ React26__default.default.createElement(
3133
+ reactNative.Text,
3134
+ {
3135
+ style: [styles26.subtitle, { color: colors.foregroundMuted }],
3136
+ numberOfLines: 1,
3137
+ allowFontScaling: true
3138
+ },
3139
+ subtitle
3140
+ ) : null),
3036
3141
  rightRender !== void 0 ? /* @__PURE__ */ React26__default.default.createElement(
3037
3142
  reactNative.View,
3038
3143
  {
@@ -3072,10 +3177,18 @@ var styles26 = reactNative.StyleSheet.create({
3072
3177
  justifyContent: "center",
3073
3178
  flexShrink: 0
3074
3179
  },
3180
+ labelContainer: {
3181
+ flex: 1,
3182
+ justifyContent: "center"
3183
+ },
3075
3184
  label: {
3076
3185
  fontFamily: "Poppins-Medium",
3077
- fontSize: ms(15),
3078
- flex: 1
3186
+ fontSize: ms(15)
3187
+ },
3188
+ subtitle: {
3189
+ fontFamily: "Poppins-Regular",
3190
+ fontSize: ms(12),
3191
+ marginTop: vs(1)
3079
3192
  },
3080
3193
  rightContainer: {
3081
3194
  alignItems: "flex-end",
@@ -3090,62 +3203,37 @@ var styles26 = reactNative.StyleSheet.create({
3090
3203
  opacity: 0.45
3091
3204
  }
3092
3205
  });
3093
- var nativeDriver12 = reactNative.Platform.OS !== "web";
3094
- function Chip({ label, selected = false, onPress, icon, iconName, style }) {
3206
+ function Chip({ label, selected = false, onPress, icon, iconName, style, accessibilityLabel }) {
3095
3207
  const { colors } = useTheme();
3096
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
3097
- const pressAnim = React26.useRef(new reactNative.Animated.Value(selected ? 1 : 0)).current;
3098
- React26.useEffect(() => {
3099
- reactNative.Animated.timing(pressAnim, {
3100
- toValue: selected ? 1 : 0,
3101
- duration: 150,
3102
- easing: reactNative.Easing.out(reactNative.Easing.ease),
3103
- useNativeDriver: false
3104
- }).start();
3105
- }, [selected, pressAnim]);
3106
- const handlePressIn = () => {
3107
- reactNative.Animated.spring(scale2, {
3108
- toValue: 0.95,
3109
- useNativeDriver: nativeDriver12,
3110
- speed: 40,
3111
- bounciness: 0
3112
- }).start();
3113
- };
3114
- const handlePressOut = () => {
3115
- reactNative.Animated.spring(scale2, {
3116
- toValue: 1,
3117
- useNativeDriver: nativeDriver12,
3118
- speed: 40,
3119
- bounciness: 4
3120
- }).start();
3121
- };
3208
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3209
+ pressScale: PRESS_SCALE.chip
3210
+ });
3211
+ const colorProgress = useColorTransition(selected);
3212
+ const surfaceStyle = Animated9.useAnimatedStyle(() => ({
3213
+ backgroundColor: Animated9.interpolateColor(colorProgress.value, [0, 1], [colors.surface, colors.primary]),
3214
+ borderColor: Animated9.interpolateColor(colorProgress.value, [0, 1], [colors.border, colors.primary])
3215
+ }));
3216
+ const textStyle = Animated9.useAnimatedStyle(() => ({
3217
+ color: Animated9.interpolateColor(colorProgress.value, [0, 1], [colors.foreground, colors.primaryForeground])
3218
+ }));
3122
3219
  const handlePress = () => {
3123
3220
  selectionAsync();
3124
3221
  onPress?.();
3125
3222
  };
3126
- const backgroundColor = pressAnim.interpolate({
3127
- inputRange: [0, 1],
3128
- outputRange: [colors.surface, colors.primary]
3129
- });
3130
- const textColor = pressAnim.interpolate({
3131
- inputRange: [0, 1],
3132
- outputRange: [colors.foreground, colors.primaryForeground]
3133
- });
3134
- const borderColor = pressAnim.interpolate({
3135
- inputRange: [0, 1],
3136
- outputRange: [colors.border, colors.primary]
3137
- });
3138
3223
  const resolvedIcon = iconName ? renderIcon(iconName, ms(13), selected ? colors.primaryForeground : colors.foreground) : icon;
3139
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [styles27.wrapper, { transform: [{ scale: scale2 }] }, style] }, /* @__PURE__ */ React26__default.default.createElement(
3224
+ return /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [styles27.wrapper, scaleStyle, style], ...hoverHandlers }, /* @__PURE__ */ React26__default.default.createElement(
3140
3225
  reactNative.TouchableOpacity,
3141
3226
  {
3142
3227
  onPress: handlePress,
3143
- onPressIn: handlePressIn,
3144
- onPressOut: handlePressOut,
3228
+ onPressIn,
3229
+ onPressOut,
3145
3230
  activeOpacity: 1,
3146
- touchSoundDisabled: true
3231
+ touchSoundDisabled: true,
3232
+ accessibilityRole: "button",
3233
+ accessibilityLabel: accessibilityLabel ?? label,
3234
+ accessibilityState: { selected }
3147
3235
  },
3148
- /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: [styles27.chip, { backgroundColor, borderColor }] }, resolvedIcon ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles27.chipIcon }, resolvedIcon) : null, /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.Text, { style: [styles27.label, { color: textColor }], allowFontScaling: true }, label))
3236
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [styles27.chip, surfaceStyle] }, resolvedIcon ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles27.chipIcon }, resolvedIcon) : null, /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.Text, { style: [styles27.label, textStyle], allowFontScaling: true }, label))
3149
3237
  ));
3150
3238
  }
3151
3239
  function ChipGroup({ options, value, onValueChange, multiSelect = false, style }) {
@@ -3156,12 +3244,7 @@ function ChipGroup({ options, value, onValueChange, multiSelect = false, style }
3156
3244
  }
3157
3245
  const currentArray = Array.isArray(value) ? value : value ? [value] : [];
3158
3246
  const isSelected2 = currentArray.includes(optionValue);
3159
- let newArray;
3160
- if (isSelected2) {
3161
- newArray = currentArray.filter((v) => v !== optionValue);
3162
- } else {
3163
- newArray = [...currentArray, optionValue];
3164
- }
3247
+ const newArray = isSelected2 ? currentArray.filter((v) => v !== optionValue) : [...currentArray, optionValue];
3165
3248
  onValueChange?.(newArray);
3166
3249
  };
3167
3250
  const isSelected = (optionValue) => {
@@ -3375,22 +3458,36 @@ function MonthPicker({ value, onChange, locale = "en", formatLabel, style }) {
3375
3458
  onChange({ month: value.month + 1, year: value.year });
3376
3459
  }
3377
3460
  };
3378
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles30.container, style] }, /* @__PURE__ */ React26__default.default.createElement(
3461
+ return /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles30.container, style], accessibilityRole: "adjustable", accessibilityLabel: getLabel() }, /* @__PURE__ */ React26__default.default.createElement(
3379
3462
  reactNative.TouchableOpacity,
3380
3463
  {
3381
3464
  style: styles30.arrow,
3382
3465
  onPress: handlePrev,
3383
3466
  activeOpacity: 0.6,
3384
- touchSoundDisabled: true
3467
+ touchSoundDisabled: true,
3468
+ accessibilityRole: "button",
3469
+ accessibilityLabel: "Previous month",
3470
+ hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }
3385
3471
  },
3386
3472
  /* @__PURE__ */ React26__default.default.createElement(vectorIcons.Entypo, { name: "chevron-left", size: 22, color: colors.foreground })
3387
- ), /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles30.label, { color: colors.foreground }], allowFontScaling: true }, getLabel()), /* @__PURE__ */ React26__default.default.createElement(
3473
+ ), /* @__PURE__ */ React26__default.default.createElement(
3474
+ reactNative.Text,
3475
+ {
3476
+ style: [styles30.label, { color: colors.foreground }],
3477
+ allowFontScaling: true,
3478
+ accessibilityLiveRegion: "polite"
3479
+ },
3480
+ getLabel()
3481
+ ), /* @__PURE__ */ React26__default.default.createElement(
3388
3482
  reactNative.TouchableOpacity,
3389
3483
  {
3390
3484
  style: styles30.arrow,
3391
3485
  onPress: handleNext,
3392
3486
  activeOpacity: 0.6,
3393
- touchSoundDisabled: true
3487
+ touchSoundDisabled: true,
3488
+ accessibilityRole: "button",
3489
+ accessibilityLabel: "Next month",
3490
+ hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }
3394
3491
  },
3395
3492
  /* @__PURE__ */ React26__default.default.createElement(vectorIcons.Entypo, { name: "chevron-right", size: 22, color: colors.foreground })
3396
3493
  ));
@@ -3415,18 +3512,6 @@ var styles30 = reactNative.StyleSheet.create({
3415
3512
  minWidth: s(160)
3416
3513
  }
3417
3514
  });
3418
- function useHover() {
3419
- const [hovered, setHovered] = React26.useState(false);
3420
- const onMouseEnter = React26.useCallback(() => setHovered(true), []);
3421
- const onMouseLeave = React26.useCallback(() => setHovered(false), []);
3422
- if (reactNative.Platform.OS !== "web") {
3423
- return { hovered: false, hoverHandlers: {} };
3424
- }
3425
- return { hovered, hoverHandlers: { onMouseEnter, onMouseLeave } };
3426
- }
3427
-
3428
- // src/components/MediaCard/MediaCard.tsx
3429
- var nativeDriver13 = reactNative.Platform.OS !== "web";
3430
3515
  var aspectRatioMap = {
3431
3516
  "1:1": 1,
3432
3517
  "4:3": 3 / 4,
@@ -3448,19 +3533,17 @@ function MediaCard({
3448
3533
  onPress,
3449
3534
  style,
3450
3535
  imageStyle,
3451
- footer
3536
+ footer,
3537
+ accessibilityLabel
3452
3538
  }) {
3453
3539
  const { colors } = useTheme();
3454
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
3455
3540
  const { hovered, hoverHandlers } = useHover();
3456
- const handlePressIn = () => {
3457
- if (!onPress) return;
3458
- reactNative.Animated.spring(scale2, { toValue: 0.98, useNativeDriver: nativeDriver13, speed: 40, bounciness: 0 }).start();
3459
- };
3460
- const handlePressOut = () => {
3461
- if (!onPress) return;
3462
- reactNative.Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver13, speed: 40, bounciness: 4 }).start();
3463
- };
3541
+ const { animatedStyle, onPressIn, onPressOut } = usePressScale({
3542
+ pressScale: PRESS_SCALE.card,
3543
+ pressInSpring: SPRINGS.surfacePressIn,
3544
+ pressOutSpring: SPRINGS.surfacePressOut,
3545
+ disabled: !onPress
3546
+ });
3464
3547
  const handlePress = () => {
3465
3548
  if (!onPress) return;
3466
3549
  impactLight();
@@ -3468,6 +3551,7 @@ function MediaCard({
3468
3551
  };
3469
3552
  const ratio = aspectRatioMap[aspectRatio];
3470
3553
  const resolvedActionIcon = actionIconName ? renderIcon(actionIconName, 18, actionActive ? colors.primary : colors.background) : actionIcon ?? renderIcon("heart", 18, actionActive ? colors.primary : colors.background);
3554
+ const a11yLabel = accessibilityLabel ?? [title, subtitle].filter(Boolean).join(". ");
3471
3555
  const cardContent = /* @__PURE__ */ React26__default.default.createElement(
3472
3556
  reactNative.View,
3473
3557
  {
@@ -3494,21 +3578,26 @@ function MediaCard({
3494
3578
  onActionPress?.();
3495
3579
  },
3496
3580
  activeOpacity: 0.8,
3497
- touchSoundDisabled: true
3581
+ touchSoundDisabled: true,
3582
+ accessibilityRole: "button",
3583
+ accessibilityLabel: actionIconName ?? "action",
3584
+ accessibilityState: { selected: actionActive }
3498
3585
  },
3499
3586
  resolvedActionIcon
3500
3587
  )),
3501
3588
  (title || subtitle || caption || footer) && /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles31.meta }, title ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles31.title, { color: colors.foreground }], numberOfLines: 2, allowFontScaling: true }, title) : null, subtitle ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles31.subtitle, { color: colors.foregroundSubtle }], numberOfLines: 1, allowFontScaling: true }, subtitle) : null, caption ? /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles31.caption, { color: colors.foregroundMuted }], numberOfLines: 1, allowFontScaling: true }, caption) : null, footer)
3502
3589
  );
3503
3590
  if (onPress) {
3504
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26__default.default.createElement(
3591
+ return /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: animatedStyle }, /* @__PURE__ */ React26__default.default.createElement(
3505
3592
  reactNative.TouchableOpacity,
3506
3593
  {
3507
3594
  onPress: handlePress,
3508
- onPressIn: handlePressIn,
3509
- onPressOut: handlePressOut,
3595
+ onPressIn,
3596
+ onPressOut,
3510
3597
  activeOpacity: 1,
3511
- touchSoundDisabled: true
3598
+ touchSoundDisabled: true,
3599
+ accessibilityRole: "button",
3600
+ accessibilityLabel: a11yLabel
3512
3601
  },
3513
3602
  cardContent
3514
3603
  ));
@@ -3518,12 +3607,10 @@ function MediaCard({
3518
3607
  var styles31 = reactNative.StyleSheet.create({
3519
3608
  card: {
3520
3609
  borderRadius: RADIUS.md,
3521
- // 14px — Airbnb property card spec
3522
3610
  overflow: "hidden",
3523
3611
  backgroundColor: "transparent"
3524
3612
  },
3525
3613
  cardHovered: {
3526
- // Web hover: lift shadow
3527
3614
  ...SHADOWS.md
3528
3615
  },
3529
3616
  imageContainer: {
@@ -3574,43 +3661,38 @@ var styles31 = reactNative.StyleSheet.create({
3574
3661
  lineHeight: mvs(16)
3575
3662
  }
3576
3663
  });
3577
- var nativeDriver14 = reactNative.Platform.OS !== "web";
3578
3664
  function CategoryChip({
3579
3665
  item,
3580
3666
  selected,
3581
3667
  onPress
3582
3668
  }) {
3583
3669
  const { colors } = useTheme();
3584
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
3585
- const handlePressIn = () => {
3586
- reactNative.Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver14, speed: 40, bounciness: 0 }).start();
3587
- };
3588
- const handlePressOut = () => {
3589
- reactNative.Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver14, speed: 40, bounciness: 4 }).start();
3590
- };
3591
- const bgColor = selected ? colors.primary : colors.surface;
3592
- const textColor = selected ? colors.primaryForeground : colors.foregroundSubtle;
3593
- const borderColor = selected ? colors.primary : colors.border;
3594
- const resolvedIcon = typeof item.icon === "string" ? renderIcon(item.icon, 16, textColor) : item.icon ?? null;
3595
- return /* @__PURE__ */ React26__default.default.createElement(reactNative.Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26__default.default.createElement(
3670
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3671
+ pressScale: PRESS_SCALE.chip
3672
+ });
3673
+ const progress = useColorTransition(selected);
3674
+ const surfaceStyle = Animated9.useAnimatedStyle(() => ({
3675
+ backgroundColor: Animated9.interpolateColor(progress.value, [0, 1], [colors.surface, colors.primary]),
3676
+ borderColor: Animated9.interpolateColor(progress.value, [0, 1], [colors.border, colors.primary])
3677
+ }));
3678
+ const textColorStyle = Animated9.useAnimatedStyle(() => ({
3679
+ color: Animated9.interpolateColor(progress.value, [0, 1], [colors.foregroundSubtle, colors.primaryForeground])
3680
+ }));
3681
+ const iconColor = selected ? colors.primaryForeground : colors.foregroundSubtle;
3682
+ const resolvedIcon = typeof item.icon === "string" ? renderIcon(item.icon, 16, iconColor) : item.icon ?? null;
3683
+ return /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: scaleStyle, ...hoverHandlers }, /* @__PURE__ */ React26__default.default.createElement(
3596
3684
  reactNative.TouchableOpacity,
3597
3685
  {
3598
- style: [
3599
- styles32.chip,
3600
- {
3601
- backgroundColor: bgColor,
3602
- borderColor
3603
- }
3604
- ],
3605
3686
  onPress,
3606
- onPressIn: handlePressIn,
3607
- onPressOut: handlePressOut,
3687
+ onPressIn,
3688
+ onPressOut,
3608
3689
  activeOpacity: 1,
3609
- touchSoundDisabled: true
3690
+ touchSoundDisabled: true,
3691
+ accessibilityRole: "button",
3692
+ accessibilityLabel: item.label,
3693
+ accessibilityState: { selected }
3610
3694
  },
3611
- resolvedIcon && /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles32.chipIcon }, resolvedIcon),
3612
- /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles32.chipLabel, { color: textColor }], allowFontScaling: true }, item.label),
3613
- item.badge !== void 0 && item.badge > 0 && /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles32.chipBadge, { backgroundColor: colors.primary }] }, /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles32.chipBadgeText, { color: colors.primaryForeground }] }, Math.min(item.badge, 99)))
3695
+ /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.View, { style: [styles32.chip, surfaceStyle] }, resolvedIcon && /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles32.chipIcon }, resolvedIcon), /* @__PURE__ */ React26__default.default.createElement(Animated9__default.default.Text, { style: [styles32.chipLabel, textColorStyle], allowFontScaling: true }, item.label), item.badge !== void 0 && item.badge > 0 && /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: [styles32.chipBadge, { backgroundColor: colors.primary }] }, /* @__PURE__ */ React26__default.default.createElement(reactNative.Text, { style: [styles32.chipBadgeText, { color: colors.primaryForeground }] }, Math.min(item.badge, 99))))
3614
3696
  ));
3615
3697
  }
3616
3698
  function CategoryStrip({
@@ -3619,7 +3701,8 @@ function CategoryStrip({
3619
3701
  onValueChange,
3620
3702
  multiSelect = false,
3621
3703
  style,
3622
- itemStyle
3704
+ itemStyle,
3705
+ accessibilityLabel
3623
3706
  }) {
3624
3707
  const selected = Array.isArray(value) ? value : value ? [value] : [];
3625
3708
  const handlePress = (v) => {
@@ -3638,7 +3721,9 @@ function CategoryStrip({
3638
3721
  horizontal: true,
3639
3722
  showsHorizontalScrollIndicator: false,
3640
3723
  contentContainerStyle: [styles32.container, style],
3641
- style: styles32.scroll
3724
+ style: styles32.scroll,
3725
+ accessibilityRole: multiSelect ? void 0 : "radiogroup",
3726
+ accessibilityLabel
3642
3727
  },
3643
3728
  categories.map((cat) => /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { key: cat.value, style: itemStyle }, /* @__PURE__ */ React26__default.default.createElement(
3644
3729
  CategoryChip,
@@ -3691,62 +3776,45 @@ var styles32 = reactNative.StyleSheet.create({
3691
3776
  lineHeight: 14
3692
3777
  }
3693
3778
  });
3694
- var nativeDriver15 = reactNative.Platform.OS !== "web";
3695
3779
  function Pressable2({
3696
3780
  children,
3697
3781
  onPress,
3698
- pressScale = 0.98,
3699
- bounciness = 4,
3782
+ pressScale = PRESS_SCALE.card,
3700
3783
  haptics = true,
3701
3784
  style,
3702
3785
  disabled,
3703
3786
  hoverScale = 1.02,
3704
3787
  ...touchableProps
3705
3788
  }) {
3706
- const scale2 = React26.useRef(new reactNative.Animated.Value(1)).current;
3707
- const { hovered, hoverHandlers } = useHover();
3708
- const handlePressIn = () => {
3709
- if (disabled) return;
3710
- reactNative.Animated.spring(scale2, {
3711
- toValue: pressScale,
3712
- useNativeDriver: nativeDriver15,
3713
- speed: 40,
3714
- bounciness: 0
3715
- }).start();
3716
- };
3717
- const handlePressOut = () => {
3718
- if (disabled) return;
3719
- reactNative.Animated.spring(scale2, {
3720
- toValue: 1,
3721
- useNativeDriver: nativeDriver15,
3722
- speed: 40,
3723
- bounciness
3724
- }).start();
3725
- };
3789
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3790
+ pressScale,
3791
+ hoverScale,
3792
+ pressInSpring: SPRINGS.surfacePressIn,
3793
+ pressOutSpring: SPRINGS.surfacePressOut,
3794
+ disabled
3795
+ });
3726
3796
  const handlePress = () => {
3727
3797
  if (disabled || !onPress) return;
3728
3798
  if (haptics) impactLight();
3729
3799
  onPress();
3730
3800
  };
3731
- const hoverScaleValue = hovered && hoverScale !== 1 ? hoverScale : 1;
3732
3801
  return /* @__PURE__ */ React26__default.default.createElement(
3733
- reactNative.Animated.View,
3802
+ Animated9__default.default.View,
3734
3803
  {
3735
- style: [
3736
- { transform: [{ scale: reactNative.Animated.multiply(scale2, hoverScaleValue) }] },
3737
- style
3738
- ],
3804
+ style: [animatedStyle, style],
3739
3805
  ...reactNative.Platform.OS === "web" ? hoverHandlers : {}
3740
3806
  },
3741
3807
  /* @__PURE__ */ React26__default.default.createElement(
3742
3808
  reactNative.TouchableOpacity,
3743
3809
  {
3744
3810
  onPress: handlePress,
3745
- onPressIn: handlePressIn,
3746
- onPressOut: handlePressOut,
3811
+ onPressIn,
3812
+ onPressOut,
3747
3813
  activeOpacity: 1,
3748
3814
  disabled,
3749
3815
  touchSoundDisabled: true,
3816
+ accessibilityRole: "button",
3817
+ accessibilityState: { disabled: !!disabled },
3750
3818
  ...touchableProps
3751
3819
  },
3752
3820
  children
@@ -3792,14 +3860,14 @@ function DetailRow({
3792
3860
  allowFontScaling: true
3793
3861
  },
3794
3862
  label
3795
- ) : label), separatorStyle ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: separatorStyle }) : /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles33.spacer }), /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles33.valueSide }, /* @__PURE__ */ React26__default.default.createElement(
3863
+ ) : label), separatorStyle ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: separatorStyle }) : /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles33.spacer }), /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles33.valueSide }, typeof value === "string" ? /* @__PURE__ */ React26__default.default.createElement(
3796
3864
  reactNative.Text,
3797
3865
  {
3798
3866
  style: [styles33.valueText, { color: valueColor ?? colors.foreground }, valueStyle],
3799
3867
  allowFontScaling: true
3800
3868
  },
3801
3869
  value
3802
- ), resolvedRightIcon ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles33.icon }, resolvedRightIcon) : null));
3870
+ ) : value, resolvedRightIcon ? /* @__PURE__ */ React26__default.default.createElement(reactNative.View, { style: styles33.icon }, resolvedRightIcon) : null));
3803
3871
  }
3804
3872
  var styles33 = reactNative.StyleSheet.create({
3805
3873
  row: {