@retray-dev/ui-kit 6.1.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 (32) hide show
  1. package/COMPONENTS.md +4 -4
  2. package/dist/index.d.mts +42 -21
  3. package/dist/index.d.ts +42 -21
  4. package/dist/index.js +679 -628
  5. package/dist/index.mjs +672 -621
  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/IconButton/IconButton.tsx +20 -18
  14. package/src/components/Input/Input.tsx +39 -22
  15. package/src/components/ListItem/ListItem.tsx +22 -34
  16. package/src/components/MediaCard/MediaCard.tsx +24 -24
  17. package/src/components/MenuItem/MenuItem.tsx +22 -31
  18. package/src/components/MonthPicker/MonthPicker.tsx +12 -2
  19. package/src/components/Pressable/Pressable.tsx +27 -46
  20. package/src/components/Progress/Progress.tsx +21 -12
  21. package/src/components/RadioGroup/RadioGroup.tsx +52 -26
  22. package/src/components/Select/Select.tsx +17 -15
  23. package/src/components/Sheet/Sheet.tsx +4 -1
  24. package/src/components/Skeleton/Skeleton.tsx +24 -13
  25. package/src/components/Slider/Slider.tsx +11 -1
  26. package/src/components/Switch/Switch.tsx +44 -49
  27. package/src/components/Tabs/Tabs.tsx +39 -31
  28. package/src/components/Textarea/Textarea.tsx +29 -12
  29. package/src/components/Toggle/Toggle.tsx +39 -45
  30. package/src/utils/animations.ts +58 -0
  31. package/src/utils/useColorTransition.ts +40 -0
  32. package/src/utils/usePressScale.ts +73 -0
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import React26, { createContext, useMemo, useContext, useRef, useState, useEffect, useCallback } from 'react';
2
- import { Platform, StyleSheet, Dimensions, useColorScheme, Animated, TouchableOpacity, ActivityIndicator, Text, View, TextInput, Easing as Easing$1, Image, Modal, ScrollView, Pressable } from 'react-native';
1
+ import React26, { createContext, useMemo, useContext, useState, useEffect, useRef, useCallback } from 'react';
2
+ import { Platform, StyleSheet, Dimensions, useColorScheme, TouchableOpacity, ActivityIndicator, Text, View, TextInput, Image, Modal, ScrollView, Pressable } from 'react-native';
3
+ import Animated9, { Easing, useAnimatedStyle, interpolateColor, useSharedValue, withRepeat, withTiming, withSpring, useDerivedValue } from 'react-native-reanimated';
3
4
  import { verticalScale, scale, moderateVerticalScale, moderateScale } from 'react-native-size-matters';
4
5
  import AntDesign from '@expo/vector-icons/AntDesign';
5
6
  import Entypo from '@expo/vector-icons/Entypo';
@@ -9,7 +10,6 @@ import MaterialIcons from '@expo/vector-icons/MaterialIcons';
9
10
  import Ionicons from '@expo/vector-icons/Ionicons';
10
11
  import { AntDesign as AntDesign$1, FontAwesome5 as FontAwesome5$1, MaterialIcons as MaterialIcons$1, Entypo as Entypo$1, Feather as Feather$1 } from '@expo/vector-icons';
11
12
  import { LinearGradient } from 'expo-linear-gradient';
12
- import Animated12, { Easing, useSharedValue, useDerivedValue, withTiming, useAnimatedStyle } from 'react-native-reanimated';
13
13
  import RNSlider from '@react-native-community/slider';
14
14
  import { BottomSheetBackdrop, BottomSheetFooter, BottomSheetModal, BottomSheetScrollView, BottomSheetView } from '@gorhom/bottom-sheet';
15
15
  export { BottomSheetModalProvider, BottomSheetTextInput as SheetTextInput } from '@gorhom/bottom-sheet';
@@ -407,9 +407,87 @@ var TYPOGRAPHY = {
407
407
  letterSpacing: 0
408
408
  }
409
409
  };
410
+ var SPRINGS = {
411
+ /** Tight, premium press feel — Buttons, Toggle, Tabs triggers. */
412
+ pressIn: { stiffness: 600, damping: 35, mass: 0.8 },
413
+ pressOut: { stiffness: 280, damping: 22, mass: 0.8 },
414
+ /** Slightly softer for larger surfaces — Card, ListItem, MenuItem. */
415
+ surfacePressIn: { stiffness: 380, damping: 30, mass: 0.95 },
416
+ surfacePressOut: { stiffness: 220, damping: 20, mass: 0.95 },
417
+ /** Settled transitions for moving indicators — Tabs pill, Switch thumb. */
418
+ glide: { stiffness: 380, damping: 38, mass: 1 },
419
+ /** Elastic indicator — Switch thumb, RadioGroup dot. */
420
+ elastic: { stiffness: 320, damping: 22, mass: 0.7 }
421
+ };
422
+ var TIMINGS = {
423
+ /** Color/opacity transitions on toggles, checkboxes, switches. */
424
+ state: { duration: 160 },
425
+ /** Focus ring on inputs. */
426
+ focusIn: { duration: 140 },
427
+ focusOut: { duration: 100 },
428
+ /** Accordion / collapsible content. */
429
+ expand: { duration: 240 },
430
+ collapse: { duration: 200 },
431
+ /** Skeleton shimmer cycle (full pass). */
432
+ shimmer: { duration: 1400 }
433
+ };
434
+ var EASINGS = {
435
+ /** Material-style ease-out — natural deceleration for state changes. */
436
+ standard: Easing.bezier(0.2, 0, 0, 1),
437
+ /** Strong ease-out for expanding surfaces (Accordion open). */
438
+ expand: Easing.bezier(0.23, 1, 0.32, 1),
439
+ /** Quick ease-in for collapsing. */
440
+ collapse: Easing.in(Easing.ease)
441
+ };
442
+ var PRESS_SCALE = {
443
+ button: 0.95,
444
+ card: 0.98,
445
+ row: 0.97,
446
+ chip: 0.94
447
+ };
448
+ function useHover() {
449
+ const [hovered, setHovered] = useState(false);
450
+ const onMouseEnter = useCallback(() => setHovered(true), []);
451
+ const onMouseLeave = useCallback(() => setHovered(false), []);
452
+ if (Platform.OS !== "web") {
453
+ return { hovered: false, hoverHandlers: {} };
454
+ }
455
+ return { hovered, hoverHandlers: { onMouseEnter, onMouseLeave } };
456
+ }
457
+
458
+ // src/utils/usePressScale.ts
459
+ function usePressScale({
460
+ pressScale = PRESS_SCALE.button,
461
+ hoverScale = 1.02,
462
+ pressInSpring = SPRINGS.pressIn,
463
+ pressOutSpring = SPRINGS.pressOut,
464
+ disabled = false
465
+ } = {}) {
466
+ const scale2 = useSharedValue(1);
467
+ const { hovered, hoverHandlers } = useHover();
468
+ const onPressIn = useCallback(() => {
469
+ if (disabled) return;
470
+ scale2.value = withSpring(pressScale, pressInSpring);
471
+ }, [disabled, pressScale, pressInSpring, scale2]);
472
+ const onPressOut = useCallback(() => {
473
+ if (disabled) return;
474
+ scale2.value = withSpring(1, pressOutSpring);
475
+ }, [disabled, pressOutSpring, scale2]);
476
+ const hoverActive = Platform.OS === "web" && hovered && hoverScale !== 1 && !disabled;
477
+ const animatedStyle = useAnimatedStyle(() => ({
478
+ transform: [
479
+ { scale: scale2.value * (hoverActive ? hoverScale : 1) }
480
+ ]
481
+ }));
482
+ return {
483
+ animatedStyle,
484
+ onPressIn,
485
+ onPressOut,
486
+ hoverHandlers
487
+ };
488
+ }
410
489
 
411
490
  // src/components/Button/Button.tsx
412
- var nativeDriver = Platform.OS !== "web";
413
491
  var containerSizeStyles = {
414
492
  sm: { paddingHorizontal: s(16), paddingVertical: vs(10), minHeight: 40 },
415
493
  md: { paddingHorizontal: s(24), paddingVertical: vs(14), minHeight: 48 },
@@ -434,18 +512,16 @@ function Button({
434
512
  disabled,
435
513
  style,
436
514
  onPress,
515
+ accessibilityLabel,
516
+ accessibilityHint,
437
517
  ...props
438
518
  }) {
439
519
  const { colors } = useTheme();
440
520
  const isDisabled = disabled || loading;
441
- const scale2 = useRef(new Animated.Value(1)).current;
442
- const handlePressIn = () => {
443
- if (isDisabled) return;
444
- Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver, stiffness: 600, damping: 35, mass: 0.8 }).start();
445
- };
446
- const handlePressOut = () => {
447
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver, stiffness: 280, damping: 22, mass: 0.8 }).start();
448
- };
521
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
522
+ pressScale: PRESS_SCALE.button,
523
+ disabled: isDisabled
524
+ });
449
525
  const handlePress = (e) => {
450
526
  impactMedium();
451
527
  onPress?.(e);
@@ -468,43 +544,54 @@ function Button({
468
544
  const styleArray = Array.isArray(style) ? style : style ? [style] : [];
469
545
  const flatStyle = StyleSheet.flatten(styleArray);
470
546
  const { flex, ...restStyle } = flatStyle || {};
471
- return /* @__PURE__ */ React26.createElement(Animated.View, { style: [fullWidth && styles.fullWidth, flex !== void 0 && { flex }, { transform: [{ scale: scale2 }] }] }, /* @__PURE__ */ React26.createElement(
472
- TouchableOpacity,
547
+ return /* @__PURE__ */ React26.createElement(
548
+ Animated9.View,
473
549
  {
474
- style: [
475
- styles.base,
476
- containerVariantStyle,
477
- containerSizeStyles[size],
478
- fullWidth && styles.fullWidth,
479
- isDisabled && styles.disabled,
480
- restStyle
481
- ],
482
- disabled: isDisabled,
483
- activeOpacity: 1,
484
- touchSoundDisabled: true,
485
- onPress: handlePress,
486
- onPressIn: handlePressIn,
487
- onPressOut: handlePressOut,
488
- ...props
550
+ style: [fullWidth && styles.fullWidth, flex !== void 0 && { flex }, animatedStyle],
551
+ ...hoverHandlers
489
552
  },
490
- loading ? /* @__PURE__ */ React26.createElement(React26.Fragment, null, /* @__PURE__ */ React26.createElement(ActivityIndicator, { size: "small", color: spinnerColor, style: { marginRight: s(6) } }), /* @__PURE__ */ React26.createElement(
491
- Text,
492
- {
493
- style: [styles.label, labelVariantStyle, labelSizeStyles[size], styles.labelLoading],
494
- allowFontScaling: true,
495
- numberOfLines: 1
496
- },
497
- label
498
- )) : /* @__PURE__ */ React26.createElement(React26.Fragment, null, effectiveIcon && iconPosition === "left" && /* @__PURE__ */ React26.createElement(React26.Fragment, null, effectiveIcon), /* @__PURE__ */ React26.createElement(
499
- Text,
553
+ /* @__PURE__ */ React26.createElement(
554
+ TouchableOpacity,
500
555
  {
501
- style: [styles.label, labelVariantStyle, labelSizeStyles[size], effectiveIcon ? styles.labelWithIcon : void 0],
502
- allowFontScaling: true,
503
- numberOfLines: 1
556
+ style: [
557
+ styles.base,
558
+ containerVariantStyle,
559
+ containerSizeStyles[size],
560
+ fullWidth && styles.fullWidth,
561
+ isDisabled && styles.disabled,
562
+ restStyle
563
+ ],
564
+ disabled: isDisabled,
565
+ activeOpacity: 1,
566
+ touchSoundDisabled: true,
567
+ onPress: handlePress,
568
+ onPressIn,
569
+ onPressOut,
570
+ accessibilityRole: "button",
571
+ accessibilityLabel: accessibilityLabel ?? label,
572
+ accessibilityHint,
573
+ accessibilityState: { disabled: isDisabled, busy: loading },
574
+ ...props
504
575
  },
505
- label
506
- ), effectiveIcon && iconPosition === "right" && /* @__PURE__ */ React26.createElement(React26.Fragment, null, effectiveIcon))
507
- ));
576
+ loading ? /* @__PURE__ */ React26.createElement(React26.Fragment, null, /* @__PURE__ */ React26.createElement(ActivityIndicator, { size: "small", color: spinnerColor, style: { marginRight: s(6) } }), /* @__PURE__ */ React26.createElement(
577
+ Text,
578
+ {
579
+ style: [styles.label, labelVariantStyle, labelSizeStyles[size], styles.labelLoading],
580
+ allowFontScaling: true,
581
+ numberOfLines: 1
582
+ },
583
+ label
584
+ )) : /* @__PURE__ */ React26.createElement(React26.Fragment, null, effectiveIcon && iconPosition === "left" && /* @__PURE__ */ React26.createElement(React26.Fragment, null, effectiveIcon), /* @__PURE__ */ React26.createElement(
585
+ Text,
586
+ {
587
+ style: [styles.label, labelVariantStyle, labelSizeStyles[size], effectiveIcon ? styles.labelWithIcon : void 0],
588
+ allowFontScaling: true,
589
+ numberOfLines: 1
590
+ },
591
+ label
592
+ ), effectiveIcon && iconPosition === "right" && /* @__PURE__ */ React26.createElement(React26.Fragment, null, effectiveIcon))
593
+ )
594
+ );
508
595
  }
509
596
  var styles = StyleSheet.create({
510
597
  base: {
@@ -564,7 +651,6 @@ var styles2 = StyleSheet.create({
564
651
  flexDirection: "column"
565
652
  }
566
653
  });
567
- var nativeDriver2 = Platform.OS !== "web";
568
654
  var sizeMap = {
569
655
  sm: { container: s(32), icon: 16 },
570
656
  md: { container: s(44), icon: 20 },
@@ -581,18 +667,16 @@ function IconButton({
581
667
  disabled,
582
668
  style,
583
669
  onPress,
670
+ accessibilityLabel,
671
+ accessibilityHint,
584
672
  ...props
585
673
  }) {
586
674
  const { colors } = useTheme();
587
675
  const isDisabled = disabled || loading;
588
- const scale2 = useRef(new Animated.Value(1)).current;
589
- const handlePressIn = () => {
590
- if (isDisabled) return;
591
- Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver2, speed: 40, bounciness: 0 }).start();
592
- };
593
- const handlePressOut = () => {
594
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver2, speed: 40, bounciness: 4 }).start();
595
- };
676
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
677
+ pressScale: PRESS_SCALE.button,
678
+ disabled: isDisabled
679
+ });
596
680
  const handlePress = (e) => {
597
681
  impactLight();
598
682
  onPress?.(e);
@@ -617,30 +701,42 @@ function IconButton({
617
701
  const showBadge = badge !== void 0 && badge !== false && badge !== 0;
618
702
  const badgeCount = typeof badge === "number" ? Math.min(badge, 99) : null;
619
703
  const showCount = typeof badge === "number" && badge > 0;
620
- return /* @__PURE__ */ React26.createElement(Animated.View, { style: [styles3.wrapper, { transform: [{ scale: scale2 }] }] }, /* @__PURE__ */ React26.createElement(
621
- TouchableOpacity,
704
+ return /* @__PURE__ */ React26.createElement(
705
+ Animated9.View,
622
706
  {
623
- style: [
624
- styles3.base,
625
- containerVariantStyle,
626
- { width: containerSize, height: containerSize },
627
- isDisabled && styles3.disabled,
628
- style
629
- ],
630
- disabled: isDisabled,
631
- activeOpacity: 1,
632
- touchSoundDisabled: true,
633
- onPress: handlePress,
634
- onPressIn: handlePressIn,
635
- onPressOut: handlePressOut,
636
- ...props
707
+ style: [styles3.wrapper, animatedStyle],
708
+ ...hoverHandlers
637
709
  },
638
- loading ? /* @__PURE__ */ React26.createElement(ActivityIndicator, { size: "small", color: spinnerColor }) : resolvedIcon
639
- ), showBadge && /* @__PURE__ */ React26.createElement(View, { style: [
640
- styles3.badge,
641
- { backgroundColor: colors.primary },
642
- showCount ? styles3.badgeCount : styles3.badgeDot
643
- ] }, showCount && /* @__PURE__ */ React26.createElement(Text, { style: [styles3.badgeText, { color: colors.primaryForeground }] }, badgeCount)));
710
+ /* @__PURE__ */ React26.createElement(
711
+ TouchableOpacity,
712
+ {
713
+ style: [
714
+ styles3.base,
715
+ containerVariantStyle,
716
+ { width: containerSize, height: containerSize },
717
+ isDisabled && styles3.disabled,
718
+ style
719
+ ],
720
+ disabled: isDisabled,
721
+ activeOpacity: 1,
722
+ touchSoundDisabled: true,
723
+ onPress: handlePress,
724
+ onPressIn,
725
+ onPressOut,
726
+ accessibilityRole: "button",
727
+ accessibilityLabel: accessibilityLabel ?? iconName ?? "icon button",
728
+ accessibilityHint,
729
+ accessibilityState: { disabled: isDisabled, busy: loading },
730
+ ...props
731
+ },
732
+ loading ? /* @__PURE__ */ React26.createElement(ActivityIndicator, { size: "small", color: spinnerColor }) : resolvedIcon
733
+ ),
734
+ showBadge && /* @__PURE__ */ React26.createElement(View, { style: [
735
+ styles3.badge,
736
+ { backgroundColor: colors.primary },
737
+ showCount ? styles3.badgeCount : styles3.badgeDot
738
+ ] }, showCount && /* @__PURE__ */ React26.createElement(Text, { style: [styles3.badgeText, { color: colors.primaryForeground }] }, badgeCount))
739
+ );
644
740
  }
645
741
  var styles3 = StyleSheet.create({
646
742
  wrapper: {
@@ -729,29 +825,49 @@ function Text3({ variant = "body-md", color, style, children, ...props }) {
729
825
  children
730
826
  );
731
827
  }
828
+ function useColorTransition(active, options = {}) {
829
+ const { duration = TIMINGS.state.duration } = options;
830
+ const progress = useSharedValue(active ? 1 : 0);
831
+ useEffect(() => {
832
+ progress.value = withTiming(active ? 1 : 0, { duration, easing: EASINGS.standard });
833
+ }, [active, duration, progress]);
834
+ return progress;
835
+ }
836
+
837
+ // src/components/Input/Input.tsx
732
838
  var webInputResetStyle = Platform.OS === "web" ? { outlineStyle: "none", outlineWidth: 0, outlineColor: "transparent", boxShadow: "none" } : {};
733
- function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suffixStyle, prefixIcon, suffixIcon, prefixIconColor, suffixIconColor, type = "text", containerStyle, inputWrapperStyle, style, onFocus, onBlur, secureTextEntry, editable, ...props }) {
839
+ 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 }) {
734
840
  const { colors } = useTheme();
735
841
  const [focused, setFocused] = useState(false);
736
842
  const [showPassword, setShowPassword] = useState(false);
737
- const focusAnim = useRef(new Animated.Value(0)).current;
843
+ const focusProgress = useColorTransition(focused, {
844
+ duration: focused ? TIMINGS.focusIn.duration : TIMINGS.focusOut.duration
845
+ });
738
846
  const isDisabled = disabled || editable === false;
739
847
  const isPassword = type === "password";
740
848
  const effectiveSecure = isPassword ? !showPassword : secureTextEntry;
741
849
  const effectivePrefix = prefixIcon ? renderIcon(prefixIcon, 20, prefixIconColor ?? colors.foregroundMuted) : prefix;
742
- const effectiveSuffix = isPassword && !suffix && !suffixIcon ? /* @__PURE__ */ React26.createElement(TouchableOpacity, { onPress: () => setShowPassword(!showPassword), style: styles4.passwordToggle, activeOpacity: 0.6 }, /* @__PURE__ */ React26.createElement(AntDesign$1, { name: showPassword ? "eye" : "eye-invisible", size: 20, color: colors.foregroundMuted })) : suffixIcon ? renderIcon(suffixIcon, 20, suffixIconColor ?? colors.foregroundMuted) : suffix;
850
+ const effectiveSuffix = isPassword && !suffix && !suffixIcon ? /* @__PURE__ */ React26.createElement(
851
+ TouchableOpacity,
852
+ {
853
+ onPress: () => setShowPassword(!showPassword),
854
+ style: styles4.passwordToggle,
855
+ activeOpacity: 0.6,
856
+ accessibilityRole: "button",
857
+ accessibilityLabel: showPassword ? "Hide password" : "Show password"
858
+ },
859
+ /* @__PURE__ */ React26.createElement(AntDesign$1, { name: showPassword ? "eye" : "eye-invisible", size: 20, color: colors.foregroundMuted })
860
+ ) : suffixIcon ? renderIcon(suffixIcon, 20, suffixIconColor ?? colors.foregroundMuted) : suffix;
861
+ const borderColorStyle = useAnimatedStyle(() => ({
862
+ borderColor: error ? colors.destructive : interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary])
863
+ }));
743
864
  return /* @__PURE__ */ React26.createElement(View, { style: [styles4.container, isDisabled && styles4.containerDisabled, containerStyle] }, label ? /* @__PURE__ */ React26.createElement(Text, { style: [styles4.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, /* @__PURE__ */ React26.createElement(
744
- Animated.View,
865
+ Animated9.View,
745
866
  {
746
867
  style: [
747
868
  styles4.inputWrapper,
748
- {
749
- borderColor: error ? colors.destructive : focusAnim.interpolate({
750
- inputRange: [0, 1],
751
- outputRange: [colors.border, colors.primary]
752
- }),
753
- backgroundColor: isDisabled ? colors.surface : colors.background
754
- },
869
+ { backgroundColor: isDisabled ? colors.surface : colors.background },
870
+ borderColorStyle,
755
871
  inputWrapperStyle
756
872
  ]
757
873
  },
@@ -761,31 +877,36 @@ function Input({ label, error, hint, disabled, prefix, suffix, prefixStyle, suff
761
877
  {
762
878
  style: [
763
879
  styles4.input,
764
- {
765
- color: colors.foreground
766
- },
880
+ { color: colors.foreground },
767
881
  webInputResetStyle,
768
882
  style
769
883
  ],
770
884
  onFocus: (e) => {
771
885
  setFocused(true);
772
- Animated.timing(focusAnim, { toValue: 1, duration: 120, easing: Easing$1.out(Easing$1.ease), useNativeDriver: false }).start();
773
886
  onFocus?.(e);
774
887
  },
775
888
  onBlur: (e) => {
776
889
  setFocused(false);
777
- Animated.timing(focusAnim, { toValue: 0, duration: 80, easing: Easing$1.out(Easing$1.ease), useNativeDriver: false }).start();
778
890
  onBlur?.(e);
779
891
  },
780
892
  placeholderTextColor: colors.foregroundMuted,
781
893
  allowFontScaling: true,
782
894
  secureTextEntry: effectiveSecure,
783
895
  editable: isDisabled ? false : editable,
896
+ accessibilityLabel: accessibilityLabel ?? label,
784
897
  ...props
785
898
  }
786
899
  ),
787
900
  effectiveSuffix ? typeof effectiveSuffix === "string" ? /* @__PURE__ */ React26.createElement(Text, { style: [styles4.suffixText, { color: colors.foregroundMuted }, suffixStyle], allowFontScaling: true }, effectiveSuffix) : /* @__PURE__ */ React26.createElement(View, { style: styles4.suffixContainer }, effectiveSuffix) : null
788
- ), error ? /* @__PURE__ */ React26.createElement(Text, { style: [styles4.helperText, { color: colors.destructive }], allowFontScaling: true }, error) : null, !error && hint ? /* @__PURE__ */ React26.createElement(Text, { style: [styles4.helperText, { color: colors.foregroundMuted }], allowFontScaling: true }, hint) : null);
901
+ ), error ? /* @__PURE__ */ React26.createElement(
902
+ Text,
903
+ {
904
+ style: [styles4.helperText, { color: colors.destructive }],
905
+ allowFontScaling: true,
906
+ accessibilityLiveRegion: "polite"
907
+ },
908
+ error
909
+ ) : null, !error && hint ? /* @__PURE__ */ React26.createElement(Text, { style: [styles4.helperText, { color: colors.foregroundMuted }], allowFontScaling: true }, hint) : null);
789
910
  }
790
911
  var styles4 = StyleSheet.create({
791
912
  container: {
@@ -797,7 +918,6 @@ var styles4 = StyleSheet.create({
797
918
  label: {
798
919
  fontFamily: "Poppins-Medium",
799
920
  fontSize: ms(14)
800
- // caption size for input labels
801
921
  },
802
922
  inputWrapper: {
803
923
  flexDirection: "row",
@@ -894,30 +1014,14 @@ var styles5 = StyleSheet.create({
894
1014
  fontFamily: "Poppins-Medium"
895
1015
  }
896
1016
  });
897
- var nativeDriver3 = Platform.OS !== "web";
898
- function Card({ children, variant = "elevated", onPress, style }) {
1017
+ function Card({ children, variant = "elevated", onPress, style, accessibilityLabel }) {
899
1018
  const { colors } = useTheme();
900
- const scale2 = useRef(new Animated.Value(1)).current;
901
- const handlePressIn = () => {
902
- if (!onPress) return;
903
- Animated.spring(scale2, {
904
- toValue: 0.98,
905
- useNativeDriver: nativeDriver3,
906
- stiffness: 400,
907
- damping: 30,
908
- mass: 1
909
- }).start();
910
- };
911
- const handlePressOut = () => {
912
- if (!onPress) return;
913
- Animated.spring(scale2, {
914
- toValue: 1,
915
- useNativeDriver: nativeDriver3,
916
- stiffness: 250,
917
- damping: 24,
918
- mass: 1
919
- }).start();
920
- };
1019
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
1020
+ pressScale: PRESS_SCALE.card,
1021
+ pressInSpring: SPRINGS.surfacePressIn,
1022
+ pressOutSpring: SPRINGS.surfacePressOut,
1023
+ disabled: !onPress
1024
+ });
921
1025
  const handlePress = () => {
922
1026
  if (!onPress) return;
923
1027
  impactLight();
@@ -948,14 +1052,16 @@ function Card({ children, variant = "elevated", onPress, style }) {
948
1052
  }[variant];
949
1053
  const cardContent = /* @__PURE__ */ React26.createElement(View, { style: [styles6.card, variantStyle, style] }, children);
950
1054
  if (onPress) {
951
- return /* @__PURE__ */ React26.createElement(Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26.createElement(
1055
+ return /* @__PURE__ */ React26.createElement(Animated9.View, { style: animatedStyle, ...hoverHandlers }, /* @__PURE__ */ React26.createElement(
952
1056
  TouchableOpacity,
953
1057
  {
954
1058
  onPress: handlePress,
955
- onPressIn: handlePressIn,
956
- onPressOut: handlePressOut,
1059
+ onPressIn,
1060
+ onPressOut,
957
1061
  activeOpacity: 1,
958
- touchSoundDisabled: true
1062
+ touchSoundDisabled: true,
1063
+ accessibilityRole: "button",
1064
+ accessibilityLabel
959
1065
  },
960
1066
  cardContent
961
1067
  ));
@@ -982,7 +1088,6 @@ function CardFooter({ children, style }) {
982
1088
  var styles6 = StyleSheet.create({
983
1089
  card: {
984
1090
  borderRadius: RADIUS.md,
985
- // 14px — Airbnb property card spec
986
1091
  borderWidth: 1
987
1092
  },
988
1093
  header: {
@@ -1080,20 +1185,19 @@ function Skeleton({
1080
1185
  style
1081
1186
  }) {
1082
1187
  const { colors, colorScheme } = useTheme();
1083
- const shimmerAnim = useRef(new Animated.Value(0)).current;
1188
+ const shimmer = useSharedValue(0);
1084
1189
  const [containerWidth, setContainerWidth] = useState(300);
1085
1190
  const shimmerHighlight = colorScheme === "dark" ? "rgba(255,255,255,0.08)" : "rgba(255,255,255,0.7)";
1086
1191
  useEffect(() => {
1087
- const animation = Animated.loop(
1088
- Animated.timing(shimmerAnim, { toValue: 1, duration: 1200, useNativeDriver: true })
1192
+ shimmer.value = withRepeat(
1193
+ withTiming(1, { duration: TIMINGS.shimmer.duration, easing: Easing.linear }),
1194
+ -1,
1195
+ false
1089
1196
  );
1090
- animation.start();
1091
- return () => animation.stop();
1092
- }, [shimmerAnim]);
1093
- const translateX = shimmerAnim.interpolate({
1094
- inputRange: [0, 1],
1095
- outputRange: [-containerWidth, containerWidth]
1096
- });
1197
+ }, [shimmer]);
1198
+ const shimmerStyle = useAnimatedStyle(() => ({
1199
+ transform: [{ translateX: -containerWidth + shimmer.value * (containerWidth * 2) }]
1200
+ }));
1097
1201
  const resolvedWidth = preset === "circle" ? s(diameter) : preset === "text" ? "60%" : width;
1098
1202
  const resolvedHeight = preset === "circle" ? s(diameter) : preset === "text" ? 14 : height;
1099
1203
  const resolvedRadius = preset === "circle" ? 9999 : preset === "text" ? 4 : borderRadius;
@@ -1105,9 +1209,12 @@ function Skeleton({
1105
1209
  { width: resolvedWidth, height: resolvedHeight, borderRadius: resolvedRadius, backgroundColor: colors.surface },
1106
1210
  style
1107
1211
  ],
1108
- onLayout: (e) => setContainerWidth(e.nativeEvent.layout.width)
1212
+ onLayout: (e) => setContainerWidth(e.nativeEvent.layout.width),
1213
+ accessibilityRole: "progressbar",
1214
+ accessibilityLabel: "Loading",
1215
+ accessibilityState: { busy: true }
1109
1216
  },
1110
- /* @__PURE__ */ React26.createElement(Animated.View, { style: [StyleSheet.absoluteFill, { transform: [{ translateX }] }] }, /* @__PURE__ */ React26.createElement(
1217
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: [StyleSheet.absoluteFill, shimmerStyle] }, /* @__PURE__ */ React26.createElement(
1111
1218
  LinearGradient,
1112
1219
  {
1113
1220
  colors: ["transparent", shimmerHighlight, "transparent"],
@@ -1253,31 +1360,32 @@ var styles11 = StyleSheet.create({
1253
1360
  lineHeight: ms(17)
1254
1361
  }
1255
1362
  });
1256
- function Progress({ value = 0, max = 100, variant = "default", style }) {
1363
+ function Progress({ value = 0, max = 100, variant = "default", style, accessibilityLabel }) {
1257
1364
  const { colors } = useTheme();
1258
1365
  const percent = Math.min(Math.max(value / max * 100, 0), 100);
1259
1366
  const [trackWidth, setTrackWidth] = useState(0);
1260
- const animatedWidth = useRef(new Animated.Value(0)).current;
1367
+ const animatedWidth = useSharedValue(0);
1261
1368
  useEffect(() => {
1262
1369
  if (trackWidth === 0) return;
1263
- Animated.spring(animatedWidth, {
1264
- toValue: percent / 100 * trackWidth,
1265
- useNativeDriver: false,
1266
- speed: 20,
1267
- bounciness: 0
1268
- }).start();
1269
- }, [percent, trackWidth]);
1370
+ animatedWidth.value = withSpring(percent / 100 * trackWidth, SPRINGS.glide);
1371
+ }, [percent, trackWidth, animatedWidth]);
1372
+ const indicatorAnimatedStyle = useAnimatedStyle(() => ({
1373
+ width: animatedWidth.value
1374
+ }));
1270
1375
  const indicatorColor = variant === "success" ? colors.success : variant === "warning" ? colors.warning : variant === "destructive" ? colors.destructive : colors.primary;
1271
1376
  return /* @__PURE__ */ React26.createElement(
1272
1377
  View,
1273
1378
  {
1274
1379
  style: [styles12.track, { backgroundColor: colors.surface }, style],
1275
- onLayout: (e) => setTrackWidth(e.nativeEvent.layout.width)
1380
+ onLayout: (e) => setTrackWidth(e.nativeEvent.layout.width),
1381
+ accessibilityRole: "progressbar",
1382
+ accessibilityLabel,
1383
+ accessibilityValue: { min: 0, max: 100, now: Math.round(percent) }
1276
1384
  },
1277
1385
  /* @__PURE__ */ React26.createElement(
1278
- Animated.View,
1386
+ Animated9.View,
1279
1387
  {
1280
- style: [styles12.indicator, { width: animatedWidth, backgroundColor: indicatorColor }]
1388
+ style: [styles12.indicator, { backgroundColor: indicatorColor }, indicatorAnimatedStyle]
1281
1389
  }
1282
1390
  )
1283
1391
  );
@@ -1394,20 +1502,25 @@ function Textarea({
1394
1502
  style,
1395
1503
  onFocus,
1396
1504
  onBlur,
1505
+ accessibilityLabel,
1397
1506
  ...props
1398
1507
  }) {
1399
1508
  const { colors } = useTheme();
1400
1509
  const [focused, setFocused] = useState(false);
1510
+ const focusProgress = useColorTransition(focused, {
1511
+ duration: focused ? TIMINGS.focusIn.duration : TIMINGS.focusOut.duration
1512
+ });
1401
1513
  const resolvedPrefixIcon = prefixIcon ? renderIcon(prefixIcon, ms(16), prefixIconColor ?? colors.foregroundMuted) : prefixIconNode;
1514
+ const borderColorStyle = useAnimatedStyle(() => ({
1515
+ borderColor: error ? colors.destructive : interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary])
1516
+ }));
1402
1517
  return /* @__PURE__ */ React26.createElement(View, { style: [styles14.container, containerStyle] }, label ? /* @__PURE__ */ React26.createElement(Text, { style: [styles14.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, /* @__PURE__ */ React26.createElement(
1403
- View,
1518
+ Animated9.View,
1404
1519
  {
1405
1520
  style: [
1406
1521
  styles14.inputWrapper,
1407
- {
1408
- borderColor: error ? colors.destructive : focused ? colors.ring ?? colors.primary : colors.border,
1409
- backgroundColor: colors.background
1410
- }
1522
+ { backgroundColor: colors.background },
1523
+ borderColorStyle
1411
1524
  ]
1412
1525
  },
1413
1526
  resolvedPrefixIcon ? /* @__PURE__ */ React26.createElement(View, { style: styles14.prefixIcon }, resolvedPrefixIcon) : null,
@@ -1436,10 +1549,19 @@ function Textarea({
1436
1549
  },
1437
1550
  placeholderTextColor: colors.foregroundMuted,
1438
1551
  allowFontScaling: true,
1552
+ accessibilityLabel: accessibilityLabel ?? label,
1439
1553
  ...props
1440
1554
  }
1441
1555
  )
1442
- ), error ? /* @__PURE__ */ React26.createElement(Text, { style: [styles14.helperText, { color: colors.destructive }], allowFontScaling: true }, error) : null, !error && hint ? /* @__PURE__ */ React26.createElement(Text, { style: [styles14.helperText, { color: colors.foregroundMuted }], allowFontScaling: true }, hint) : null);
1556
+ ), error ? /* @__PURE__ */ React26.createElement(
1557
+ Text,
1558
+ {
1559
+ style: [styles14.helperText, { color: colors.destructive }],
1560
+ allowFontScaling: true,
1561
+ accessibilityLiveRegion: "polite"
1562
+ },
1563
+ error
1564
+ ) : null, !error && hint ? /* @__PURE__ */ React26.createElement(Text, { style: [styles14.helperText, { color: colors.foregroundMuted }], allowFontScaling: true }, hint) : null);
1443
1565
  }
1444
1566
  var styles14 = StyleSheet.create({
1445
1567
  container: {
@@ -1452,7 +1574,7 @@ var styles14 = StyleSheet.create({
1452
1574
  marginBottom: vs(2)
1453
1575
  },
1454
1576
  inputWrapper: {
1455
- borderWidth: 1,
1577
+ borderWidth: 2,
1456
1578
  borderRadius: 8,
1457
1579
  paddingHorizontal: s(14),
1458
1580
  paddingVertical: vs(11),
@@ -1477,47 +1599,27 @@ var styles14 = StyleSheet.create({
1477
1599
  marginTop: vs(4)
1478
1600
  }
1479
1601
  });
1480
- var nativeDriver4 = Platform.OS !== "web";
1481
1602
  function Checkbox({
1482
1603
  checked = false,
1483
1604
  onCheckedChange,
1484
1605
  label,
1485
1606
  disabled,
1486
- style
1607
+ style,
1608
+ accessibilityLabel
1487
1609
  }) {
1488
1610
  const { colors } = useTheme();
1489
- const scale2 = useRef(new Animated.Value(1)).current;
1490
- const bgOpacity = useRef(new Animated.Value(checked ? 1 : 0)).current;
1491
- const checkOpacity = useRef(new Animated.Value(checked ? 1 : 0)).current;
1492
- useEffect(() => {
1493
- Animated.parallel([
1494
- Animated.timing(bgOpacity, {
1495
- toValue: checked ? 1 : 0,
1496
- duration: 150,
1497
- useNativeDriver: false
1498
- }),
1499
- Animated.timing(checkOpacity, {
1500
- toValue: checked ? 1 : 0,
1501
- duration: 120,
1502
- useNativeDriver: false
1503
- })
1504
- ]).start();
1505
- }, [checked, bgOpacity, checkOpacity]);
1506
- const borderColor = bgOpacity.interpolate({
1507
- inputRange: [0, 1],
1508
- outputRange: [colors.border, colors.primary]
1611
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut } = usePressScale({
1612
+ pressScale: PRESS_SCALE.button,
1613
+ disabled
1509
1614
  });
1510
- const backgroundColor = bgOpacity.interpolate({
1511
- inputRange: [0, 1],
1512
- outputRange: ["transparent", colors.primary]
1513
- });
1514
- const handlePressIn = () => {
1515
- if (disabled) return;
1516
- Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver4, speed: 40, bounciness: 0 }).start();
1517
- };
1518
- const handlePressOut = () => {
1519
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver4, speed: 40, bounciness: 0 }).start();
1520
- };
1615
+ const progress = useColorTransition(checked);
1616
+ const boxStyle = useAnimatedStyle(() => ({
1617
+ borderColor: interpolateColor(progress.value, [0, 1], [colors.border, colors.primary]),
1618
+ backgroundColor: interpolateColor(progress.value, [0, 1], ["transparent", colors.primary])
1619
+ }));
1620
+ const checkStyle = useAnimatedStyle(() => ({
1621
+ opacity: withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard })
1622
+ }));
1521
1623
  return /* @__PURE__ */ React26.createElement(
1522
1624
  TouchableOpacity,
1523
1625
  {
@@ -1526,25 +1628,21 @@ function Checkbox({
1526
1628
  selectionAsync();
1527
1629
  onCheckedChange?.(!checked);
1528
1630
  },
1529
- onPressIn: handlePressIn,
1530
- onPressOut: handlePressOut,
1631
+ onPressIn,
1632
+ onPressOut,
1531
1633
  disabled,
1532
1634
  activeOpacity: 1,
1533
- touchSoundDisabled: true
1635
+ touchSoundDisabled: true,
1636
+ accessibilityRole: "checkbox",
1637
+ accessibilityLabel: accessibilityLabel ?? label,
1638
+ accessibilityState: { checked, disabled: !!disabled }
1534
1639
  },
1535
- /* @__PURE__ */ React26.createElement(Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26.createElement(
1536
- Animated.View,
1640
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: scaleStyle }, /* @__PURE__ */ React26.createElement(
1641
+ Animated9.View,
1537
1642
  {
1538
- style: [
1539
- styles15.box,
1540
- {
1541
- borderColor,
1542
- backgroundColor,
1543
- opacity: disabled ? 0.45 : 1
1544
- }
1545
- ]
1643
+ style: [styles15.box, { opacity: disabled ? 0.45 : 1 }, boxStyle]
1546
1644
  },
1547
- /* @__PURE__ */ React26.createElement(Animated.View, { style: { opacity: checkOpacity } }, /* @__PURE__ */ React26.createElement(View, { style: [styles15.checkmark, { borderColor: colors.primaryForeground }] }))
1645
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: checkStyle }, /* @__PURE__ */ React26.createElement(View, { style: [styles15.checkmark, { borderColor: colors.primaryForeground }] }))
1548
1646
  )),
1549
1647
  label ? /* @__PURE__ */ React26.createElement(
1550
1648
  Text,
@@ -1583,47 +1681,34 @@ var styles15 = StyleSheet.create({
1583
1681
  lineHeight: mvs(20)
1584
1682
  }
1585
1683
  });
1586
- var nativeDriver5 = Platform.OS !== "web";
1587
1684
  var TRACK_WIDTH = s(52);
1588
1685
  var TRACK_HEIGHT = s(30);
1589
1686
  var THUMB_SIZE = s(24);
1590
1687
  var THUMB_OFFSET = s(3);
1591
1688
  var THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2;
1592
1689
  var ICON_SIZE = s(13);
1593
- function Switch({ checked = false, onCheckedChange, disabled, style }) {
1690
+ function Switch({ checked = false, onCheckedChange, disabled, style, accessibilityLabel }) {
1594
1691
  const { colors } = useTheme();
1595
- const translateX = useRef(new Animated.Value(checked ? THUMB_TRAVEL : 0)).current;
1596
- const trackOpacity = useRef(new Animated.Value(checked ? 1 : 0)).current;
1597
- const checkOpacity = useRef(new Animated.Value(checked ? 1 : 0)).current;
1598
- const crossOpacity = useRef(new Animated.Value(checked ? 0 : 1)).current;
1692
+ const progress = useSharedValue(checked ? 1 : 0);
1599
1693
  useEffect(() => {
1600
- Animated.parallel([
1601
- Animated.spring(translateX, {
1602
- toValue: checked ? THUMB_TRAVEL : 0,
1603
- useNativeDriver: nativeDriver5,
1604
- bounciness: 4
1605
- }),
1606
- Animated.timing(trackOpacity, {
1607
- toValue: checked ? 1 : 0,
1608
- duration: 150,
1609
- useNativeDriver: false
1610
- }),
1611
- Animated.timing(checkOpacity, {
1612
- toValue: checked ? 1 : 0,
1613
- duration: 120,
1614
- useNativeDriver: true
1615
- }),
1616
- Animated.timing(crossOpacity, {
1617
- toValue: checked ? 0 : 1,
1618
- duration: 120,
1619
- useNativeDriver: true
1620
- })
1621
- ]).start();
1622
- }, [checked, translateX, trackOpacity, checkOpacity, crossOpacity]);
1623
- const trackColor = trackOpacity.interpolate({
1624
- inputRange: [0, 1],
1625
- outputRange: [colors.surface, colors.primary]
1626
- });
1694
+ progress.value = withSpring(checked ? 1 : 0, SPRINGS.elastic);
1695
+ }, [checked, progress]);
1696
+ const thumbStyle = useAnimatedStyle(() => ({
1697
+ transform: [{ translateX: progress.value * THUMB_TRAVEL }]
1698
+ }));
1699
+ const trackStyle = useAnimatedStyle(() => ({
1700
+ backgroundColor: interpolateColor(
1701
+ progress.value,
1702
+ [0, 1],
1703
+ [colors.surfaceStrong, colors.primary]
1704
+ )
1705
+ }));
1706
+ const checkIconStyle = useAnimatedStyle(() => ({
1707
+ opacity: withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard })
1708
+ }));
1709
+ const crossIconStyle = useAnimatedStyle(() => ({
1710
+ opacity: withTiming(checked ? 0 : 1, { duration: TIMINGS.state.duration, easing: EASINGS.standard })
1711
+ }));
1627
1712
  return /* @__PURE__ */ React26.createElement(View, { style: [{ opacity: disabled ? 0.45 : 1 }, style] }, /* @__PURE__ */ React26.createElement(
1628
1713
  TouchableOpacity,
1629
1714
  {
@@ -1634,29 +1719,25 @@ function Switch({ checked = false, onCheckedChange, disabled, style }) {
1634
1719
  disabled,
1635
1720
  activeOpacity: 0.8,
1636
1721
  touchSoundDisabled: true,
1637
- style: styles16.wrapper
1722
+ accessibilityRole: "switch",
1723
+ accessibilityLabel,
1724
+ accessibilityState: { checked, disabled: !!disabled }
1638
1725
  },
1639
- /* @__PURE__ */ React26.createElement(Animated.View, { style: [styles16.track, { backgroundColor: trackColor }] }, /* @__PURE__ */ React26.createElement(
1640
- Animated.View,
1726
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: [styles16.track, trackStyle] }, /* @__PURE__ */ React26.createElement(
1727
+ Animated9.View,
1641
1728
  {
1642
- style: [
1643
- styles16.thumb,
1644
- { backgroundColor: colors.primaryForeground, transform: [{ translateX }] }
1645
- ]
1729
+ style: [styles16.thumb, { backgroundColor: colors.primaryForeground }, thumbStyle]
1646
1730
  },
1647
- /* @__PURE__ */ React26.createElement(Animated.View, { style: [styles16.iconWrapper, { opacity: checkOpacity }] }, /* @__PURE__ */ React26.createElement(Feather$1, { name: "check", size: ICON_SIZE, color: colors.primary })),
1648
- /* @__PURE__ */ React26.createElement(Animated.View, { style: [styles16.iconWrapper, { opacity: crossOpacity }] }, /* @__PURE__ */ React26.createElement(Feather$1, { name: "x", size: ICON_SIZE, color: colors.foregroundMuted }))
1731
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: [styles16.iconWrapper, checkIconStyle] }, /* @__PURE__ */ React26.createElement(Feather$1, { name: "check", size: ICON_SIZE, color: colors.primary })),
1732
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: [styles16.iconWrapper, crossIconStyle] }, /* @__PURE__ */ React26.createElement(Feather$1, { name: "x", size: ICON_SIZE, color: colors.foregroundMuted }))
1649
1733
  ))
1650
1734
  ));
1651
1735
  }
1652
1736
  var styles16 = StyleSheet.create({
1653
- wrapper: {},
1654
1737
  track: {
1655
1738
  width: TRACK_WIDTH,
1656
1739
  height: TRACK_HEIGHT,
1657
1740
  borderRadius: TRACK_HEIGHT / 2
1658
- // No justifyContent/alignItems — thumb uses absolute positioning
1659
- // so the track's flex layout doesn't interfere with translateX animation
1660
1741
  },
1661
1742
  thumb: {
1662
1743
  position: "absolute",
@@ -1677,7 +1758,6 @@ var styles16 = StyleSheet.create({
1677
1758
  position: "absolute"
1678
1759
  }
1679
1760
  });
1680
- var nativeDriver6 = Platform.OS !== "web";
1681
1761
  var sizeStyles = {
1682
1762
  sm: { paddingHorizontal: s(12), paddingVertical: vs(8), minWidth: s(40), minHeight: vs(40) },
1683
1763
  md: { paddingHorizontal: s(16), paddingVertical: vs(12), minWidth: s(44), minHeight: vs(44) },
@@ -1698,38 +1778,23 @@ function Toggle({
1698
1778
  activeIconColor,
1699
1779
  disabled,
1700
1780
  style,
1781
+ accessibilityLabel,
1701
1782
  ...props
1702
1783
  }) {
1703
1784
  const { colors } = useTheme();
1704
- const scale2 = useRef(new Animated.Value(1)).current;
1705
- const pressAnim = useRef(new Animated.Value(pressed ? 1 : 0)).current;
1706
- useEffect(() => {
1707
- Animated.timing(pressAnim, {
1708
- toValue: pressed ? 1 : 0,
1709
- duration: 150,
1710
- easing: Easing$1.out(Easing$1.ease),
1711
- useNativeDriver: false
1712
- }).start();
1713
- }, [pressed, pressAnim]);
1714
- const handlePressIn = () => {
1715
- if (disabled) return;
1716
- Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver6, stiffness: 600, damping: 35, mass: 0.8 }).start();
1717
- };
1718
- const handlePressOut = () => {
1719
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver6, stiffness: 280, damping: 22, mass: 0.8 }).start();
1720
- };
1721
- const borderColor = pressAnim.interpolate({
1722
- inputRange: [0, 1],
1723
- outputRange: [variant === "outline" ? colors.border : "transparent", colors.primary]
1724
- });
1725
- const backgroundColor = pressAnim.interpolate({
1726
- inputRange: [0, 1],
1727
- outputRange: ["transparent", colors.surfaceStrong]
1728
- });
1729
- const textColor = pressAnim.interpolate({
1730
- inputRange: [0, 1],
1731
- outputRange: [colors.foreground, colors.primary]
1785
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
1786
+ pressScale: PRESS_SCALE.button,
1787
+ disabled
1732
1788
  });
1789
+ const progress = useColorTransition(pressed);
1790
+ const inactiveBorder = variant === "outline" ? colors.border : "transparent";
1791
+ const surfaceStyle = useAnimatedStyle(() => ({
1792
+ borderColor: interpolateColor(progress.value, [0, 1], [inactiveBorder, colors.primary]),
1793
+ backgroundColor: interpolateColor(progress.value, [0, 1], ["transparent", colors.surfaceStrong])
1794
+ }));
1795
+ const textStyle = useAnimatedStyle(() => ({
1796
+ color: interpolateColor(progress.value, [0, 1], [colors.foreground, colors.primary])
1797
+ }));
1733
1798
  const iconSize = iconSizeMap2[size];
1734
1799
  const LeftIcon = () => {
1735
1800
  const renderProp = (prop) => {
@@ -1748,32 +1813,43 @@ function Toggle({
1748
1813
  if (custom) return /* @__PURE__ */ React26.createElement(React26.Fragment, null, custom);
1749
1814
  return /* @__PURE__ */ React26.createElement(FontAwesome5$1, { name: "circle", size: iconSize, color: colors.foregroundMuted });
1750
1815
  };
1751
- return /* @__PURE__ */ React26.createElement(Animated.View, { style: [{ transform: [{ scale: scale2 }] }, disabled && styles17.disabled, style] }, /* @__PURE__ */ React26.createElement(
1752
- TouchableOpacity,
1816
+ return /* @__PURE__ */ React26.createElement(
1817
+ Animated9.View,
1753
1818
  {
1754
- onPress: () => {
1755
- selectionAsync();
1756
- onPressedChange?.(!pressed);
1757
- },
1758
- onPressIn: handlePressIn,
1759
- onPressOut: handlePressOut,
1760
- disabled,
1761
- activeOpacity: 1,
1762
- touchSoundDisabled: true,
1763
- ...props
1819
+ style: [scaleStyle, disabled && styles17.disabled, style],
1820
+ ...hoverHandlers
1764
1821
  },
1765
1822
  /* @__PURE__ */ React26.createElement(
1766
- Animated.View,
1823
+ TouchableOpacity,
1767
1824
  {
1768
- style: [
1769
- styles17.base,
1770
- sizeStyles[size],
1771
- { borderColor, backgroundColor, borderWidth: 2 }
1772
- ]
1825
+ onPress: () => {
1826
+ selectionAsync();
1827
+ onPressedChange?.(!pressed);
1828
+ },
1829
+ onPressIn,
1830
+ onPressOut,
1831
+ disabled,
1832
+ activeOpacity: 1,
1833
+ touchSoundDisabled: true,
1834
+ accessibilityRole: "button",
1835
+ accessibilityLabel: accessibilityLabel ?? label,
1836
+ accessibilityState: { selected: pressed, disabled: !!disabled },
1837
+ ...props
1773
1838
  },
1774
- /* @__PURE__ */ React26.createElement(View, { style: styles17.inner }, /* @__PURE__ */ React26.createElement(LeftIcon, null), label ? /* @__PURE__ */ React26.createElement(Animated.Text, { style: [styles17.label, { color: textColor }], allowFontScaling: true }, label) : null)
1839
+ /* @__PURE__ */ React26.createElement(
1840
+ Animated9.View,
1841
+ {
1842
+ style: [
1843
+ styles17.base,
1844
+ sizeStyles[size],
1845
+ { borderWidth: 2 },
1846
+ surfaceStyle
1847
+ ]
1848
+ },
1849
+ /* @__PURE__ */ React26.createElement(View, { style: styles17.inner }, /* @__PURE__ */ React26.createElement(LeftIcon, null), label ? /* @__PURE__ */ React26.createElement(Animated9.Text, { style: [styles17.label, textStyle], allowFontScaling: true }, label) : null)
1850
+ )
1775
1851
  )
1776
- ));
1852
+ );
1777
1853
  }
1778
1854
  var styles17 = StyleSheet.create({
1779
1855
  base: {
@@ -1793,21 +1869,28 @@ var styles17 = StyleSheet.create({
1793
1869
  fontSize: ms(14)
1794
1870
  }
1795
1871
  });
1796
- var nativeDriver7 = Platform.OS !== "web";
1797
1872
  function RadioItem({
1798
1873
  option,
1799
1874
  selected,
1800
1875
  onSelect
1801
1876
  }) {
1802
1877
  const { colors } = useTheme();
1803
- const scale2 = useRef(new Animated.Value(1)).current;
1804
- const handlePressIn = () => {
1805
- if (option.disabled) return;
1806
- Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver7, speed: 40, bounciness: 0 }).start();
1807
- };
1808
- const handlePressOut = () => {
1809
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver7, speed: 40, bounciness: 4 }).start();
1810
- };
1878
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut } = usePressScale({
1879
+ pressScale: PRESS_SCALE.button,
1880
+ disabled: option.disabled
1881
+ });
1882
+ const colorProgress = useColorTransition(selected);
1883
+ const dotScale = useSharedValue(selected ? 1 : 0);
1884
+ useEffect(() => {
1885
+ dotScale.value = withSpring(selected ? 1 : 0, SPRINGS.elastic);
1886
+ }, [selected, dotScale]);
1887
+ const radioStyle = useAnimatedStyle(() => ({
1888
+ borderColor: interpolateColor(colorProgress.value, [0, 1], [colors.border, colors.primary])
1889
+ }));
1890
+ const dotStyle = useAnimatedStyle(() => ({
1891
+ transform: [{ scale: dotScale.value }],
1892
+ opacity: dotScale.value
1893
+ }));
1811
1894
  return /* @__PURE__ */ React26.createElement(
1812
1895
  TouchableOpacity,
1813
1896
  {
@@ -1818,26 +1901,26 @@ function RadioItem({
1818
1901
  onSelect();
1819
1902
  }
1820
1903
  },
1821
- onPressIn: handlePressIn,
1822
- onPressOut: handlePressOut,
1904
+ onPressIn,
1905
+ onPressOut,
1823
1906
  activeOpacity: 1,
1824
1907
  touchSoundDisabled: true,
1825
- disabled: option.disabled
1908
+ disabled: option.disabled,
1909
+ accessibilityRole: "radio",
1910
+ accessibilityLabel: option.label,
1911
+ accessibilityState: { checked: selected, disabled: !!option.disabled }
1826
1912
  },
1827
- /* @__PURE__ */ React26.createElement(
1828
- Animated.View,
1913
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: scaleStyle }, /* @__PURE__ */ React26.createElement(
1914
+ Animated9.View,
1829
1915
  {
1830
1916
  style: [
1831
1917
  styles18.radio,
1832
- {
1833
- borderColor: selected ? colors.primary : colors.border,
1834
- opacity: option.disabled ? 0.45 : 1,
1835
- transform: [{ scale: scale2 }]
1836
- }
1918
+ { opacity: option.disabled ? 0.45 : 1 },
1919
+ radioStyle
1837
1920
  ]
1838
1921
  },
1839
- selected ? /* @__PURE__ */ React26.createElement(View, { style: [styles18.dot, { backgroundColor: colors.primary }] }) : null
1840
- ),
1922
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: [styles18.dot, { backgroundColor: colors.primary }, dotStyle] })
1923
+ )),
1841
1924
  /* @__PURE__ */ React26.createElement(
1842
1925
  Text,
1843
1926
  {
@@ -1856,17 +1939,26 @@ function RadioGroup({
1856
1939
  value,
1857
1940
  onValueChange,
1858
1941
  orientation = "vertical",
1859
- style
1942
+ style,
1943
+ accessibilityLabel
1860
1944
  }) {
1861
- return /* @__PURE__ */ React26.createElement(View, { style: [styles18.container, orientation === "horizontal" && styles18.horizontal, style] }, options.map((option) => /* @__PURE__ */ React26.createElement(
1862
- RadioItem,
1945
+ return /* @__PURE__ */ React26.createElement(
1946
+ View,
1863
1947
  {
1864
- key: option.value,
1865
- option,
1866
- selected: option.value === value,
1867
- onSelect: () => onValueChange?.(option.value)
1868
- }
1869
- )));
1948
+ style: [styles18.container, orientation === "horizontal" && styles18.horizontal, style],
1949
+ accessibilityRole: "radiogroup",
1950
+ accessibilityLabel
1951
+ },
1952
+ options.map((option) => /* @__PURE__ */ React26.createElement(
1953
+ RadioItem,
1954
+ {
1955
+ key: option.value,
1956
+ option,
1957
+ selected: option.value === value,
1958
+ onSelect: () => onValueChange?.(option.value)
1959
+ }
1960
+ ))
1961
+ );
1870
1962
  }
1871
1963
  var styles18 = StyleSheet.create({
1872
1964
  container: {
@@ -1900,7 +1992,6 @@ var styles18 = StyleSheet.create({
1900
1992
  lineHeight: mvs(20)
1901
1993
  }
1902
1994
  });
1903
- var nativeDriver8 = Platform.OS !== "web";
1904
1995
  function TabTrigger({
1905
1996
  tab,
1906
1997
  isActive,
@@ -1909,13 +2000,9 @@ function TabTrigger({
1909
2000
  variant
1910
2001
  }) {
1911
2002
  const { colors } = useTheme();
1912
- const scale2 = useRef(new Animated.Value(1)).current;
1913
- const handlePressIn = () => {
1914
- Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver8, stiffness: 600, damping: 35, mass: 0.8 }).start();
1915
- };
1916
- const handlePressOut = () => {
1917
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver8, stiffness: 280, damping: 22, mass: 0.8 }).start();
1918
- };
2003
+ const { animatedStyle, onPressIn, onPressOut } = usePressScale({
2004
+ pressScale: PRESS_SCALE.button
2005
+ });
1919
2006
  const isUnderline = variant === "underline";
1920
2007
  return /* @__PURE__ */ React26.createElement(
1921
2008
  TouchableOpacity,
@@ -1926,13 +2013,16 @@ function TabTrigger({
1926
2013
  isUnderline && isActive && { borderBottomColor: colors.primary }
1927
2014
  ],
1928
2015
  onPress,
1929
- onPressIn: handlePressIn,
1930
- onPressOut: handlePressOut,
2016
+ onPressIn,
2017
+ onPressOut,
1931
2018
  onLayout,
1932
2019
  activeOpacity: 1,
1933
- touchSoundDisabled: true
2020
+ touchSoundDisabled: true,
2021
+ accessibilityRole: "tab",
2022
+ accessibilityState: { selected: isActive },
2023
+ accessibilityLabel: tab.label
1934
2024
  },
1935
- /* @__PURE__ */ React26.createElement(Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26.createElement(View, { style: styles19.triggerInner }, tab.icon ? typeof tab.icon === "function" ? tab.icon(isActive) : tab.icon : null, /* @__PURE__ */ React26.createElement(
2025
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: animatedStyle }, /* @__PURE__ */ React26.createElement(View, { style: styles19.triggerInner }, tab.icon ? typeof tab.icon === "function" ? tab.icon(isActive) : tab.icon : null, /* @__PURE__ */ React26.createElement(
1936
2026
  Text,
1937
2027
  {
1938
2028
  style: [
@@ -1951,20 +2041,18 @@ function Tabs({ tabs, variant = "pill", value, onValueChange, children, style })
1951
2041
  const { colors } = useTheme();
1952
2042
  const active = value ?? internal;
1953
2043
  const tabLayouts = useRef({});
1954
- const pillX = useRef(new Animated.Value(0)).current;
1955
- const pillWidth = useRef(new Animated.Value(0)).current;
2044
+ const pillX = useSharedValue(0);
2045
+ const pillWidth = useSharedValue(0);
1956
2046
  const initialised = useRef(false);
1957
2047
  const animatePill = (tabValue, animate) => {
1958
2048
  const layout = tabLayouts.current[tabValue];
1959
2049
  if (!layout) return;
1960
2050
  if (animate) {
1961
- Animated.parallel([
1962
- Animated.spring(pillX, { toValue: layout.x, useNativeDriver: false, stiffness: 380, damping: 38, mass: 1 }),
1963
- Animated.spring(pillWidth, { toValue: layout.width, useNativeDriver: false, stiffness: 380, damping: 38, mass: 1 })
1964
- ]).start();
2051
+ pillX.value = withSpring(layout.x, SPRINGS.glide);
2052
+ pillWidth.value = withSpring(layout.width, SPRINGS.glide);
1965
2053
  } else {
1966
- pillX.setValue(layout.x);
1967
- pillWidth.setValue(layout.width);
2054
+ pillX.value = layout.x;
2055
+ pillWidth.value = layout.width;
1968
2056
  }
1969
2057
  };
1970
2058
  useEffect(() => {
@@ -1975,51 +2063,63 @@ function Tabs({ tabs, variant = "pill", value, onValueChange, children, style })
1975
2063
  if (!value) setInternal(v);
1976
2064
  onValueChange?.(v);
1977
2065
  };
1978
- return /* @__PURE__ */ React26.createElement(View, { style }, /* @__PURE__ */ React26.createElement(View, { style: [
1979
- variant === "pill" ? [styles19.list, { backgroundColor: colors.surface }] : styles19.listUnderline
1980
- ] }, variant === "pill" && /* @__PURE__ */ React26.createElement(
1981
- Animated.View,
2066
+ const pillAnimatedStyle = useAnimatedStyle(() => ({
2067
+ transform: [{ translateX: pillX.value }],
2068
+ width: pillWidth.value
2069
+ }));
2070
+ return /* @__PURE__ */ React26.createElement(View, { style }, /* @__PURE__ */ React26.createElement(
2071
+ View,
1982
2072
  {
1983
2073
  style: [
1984
- styles19.pill,
1985
- {
1986
- backgroundColor: colors.background,
1987
- position: "absolute",
1988
- top: 4,
1989
- bottom: 4,
1990
- left: pillX,
1991
- width: pillWidth,
1992
- borderRadius: 8,
1993
- shadowColor: "#000",
1994
- shadowOffset: { width: 0, height: 1 },
1995
- shadowOpacity: 0.08,
1996
- shadowRadius: 2,
1997
- elevation: 2
1998
- }
1999
- ]
2000
- }
2001
- ), tabs.map((tab) => /* @__PURE__ */ React26.createElement(
2002
- TabTrigger,
2003
- {
2004
- key: tab.value,
2005
- tab,
2006
- isActive: tab.value === active,
2007
- onPress: () => handlePress(tab.value),
2008
- variant,
2009
- onLayout: (e) => {
2010
- const { x, width } = e.nativeEvent.layout;
2011
- tabLayouts.current[tab.value] = { x, width };
2012
- if (tab.value === active) {
2013
- animatePill(tab.value, false);
2014
- initialised.current = true;
2074
+ variant === "pill" ? [styles19.list, { backgroundColor: colors.surface }] : styles19.listUnderline
2075
+ ],
2076
+ accessibilityRole: "tablist"
2077
+ },
2078
+ variant === "pill" && /* @__PURE__ */ React26.createElement(
2079
+ Animated9.View,
2080
+ {
2081
+ style: [
2082
+ styles19.pill,
2083
+ {
2084
+ backgroundColor: colors.background,
2085
+ position: "absolute",
2086
+ top: 4,
2087
+ bottom: 4,
2088
+ left: 0,
2089
+ borderRadius: 8,
2090
+ shadowColor: "#000",
2091
+ shadowOffset: { width: 0, height: 1 },
2092
+ shadowOpacity: 0.08,
2093
+ shadowRadius: 2,
2094
+ elevation: 2
2095
+ },
2096
+ pillAnimatedStyle
2097
+ ]
2098
+ }
2099
+ ),
2100
+ tabs.map((tab) => /* @__PURE__ */ React26.createElement(
2101
+ TabTrigger,
2102
+ {
2103
+ key: tab.value,
2104
+ tab,
2105
+ isActive: tab.value === active,
2106
+ onPress: () => handlePress(tab.value),
2107
+ variant,
2108
+ onLayout: (e) => {
2109
+ const { x, width } = e.nativeEvent.layout;
2110
+ tabLayouts.current[tab.value] = { x, width };
2111
+ if (tab.value === active) {
2112
+ animatePill(tab.value, false);
2113
+ initialised.current = true;
2114
+ }
2015
2115
  }
2016
2116
  }
2017
- }
2018
- ))), children);
2117
+ ))
2118
+ ), children);
2019
2119
  }
2020
2120
  function TabsContent({ value, activeValue, children, style }) {
2021
2121
  if (value !== activeValue) return null;
2022
- return /* @__PURE__ */ React26.createElement(View, { style }, children);
2122
+ return /* @__PURE__ */ React26.createElement(View, { style, accessibilityRole: "none" }, children);
2023
2123
  }
2024
2124
  var styles19 = StyleSheet.create({
2025
2125
  list: {
@@ -2068,8 +2168,6 @@ var styles19 = StyleSheet.create({
2068
2168
  fontSize: ms(14)
2069
2169
  }
2070
2170
  });
2071
- var easingExpand = Easing.bezier(0.23, 1, 0.32, 1);
2072
- var easingCollapse = Easing.in(Easing.ease);
2073
2171
  function AccordionItemComponent({
2074
2172
  item,
2075
2173
  isOpen,
@@ -2084,14 +2182,14 @@ function AccordionItemComponent({
2084
2182
  }, [isOpen]);
2085
2183
  const derivedHeight = useDerivedValue(
2086
2184
  () => withTiming(height.value * Number(isExpanded.value), {
2087
- duration: 220,
2088
- easing: isExpanded.value ? easingExpand : easingCollapse
2185
+ duration: isExpanded.value ? TIMINGS.expand.duration : TIMINGS.collapse.duration,
2186
+ easing: isExpanded.value ? EASINGS.expand : EASINGS.collapse
2089
2187
  })
2090
2188
  );
2091
2189
  const derivedRotation = useDerivedValue(
2092
2190
  () => withTiming(isExpanded.value ? 1 : 0, {
2093
- duration: 220,
2094
- easing: isExpanded.value ? easingExpand : easingCollapse
2191
+ duration: isExpanded.value ? TIMINGS.expand.duration : TIMINGS.collapse.duration,
2192
+ easing: isExpanded.value ? EASINGS.expand : EASINGS.collapse
2095
2193
  })
2096
2194
  );
2097
2195
  const bodyStyle = useAnimatedStyle(() => ({
@@ -2108,11 +2206,14 @@ function AccordionItemComponent({
2108
2206
  onPress: () => {
2109
2207
  selectionAsync();
2110
2208
  onToggle();
2111
- }
2209
+ },
2210
+ accessibilityRole: "button",
2211
+ accessibilityState: { expanded: isOpen },
2212
+ accessibilityLabel: item.trigger
2112
2213
  },
2113
2214
  /* @__PURE__ */ React26.createElement(View, { style: styles20.triggerContent }, resolvedIcon ? /* @__PURE__ */ React26.createElement(View, { style: styles20.icon }, resolvedIcon) : null, /* @__PURE__ */ React26.createElement(Text, { style: [styles20.triggerText, { color: colors.foreground }], allowFontScaling: true }, item.trigger)),
2114
- /* @__PURE__ */ React26.createElement(Animated12.View, { style: [styles20.chevron, rotationStyle] }, /* @__PURE__ */ React26.createElement(Entypo$1, { name: "chevron-down", size: 18, color: colors.foregroundMuted }))
2115
- ), /* @__PURE__ */ React26.createElement(Animated12.View, { style: bodyStyle }, /* @__PURE__ */ React26.createElement(
2215
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: [styles20.chevron, rotationStyle] }, /* @__PURE__ */ React26.createElement(Entypo$1, { name: "chevron-down", size: 18, color: colors.foregroundMuted }))
2216
+ ), /* @__PURE__ */ React26.createElement(Animated9.View, { style: bodyStyle }, /* @__PURE__ */ React26.createElement(
2116
2217
  View,
2117
2218
  {
2118
2219
  style: styles20.content,
@@ -2212,23 +2313,38 @@ function Slider({
2212
2313
  }
2213
2314
  onValueChange?.(v);
2214
2315
  };
2215
- return /* @__PURE__ */ React26.createElement(View, { style: [styles21.wrapper, style], accessibilityLabel }, label || showValue ? /* @__PURE__ */ React26.createElement(View, { style: styles21.header }, label ? /* @__PURE__ */ React26.createElement(Text, { style: [styles21.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, showValue ? /* @__PURE__ */ React26.createElement(Text, { style: [styles21.valueText, { color: colors.foregroundMuted }], allowFontScaling: true }, formatValue2(value)) : null) : null, /* @__PURE__ */ React26.createElement(View, { style: disabled ? styles21.disabled : void 0 }, /* @__PURE__ */ React26.createElement(
2216
- RNSlider,
2316
+ return /* @__PURE__ */ React26.createElement(
2317
+ View,
2217
2318
  {
2218
- value,
2219
- minimumValue,
2220
- maximumValue,
2221
- step: step || 0,
2222
- disabled,
2223
- onValueChange: handleValueChange,
2224
- onSlidingComplete,
2225
- minimumTrackTintColor: colors.primary,
2226
- maximumTrackTintColor: colors.surface,
2227
- thumbTintColor: colors.primary,
2228
- style: styles21.slider,
2229
- accessibilityLabel
2230
- }
2231
- )));
2319
+ style: [styles21.wrapper, style],
2320
+ accessibilityRole: "adjustable",
2321
+ accessibilityLabel: accessibilityLabel ?? label,
2322
+ accessibilityValue: {
2323
+ min: minimumValue,
2324
+ max: maximumValue,
2325
+ now: value,
2326
+ text: formatValue2(value)
2327
+ }
2328
+ },
2329
+ label || showValue ? /* @__PURE__ */ React26.createElement(View, { style: styles21.header }, label ? /* @__PURE__ */ React26.createElement(Text, { style: [styles21.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, showValue ? /* @__PURE__ */ React26.createElement(Text, { style: [styles21.valueText, { color: colors.foregroundMuted }], allowFontScaling: true }, formatValue2(value)) : null) : null,
2330
+ /* @__PURE__ */ React26.createElement(View, { style: disabled ? styles21.disabled : void 0 }, /* @__PURE__ */ React26.createElement(
2331
+ RNSlider,
2332
+ {
2333
+ value,
2334
+ minimumValue,
2335
+ maximumValue,
2336
+ step: step || 0,
2337
+ disabled,
2338
+ onValueChange: handleValueChange,
2339
+ onSlidingComplete,
2340
+ minimumTrackTintColor: colors.primary,
2341
+ maximumTrackTintColor: colors.surface,
2342
+ thumbTintColor: colors.primary,
2343
+ style: styles21.slider,
2344
+ accessibilityLabel
2345
+ }
2346
+ ))
2347
+ );
2232
2348
  }
2233
2349
  var styles21 = StyleSheet.create({
2234
2350
  wrapper: {
@@ -2304,13 +2420,16 @@ function Sheet({
2304
2420
  }, [footer]);
2305
2421
  const effectiveSubtitle = subtitle ?? description;
2306
2422
  const showHeader = !!(title || effectiveSubtitle || showCloseButton);
2307
- const headerNode = showHeader ? /* @__PURE__ */ React26.createElement(View, { style: styles22.header }, /* @__PURE__ */ React26.createElement(View, { style: styles22.headerRow }, title ? /* @__PURE__ */ React26.createElement(Text, { style: [styles22.title, { color: colors.foreground }], allowFontScaling: true }, title) : /* @__PURE__ */ React26.createElement(View, { style: { flex: 1 } }), showCloseButton ? /* @__PURE__ */ React26.createElement(
2423
+ const headerNode = showHeader ? /* @__PURE__ */ React26.createElement(View, { style: styles22.header, accessibilityRole: "header" }, /* @__PURE__ */ React26.createElement(View, { style: styles22.headerRow }, title ? /* @__PURE__ */ React26.createElement(Text, { style: [styles22.title, { color: colors.foreground }], allowFontScaling: true }, title) : /* @__PURE__ */ React26.createElement(View, { style: { flex: 1 } }), showCloseButton ? /* @__PURE__ */ React26.createElement(
2308
2424
  TouchableOpacity,
2309
2425
  {
2310
2426
  onPress: onClose,
2311
2427
  style: styles22.closeButton,
2312
2428
  activeOpacity: 0.6,
2313
- touchSoundDisabled: true
2429
+ touchSoundDisabled: true,
2430
+ accessibilityRole: "button",
2431
+ accessibilityLabel: "Close",
2432
+ hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }
2314
2433
  },
2315
2434
  /* @__PURE__ */ React26.createElement(AntDesign$1, { name: "close", size: ms(18), color: colors.foregroundMuted })
2316
2435
  ) : null), effectiveSubtitle ? /* @__PURE__ */ React26.createElement(Text, { style: [styles22.subtitle, { color: colors.foregroundMuted }], allowFontScaling: true }, effectiveSubtitle) : null) : null;
@@ -2401,7 +2520,6 @@ var styles22 = StyleSheet.create({
2401
2520
  var isIOS = Platform.OS === "ios";
2402
2521
  var isAndroid2 = Platform.OS === "android";
2403
2522
  var isWeb2 = Platform.OS === "web";
2404
- var nativeDriver9 = Platform.OS !== "web";
2405
2523
  function Select({
2406
2524
  options,
2407
2525
  value,
@@ -2410,21 +2528,18 @@ function Select({
2410
2528
  label,
2411
2529
  error,
2412
2530
  disabled,
2413
- style
2531
+ style,
2532
+ accessibilityLabel
2414
2533
  }) {
2415
2534
  const { colors } = useTheme();
2416
- const scale2 = useRef(new Animated.Value(1)).current;
2535
+ const { animatedStyle, onPressIn, onPressOut } = usePressScale({
2536
+ pressScale: PRESS_SCALE.button,
2537
+ disabled
2538
+ });
2417
2539
  const [pickerVisible, setPickerVisible] = useState(false);
2418
2540
  const [pendingValue, setPendingValue] = useState(value);
2419
2541
  const pickerRef = useRef(null);
2420
2542
  const selected = options.find((o) => o.value === value);
2421
- const handlePressIn = () => {
2422
- if (disabled) return;
2423
- Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver9, speed: 40, bounciness: 0 }).start();
2424
- };
2425
- const handlePressOut = () => {
2426
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver9, speed: 40, bounciness: 4 }).start();
2427
- };
2428
2543
  const handleOpen = () => {
2429
2544
  if (disabled) return;
2430
2545
  selectionAsync();
@@ -2445,7 +2560,7 @@ function Select({
2445
2560
  }
2446
2561
  setPickerVisible(false);
2447
2562
  };
2448
- return /* @__PURE__ */ React26.createElement(View, { style: [styles23.container, style] }, label ? /* @__PURE__ */ React26.createElement(Text, { style: [styles23.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, !isWeb2 ? /* @__PURE__ */ React26.createElement(Animated.View, { style: { transform: [{ scale: scale2 }], opacity: disabled ? 0.45 : 1 } }, /* @__PURE__ */ React26.createElement(
2563
+ return /* @__PURE__ */ React26.createElement(View, { style: [styles23.container, style] }, label ? /* @__PURE__ */ React26.createElement(Text, { style: [styles23.label, { color: colors.foreground }], allowFontScaling: true }, label) : null, !isWeb2 ? /* @__PURE__ */ React26.createElement(Animated9.View, { style: [animatedStyle, { opacity: disabled ? 0.45 : 1 }] }, /* @__PURE__ */ React26.createElement(
2449
2564
  TouchableOpacity,
2450
2565
  {
2451
2566
  style: [
@@ -2456,10 +2571,14 @@ function Select({
2456
2571
  }
2457
2572
  ],
2458
2573
  onPress: handleOpen,
2459
- onPressIn: handlePressIn,
2460
- onPressOut: handlePressOut,
2574
+ onPressIn,
2575
+ onPressOut,
2461
2576
  activeOpacity: 1,
2462
- touchSoundDisabled: true
2577
+ touchSoundDisabled: true,
2578
+ accessibilityRole: "combobox",
2579
+ accessibilityLabel: accessibilityLabel ?? label,
2580
+ accessibilityValue: { text: selected?.label ?? placeholder },
2581
+ accessibilityState: { disabled: !!disabled, expanded: pickerVisible }
2463
2582
  },
2464
2583
  /* @__PURE__ */ React26.createElement(
2465
2584
  Text,
@@ -2773,7 +2892,6 @@ var styles24 = StyleSheet.create({
2773
2892
  textAlignVertical: "top"
2774
2893
  }
2775
2894
  });
2776
- var nativeDriver10 = Platform.OS !== "web";
2777
2895
  function ListItem({
2778
2896
  leftRender,
2779
2897
  rightRender,
@@ -2794,29 +2912,16 @@ function ListItem({
2794
2912
  style,
2795
2913
  titleStyle,
2796
2914
  subtitleStyle,
2797
- captionStyle
2915
+ captionStyle,
2916
+ accessibilityLabel
2798
2917
  }) {
2799
2918
  const { colors } = useTheme();
2800
- const scale2 = useRef(new Animated.Value(1)).current;
2801
- const handlePressIn = () => {
2802
- if (!onPress || disabled) return;
2803
- Animated.spring(scale2, {
2804
- toValue: 0.97,
2805
- useNativeDriver: nativeDriver10,
2806
- stiffness: 350,
2807
- damping: 28,
2808
- mass: 0.9
2809
- }).start();
2810
- };
2811
- const handlePressOut = () => {
2812
- Animated.spring(scale2, {
2813
- toValue: 1,
2814
- useNativeDriver: nativeDriver10,
2815
- stiffness: 220,
2816
- damping: 20,
2817
- mass: 0.9
2818
- }).start();
2819
- };
2919
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
2920
+ pressScale: PRESS_SCALE.row,
2921
+ pressInSpring: SPRINGS.surfacePressIn,
2922
+ pressOutSpring: SPRINGS.surfacePressOut,
2923
+ disabled: !onPress || disabled
2924
+ });
2820
2925
  const handlePress = () => {
2821
2926
  selectionAsync();
2822
2927
  onPress?.();
@@ -2834,16 +2939,20 @@ function ListItem({
2834
2939
  shadowRadius: 6,
2835
2940
  elevation: 2
2836
2941
  } : {};
2837
- return /* @__PURE__ */ React26.createElement(Animated.View, { style: [{ transform: [{ scale: scale2 }] }, disabled && styles25.disabled] }, /* @__PURE__ */ React26.createElement(
2942
+ const a11yLabel = accessibilityLabel ?? [title, subtitle, caption].filter(Boolean).join(". ");
2943
+ return /* @__PURE__ */ React26.createElement(Animated9.View, { style: [animatedStyle, disabled && styles25.disabled], ...hoverHandlers }, /* @__PURE__ */ React26.createElement(
2838
2944
  TouchableOpacity,
2839
2945
  {
2840
2946
  style: [styles25.container, cardStyle, style],
2841
2947
  onPress: onPress ? handlePress : void 0,
2842
- onPressIn: handlePressIn,
2843
- onPressOut: handlePressOut,
2948
+ onPressIn,
2949
+ onPressOut,
2844
2950
  disabled,
2845
2951
  activeOpacity: 1,
2846
- touchSoundDisabled: true
2952
+ touchSoundDisabled: true,
2953
+ accessibilityRole: onPress ? "button" : void 0,
2954
+ accessibilityLabel: onPress ? a11yLabel : void 0,
2955
+ accessibilityState: onPress ? { disabled: !!disabled } : void 0
2847
2956
  },
2848
2957
  effectiveLeft ? /* @__PURE__ */ React26.createElement(View, { style: styles25.leftContainer }, effectiveLeft) : null,
2849
2958
  /* @__PURE__ */ React26.createElement(View, { style: styles25.content }, /* @__PURE__ */ React26.createElement(
@@ -2937,9 +3046,6 @@ var styles25 = StyleSheet.create({
2937
3046
  fontFamily: "Poppins-Regular",
2938
3047
  fontSize: ms(14)
2939
3048
  },
2940
- chevron: {
2941
- marginLeft: s(4)
2942
- },
2943
3049
  separator: {
2944
3050
  height: StyleSheet.hairlineWidth,
2945
3051
  marginRight: 0
@@ -2948,7 +3054,6 @@ var styles25 = StyleSheet.create({
2948
3054
  opacity: 0.45
2949
3055
  }
2950
3056
  });
2951
- var nativeDriver11 = Platform.OS !== "web";
2952
3057
  function MenuItem({
2953
3058
  label,
2954
3059
  subtitle,
@@ -2962,29 +3067,16 @@ function MenuItem({
2962
3067
  variant = "plain",
2963
3068
  showSeparator = false,
2964
3069
  style,
2965
- labelStyle
3070
+ labelStyle,
3071
+ accessibilityLabel
2966
3072
  }) {
2967
3073
  const { colors } = useTheme();
2968
- const scale2 = useRef(new Animated.Value(1)).current;
2969
- const handlePressIn = () => {
2970
- if (disabled) return;
2971
- Animated.spring(scale2, {
2972
- toValue: 0.97,
2973
- useNativeDriver: nativeDriver11,
2974
- stiffness: 350,
2975
- damping: 28,
2976
- mass: 0.9
2977
- }).start();
2978
- };
2979
- const handlePressOut = () => {
2980
- Animated.spring(scale2, {
2981
- toValue: 1,
2982
- useNativeDriver: nativeDriver11,
2983
- stiffness: 220,
2984
- damping: 20,
2985
- mass: 0.9
2986
- }).start();
2987
- };
3074
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3075
+ pressScale: PRESS_SCALE.row,
3076
+ pressInSpring: SPRINGS.surfacePressIn,
3077
+ pressOutSpring: SPRINGS.surfacePressOut,
3078
+ disabled
3079
+ });
2988
3080
  const handlePress = () => {
2989
3081
  selectionAsync();
2990
3082
  onPress();
@@ -3001,16 +3093,20 @@ function MenuItem({
3001
3093
  shadowRadius: 6,
3002
3094
  elevation: 2
3003
3095
  } : {};
3004
- return /* @__PURE__ */ React26.createElement(Animated.View, { style: [{ transform: [{ scale: scale2 }] }, disabled && styles26.disabled] }, /* @__PURE__ */ React26.createElement(
3096
+ const a11yLabel = accessibilityLabel ?? (subtitle ? `${label}. ${subtitle}` : label);
3097
+ return /* @__PURE__ */ React26.createElement(Animated9.View, { style: [animatedStyle, disabled && styles26.disabled], ...hoverHandlers }, /* @__PURE__ */ React26.createElement(
3005
3098
  TouchableOpacity,
3006
3099
  {
3007
3100
  style: [styles26.container, cardStyle, style],
3008
3101
  onPress: handlePress,
3009
- onPressIn: handlePressIn,
3010
- onPressOut: handlePressOut,
3102
+ onPressIn,
3103
+ onPressOut,
3011
3104
  disabled,
3012
3105
  activeOpacity: 1,
3013
- touchSoundDisabled: true
3106
+ touchSoundDisabled: true,
3107
+ accessibilityRole: "button",
3108
+ accessibilityLabel: a11yLabel,
3109
+ accessibilityState: { disabled }
3014
3110
  },
3015
3111
  resolvedIcon ? /* @__PURE__ */ React26.createElement(View, { style: styles26.iconContainer }, resolvedIcon) : null,
3016
3112
  /* @__PURE__ */ React26.createElement(View, { style: styles26.labelContainer }, /* @__PURE__ */ React26.createElement(
@@ -3095,62 +3191,37 @@ var styles26 = StyleSheet.create({
3095
3191
  opacity: 0.45
3096
3192
  }
3097
3193
  });
3098
- var nativeDriver12 = Platform.OS !== "web";
3099
- function Chip({ label, selected = false, onPress, icon, iconName, style }) {
3194
+ function Chip({ label, selected = false, onPress, icon, iconName, style, accessibilityLabel }) {
3100
3195
  const { colors } = useTheme();
3101
- const scale2 = useRef(new Animated.Value(1)).current;
3102
- const pressAnim = useRef(new Animated.Value(selected ? 1 : 0)).current;
3103
- useEffect(() => {
3104
- Animated.timing(pressAnim, {
3105
- toValue: selected ? 1 : 0,
3106
- duration: 150,
3107
- easing: Easing$1.out(Easing$1.ease),
3108
- useNativeDriver: false
3109
- }).start();
3110
- }, [selected, pressAnim]);
3111
- const handlePressIn = () => {
3112
- Animated.spring(scale2, {
3113
- toValue: 0.95,
3114
- useNativeDriver: nativeDriver12,
3115
- speed: 40,
3116
- bounciness: 0
3117
- }).start();
3118
- };
3119
- const handlePressOut = () => {
3120
- Animated.spring(scale2, {
3121
- toValue: 1,
3122
- useNativeDriver: nativeDriver12,
3123
- speed: 40,
3124
- bounciness: 4
3125
- }).start();
3126
- };
3196
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3197
+ pressScale: PRESS_SCALE.chip
3198
+ });
3199
+ const colorProgress = useColorTransition(selected);
3200
+ const surfaceStyle = useAnimatedStyle(() => ({
3201
+ backgroundColor: interpolateColor(colorProgress.value, [0, 1], [colors.surface, colors.primary]),
3202
+ borderColor: interpolateColor(colorProgress.value, [0, 1], [colors.border, colors.primary])
3203
+ }));
3204
+ const textStyle = useAnimatedStyle(() => ({
3205
+ color: interpolateColor(colorProgress.value, [0, 1], [colors.foreground, colors.primaryForeground])
3206
+ }));
3127
3207
  const handlePress = () => {
3128
3208
  selectionAsync();
3129
3209
  onPress?.();
3130
3210
  };
3131
- const backgroundColor = pressAnim.interpolate({
3132
- inputRange: [0, 1],
3133
- outputRange: [colors.surface, colors.primary]
3134
- });
3135
- const textColor = pressAnim.interpolate({
3136
- inputRange: [0, 1],
3137
- outputRange: [colors.foreground, colors.primaryForeground]
3138
- });
3139
- const borderColor = pressAnim.interpolate({
3140
- inputRange: [0, 1],
3141
- outputRange: [colors.border, colors.primary]
3142
- });
3143
3211
  const resolvedIcon = iconName ? renderIcon(iconName, ms(13), selected ? colors.primaryForeground : colors.foreground) : icon;
3144
- return /* @__PURE__ */ React26.createElement(Animated.View, { style: [styles27.wrapper, { transform: [{ scale: scale2 }] }, style] }, /* @__PURE__ */ React26.createElement(
3212
+ return /* @__PURE__ */ React26.createElement(Animated9.View, { style: [styles27.wrapper, scaleStyle, style], ...hoverHandlers }, /* @__PURE__ */ React26.createElement(
3145
3213
  TouchableOpacity,
3146
3214
  {
3147
3215
  onPress: handlePress,
3148
- onPressIn: handlePressIn,
3149
- onPressOut: handlePressOut,
3216
+ onPressIn,
3217
+ onPressOut,
3150
3218
  activeOpacity: 1,
3151
- touchSoundDisabled: true
3219
+ touchSoundDisabled: true,
3220
+ accessibilityRole: "button",
3221
+ accessibilityLabel: accessibilityLabel ?? label,
3222
+ accessibilityState: { selected }
3152
3223
  },
3153
- /* @__PURE__ */ React26.createElement(Animated.View, { style: [styles27.chip, { backgroundColor, borderColor }] }, resolvedIcon ? /* @__PURE__ */ React26.createElement(View, { style: styles27.chipIcon }, resolvedIcon) : null, /* @__PURE__ */ React26.createElement(Animated.Text, { style: [styles27.label, { color: textColor }], allowFontScaling: true }, label))
3224
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: [styles27.chip, surfaceStyle] }, resolvedIcon ? /* @__PURE__ */ React26.createElement(View, { style: styles27.chipIcon }, resolvedIcon) : null, /* @__PURE__ */ React26.createElement(Animated9.Text, { style: [styles27.label, textStyle], allowFontScaling: true }, label))
3154
3225
  ));
3155
3226
  }
3156
3227
  function ChipGroup({ options, value, onValueChange, multiSelect = false, style }) {
@@ -3161,12 +3232,7 @@ function ChipGroup({ options, value, onValueChange, multiSelect = false, style }
3161
3232
  }
3162
3233
  const currentArray = Array.isArray(value) ? value : value ? [value] : [];
3163
3234
  const isSelected2 = currentArray.includes(optionValue);
3164
- let newArray;
3165
- if (isSelected2) {
3166
- newArray = currentArray.filter((v) => v !== optionValue);
3167
- } else {
3168
- newArray = [...currentArray, optionValue];
3169
- }
3235
+ const newArray = isSelected2 ? currentArray.filter((v) => v !== optionValue) : [...currentArray, optionValue];
3170
3236
  onValueChange?.(newArray);
3171
3237
  };
3172
3238
  const isSelected = (optionValue) => {
@@ -3380,22 +3446,36 @@ function MonthPicker({ value, onChange, locale = "en", formatLabel, style }) {
3380
3446
  onChange({ month: value.month + 1, year: value.year });
3381
3447
  }
3382
3448
  };
3383
- return /* @__PURE__ */ React26.createElement(View, { style: [styles30.container, style] }, /* @__PURE__ */ React26.createElement(
3449
+ return /* @__PURE__ */ React26.createElement(View, { style: [styles30.container, style], accessibilityRole: "adjustable", accessibilityLabel: getLabel() }, /* @__PURE__ */ React26.createElement(
3384
3450
  TouchableOpacity,
3385
3451
  {
3386
3452
  style: styles30.arrow,
3387
3453
  onPress: handlePrev,
3388
3454
  activeOpacity: 0.6,
3389
- touchSoundDisabled: true
3455
+ touchSoundDisabled: true,
3456
+ accessibilityRole: "button",
3457
+ accessibilityLabel: "Previous month",
3458
+ hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }
3390
3459
  },
3391
3460
  /* @__PURE__ */ React26.createElement(Entypo$1, { name: "chevron-left", size: 22, color: colors.foreground })
3392
- ), /* @__PURE__ */ React26.createElement(Text, { style: [styles30.label, { color: colors.foreground }], allowFontScaling: true }, getLabel()), /* @__PURE__ */ React26.createElement(
3461
+ ), /* @__PURE__ */ React26.createElement(
3462
+ Text,
3463
+ {
3464
+ style: [styles30.label, { color: colors.foreground }],
3465
+ allowFontScaling: true,
3466
+ accessibilityLiveRegion: "polite"
3467
+ },
3468
+ getLabel()
3469
+ ), /* @__PURE__ */ React26.createElement(
3393
3470
  TouchableOpacity,
3394
3471
  {
3395
3472
  style: styles30.arrow,
3396
3473
  onPress: handleNext,
3397
3474
  activeOpacity: 0.6,
3398
- touchSoundDisabled: true
3475
+ touchSoundDisabled: true,
3476
+ accessibilityRole: "button",
3477
+ accessibilityLabel: "Next month",
3478
+ hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }
3399
3479
  },
3400
3480
  /* @__PURE__ */ React26.createElement(Entypo$1, { name: "chevron-right", size: 22, color: colors.foreground })
3401
3481
  ));
@@ -3420,18 +3500,6 @@ var styles30 = StyleSheet.create({
3420
3500
  minWidth: s(160)
3421
3501
  }
3422
3502
  });
3423
- function useHover() {
3424
- const [hovered, setHovered] = useState(false);
3425
- const onMouseEnter = useCallback(() => setHovered(true), []);
3426
- const onMouseLeave = useCallback(() => setHovered(false), []);
3427
- if (Platform.OS !== "web") {
3428
- return { hovered: false, hoverHandlers: {} };
3429
- }
3430
- return { hovered, hoverHandlers: { onMouseEnter, onMouseLeave } };
3431
- }
3432
-
3433
- // src/components/MediaCard/MediaCard.tsx
3434
- var nativeDriver13 = Platform.OS !== "web";
3435
3503
  var aspectRatioMap = {
3436
3504
  "1:1": 1,
3437
3505
  "4:3": 3 / 4,
@@ -3453,19 +3521,17 @@ function MediaCard({
3453
3521
  onPress,
3454
3522
  style,
3455
3523
  imageStyle,
3456
- footer
3524
+ footer,
3525
+ accessibilityLabel
3457
3526
  }) {
3458
3527
  const { colors } = useTheme();
3459
- const scale2 = useRef(new Animated.Value(1)).current;
3460
3528
  const { hovered, hoverHandlers } = useHover();
3461
- const handlePressIn = () => {
3462
- if (!onPress) return;
3463
- Animated.spring(scale2, { toValue: 0.98, useNativeDriver: nativeDriver13, speed: 40, bounciness: 0 }).start();
3464
- };
3465
- const handlePressOut = () => {
3466
- if (!onPress) return;
3467
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver13, speed: 40, bounciness: 4 }).start();
3468
- };
3529
+ const { animatedStyle, onPressIn, onPressOut } = usePressScale({
3530
+ pressScale: PRESS_SCALE.card,
3531
+ pressInSpring: SPRINGS.surfacePressIn,
3532
+ pressOutSpring: SPRINGS.surfacePressOut,
3533
+ disabled: !onPress
3534
+ });
3469
3535
  const handlePress = () => {
3470
3536
  if (!onPress) return;
3471
3537
  impactLight();
@@ -3473,6 +3539,7 @@ function MediaCard({
3473
3539
  };
3474
3540
  const ratio = aspectRatioMap[aspectRatio];
3475
3541
  const resolvedActionIcon = actionIconName ? renderIcon(actionIconName, 18, actionActive ? colors.primary : colors.background) : actionIcon ?? renderIcon("heart", 18, actionActive ? colors.primary : colors.background);
3542
+ const a11yLabel = accessibilityLabel ?? [title, subtitle].filter(Boolean).join(". ");
3476
3543
  const cardContent = /* @__PURE__ */ React26.createElement(
3477
3544
  View,
3478
3545
  {
@@ -3499,21 +3566,26 @@ function MediaCard({
3499
3566
  onActionPress?.();
3500
3567
  },
3501
3568
  activeOpacity: 0.8,
3502
- touchSoundDisabled: true
3569
+ touchSoundDisabled: true,
3570
+ accessibilityRole: "button",
3571
+ accessibilityLabel: actionIconName ?? "action",
3572
+ accessibilityState: { selected: actionActive }
3503
3573
  },
3504
3574
  resolvedActionIcon
3505
3575
  )),
3506
3576
  (title || subtitle || caption || footer) && /* @__PURE__ */ React26.createElement(View, { style: styles31.meta }, title ? /* @__PURE__ */ React26.createElement(Text, { style: [styles31.title, { color: colors.foreground }], numberOfLines: 2, allowFontScaling: true }, title) : null, subtitle ? /* @__PURE__ */ React26.createElement(Text, { style: [styles31.subtitle, { color: colors.foregroundSubtle }], numberOfLines: 1, allowFontScaling: true }, subtitle) : null, caption ? /* @__PURE__ */ React26.createElement(Text, { style: [styles31.caption, { color: colors.foregroundMuted }], numberOfLines: 1, allowFontScaling: true }, caption) : null, footer)
3507
3577
  );
3508
3578
  if (onPress) {
3509
- return /* @__PURE__ */ React26.createElement(Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26.createElement(
3579
+ return /* @__PURE__ */ React26.createElement(Animated9.View, { style: animatedStyle }, /* @__PURE__ */ React26.createElement(
3510
3580
  TouchableOpacity,
3511
3581
  {
3512
3582
  onPress: handlePress,
3513
- onPressIn: handlePressIn,
3514
- onPressOut: handlePressOut,
3583
+ onPressIn,
3584
+ onPressOut,
3515
3585
  activeOpacity: 1,
3516
- touchSoundDisabled: true
3586
+ touchSoundDisabled: true,
3587
+ accessibilityRole: "button",
3588
+ accessibilityLabel: a11yLabel
3517
3589
  },
3518
3590
  cardContent
3519
3591
  ));
@@ -3523,12 +3595,10 @@ function MediaCard({
3523
3595
  var styles31 = StyleSheet.create({
3524
3596
  card: {
3525
3597
  borderRadius: RADIUS.md,
3526
- // 14px — Airbnb property card spec
3527
3598
  overflow: "hidden",
3528
3599
  backgroundColor: "transparent"
3529
3600
  },
3530
3601
  cardHovered: {
3531
- // Web hover: lift shadow
3532
3602
  ...SHADOWS.md
3533
3603
  },
3534
3604
  imageContainer: {
@@ -3579,43 +3649,38 @@ var styles31 = StyleSheet.create({
3579
3649
  lineHeight: mvs(16)
3580
3650
  }
3581
3651
  });
3582
- var nativeDriver14 = Platform.OS !== "web";
3583
3652
  function CategoryChip({
3584
3653
  item,
3585
3654
  selected,
3586
3655
  onPress
3587
3656
  }) {
3588
3657
  const { colors } = useTheme();
3589
- const scale2 = useRef(new Animated.Value(1)).current;
3590
- const handlePressIn = () => {
3591
- Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver14, speed: 40, bounciness: 0 }).start();
3592
- };
3593
- const handlePressOut = () => {
3594
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver14, speed: 40, bounciness: 4 }).start();
3595
- };
3596
- const bgColor = selected ? colors.primary : colors.surface;
3597
- const textColor = selected ? colors.primaryForeground : colors.foregroundSubtle;
3598
- const borderColor = selected ? colors.primary : colors.border;
3599
- const resolvedIcon = typeof item.icon === "string" ? renderIcon(item.icon, 16, textColor) : item.icon ?? null;
3600
- return /* @__PURE__ */ React26.createElement(Animated.View, { style: { transform: [{ scale: scale2 }] } }, /* @__PURE__ */ React26.createElement(
3658
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3659
+ pressScale: PRESS_SCALE.chip
3660
+ });
3661
+ const progress = useColorTransition(selected);
3662
+ const surfaceStyle = useAnimatedStyle(() => ({
3663
+ backgroundColor: interpolateColor(progress.value, [0, 1], [colors.surface, colors.primary]),
3664
+ borderColor: interpolateColor(progress.value, [0, 1], [colors.border, colors.primary])
3665
+ }));
3666
+ const textColorStyle = useAnimatedStyle(() => ({
3667
+ color: interpolateColor(progress.value, [0, 1], [colors.foregroundSubtle, colors.primaryForeground])
3668
+ }));
3669
+ const iconColor = selected ? colors.primaryForeground : colors.foregroundSubtle;
3670
+ const resolvedIcon = typeof item.icon === "string" ? renderIcon(item.icon, 16, iconColor) : item.icon ?? null;
3671
+ return /* @__PURE__ */ React26.createElement(Animated9.View, { style: scaleStyle, ...hoverHandlers }, /* @__PURE__ */ React26.createElement(
3601
3672
  TouchableOpacity,
3602
3673
  {
3603
- style: [
3604
- styles32.chip,
3605
- {
3606
- backgroundColor: bgColor,
3607
- borderColor
3608
- }
3609
- ],
3610
3674
  onPress,
3611
- onPressIn: handlePressIn,
3612
- onPressOut: handlePressOut,
3675
+ onPressIn,
3676
+ onPressOut,
3613
3677
  activeOpacity: 1,
3614
- touchSoundDisabled: true
3678
+ touchSoundDisabled: true,
3679
+ accessibilityRole: "button",
3680
+ accessibilityLabel: item.label,
3681
+ accessibilityState: { selected }
3615
3682
  },
3616
- resolvedIcon && /* @__PURE__ */ React26.createElement(View, { style: styles32.chipIcon }, resolvedIcon),
3617
- /* @__PURE__ */ React26.createElement(Text, { style: [styles32.chipLabel, { color: textColor }], allowFontScaling: true }, item.label),
3618
- item.badge !== void 0 && item.badge > 0 && /* @__PURE__ */ React26.createElement(View, { style: [styles32.chipBadge, { backgroundColor: colors.primary }] }, /* @__PURE__ */ React26.createElement(Text, { style: [styles32.chipBadgeText, { color: colors.primaryForeground }] }, Math.min(item.badge, 99)))
3683
+ /* @__PURE__ */ React26.createElement(Animated9.View, { style: [styles32.chip, surfaceStyle] }, resolvedIcon && /* @__PURE__ */ React26.createElement(View, { style: styles32.chipIcon }, resolvedIcon), /* @__PURE__ */ React26.createElement(Animated9.Text, { style: [styles32.chipLabel, textColorStyle], allowFontScaling: true }, item.label), item.badge !== void 0 && item.badge > 0 && /* @__PURE__ */ React26.createElement(View, { style: [styles32.chipBadge, { backgroundColor: colors.primary }] }, /* @__PURE__ */ React26.createElement(Text, { style: [styles32.chipBadgeText, { color: colors.primaryForeground }] }, Math.min(item.badge, 99))))
3619
3684
  ));
3620
3685
  }
3621
3686
  function CategoryStrip({
@@ -3624,7 +3689,8 @@ function CategoryStrip({
3624
3689
  onValueChange,
3625
3690
  multiSelect = false,
3626
3691
  style,
3627
- itemStyle
3692
+ itemStyle,
3693
+ accessibilityLabel
3628
3694
  }) {
3629
3695
  const selected = Array.isArray(value) ? value : value ? [value] : [];
3630
3696
  const handlePress = (v) => {
@@ -3643,7 +3709,9 @@ function CategoryStrip({
3643
3709
  horizontal: true,
3644
3710
  showsHorizontalScrollIndicator: false,
3645
3711
  contentContainerStyle: [styles32.container, style],
3646
- style: styles32.scroll
3712
+ style: styles32.scroll,
3713
+ accessibilityRole: multiSelect ? void 0 : "radiogroup",
3714
+ accessibilityLabel
3647
3715
  },
3648
3716
  categories.map((cat) => /* @__PURE__ */ React26.createElement(View, { key: cat.value, style: itemStyle }, /* @__PURE__ */ React26.createElement(
3649
3717
  CategoryChip,
@@ -3696,62 +3764,45 @@ var styles32 = StyleSheet.create({
3696
3764
  lineHeight: 14
3697
3765
  }
3698
3766
  });
3699
- var nativeDriver15 = Platform.OS !== "web";
3700
3767
  function Pressable2({
3701
3768
  children,
3702
3769
  onPress,
3703
- pressScale = 0.98,
3704
- bounciness = 4,
3770
+ pressScale = PRESS_SCALE.card,
3705
3771
  haptics = true,
3706
3772
  style,
3707
3773
  disabled,
3708
3774
  hoverScale = 1.02,
3709
3775
  ...touchableProps
3710
3776
  }) {
3711
- const scale2 = useRef(new Animated.Value(1)).current;
3712
- const { hovered, hoverHandlers } = useHover();
3713
- const handlePressIn = () => {
3714
- if (disabled) return;
3715
- Animated.spring(scale2, {
3716
- toValue: pressScale,
3717
- useNativeDriver: nativeDriver15,
3718
- speed: 40,
3719
- bounciness: 0
3720
- }).start();
3721
- };
3722
- const handlePressOut = () => {
3723
- if (disabled) return;
3724
- Animated.spring(scale2, {
3725
- toValue: 1,
3726
- useNativeDriver: nativeDriver15,
3727
- speed: 40,
3728
- bounciness
3729
- }).start();
3730
- };
3777
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3778
+ pressScale,
3779
+ hoverScale,
3780
+ pressInSpring: SPRINGS.surfacePressIn,
3781
+ pressOutSpring: SPRINGS.surfacePressOut,
3782
+ disabled
3783
+ });
3731
3784
  const handlePress = () => {
3732
3785
  if (disabled || !onPress) return;
3733
3786
  if (haptics) impactLight();
3734
3787
  onPress();
3735
3788
  };
3736
- const hoverScaleValue = hovered && hoverScale !== 1 ? hoverScale : 1;
3737
3789
  return /* @__PURE__ */ React26.createElement(
3738
- Animated.View,
3790
+ Animated9.View,
3739
3791
  {
3740
- style: [
3741
- { transform: [{ scale: Animated.multiply(scale2, hoverScaleValue) }] },
3742
- style
3743
- ],
3792
+ style: [animatedStyle, style],
3744
3793
  ...Platform.OS === "web" ? hoverHandlers : {}
3745
3794
  },
3746
3795
  /* @__PURE__ */ React26.createElement(
3747
3796
  TouchableOpacity,
3748
3797
  {
3749
3798
  onPress: handlePress,
3750
- onPressIn: handlePressIn,
3751
- onPressOut: handlePressOut,
3799
+ onPressIn,
3800
+ onPressOut,
3752
3801
  activeOpacity: 1,
3753
3802
  disabled,
3754
3803
  touchSoundDisabled: true,
3804
+ accessibilityRole: "button",
3805
+ accessibilityState: { disabled: !!disabled },
3755
3806
  ...touchableProps
3756
3807
  },
3757
3808
  children