@retray-dev/ui-kit 6.0.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/COMPONENTS.md +8 -7
  2. package/dist/index.d.mts +47 -23
  3. package/dist/index.d.ts +47 -23
  4. package/dist/index.js +702 -634
  5. package/dist/index.mjs +695 -627
  6. package/package.json +1 -1
  7. package/src/components/Accordion/Accordion.tsx +10 -12
  8. package/src/components/Button/Button.tsx +20 -18
  9. package/src/components/Card/Card.tsx +21 -33
  10. package/src/components/CategoryStrip/CategoryStrip.tsx +45 -38
  11. package/src/components/Checkbox/Checkbox.tsx +31 -50
  12. package/src/components/Chip/Chip.tsx +34 -71
  13. package/src/components/DetailRow/DetailRow.tsx +13 -8
  14. package/src/components/IconButton/IconButton.tsx +20 -18
  15. package/src/components/Input/Input.tsx +39 -22
  16. package/src/components/ListItem/ListItem.tsx +22 -34
  17. package/src/components/MediaCard/MediaCard.tsx +24 -24
  18. package/src/components/MenuItem/MenuItem.tsx +52 -39
  19. package/src/components/MonthPicker/MonthPicker.tsx +12 -2
  20. package/src/components/Pressable/Pressable.tsx +27 -46
  21. package/src/components/Progress/Progress.tsx +21 -12
  22. package/src/components/RadioGroup/RadioGroup.tsx +52 -26
  23. package/src/components/Select/Select.tsx +17 -15
  24. package/src/components/Sheet/Sheet.tsx +4 -1
  25. package/src/components/Skeleton/Skeleton.tsx +24 -13
  26. package/src/components/Slider/Slider.tsx +11 -1
  27. package/src/components/Switch/Switch.tsx +44 -49
  28. package/src/components/Tabs/Tabs.tsx +39 -31
  29. package/src/components/Textarea/Textarea.tsx +29 -12
  30. package/src/components/Toggle/Toggle.tsx +39 -45
  31. package/src/utils/animations.ts +58 -0
  32. package/src/utils/useColorTransition.ts +40 -0
  33. package/src/utils/usePressScale.ts +73 -0
package/dist/index.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]
1509
- });
1510
- const backgroundColor = bgOpacity.interpolate({
1511
- inputRange: [0, 1],
1512
- outputRange: ["transparent", colors.primary]
1611
+ const { animatedStyle: scaleStyle, onPressIn, onPressOut } = usePressScale({
1612
+ pressScale: PRESS_SCALE.button,
1613
+ disabled
1513
1614
  });
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,9 +3054,9 @@ 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,
3059
+ subtitle,
2954
3060
  iconName,
2955
3061
  icon,
2956
3062
  iconColor,
@@ -2961,29 +3067,16 @@ function MenuItem({
2961
3067
  variant = "plain",
2962
3068
  showSeparator = false,
2963
3069
  style,
2964
- labelStyle
3070
+ labelStyle,
3071
+ accessibilityLabel
2965
3072
  }) {
2966
3073
  const { colors } = useTheme();
2967
- const scale2 = useRef(new Animated.Value(1)).current;
2968
- const handlePressIn = () => {
2969
- if (disabled) return;
2970
- Animated.spring(scale2, {
2971
- toValue: 0.97,
2972
- useNativeDriver: nativeDriver11,
2973
- stiffness: 350,
2974
- damping: 28,
2975
- mass: 0.9
2976
- }).start();
2977
- };
2978
- const handlePressOut = () => {
2979
- Animated.spring(scale2, {
2980
- toValue: 1,
2981
- useNativeDriver: nativeDriver11,
2982
- stiffness: 220,
2983
- damping: 20,
2984
- mass: 0.9
2985
- }).start();
2986
- };
3074
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3075
+ pressScale: PRESS_SCALE.row,
3076
+ pressInSpring: SPRINGS.surfacePressIn,
3077
+ pressOutSpring: SPRINGS.surfacePressOut,
3078
+ disabled
3079
+ });
2987
3080
  const handlePress = () => {
2988
3081
  selectionAsync();
2989
3082
  onPress();
@@ -3000,19 +3093,23 @@ function MenuItem({
3000
3093
  shadowRadius: 6,
3001
3094
  elevation: 2
3002
3095
  } : {};
3003
- 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(
3004
3098
  TouchableOpacity,
3005
3099
  {
3006
3100
  style: [styles26.container, cardStyle, style],
3007
3101
  onPress: handlePress,
3008
- onPressIn: handlePressIn,
3009
- onPressOut: handlePressOut,
3102
+ onPressIn,
3103
+ onPressOut,
3010
3104
  disabled,
3011
3105
  activeOpacity: 1,
3012
- touchSoundDisabled: true
3106
+ touchSoundDisabled: true,
3107
+ accessibilityRole: "button",
3108
+ accessibilityLabel: a11yLabel,
3109
+ accessibilityState: { disabled }
3013
3110
  },
3014
3111
  resolvedIcon ? /* @__PURE__ */ React26.createElement(View, { style: styles26.iconContainer }, resolvedIcon) : null,
3015
- /* @__PURE__ */ React26.createElement(
3112
+ /* @__PURE__ */ React26.createElement(View, { style: styles26.labelContainer }, /* @__PURE__ */ React26.createElement(
3016
3113
  Text,
3017
3114
  {
3018
3115
  style: [styles26.label, { color: colors.foreground }, labelStyle],
@@ -3020,7 +3117,15 @@ function MenuItem({
3020
3117
  allowFontScaling: true
3021
3118
  },
3022
3119
  label
3023
- ),
3120
+ ), subtitle ? /* @__PURE__ */ React26.createElement(
3121
+ Text,
3122
+ {
3123
+ style: [styles26.subtitle, { color: colors.foregroundMuted }],
3124
+ numberOfLines: 1,
3125
+ allowFontScaling: true
3126
+ },
3127
+ subtitle
3128
+ ) : null),
3024
3129
  rightRender !== void 0 ? /* @__PURE__ */ React26.createElement(
3025
3130
  View,
3026
3131
  {
@@ -3060,10 +3165,18 @@ var styles26 = StyleSheet.create({
3060
3165
  justifyContent: "center",
3061
3166
  flexShrink: 0
3062
3167
  },
3168
+ labelContainer: {
3169
+ flex: 1,
3170
+ justifyContent: "center"
3171
+ },
3063
3172
  label: {
3064
3173
  fontFamily: "Poppins-Medium",
3065
- fontSize: ms(15),
3066
- flex: 1
3174
+ fontSize: ms(15)
3175
+ },
3176
+ subtitle: {
3177
+ fontFamily: "Poppins-Regular",
3178
+ fontSize: ms(12),
3179
+ marginTop: vs(1)
3067
3180
  },
3068
3181
  rightContainer: {
3069
3182
  alignItems: "flex-end",
@@ -3078,62 +3191,37 @@ var styles26 = StyleSheet.create({
3078
3191
  opacity: 0.45
3079
3192
  }
3080
3193
  });
3081
- var nativeDriver12 = Platform.OS !== "web";
3082
- function Chip({ label, selected = false, onPress, icon, iconName, style }) {
3194
+ function Chip({ label, selected = false, onPress, icon, iconName, style, accessibilityLabel }) {
3083
3195
  const { colors } = useTheme();
3084
- const scale2 = useRef(new Animated.Value(1)).current;
3085
- const pressAnim = useRef(new Animated.Value(selected ? 1 : 0)).current;
3086
- useEffect(() => {
3087
- Animated.timing(pressAnim, {
3088
- toValue: selected ? 1 : 0,
3089
- duration: 150,
3090
- easing: Easing$1.out(Easing$1.ease),
3091
- useNativeDriver: false
3092
- }).start();
3093
- }, [selected, pressAnim]);
3094
- const handlePressIn = () => {
3095
- Animated.spring(scale2, {
3096
- toValue: 0.95,
3097
- useNativeDriver: nativeDriver12,
3098
- speed: 40,
3099
- bounciness: 0
3100
- }).start();
3101
- };
3102
- const handlePressOut = () => {
3103
- Animated.spring(scale2, {
3104
- toValue: 1,
3105
- useNativeDriver: nativeDriver12,
3106
- speed: 40,
3107
- bounciness: 4
3108
- }).start();
3109
- };
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
+ }));
3110
3207
  const handlePress = () => {
3111
3208
  selectionAsync();
3112
3209
  onPress?.();
3113
3210
  };
3114
- const backgroundColor = pressAnim.interpolate({
3115
- inputRange: [0, 1],
3116
- outputRange: [colors.surface, colors.primary]
3117
- });
3118
- const textColor = pressAnim.interpolate({
3119
- inputRange: [0, 1],
3120
- outputRange: [colors.foreground, colors.primaryForeground]
3121
- });
3122
- const borderColor = pressAnim.interpolate({
3123
- inputRange: [0, 1],
3124
- outputRange: [colors.border, colors.primary]
3125
- });
3126
3211
  const resolvedIcon = iconName ? renderIcon(iconName, ms(13), selected ? colors.primaryForeground : colors.foreground) : icon;
3127
- 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(
3128
3213
  TouchableOpacity,
3129
3214
  {
3130
3215
  onPress: handlePress,
3131
- onPressIn: handlePressIn,
3132
- onPressOut: handlePressOut,
3216
+ onPressIn,
3217
+ onPressOut,
3133
3218
  activeOpacity: 1,
3134
- touchSoundDisabled: true
3219
+ touchSoundDisabled: true,
3220
+ accessibilityRole: "button",
3221
+ accessibilityLabel: accessibilityLabel ?? label,
3222
+ accessibilityState: { selected }
3135
3223
  },
3136
- /* @__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))
3137
3225
  ));
3138
3226
  }
3139
3227
  function ChipGroup({ options, value, onValueChange, multiSelect = false, style }) {
@@ -3144,12 +3232,7 @@ function ChipGroup({ options, value, onValueChange, multiSelect = false, style }
3144
3232
  }
3145
3233
  const currentArray = Array.isArray(value) ? value : value ? [value] : [];
3146
3234
  const isSelected2 = currentArray.includes(optionValue);
3147
- let newArray;
3148
- if (isSelected2) {
3149
- newArray = currentArray.filter((v) => v !== optionValue);
3150
- } else {
3151
- newArray = [...currentArray, optionValue];
3152
- }
3235
+ const newArray = isSelected2 ? currentArray.filter((v) => v !== optionValue) : [...currentArray, optionValue];
3153
3236
  onValueChange?.(newArray);
3154
3237
  };
3155
3238
  const isSelected = (optionValue) => {
@@ -3363,22 +3446,36 @@ function MonthPicker({ value, onChange, locale = "en", formatLabel, style }) {
3363
3446
  onChange({ month: value.month + 1, year: value.year });
3364
3447
  }
3365
3448
  };
3366
- 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(
3367
3450
  TouchableOpacity,
3368
3451
  {
3369
3452
  style: styles30.arrow,
3370
3453
  onPress: handlePrev,
3371
3454
  activeOpacity: 0.6,
3372
- touchSoundDisabled: true
3455
+ touchSoundDisabled: true,
3456
+ accessibilityRole: "button",
3457
+ accessibilityLabel: "Previous month",
3458
+ hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }
3373
3459
  },
3374
3460
  /* @__PURE__ */ React26.createElement(Entypo$1, { name: "chevron-left", size: 22, color: colors.foreground })
3375
- ), /* @__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(
3376
3470
  TouchableOpacity,
3377
3471
  {
3378
3472
  style: styles30.arrow,
3379
3473
  onPress: handleNext,
3380
3474
  activeOpacity: 0.6,
3381
- touchSoundDisabled: true
3475
+ touchSoundDisabled: true,
3476
+ accessibilityRole: "button",
3477
+ accessibilityLabel: "Next month",
3478
+ hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }
3382
3479
  },
3383
3480
  /* @__PURE__ */ React26.createElement(Entypo$1, { name: "chevron-right", size: 22, color: colors.foreground })
3384
3481
  ));
@@ -3403,18 +3500,6 @@ var styles30 = StyleSheet.create({
3403
3500
  minWidth: s(160)
3404
3501
  }
3405
3502
  });
3406
- function useHover() {
3407
- const [hovered, setHovered] = useState(false);
3408
- const onMouseEnter = useCallback(() => setHovered(true), []);
3409
- const onMouseLeave = useCallback(() => setHovered(false), []);
3410
- if (Platform.OS !== "web") {
3411
- return { hovered: false, hoverHandlers: {} };
3412
- }
3413
- return { hovered, hoverHandlers: { onMouseEnter, onMouseLeave } };
3414
- }
3415
-
3416
- // src/components/MediaCard/MediaCard.tsx
3417
- var nativeDriver13 = Platform.OS !== "web";
3418
3503
  var aspectRatioMap = {
3419
3504
  "1:1": 1,
3420
3505
  "4:3": 3 / 4,
@@ -3436,19 +3521,17 @@ function MediaCard({
3436
3521
  onPress,
3437
3522
  style,
3438
3523
  imageStyle,
3439
- footer
3524
+ footer,
3525
+ accessibilityLabel
3440
3526
  }) {
3441
3527
  const { colors } = useTheme();
3442
- const scale2 = useRef(new Animated.Value(1)).current;
3443
3528
  const { hovered, hoverHandlers } = useHover();
3444
- const handlePressIn = () => {
3445
- if (!onPress) return;
3446
- Animated.spring(scale2, { toValue: 0.98, useNativeDriver: nativeDriver13, speed: 40, bounciness: 0 }).start();
3447
- };
3448
- const handlePressOut = () => {
3449
- if (!onPress) return;
3450
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver13, speed: 40, bounciness: 4 }).start();
3451
- };
3529
+ const { animatedStyle, onPressIn, onPressOut } = usePressScale({
3530
+ pressScale: PRESS_SCALE.card,
3531
+ pressInSpring: SPRINGS.surfacePressIn,
3532
+ pressOutSpring: SPRINGS.surfacePressOut,
3533
+ disabled: !onPress
3534
+ });
3452
3535
  const handlePress = () => {
3453
3536
  if (!onPress) return;
3454
3537
  impactLight();
@@ -3456,6 +3539,7 @@ function MediaCard({
3456
3539
  };
3457
3540
  const ratio = aspectRatioMap[aspectRatio];
3458
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(". ");
3459
3543
  const cardContent = /* @__PURE__ */ React26.createElement(
3460
3544
  View,
3461
3545
  {
@@ -3482,21 +3566,26 @@ function MediaCard({
3482
3566
  onActionPress?.();
3483
3567
  },
3484
3568
  activeOpacity: 0.8,
3485
- touchSoundDisabled: true
3569
+ touchSoundDisabled: true,
3570
+ accessibilityRole: "button",
3571
+ accessibilityLabel: actionIconName ?? "action",
3572
+ accessibilityState: { selected: actionActive }
3486
3573
  },
3487
3574
  resolvedActionIcon
3488
3575
  )),
3489
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)
3490
3577
  );
3491
3578
  if (onPress) {
3492
- 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(
3493
3580
  TouchableOpacity,
3494
3581
  {
3495
3582
  onPress: handlePress,
3496
- onPressIn: handlePressIn,
3497
- onPressOut: handlePressOut,
3583
+ onPressIn,
3584
+ onPressOut,
3498
3585
  activeOpacity: 1,
3499
- touchSoundDisabled: true
3586
+ touchSoundDisabled: true,
3587
+ accessibilityRole: "button",
3588
+ accessibilityLabel: a11yLabel
3500
3589
  },
3501
3590
  cardContent
3502
3591
  ));
@@ -3506,12 +3595,10 @@ function MediaCard({
3506
3595
  var styles31 = StyleSheet.create({
3507
3596
  card: {
3508
3597
  borderRadius: RADIUS.md,
3509
- // 14px — Airbnb property card spec
3510
3598
  overflow: "hidden",
3511
3599
  backgroundColor: "transparent"
3512
3600
  },
3513
3601
  cardHovered: {
3514
- // Web hover: lift shadow
3515
3602
  ...SHADOWS.md
3516
3603
  },
3517
3604
  imageContainer: {
@@ -3562,43 +3649,38 @@ var styles31 = StyleSheet.create({
3562
3649
  lineHeight: mvs(16)
3563
3650
  }
3564
3651
  });
3565
- var nativeDriver14 = Platform.OS !== "web";
3566
3652
  function CategoryChip({
3567
3653
  item,
3568
3654
  selected,
3569
3655
  onPress
3570
3656
  }) {
3571
3657
  const { colors } = useTheme();
3572
- const scale2 = useRef(new Animated.Value(1)).current;
3573
- const handlePressIn = () => {
3574
- Animated.spring(scale2, { toValue: 0.95, useNativeDriver: nativeDriver14, speed: 40, bounciness: 0 }).start();
3575
- };
3576
- const handlePressOut = () => {
3577
- Animated.spring(scale2, { toValue: 1, useNativeDriver: nativeDriver14, speed: 40, bounciness: 4 }).start();
3578
- };
3579
- const bgColor = selected ? colors.primary : colors.surface;
3580
- const textColor = selected ? colors.primaryForeground : colors.foregroundSubtle;
3581
- const borderColor = selected ? colors.primary : colors.border;
3582
- const resolvedIcon = typeof item.icon === "string" ? renderIcon(item.icon, 16, textColor) : item.icon ?? null;
3583
- 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(
3584
3672
  TouchableOpacity,
3585
3673
  {
3586
- style: [
3587
- styles32.chip,
3588
- {
3589
- backgroundColor: bgColor,
3590
- borderColor
3591
- }
3592
- ],
3593
3674
  onPress,
3594
- onPressIn: handlePressIn,
3595
- onPressOut: handlePressOut,
3675
+ onPressIn,
3676
+ onPressOut,
3596
3677
  activeOpacity: 1,
3597
- touchSoundDisabled: true
3678
+ touchSoundDisabled: true,
3679
+ accessibilityRole: "button",
3680
+ accessibilityLabel: item.label,
3681
+ accessibilityState: { selected }
3598
3682
  },
3599
- resolvedIcon && /* @__PURE__ */ React26.createElement(View, { style: styles32.chipIcon }, resolvedIcon),
3600
- /* @__PURE__ */ React26.createElement(Text, { style: [styles32.chipLabel, { color: textColor }], allowFontScaling: true }, item.label),
3601
- 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))))
3602
3684
  ));
3603
3685
  }
3604
3686
  function CategoryStrip({
@@ -3607,7 +3689,8 @@ function CategoryStrip({
3607
3689
  onValueChange,
3608
3690
  multiSelect = false,
3609
3691
  style,
3610
- itemStyle
3692
+ itemStyle,
3693
+ accessibilityLabel
3611
3694
  }) {
3612
3695
  const selected = Array.isArray(value) ? value : value ? [value] : [];
3613
3696
  const handlePress = (v) => {
@@ -3626,7 +3709,9 @@ function CategoryStrip({
3626
3709
  horizontal: true,
3627
3710
  showsHorizontalScrollIndicator: false,
3628
3711
  contentContainerStyle: [styles32.container, style],
3629
- style: styles32.scroll
3712
+ style: styles32.scroll,
3713
+ accessibilityRole: multiSelect ? void 0 : "radiogroup",
3714
+ accessibilityLabel
3630
3715
  },
3631
3716
  categories.map((cat) => /* @__PURE__ */ React26.createElement(View, { key: cat.value, style: itemStyle }, /* @__PURE__ */ React26.createElement(
3632
3717
  CategoryChip,
@@ -3679,62 +3764,45 @@ var styles32 = StyleSheet.create({
3679
3764
  lineHeight: 14
3680
3765
  }
3681
3766
  });
3682
- var nativeDriver15 = Platform.OS !== "web";
3683
3767
  function Pressable2({
3684
3768
  children,
3685
3769
  onPress,
3686
- pressScale = 0.98,
3687
- bounciness = 4,
3770
+ pressScale = PRESS_SCALE.card,
3688
3771
  haptics = true,
3689
3772
  style,
3690
3773
  disabled,
3691
3774
  hoverScale = 1.02,
3692
3775
  ...touchableProps
3693
3776
  }) {
3694
- const scale2 = useRef(new Animated.Value(1)).current;
3695
- const { hovered, hoverHandlers } = useHover();
3696
- const handlePressIn = () => {
3697
- if (disabled) return;
3698
- Animated.spring(scale2, {
3699
- toValue: pressScale,
3700
- useNativeDriver: nativeDriver15,
3701
- speed: 40,
3702
- bounciness: 0
3703
- }).start();
3704
- };
3705
- const handlePressOut = () => {
3706
- if (disabled) return;
3707
- Animated.spring(scale2, {
3708
- toValue: 1,
3709
- useNativeDriver: nativeDriver15,
3710
- speed: 40,
3711
- bounciness
3712
- }).start();
3713
- };
3777
+ const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
3778
+ pressScale,
3779
+ hoverScale,
3780
+ pressInSpring: SPRINGS.surfacePressIn,
3781
+ pressOutSpring: SPRINGS.surfacePressOut,
3782
+ disabled
3783
+ });
3714
3784
  const handlePress = () => {
3715
3785
  if (disabled || !onPress) return;
3716
3786
  if (haptics) impactLight();
3717
3787
  onPress();
3718
3788
  };
3719
- const hoverScaleValue = hovered && hoverScale !== 1 ? hoverScale : 1;
3720
3789
  return /* @__PURE__ */ React26.createElement(
3721
- Animated.View,
3790
+ Animated9.View,
3722
3791
  {
3723
- style: [
3724
- { transform: [{ scale: Animated.multiply(scale2, hoverScaleValue) }] },
3725
- style
3726
- ],
3792
+ style: [animatedStyle, style],
3727
3793
  ...Platform.OS === "web" ? hoverHandlers : {}
3728
3794
  },
3729
3795
  /* @__PURE__ */ React26.createElement(
3730
3796
  TouchableOpacity,
3731
3797
  {
3732
3798
  onPress: handlePress,
3733
- onPressIn: handlePressIn,
3734
- onPressOut: handlePressOut,
3799
+ onPressIn,
3800
+ onPressOut,
3735
3801
  activeOpacity: 1,
3736
3802
  disabled,
3737
3803
  touchSoundDisabled: true,
3804
+ accessibilityRole: "button",
3805
+ accessibilityState: { disabled: !!disabled },
3738
3806
  ...touchableProps
3739
3807
  },
3740
3808
  children
@@ -3780,14 +3848,14 @@ function DetailRow({
3780
3848
  allowFontScaling: true
3781
3849
  },
3782
3850
  label
3783
- ) : label), separatorStyle ? /* @__PURE__ */ React26.createElement(View, { style: separatorStyle }) : /* @__PURE__ */ React26.createElement(View, { style: styles33.spacer }), /* @__PURE__ */ React26.createElement(View, { style: styles33.valueSide }, /* @__PURE__ */ React26.createElement(
3851
+ ) : label), separatorStyle ? /* @__PURE__ */ React26.createElement(View, { style: separatorStyle }) : /* @__PURE__ */ React26.createElement(View, { style: styles33.spacer }), /* @__PURE__ */ React26.createElement(View, { style: styles33.valueSide }, typeof value === "string" ? /* @__PURE__ */ React26.createElement(
3784
3852
  Text,
3785
3853
  {
3786
3854
  style: [styles33.valueText, { color: valueColor ?? colors.foreground }, valueStyle],
3787
3855
  allowFontScaling: true
3788
3856
  },
3789
3857
  value
3790
- ), resolvedRightIcon ? /* @__PURE__ */ React26.createElement(View, { style: styles33.icon }, resolvedRightIcon) : null));
3858
+ ) : value, resolvedRightIcon ? /* @__PURE__ */ React26.createElement(View, { style: styles33.icon }, resolvedRightIcon) : null));
3791
3859
  }
3792
3860
  var styles33 = StyleSheet.create({
3793
3861
  row: {