zkit-ui 1.0.1 → 1.0.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/Button/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/Button/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAEV,cAAc,EAId,SAAS,EACT,SAAS,EACT,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,SAAS,EAKT,IAAI,EACL,MAAM,cAAc,CAAC;AAetB,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAC5E,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC3F,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AACxD,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1D,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,KAAK,CAAC;AAClD,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,MAAM,GACN,SAAS,GACT,OAAO,GACP,WAAW,GACX,iBAAiB,GACjB,eAAe,CAAC;AACpB,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,SAAS,CAAC;AACrD,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,CAAC;AACnD,MAAM,MAAM,uBAAuB,GAC/B,UAAU,GACV,SAAS,GACT,WAAW,GACX,QAAQ,GACR,iBAAiB,GACjB,gBAAgB,GAChB,cAAc,GACd,aAAa,GACb,GAAG,MAAM,KAAK,CAAC;AACnB,MAAM,MAAM,mBAAmB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,uBAAuB,CAAC;IACpC,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,GAAG,CAAC,EAAE,mBAAmB,CAAC;CAC3B,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC3D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AACF,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;AACjE,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AACF,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AACF,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,KAAK,oBAAoB,GAAG,IAAI,CAC9B,KAAK,CAAC,wBAAwB,CAAC,OAAO,SAAS,CAAC,EAChD,mBAAmB,GAAG,oBAAoB,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,CAC/E,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,KAAK,CAAC,YAAY,CAAC,OAAO,SAAS,CAAC,CAAC;AAE7D,MAAM,WAAW,WAAY,SAAQ,oBAAoB;IACvD,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEhC,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAC;IAEtB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,kBAAkB,CAAC,EAAE,KAAK,CAAC,wBAAwB,CAAC,OAAO,SAAS,CAAC,CAAC,oBAAoB,CAAC,CAAC;CAC7F;AA6tDD,eAAO,MAAM,MAAM,qEAA4B,CAAC"}
@@ -53,6 +53,7 @@ const LOADING_TIMING = {
53
53
  easing: react_native_reanimated_1.Easing.out(react_native_reanimated_1.Easing.cubic),
54
54
  reduceMotion: react_native_reanimated_1.ReduceMotion.System,
55
55
  };
56
+ const ANIMATED_EXIT_RESIDENCY_MS = LOADING_TIMING.duration + 40;
56
57
  const DISABLED_OPACITY = 0.52;
57
58
  const PRESSED_OPACITY = 0.82;
58
59
  const PRESSED_SCALE = 0.985;
@@ -373,7 +374,7 @@ function resolvePressEffect(effect, interactionDisabled) {
373
374
  return 'none';
374
375
  if (effect !== 'auto')
375
376
  return effect;
376
- return 'scale-opacity';
377
+ return 'opacity';
377
378
  }
378
379
  function resolvePressEffectFlags(effect) {
379
380
  return {
@@ -538,8 +539,16 @@ function resolveWebCursorStyle(disabled) {
538
539
  return undefined;
539
540
  return { cursor: disabled ? 'not-allowed' : 'pointer' };
540
541
  }
541
- function shouldUseStaticButtonPath({ disabled, loading, pressEffect, }) {
542
- return !loading && (pressEffect === 'none' || disabled === true);
542
+ function requiresAnimatedPressEffect(effect) {
543
+ return (effect === 'scale' ||
544
+ effect === 'scale-opacity' ||
545
+ effect === 'highlight' ||
546
+ effect === 'scale-highlight');
547
+ }
548
+ function shouldRequestAnimatedPressPath({ disabled, pressEffect, }) {
549
+ if (disabled)
550
+ return false;
551
+ return requiresAnimatedPressEffect(pressEffect);
543
552
  }
544
553
  function useButtonFrame({ variant = 'solid', tone = 'primary', size = 'md', shape = 'rounded', block = false, disabled = false, loading = false, icon, iconPlacement = 'start', iconOnly = false, color, colors, border, layout, gradient, shadow = 'none', children, textStyle, accessibilityLabel, }) {
545
554
  var _a, _b;
@@ -670,7 +679,7 @@ function useButtonFrame({ variant = 'solid', tone = 'primary', size = 'md', shap
670
679
  borderStyle,
671
680
  };
672
681
  }
673
- function ButtonStaticImpl({ variant = 'solid', tone = 'primary', size = 'md', shape = 'rounded', block = false, disabled = false, loading = false, loadingMode: _loadingMode = 'inline', pressEffect: _pressEffect = 'auto', icon, iconPlacement = 'start', iconOnly = false, color, colors, border, layout, gradient, shadow = 'none', children, style, contentStyle, textStyle, accessibilityLabel, accessibilityState, hitSlop, onPress, testID, ...pressableProps }, ref) {
682
+ function ButtonStaticImpl({ variant = 'solid', tone = 'primary', size = 'md', shape = 'rounded', block = false, disabled = false, loading = false, loadingMode: _loadingMode = 'inline', pressEffect = 'auto', icon, iconPlacement = 'start', iconOnly = false, color, colors, border, layout, gradient, shadow = 'none', children, style, contentStyle, textStyle, accessibilityLabel, accessibilityState, hitSlop, onPress, testID, ...pressableProps }, ref) {
674
683
  const frame = useButtonFrame({
675
684
  accessibilityLabel,
676
685
  block,
@@ -698,19 +707,30 @@ function ButtonStaticImpl({ variant = 'solid', tone = 'primary', size = 'md', sh
698
707
  return;
699
708
  onPress === null || onPress === void 0 ? void 0 : onPress(event);
700
709
  }, [interactionDisabled, onPress]);
710
+ const pressOpacityEnabled = !interactionDisabled && (pressEffect === 'auto' || pressEffect === 'opacity');
711
+ const rootStyle = React.useCallback(({ pressed }) => [
712
+ styles.root,
713
+ rootSizeStyle,
714
+ radiusStyle,
715
+ shadowStyle,
716
+ visualDisabled ? styles.disabledRoot : null,
717
+ pressOpacityEnabled && pressed ? styles.pressedRoot : null,
718
+ webCursorStyle,
719
+ style,
720
+ ], [
721
+ pressOpacityEnabled,
722
+ radiusStyle,
723
+ rootSizeStyle,
724
+ shadowStyle,
725
+ style,
726
+ visualDisabled,
727
+ webCursorStyle,
728
+ ]);
701
729
  return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { ...pressableProps, ref: ref, accessibilityLabel: resolvedAccessibilityLabel, accessibilityRole: "button", accessibilityState: {
702
730
  ...accessibilityState,
703
731
  busy: Boolean(loading || (accessibilityState === null || accessibilityState === void 0 ? void 0 : accessibilityState.busy)),
704
732
  disabled: Boolean(interactionDisabled || (accessibilityState === null || accessibilityState === void 0 ? void 0 : accessibilityState.disabled)),
705
- }, disabled: interactionDisabled, hitSlop: hitSlop !== null && hitSlop !== void 0 ? hitSlop : defaultHitSlop, onPress: handlePress, style: [
706
- styles.root,
707
- rootSizeStyle,
708
- radiusStyle,
709
- shadowStyle,
710
- visualDisabled ? styles.disabledRoot : null,
711
- webCursorStyle,
712
- style,
713
- ], testID: testID, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { pointerEvents: "none", style: [
733
+ }, disabled: interactionDisabled, hitSlop: hitSlop !== null && hitSlop !== void 0 ? hitSlop : defaultHitSlop, onPress: handlePress, style: rootStyle, testID: testID, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { pointerEvents: "none", style: [
714
734
  styles.content,
715
735
  contentSizeStyle,
716
736
  paddingStyle,
@@ -720,6 +740,101 @@ function ButtonStaticImpl({ variant = 'solid', tone = 'primary', size = 'md', sh
720
740
  contentStyle,
721
741
  ], children: [LinearGradientComponent && gradientCfg ? ((0, jsx_runtime_1.jsx)(LinearGradientComponent, { pointerEvents: "none", colors: gradientCfg.colors, start: gradientCfg.start, end: gradientCfg.end, style: [react_native_1.StyleSheet.absoluteFillObject, radiusStyle] })) : null, iconOnly ? ((icon !== null && icon !== void 0 ? icon : resolvedText)) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [icon && iconPlacement === 'start' ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [iconBaseBoxStyle, iconPlacedBoxStyle], children: icon })) : null, resolvedText, icon && iconPlacement === 'end' ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [iconBaseBoxStyle, iconPlacedBoxStyle], children: icon })) : null] }))] }) }));
722
742
  }
743
+ function ButtonPressAnimatedImpl({ variant = 'solid', tone = 'primary', size = 'md', shape = 'rounded', block = false, disabled = false, loading = false, loadingMode: _loadingMode = 'inline', pressEffect = 'auto', icon, iconPlacement = 'start', iconOnly = false, color, colors, border, layout, gradient, shadow = 'none', children, style, contentStyle, textStyle, accessibilityLabel, accessibilityState, hitSlop, onPress, onPressIn, onPressOut, testID, ...pressableProps }, ref) {
744
+ const frame = useButtonFrame({
745
+ accessibilityLabel,
746
+ block,
747
+ border,
748
+ children,
749
+ color,
750
+ colors,
751
+ disabled,
752
+ gradient,
753
+ icon,
754
+ iconOnly,
755
+ iconPlacement,
756
+ layout,
757
+ loading,
758
+ shadow,
759
+ shape,
760
+ size,
761
+ textStyle,
762
+ tone,
763
+ variant,
764
+ });
765
+ const { accentColor, borderStyle, contentSizeStyle, defaultHitSlop, gradientCfg, hasGradientBackground, hasIcon, iconBaseBoxStyle, iconPlacedBoxStyle, interactionDisabled, LinearGradientComponent, paddingStyle, radiusStyle, resolvedAccessibilityLabel, resolvedText, rootSizeStyle, shadowStyle, visualColors, visualDisabled, webCursorStyle, } = frame;
766
+ const pressSv = (0, react_native_reanimated_1.useSharedValue)(0);
767
+ const resolvedPressEffect = React.useMemo(() => resolvePressEffect(pressEffect, interactionDisabled), [interactionDisabled, pressEffect]);
768
+ const pressEffectFlags = React.useMemo(() => resolvePressEffectFlags(resolvedPressEffect), [resolvedPressEffect]);
769
+ const pressHighlightEnabled = pressEffectFlags.highlight;
770
+ const pressOpacityEnabled = pressEffectFlags.opacity;
771
+ const pressScaleEnabled = pressEffectFlags.scale;
772
+ const pressOverlay = React.useMemo(() => resolveOverlay({
773
+ accentColor,
774
+ colors,
775
+ hasGradientBackground,
776
+ variant,
777
+ }), [accentColor, colors, hasGradientBackground, variant]);
778
+ React.useEffect(() => {
779
+ if (interactionDisabled || resolvedPressEffect === 'none') {
780
+ pressSv.value = 0;
781
+ }
782
+ }, [interactionDisabled, pressSv, resolvedPressEffect]);
783
+ const rootAnimatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
784
+ const baseOpacity = visualDisabled ? DISABLED_OPACITY : 1;
785
+ const scale = pressScaleEnabled
786
+ ? (0, react_native_reanimated_1.interpolate)(pressSv.value, [0, 1], [1, PRESSED_SCALE])
787
+ : 1;
788
+ const opacity = pressOpacityEnabled
789
+ ? (0, react_native_reanimated_1.interpolate)(pressSv.value, [0, 1], [baseOpacity, PRESSED_OPACITY])
790
+ : baseOpacity;
791
+ return {
792
+ opacity,
793
+ transform: [{ scale }],
794
+ };
795
+ }, [pressOpacityEnabled, pressScaleEnabled, visualDisabled]);
796
+ const overlayAnimatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
797
+ const opacity = pressHighlightEnabled
798
+ ? (0, react_native_reanimated_1.interpolate)(pressSv.value, [0, 1], [0, pressOverlay.strength])
799
+ : 0;
800
+ return { opacity };
801
+ }, [pressHighlightEnabled, pressOverlay.strength]);
802
+ const handlePress = React.useCallback((event) => {
803
+ if (interactionDisabled)
804
+ return;
805
+ onPress === null || onPress === void 0 ? void 0 : onPress(event);
806
+ }, [interactionDisabled, onPress]);
807
+ const handlePressIn = React.useCallback((event) => {
808
+ if (!interactionDisabled && resolvedPressEffect !== 'none') {
809
+ pressSv.value = (0, react_native_reanimated_1.withTiming)(1, PRESS_TIMING);
810
+ }
811
+ onPressIn === null || onPressIn === void 0 ? void 0 : onPressIn(event);
812
+ }, [interactionDisabled, onPressIn, pressSv, resolvedPressEffect]);
813
+ const handlePressOut = React.useCallback((event) => {
814
+ if (!interactionDisabled && resolvedPressEffect !== 'none') {
815
+ pressSv.value = (0, react_native_reanimated_1.withTiming)(0, PRESS_TIMING);
816
+ }
817
+ onPressOut === null || onPressOut === void 0 ? void 0 : onPressOut(event);
818
+ }, [interactionDisabled, onPressOut, pressSv, resolvedPressEffect]);
819
+ return ((0, jsx_runtime_1.jsx)(AnimatedPressable, { ...pressableProps, ref: ref, accessibilityLabel: resolvedAccessibilityLabel, accessibilityRole: "button", accessibilityState: {
820
+ ...accessibilityState,
821
+ busy: Boolean(loading || (accessibilityState === null || accessibilityState === void 0 ? void 0 : accessibilityState.busy)),
822
+ disabled: Boolean(interactionDisabled || (accessibilityState === null || accessibilityState === void 0 ? void 0 : accessibilityState.disabled)),
823
+ }, disabled: interactionDisabled, hitSlop: hitSlop !== null && hitSlop !== void 0 ? hitSlop : defaultHitSlop, onPress: handlePress, onPressIn: handlePressIn, onPressOut: handlePressOut, style: [styles.root, rootSizeStyle, radiusStyle, shadowStyle, rootAnimatedStyle, webCursorStyle, style], testID: testID, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { pointerEvents: "none", style: [
824
+ styles.content,
825
+ contentSizeStyle,
826
+ paddingStyle,
827
+ radiusStyle,
828
+ borderStyle,
829
+ { backgroundColor: visualColors.backgroundColor },
830
+ contentStyle,
831
+ ], children: [LinearGradientComponent && gradientCfg ? ((0, jsx_runtime_1.jsx)(LinearGradientComponent, { pointerEvents: "none", colors: gradientCfg.colors, start: gradientCfg.start, end: gradientCfg.end, style: [react_native_1.StyleSheet.absoluteFillObject, radiusStyle] })) : null, pressHighlightEnabled ? ((0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.View, { pointerEvents: "none", style: [
832
+ react_native_1.StyleSheet.absoluteFillObject,
833
+ radiusStyle,
834
+ { backgroundColor: pressOverlay.color },
835
+ overlayAnimatedStyle,
836
+ ] })) : null, iconOnly ? ((icon !== null && icon !== void 0 ? icon : resolvedText)) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [icon && iconPlacement === 'start' ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [iconBaseBoxStyle, iconPlacedBoxStyle], children: icon })) : null, resolvedText, icon && iconPlacement === 'end' ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [iconBaseBoxStyle, iconPlacedBoxStyle], children: icon })) : null] }))] }) }));
837
+ }
723
838
  function ButtonAnimatedImpl({ variant = 'solid', tone = 'primary', size = 'md', shape = 'rounded', block = false, disabled = false, loading = false, loadingMode = 'inline', pressEffect = 'auto', icon, iconPlacement = 'start', iconOnly = false, color, colors, border, layout, gradient, shadow = 'none', children, style, contentStyle, textStyle, accessibilityLabel, accessibilityState, hitSlop, onPress, onPressIn, onPressOut, testID, ...pressableProps }, ref) {
724
839
  const resolvedLoadingMode = iconOnly ? 'overlay' : loadingMode;
725
840
  const centeredLoading = resolvedLoadingMode !== 'inline';
@@ -752,7 +867,7 @@ function ButtonAnimatedImpl({ variant = 'solid', tone = 'primary', size = 'md',
752
867
  variant,
753
868
  }), [iconOnly, layout, metrics, variant]);
754
869
  const pressSv = (0, react_native_reanimated_1.useSharedValue)(0);
755
- const loadingSv = (0, react_native_reanimated_1.useSharedValue)(loading ? 1 : 0);
870
+ const loadingSv = (0, react_native_reanimated_1.useSharedValue)(0);
756
871
  const [spinnerMounted, setSpinnerMounted] = React.useState(loading);
757
872
  const hideSpinnerTimerRef = React.useRef(null);
758
873
  const resolvedPressEffect = React.useMemo(() => resolvePressEffect(pressEffect, interactionDisabled), [interactionDisabled, pressEffect]);
@@ -903,13 +1018,32 @@ function ButtonAnimatedImpl({ variant = 'solid', tone = 'primary', size = 'md',
903
1018
  }
904
1019
  const ButtonStaticWithRef = React.forwardRef(ButtonStaticImpl);
905
1020
  ButtonStaticWithRef.displayName = 'ButtonStatic';
1021
+ const ButtonPressAnimatedWithRef = React.forwardRef(ButtonPressAnimatedImpl);
1022
+ ButtonPressAnimatedWithRef.displayName = 'ButtonPressAnimated';
906
1023
  const ButtonAnimatedWithRef = React.forwardRef(ButtonAnimatedImpl);
907
- ButtonAnimatedWithRef.displayName = 'ButtonAnimated';
1024
+ ButtonAnimatedWithRef.displayName = 'ButtonLoadingAnimated';
908
1025
  function ButtonImpl(props, ref) {
909
- if (shouldUseStaticButtonPath(props)) {
910
- return (0, jsx_runtime_1.jsx)(ButtonStaticWithRef, { ...props, ref: ref });
1026
+ const loadingRequested = Boolean(props.loading);
1027
+ const animatedPressRequested = shouldRequestAnimatedPressPath(props);
1028
+ const [retainLoadingAnimated, setRetainLoadingAnimated] = React.useState(loadingRequested);
1029
+ React.useEffect(() => {
1030
+ if (loadingRequested) {
1031
+ setRetainLoadingAnimated(true);
1032
+ return undefined;
1033
+ }
1034
+ if (!retainLoadingAnimated)
1035
+ return undefined;
1036
+ const timer = setTimeout(() => {
1037
+ setRetainLoadingAnimated(false);
1038
+ }, ANIMATED_EXIT_RESIDENCY_MS);
1039
+ return () => {
1040
+ clearTimeout(timer);
1041
+ };
1042
+ }, [loadingRequested, retainLoadingAnimated]);
1043
+ if (loadingRequested || retainLoadingAnimated) {
1044
+ return (0, jsx_runtime_1.jsx)(ButtonAnimatedWithRef, { ...props, ref: ref });
911
1045
  }
912
- return (0, jsx_runtime_1.jsx)(ButtonAnimatedWithRef, { ...props, ref: ref });
1046
+ return animatedPressRequested ? ((0, jsx_runtime_1.jsx)(ButtonPressAnimatedWithRef, { ...props, ref: ref })) : ((0, jsx_runtime_1.jsx)(ButtonStaticWithRef, { ...props, ref: ref }));
913
1047
  }
914
1048
  const ButtonWithRef = React.forwardRef(ButtonImpl);
915
1049
  ButtonWithRef.displayName = 'Button';
@@ -943,6 +1077,9 @@ const styles = react_native_1.StyleSheet.create({
943
1077
  disabledRoot: {
944
1078
  opacity: DISABLED_OPACITY,
945
1079
  },
1080
+ pressedRoot: {
1081
+ opacity: PRESSED_OPACITY,
1082
+ },
946
1083
  spinnerBox: {
947
1084
  alignItems: 'center',
948
1085
  justifyContent: 'center',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zkit-ui",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -154,11 +154,11 @@ export function Demo() {
154
154
  }
155
155
  ```
156
156
 
157
- 渐变依赖 `expo-linear-gradient`,缺失时会使用第一段颜色作为稳定兜底。按压、loading 显隐与内容切换都走 Reanimated,减少 JS 抖动对关键帧的影响。
157
+ 渐变依赖 `expo-linear-gradient`,缺失时会使用第一段颜色作为稳定兜底。默认 `pressEffect="auto"` 走普通 `Pressable` 静态路径,并用原生 pressed style 做轻量透明度反馈;只有 `scale`、`scale-opacity`、`highlight`、`scale-highlight` 和 loading 过渡会升级到 Reanimated 动画路径。动静态切换由 Button 内部自动管理,loading 退出动画结束后才会回到静态路径。
158
158
 
159
159
  ## 列表高频渲染
160
160
 
161
- 在 `FlatList` / `FlashList` cell 中批量渲染普通静态按钮时,建议显式使用 `pressEffect="none"`。非 loading 场景会走轻量 `Pressable + View` 路径,避免为每个 cell 挂载 Reanimated press/loading 动画结构。
161
+ 在 `FlatList` / `FlashList` cell 中批量渲染普通按钮时,默认 `pressEffect="auto"` 已命中轻量 `Pressable + View` 路径,不会为每个 cell 挂载 Reanimated press/loading 动画结构。列表内需要更强反馈时优先使用 `pressEffect="opacity"`;只有确实需要缩放、高亮遮罩或 loading 动画时再使用对应动画配置。
162
162
 
163
163
  ## 常用 Props
164
164
 
@@ -4,6 +4,7 @@ import type {
4
4
  DimensionValue,
5
5
  GestureResponderEvent,
6
6
  Insets,
7
+ PressableStateCallbackType,
7
8
  StyleProp,
8
9
  TextStyle,
9
10
  ViewStyle,
@@ -201,6 +202,7 @@ const LOADING_TIMING = {
201
202
  easing: Easing.out(Easing.cubic),
202
203
  reduceMotion: ReduceMotion.System,
203
204
  } as const;
205
+ const ANIMATED_EXIT_RESIDENCY_MS = LOADING_TIMING.duration + 40;
204
206
 
205
207
  const DISABLED_OPACITY = 0.52;
206
208
  const PRESSED_OPACITY = 0.82;
@@ -574,7 +576,7 @@ function resolveVisualColors({
574
576
  function resolvePressEffect(effect: ButtonPressEffect, interactionDisabled: boolean): ButtonPressEffect {
575
577
  if (interactionDisabled) return 'none';
576
578
  if (effect !== 'auto') return effect;
577
- return 'scale-opacity';
579
+ return 'opacity';
578
580
  }
579
581
 
580
582
  function resolvePressEffectFlags(effect: ButtonPressEffect): PressEffectFlags {
@@ -837,16 +839,24 @@ function resolveWebCursorStyle(disabled: boolean): ViewStyle | undefined {
837
839
  return { cursor: disabled ? 'not-allowed' : 'pointer' } as ViewStyle;
838
840
  }
839
841
 
840
- function shouldUseStaticButtonPath({
842
+ function requiresAnimatedPressEffect(effect: ButtonPressEffect | undefined) {
843
+ return (
844
+ effect === 'scale' ||
845
+ effect === 'scale-opacity' ||
846
+ effect === 'highlight' ||
847
+ effect === 'scale-highlight'
848
+ );
849
+ }
850
+
851
+ function shouldRequestAnimatedPressPath({
841
852
  disabled,
842
- loading,
843
853
  pressEffect,
844
854
  }: {
845
855
  disabled?: boolean;
846
- loading?: boolean;
847
856
  pressEffect?: ButtonPressEffect;
848
857
  }) {
849
- return !loading && (pressEffect === 'none' || disabled === true);
858
+ if (disabled) return false;
859
+ return requiresAnimatedPressEffect(pressEffect);
850
860
  }
851
861
 
852
862
  function useButtonFrame({
@@ -1079,7 +1089,7 @@ function ButtonStaticImpl(
1079
1089
  disabled = false,
1080
1090
  loading = false,
1081
1091
  loadingMode: _loadingMode = 'inline',
1082
- pressEffect: _pressEffect = 'auto',
1092
+ pressEffect = 'auto',
1083
1093
  icon,
1084
1094
  iconPlacement = 'start',
1085
1095
  iconOnly = false,
@@ -1151,6 +1161,28 @@ function ButtonStaticImpl(
1151
1161
  },
1152
1162
  [interactionDisabled, onPress]
1153
1163
  );
1164
+ const pressOpacityEnabled = !interactionDisabled && (pressEffect === 'auto' || pressEffect === 'opacity');
1165
+ const rootStyle = React.useCallback(
1166
+ ({ pressed }: PressableStateCallbackType) => [
1167
+ styles.root,
1168
+ rootSizeStyle,
1169
+ radiusStyle,
1170
+ shadowStyle,
1171
+ visualDisabled ? styles.disabledRoot : null,
1172
+ pressOpacityEnabled && pressed ? styles.pressedRoot : null,
1173
+ webCursorStyle,
1174
+ style,
1175
+ ],
1176
+ [
1177
+ pressOpacityEnabled,
1178
+ radiusStyle,
1179
+ rootSizeStyle,
1180
+ shadowStyle,
1181
+ style,
1182
+ visualDisabled,
1183
+ webCursorStyle,
1184
+ ]
1185
+ );
1154
1186
 
1155
1187
  return (
1156
1188
  <Pressable
@@ -1166,15 +1198,7 @@ function ButtonStaticImpl(
1166
1198
  disabled={interactionDisabled}
1167
1199
  hitSlop={hitSlop ?? defaultHitSlop}
1168
1200
  onPress={handlePress}
1169
- style={[
1170
- styles.root,
1171
- rootSizeStyle,
1172
- radiusStyle,
1173
- shadowStyle,
1174
- visualDisabled ? styles.disabledRoot : null,
1175
- webCursorStyle,
1176
- style,
1177
- ]}
1201
+ style={rootStyle}
1178
1202
  testID={testID}
1179
1203
  >
1180
1204
  <View
@@ -1217,6 +1241,235 @@ function ButtonStaticImpl(
1217
1241
  );
1218
1242
  }
1219
1243
 
1244
+ function ButtonPressAnimatedImpl(
1245
+ {
1246
+ variant = 'solid',
1247
+ tone = 'primary',
1248
+ size = 'md',
1249
+ shape = 'rounded',
1250
+ block = false,
1251
+ disabled = false,
1252
+ loading = false,
1253
+ loadingMode: _loadingMode = 'inline',
1254
+ pressEffect = 'auto',
1255
+ icon,
1256
+ iconPlacement = 'start',
1257
+ iconOnly = false,
1258
+ color,
1259
+ colors,
1260
+ border,
1261
+ layout,
1262
+ gradient,
1263
+ shadow = 'none',
1264
+ children,
1265
+ style,
1266
+ contentStyle,
1267
+ textStyle,
1268
+ accessibilityLabel,
1269
+ accessibilityState,
1270
+ hitSlop,
1271
+ onPress,
1272
+ onPressIn,
1273
+ onPressOut,
1274
+ testID,
1275
+ ...pressableProps
1276
+ }: ButtonProps,
1277
+ ref: React.ForwardedRef<ButtonRef>
1278
+ ) {
1279
+ const frame = useButtonFrame({
1280
+ accessibilityLabel,
1281
+ block,
1282
+ border,
1283
+ children,
1284
+ color,
1285
+ colors,
1286
+ disabled,
1287
+ gradient,
1288
+ icon,
1289
+ iconOnly,
1290
+ iconPlacement,
1291
+ layout,
1292
+ loading,
1293
+ shadow,
1294
+ shape,
1295
+ size,
1296
+ textStyle,
1297
+ tone,
1298
+ variant,
1299
+ });
1300
+ const {
1301
+ accentColor,
1302
+ borderStyle,
1303
+ contentSizeStyle,
1304
+ defaultHitSlop,
1305
+ gradientCfg,
1306
+ hasGradientBackground,
1307
+ hasIcon,
1308
+ iconBaseBoxStyle,
1309
+ iconPlacedBoxStyle,
1310
+ interactionDisabled,
1311
+ LinearGradientComponent,
1312
+ paddingStyle,
1313
+ radiusStyle,
1314
+ resolvedAccessibilityLabel,
1315
+ resolvedText,
1316
+ rootSizeStyle,
1317
+ shadowStyle,
1318
+ visualColors,
1319
+ visualDisabled,
1320
+ webCursorStyle,
1321
+ } = frame;
1322
+
1323
+ const pressSv = useSharedValue(0);
1324
+ const resolvedPressEffect = React.useMemo(
1325
+ () => resolvePressEffect(pressEffect, interactionDisabled),
1326
+ [interactionDisabled, pressEffect]
1327
+ );
1328
+ const pressEffectFlags = React.useMemo(
1329
+ () => resolvePressEffectFlags(resolvedPressEffect),
1330
+ [resolvedPressEffect]
1331
+ );
1332
+ const pressHighlightEnabled = pressEffectFlags.highlight;
1333
+ const pressOpacityEnabled = pressEffectFlags.opacity;
1334
+ const pressScaleEnabled = pressEffectFlags.scale;
1335
+ const pressOverlay = React.useMemo(
1336
+ () =>
1337
+ resolveOverlay({
1338
+ accentColor,
1339
+ colors,
1340
+ hasGradientBackground,
1341
+ variant,
1342
+ }),
1343
+ [accentColor, colors, hasGradientBackground, variant]
1344
+ );
1345
+
1346
+ React.useEffect(() => {
1347
+ if (interactionDisabled || resolvedPressEffect === 'none') {
1348
+ pressSv.value = 0;
1349
+ }
1350
+ }, [interactionDisabled, pressSv, resolvedPressEffect]);
1351
+
1352
+ const rootAnimatedStyle = useAnimatedStyle(() => {
1353
+ const baseOpacity = visualDisabled ? DISABLED_OPACITY : 1;
1354
+ const scale = pressScaleEnabled
1355
+ ? interpolate(pressSv.value, [0, 1], [1, PRESSED_SCALE])
1356
+ : 1;
1357
+ const opacity = pressOpacityEnabled
1358
+ ? interpolate(pressSv.value, [0, 1], [baseOpacity, PRESSED_OPACITY])
1359
+ : baseOpacity;
1360
+
1361
+ return {
1362
+ opacity,
1363
+ transform: [{ scale }],
1364
+ };
1365
+ }, [pressOpacityEnabled, pressScaleEnabled, visualDisabled]);
1366
+
1367
+ const overlayAnimatedStyle = useAnimatedStyle(() => {
1368
+ const opacity = pressHighlightEnabled
1369
+ ? interpolate(pressSv.value, [0, 1], [0, pressOverlay.strength])
1370
+ : 0;
1371
+ return { opacity };
1372
+ }, [pressHighlightEnabled, pressOverlay.strength]);
1373
+
1374
+ const handlePress = React.useCallback(
1375
+ (event: GestureResponderEvent) => {
1376
+ if (interactionDisabled) return;
1377
+ onPress?.(event);
1378
+ },
1379
+ [interactionDisabled, onPress]
1380
+ );
1381
+
1382
+ const handlePressIn = React.useCallback(
1383
+ (event: GestureResponderEvent) => {
1384
+ if (!interactionDisabled && resolvedPressEffect !== 'none') {
1385
+ pressSv.value = withTiming(1, PRESS_TIMING);
1386
+ }
1387
+ onPressIn?.(event);
1388
+ },
1389
+ [interactionDisabled, onPressIn, pressSv, resolvedPressEffect]
1390
+ );
1391
+
1392
+ const handlePressOut = React.useCallback(
1393
+ (event: GestureResponderEvent) => {
1394
+ if (!interactionDisabled && resolvedPressEffect !== 'none') {
1395
+ pressSv.value = withTiming(0, PRESS_TIMING);
1396
+ }
1397
+ onPressOut?.(event);
1398
+ },
1399
+ [interactionDisabled, onPressOut, pressSv, resolvedPressEffect]
1400
+ );
1401
+
1402
+ return (
1403
+ <AnimatedPressable
1404
+ {...pressableProps}
1405
+ ref={ref}
1406
+ accessibilityLabel={resolvedAccessibilityLabel}
1407
+ accessibilityRole="button"
1408
+ accessibilityState={{
1409
+ ...accessibilityState,
1410
+ busy: Boolean(loading || accessibilityState?.busy),
1411
+ disabled: Boolean(interactionDisabled || accessibilityState?.disabled),
1412
+ }}
1413
+ disabled={interactionDisabled}
1414
+ hitSlop={hitSlop ?? defaultHitSlop}
1415
+ onPress={handlePress}
1416
+ onPressIn={handlePressIn}
1417
+ onPressOut={handlePressOut}
1418
+ style={[styles.root, rootSizeStyle, radiusStyle, shadowStyle, rootAnimatedStyle, webCursorStyle, style]}
1419
+ testID={testID}
1420
+ >
1421
+ <View
1422
+ pointerEvents="none"
1423
+ style={[
1424
+ styles.content,
1425
+ contentSizeStyle,
1426
+ paddingStyle,
1427
+ radiusStyle,
1428
+ borderStyle,
1429
+ { backgroundColor: visualColors.backgroundColor },
1430
+ contentStyle,
1431
+ ]}
1432
+ >
1433
+ {LinearGradientComponent && gradientCfg ? (
1434
+ <LinearGradientComponent
1435
+ pointerEvents="none"
1436
+ colors={gradientCfg.colors}
1437
+ start={gradientCfg.start}
1438
+ end={gradientCfg.end}
1439
+ style={[StyleSheet.absoluteFillObject, radiusStyle]}
1440
+ />
1441
+ ) : null}
1442
+
1443
+ {pressHighlightEnabled ? (
1444
+ <Animated.View
1445
+ pointerEvents="none"
1446
+ style={[
1447
+ StyleSheet.absoluteFillObject,
1448
+ radiusStyle,
1449
+ { backgroundColor: pressOverlay.color },
1450
+ overlayAnimatedStyle,
1451
+ ]}
1452
+ />
1453
+ ) : null}
1454
+
1455
+ {iconOnly ? (
1456
+ (icon ?? resolvedText)
1457
+ ) : (
1458
+ <>
1459
+ {icon && iconPlacement === 'start' ? (
1460
+ <View style={[iconBaseBoxStyle, iconPlacedBoxStyle]}>{icon}</View>
1461
+ ) : null}
1462
+ {resolvedText}
1463
+ {icon && iconPlacement === 'end' ? (
1464
+ <View style={[iconBaseBoxStyle, iconPlacedBoxStyle]}>{icon}</View>
1465
+ ) : null}
1466
+ </>
1467
+ )}
1468
+ </View>
1469
+ </AnimatedPressable>
1470
+ );
1471
+ }
1472
+
1220
1473
  function ButtonAnimatedImpl(
1221
1474
  {
1222
1475
  variant = 'solid',
@@ -1314,7 +1567,7 @@ function ButtonAnimatedImpl(
1314
1567
  );
1315
1568
 
1316
1569
  const pressSv = useSharedValue(0);
1317
- const loadingSv = useSharedValue(loading ? 1 : 0);
1570
+ const loadingSv = useSharedValue(0);
1318
1571
  const [spinnerMounted, setSpinnerMounted] = React.useState(loading);
1319
1572
  const hideSpinnerTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
1320
1573
 
@@ -1602,15 +1855,43 @@ function ButtonAnimatedImpl(
1602
1855
  const ButtonStaticWithRef = React.forwardRef<ButtonRef, ButtonProps>(ButtonStaticImpl);
1603
1856
  ButtonStaticWithRef.displayName = 'ButtonStatic';
1604
1857
 
1858
+ const ButtonPressAnimatedWithRef = React.forwardRef<ButtonRef, ButtonProps>(ButtonPressAnimatedImpl);
1859
+ ButtonPressAnimatedWithRef.displayName = 'ButtonPressAnimated';
1860
+
1605
1861
  const ButtonAnimatedWithRef = React.forwardRef<ButtonRef, ButtonProps>(ButtonAnimatedImpl);
1606
- ButtonAnimatedWithRef.displayName = 'ButtonAnimated';
1862
+ ButtonAnimatedWithRef.displayName = 'ButtonLoadingAnimated';
1607
1863
 
1608
1864
  function ButtonImpl(props: ButtonProps, ref: React.ForwardedRef<ButtonRef>) {
1609
- if (shouldUseStaticButtonPath(props)) {
1610
- return <ButtonStaticWithRef {...props} ref={ref} />;
1865
+ const loadingRequested = Boolean(props.loading);
1866
+ const animatedPressRequested = shouldRequestAnimatedPressPath(props);
1867
+ const [retainLoadingAnimated, setRetainLoadingAnimated] = React.useState(loadingRequested);
1868
+
1869
+ React.useEffect(() => {
1870
+ if (loadingRequested) {
1871
+ setRetainLoadingAnimated(true);
1872
+ return undefined;
1873
+ }
1874
+
1875
+ if (!retainLoadingAnimated) return undefined;
1876
+
1877
+ const timer = setTimeout(() => {
1878
+ setRetainLoadingAnimated(false);
1879
+ }, ANIMATED_EXIT_RESIDENCY_MS);
1880
+
1881
+ return () => {
1882
+ clearTimeout(timer);
1883
+ };
1884
+ }, [loadingRequested, retainLoadingAnimated]);
1885
+
1886
+ if (loadingRequested || retainLoadingAnimated) {
1887
+ return <ButtonAnimatedWithRef {...props} ref={ref} />;
1611
1888
  }
1612
1889
 
1613
- return <ButtonAnimatedWithRef {...props} ref={ref} />;
1890
+ return animatedPressRequested ? (
1891
+ <ButtonPressAnimatedWithRef {...props} ref={ref} />
1892
+ ) : (
1893
+ <ButtonStaticWithRef {...props} ref={ref} />
1894
+ );
1614
1895
  }
1615
1896
 
1616
1897
  const ButtonWithRef = React.forwardRef<ButtonRef, ButtonProps>(ButtonImpl);
@@ -1647,6 +1928,9 @@ const styles = StyleSheet.create({
1647
1928
  disabledRoot: {
1648
1929
  opacity: DISABLED_OPACITY,
1649
1930
  },
1931
+ pressedRoot: {
1932
+ opacity: PRESSED_OPACITY,
1933
+ },
1650
1934
  spinnerBox: {
1651
1935
  alignItems: 'center',
1652
1936
  justifyContent: 'center',