@seed-design/react-drawer 1.0.4 → 1.0.6

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.
@@ -5,7 +5,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog';
5
5
  import { useCallbackRef } from '@radix-ui/react-use-callback-ref';
6
6
  import { dataAttr } from '@seed-design/dom-utils';
7
7
  import { Primitive } from '@seed-design/react-primitive';
8
- import React, { useState, useRef, useCallback, useEffect, useMemo, createContext, useContext, forwardRef } from 'react';
8
+ import React, { useState, useCallback, useRef, useEffect, useMemo, createContext, useContext, forwardRef } from 'react';
9
9
  import { useControllableState } from '@radix-ui/react-use-controllable-state';
10
10
 
11
11
  function isMobileFirefox() {
@@ -482,8 +482,13 @@ function useDrawer(props) {
482
482
  }
483
483
  });
484
484
  const [hasBeenOpened, setHasBeenOpened] = useState(false);
485
+ const [hasAnimationDone, setHasAnimationDone] = useState(false);
485
486
  const [isDragging, setIsDragging] = useState(false);
486
487
  const [shouldOverlayAnimate, setShouldOverlayAnimate] = useState(false);
488
+ const [isCloseButtonRendered, setIsCloseButtonRendered] = useState(false);
489
+ const closeButtonRef = useCallback((node)=>{
490
+ setIsCloseButtonRendered(!!node);
491
+ }, []);
487
492
  const overlayRef = useRef(null);
488
493
  const openTime = useRef(null);
489
494
  const dragStartTime = useRef(null);
@@ -492,7 +497,6 @@ function useDrawer(props) {
492
497
  const isAllowedToDrag = useRef(false);
493
498
  const pointerStart = useRef(0);
494
499
  const keyboardIsOpen = useRef(false);
495
- const shouldAnimate = useRef(!defaultOpen);
496
500
  const previousDiffFromInitial = useRef(0);
497
501
  const drawerRef = useRef(null);
498
502
  const drawerHeightRef = useRef(drawerRef.current?.getBoundingClientRect().height || 0);
@@ -746,11 +750,6 @@ function useDrawer(props) {
746
750
  }, [
747
751
  isOpen
748
752
  ]);
749
- useEffect(()=>{
750
- window.requestAnimationFrame(()=>{
751
- shouldAnimate.current = true;
752
- });
753
- }, []);
754
753
  useEffect(()=>{
755
754
  function onVisualViewportChange() {
756
755
  if (!drawerRef.current || !repositionInputs) return;
@@ -812,6 +811,7 @@ function useDrawer(props) {
812
811
  }, [
813
812
  modal
814
813
  ]);
814
+ // Effect 1: Track drawer open state
815
815
  useEffect(()=>{
816
816
  if (isOpen) {
817
817
  setHasBeenOpened(true);
@@ -819,6 +819,24 @@ function useDrawer(props) {
819
819
  }, [
820
820
  isOpen
821
821
  ]);
822
+ // Effect 2: Handle animation state and timer
823
+ useEffect(()=>{
824
+ if (isOpen) {
825
+ // Only reset animation state if this is the first open
826
+ if (!hasBeenOpened) {
827
+ setHasAnimationDone(false);
828
+ }
829
+ const timeoutId = setTimeout(()=>{
830
+ setHasAnimationDone(true);
831
+ }, TRANSITIONS.ENTER_DURATION * 1000);
832
+ return ()=>clearTimeout(timeoutId);
833
+ }
834
+ // Reset animation state when drawer closes
835
+ setHasAnimationDone(false);
836
+ }, [
837
+ isOpen,
838
+ hasBeenOpened
839
+ ]);
822
840
  useEffect(()=>{
823
841
  if (isOpen && snapPoints && fadeFromIndex === 0) {
824
842
  setShouldOverlayAnimate(true);
@@ -845,7 +863,6 @@ function useDrawer(props) {
845
863
  onRelease,
846
864
  onDrag,
847
865
  dismissible,
848
- shouldAnimate,
849
866
  handleOnly,
850
867
  isOpen,
851
868
  isDragging,
@@ -862,7 +879,10 @@ function useDrawer(props) {
862
879
  setHasBeenOpened,
863
880
  setIsOpen,
864
881
  closeOnInteractOutside,
865
- closeOnEscape
882
+ closeOnEscape,
883
+ hasAnimationDone,
884
+ closeButtonRef,
885
+ isCloseButtonRendered
866
886
  }), [
867
887
  activeSnapPoint,
868
888
  snapPoints,
@@ -887,7 +907,10 @@ function useDrawer(props) {
887
907
  closeOnEscape,
888
908
  onRelease,
889
909
  onDrag,
890
- onPress
910
+ onPress,
911
+ hasAnimationDone,
912
+ closeButtonRef,
913
+ isCloseButtonRendered
891
914
  ]);
892
915
  }
893
916
 
@@ -937,11 +960,10 @@ const DrawerPositioner = /*#__PURE__*/ forwardRef((props, ref)=>{
937
960
  });
938
961
  DrawerPositioner.displayName = "DrawerPositioner";
939
962
  const DrawerBackdrop = /*#__PURE__*/ forwardRef((props, ref)=>{
940
- const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldAnimate, shouldOverlayAnimate } = useDrawerContext();
963
+ const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldOverlayAnimate, hasAnimationDone } = useDrawerContext();
941
964
  const composedRef = useComposedRefs(ref, overlayRef);
942
965
  const hasSnapPoints = snapPoints && snapPoints.length > 0;
943
966
  const onMouseUp = useCallbackRef((event)=>onRelease(event));
944
- // Overlay is the component that is locking scroll, removing it will unlock the scroll without having to dig into Radix's Dialog library
945
967
  if (!modal) {
946
968
  return null;
947
969
  }
@@ -950,16 +972,16 @@ const DrawerBackdrop = /*#__PURE__*/ forwardRef((props, ref)=>{
950
972
  onMouseUp: onMouseUp,
951
973
  "data-snap-points": isOpen && hasSnapPoints ? "true" : "false",
952
974
  "data-snap-points-overlay": isOpen && shouldFade ? "true" : "false",
953
- "data-animate": shouldAnimate?.current ? "true" : "false",
954
975
  "data-should-overlay-animate": shouldOverlayAnimate ? "true" : "false",
955
976
  "data-open": dataAttr(isOpen),
977
+ "data-animation-done": hasAnimationDone ? "true" : "false",
956
978
  ...props
957
979
  });
958
980
  });
959
981
  DrawerBackdrop.displayName = "DrawerBackdrop";
960
982
  const DrawerContent = /*#__PURE__*/ forwardRef((props, ref)=>{
961
983
  const { onPointerDownOutside, style, onOpenAutoFocus, ...restProps } = props;
962
- const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, shouldAnimate, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible } = useDrawerContext();
984
+ const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible, hasAnimationDone } = useDrawerContext();
963
985
  // Needed to use transition instead of animations
964
986
  const [delayedSnapPoints, setDelayedSnapPoints] = useState(false);
965
987
  const composedRef = useComposedRefs(ref, drawerRef);
@@ -1006,10 +1028,10 @@ const DrawerContent = /*#__PURE__*/ forwardRef((props, ref)=>{
1006
1028
  "data-delayed-snap-points": delayedSnapPoints ? "true" : "false",
1007
1029
  "data-drawer-direction": direction,
1008
1030
  "data-open": dataAttr(isOpen),
1031
+ "data-animation-done": hasAnimationDone ? "true" : "false",
1009
1032
  "data-drawer": "",
1010
1033
  "data-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1011
1034
  "data-custom-container": container ? "true" : "false",
1012
- "data-animate": shouldAnimate?.current ? "true" : "false",
1013
1035
  ...restProps,
1014
1036
  ref: composedRef,
1015
1037
  style: snapPointsOffset && snapPointsOffset.length > 0 ? {
@@ -1042,10 +1064,10 @@ const DrawerContent = /*#__PURE__*/ forwardRef((props, ref)=>{
1042
1064
  }
1043
1065
  },
1044
1066
  onFocusOutside: (e)=>{
1045
- if (!modal) {
1046
- e.preventDefault();
1047
- return;
1048
- }
1067
+ props.onFocusOutside?.(e);
1068
+ // Always prevent focusOutside to avoid conflicts when focus moves between modals
1069
+ // (e.g., when Dialog closes and restores focus while BottomSheet is opening)
1070
+ e.preventDefault();
1049
1071
  },
1050
1072
  onPointerMove: (event)=>{
1051
1073
  lastKnownPointerEventRef.current = event;
@@ -1082,7 +1104,8 @@ const DrawerContent = /*#__PURE__*/ forwardRef((props, ref)=>{
1082
1104
  }
1083
1105
  },
1084
1106
  onInteractOutside: (e)=>{
1085
- if (dismissible && closeOnInteractOutside) {
1107
+ // Only close if event is not prevented (e.g., by onFocusOutside or onPointerDownOutside)
1108
+ if (dismissible && closeOnInteractOutside && !e.defaultPrevented) {
1086
1109
  closeDrawer();
1087
1110
  }
1088
1111
  props.onInteractOutside?.(e);
@@ -1098,15 +1121,25 @@ const DrawerContent = /*#__PURE__*/ forwardRef((props, ref)=>{
1098
1121
  DrawerContent.displayName = "DrawerContent";
1099
1122
  const DrawerTitle = DialogPrimitive.Title;
1100
1123
  const DrawerDescription = DialogPrimitive.Description;
1124
+ const DrawerHeader = /*#__PURE__*/ forwardRef((props, ref)=>{
1125
+ const { isCloseButtonRendered } = useDrawerContext();
1126
+ return /*#__PURE__*/ jsx(Primitive.div, {
1127
+ ref: ref,
1128
+ "data-show-close-button": dataAttr(isCloseButtonRendered),
1129
+ ...props
1130
+ });
1131
+ });
1132
+ DrawerHeader.displayName = "DrawerHeader";
1101
1133
  const DrawerCloseButton = /*#__PURE__*/ forwardRef((props, ref)=>{
1102
- const api = useDrawerContext();
1134
+ const { closeButtonRef, setIsOpen } = useDrawerContext();
1135
+ const composedRef = useComposedRefs(ref, closeButtonRef);
1103
1136
  return /*#__PURE__*/ jsx(Primitive.button, {
1104
- ref: ref,
1137
+ ref: composedRef,
1105
1138
  ...props,
1106
1139
  onClick: (e)=>{
1107
1140
  props.onClick?.(e);
1108
1141
  if (e.defaultPrevented) return;
1109
- api.setIsOpen(false);
1142
+ setIsOpen(false);
1110
1143
  }
1111
1144
  });
1112
1145
  });
@@ -1187,4 +1220,4 @@ const DrawerHandle = /*#__PURE__*/ forwardRef((props, ref)=>{
1187
1220
  });
1188
1221
  DrawerHandle.displayName = "DrawerHandle";
1189
1222
 
1190
- export { DrawerBackdrop as D, DrawerCloseButton as a, DrawerContent as b, DrawerDescription as c, DrawerHandle as d, DrawerPositioner as e, DrawerRoot as f, DrawerTitle as g, DrawerTrigger as h, useDrawerContext as i, useDrawer as u };
1223
+ export { DrawerBackdrop as D, DrawerCloseButton as a, DrawerContent as b, DrawerDescription as c, DrawerHandle as d, DrawerHeader as e, DrawerPositioner as f, DrawerRoot as g, DrawerTitle as h, DrawerTrigger as i, useDrawerContext as j, useDrawer as u };
@@ -505,8 +505,13 @@ function useDrawer(props) {
505
505
  }
506
506
  });
507
507
  const [hasBeenOpened, setHasBeenOpened] = React.useState(false);
508
+ const [hasAnimationDone, setHasAnimationDone] = React.useState(false);
508
509
  const [isDragging, setIsDragging] = React.useState(false);
509
510
  const [shouldOverlayAnimate, setShouldOverlayAnimate] = React.useState(false);
511
+ const [isCloseButtonRendered, setIsCloseButtonRendered] = React.useState(false);
512
+ const closeButtonRef = React.useCallback((node)=>{
513
+ setIsCloseButtonRendered(!!node);
514
+ }, []);
510
515
  const overlayRef = React.useRef(null);
511
516
  const openTime = React.useRef(null);
512
517
  const dragStartTime = React.useRef(null);
@@ -515,7 +520,6 @@ function useDrawer(props) {
515
520
  const isAllowedToDrag = React.useRef(false);
516
521
  const pointerStart = React.useRef(0);
517
522
  const keyboardIsOpen = React.useRef(false);
518
- const shouldAnimate = React.useRef(!defaultOpen);
519
523
  const previousDiffFromInitial = React.useRef(0);
520
524
  const drawerRef = React.useRef(null);
521
525
  const drawerHeightRef = React.useRef(drawerRef.current?.getBoundingClientRect().height || 0);
@@ -769,11 +773,6 @@ function useDrawer(props) {
769
773
  }, [
770
774
  isOpen
771
775
  ]);
772
- React.useEffect(()=>{
773
- window.requestAnimationFrame(()=>{
774
- shouldAnimate.current = true;
775
- });
776
- }, []);
777
776
  React.useEffect(()=>{
778
777
  function onVisualViewportChange() {
779
778
  if (!drawerRef.current || !repositionInputs) return;
@@ -835,6 +834,7 @@ function useDrawer(props) {
835
834
  }, [
836
835
  modal
837
836
  ]);
837
+ // Effect 1: Track drawer open state
838
838
  React.useEffect(()=>{
839
839
  if (isOpen) {
840
840
  setHasBeenOpened(true);
@@ -842,6 +842,24 @@ function useDrawer(props) {
842
842
  }, [
843
843
  isOpen
844
844
  ]);
845
+ // Effect 2: Handle animation state and timer
846
+ React.useEffect(()=>{
847
+ if (isOpen) {
848
+ // Only reset animation state if this is the first open
849
+ if (!hasBeenOpened) {
850
+ setHasAnimationDone(false);
851
+ }
852
+ const timeoutId = setTimeout(()=>{
853
+ setHasAnimationDone(true);
854
+ }, TRANSITIONS.ENTER_DURATION * 1000);
855
+ return ()=>clearTimeout(timeoutId);
856
+ }
857
+ // Reset animation state when drawer closes
858
+ setHasAnimationDone(false);
859
+ }, [
860
+ isOpen,
861
+ hasBeenOpened
862
+ ]);
845
863
  React.useEffect(()=>{
846
864
  if (isOpen && snapPoints && fadeFromIndex === 0) {
847
865
  setShouldOverlayAnimate(true);
@@ -868,7 +886,6 @@ function useDrawer(props) {
868
886
  onRelease,
869
887
  onDrag,
870
888
  dismissible,
871
- shouldAnimate,
872
889
  handleOnly,
873
890
  isOpen,
874
891
  isDragging,
@@ -885,7 +902,10 @@ function useDrawer(props) {
885
902
  setHasBeenOpened,
886
903
  setIsOpen,
887
904
  closeOnInteractOutside,
888
- closeOnEscape
905
+ closeOnEscape,
906
+ hasAnimationDone,
907
+ closeButtonRef,
908
+ isCloseButtonRendered
889
909
  }), [
890
910
  activeSnapPoint,
891
911
  snapPoints,
@@ -910,7 +930,10 @@ function useDrawer(props) {
910
930
  closeOnEscape,
911
931
  onRelease,
912
932
  onDrag,
913
- onPress
933
+ onPress,
934
+ hasAnimationDone,
935
+ closeButtonRef,
936
+ isCloseButtonRendered
914
937
  ]);
915
938
  }
916
939
 
@@ -960,11 +983,10 @@ const DrawerPositioner = /*#__PURE__*/ React.forwardRef((props, ref)=>{
960
983
  });
961
984
  DrawerPositioner.displayName = "DrawerPositioner";
962
985
  const DrawerBackdrop = /*#__PURE__*/ React.forwardRef((props, ref)=>{
963
- const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldAnimate, shouldOverlayAnimate } = useDrawerContext();
986
+ const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldOverlayAnimate, hasAnimationDone } = useDrawerContext();
964
987
  const composedRef = reactComposeRefs.useComposedRefs(ref, overlayRef);
965
988
  const hasSnapPoints = snapPoints && snapPoints.length > 0;
966
989
  const onMouseUp = reactUseCallbackRef.useCallbackRef((event)=>onRelease(event));
967
- // Overlay is the component that is locking scroll, removing it will unlock the scroll without having to dig into Radix's Dialog library
968
990
  if (!modal) {
969
991
  return null;
970
992
  }
@@ -973,16 +995,16 @@ const DrawerBackdrop = /*#__PURE__*/ React.forwardRef((props, ref)=>{
973
995
  onMouseUp: onMouseUp,
974
996
  "data-snap-points": isOpen && hasSnapPoints ? "true" : "false",
975
997
  "data-snap-points-overlay": isOpen && shouldFade ? "true" : "false",
976
- "data-animate": shouldAnimate?.current ? "true" : "false",
977
998
  "data-should-overlay-animate": shouldOverlayAnimate ? "true" : "false",
978
999
  "data-open": domUtils.dataAttr(isOpen),
1000
+ "data-animation-done": hasAnimationDone ? "true" : "false",
979
1001
  ...props
980
1002
  });
981
1003
  });
982
1004
  DrawerBackdrop.displayName = "DrawerBackdrop";
983
1005
  const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
984
1006
  const { onPointerDownOutside, style, onOpenAutoFocus, ...restProps } = props;
985
- const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, shouldAnimate, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible } = useDrawerContext();
1007
+ const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible, hasAnimationDone } = useDrawerContext();
986
1008
  // Needed to use transition instead of animations
987
1009
  const [delayedSnapPoints, setDelayedSnapPoints] = React.useState(false);
988
1010
  const composedRef = reactComposeRefs.useComposedRefs(ref, drawerRef);
@@ -1029,10 +1051,10 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1029
1051
  "data-delayed-snap-points": delayedSnapPoints ? "true" : "false",
1030
1052
  "data-drawer-direction": direction,
1031
1053
  "data-open": domUtils.dataAttr(isOpen),
1054
+ "data-animation-done": hasAnimationDone ? "true" : "false",
1032
1055
  "data-drawer": "",
1033
1056
  "data-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1034
1057
  "data-custom-container": container ? "true" : "false",
1035
- "data-animate": shouldAnimate?.current ? "true" : "false",
1036
1058
  ...restProps,
1037
1059
  ref: composedRef,
1038
1060
  style: snapPointsOffset && snapPointsOffset.length > 0 ? {
@@ -1065,10 +1087,10 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1065
1087
  }
1066
1088
  },
1067
1089
  onFocusOutside: (e)=>{
1068
- if (!modal) {
1069
- e.preventDefault();
1070
- return;
1071
- }
1090
+ props.onFocusOutside?.(e);
1091
+ // Always prevent focusOutside to avoid conflicts when focus moves between modals
1092
+ // (e.g., when Dialog closes and restores focus while BottomSheet is opening)
1093
+ e.preventDefault();
1072
1094
  },
1073
1095
  onPointerMove: (event)=>{
1074
1096
  lastKnownPointerEventRef.current = event;
@@ -1105,7 +1127,8 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1105
1127
  }
1106
1128
  },
1107
1129
  onInteractOutside: (e)=>{
1108
- if (dismissible && closeOnInteractOutside) {
1130
+ // Only close if event is not prevented (e.g., by onFocusOutside or onPointerDownOutside)
1131
+ if (dismissible && closeOnInteractOutside && !e.defaultPrevented) {
1109
1132
  closeDrawer();
1110
1133
  }
1111
1134
  props.onInteractOutside?.(e);
@@ -1121,15 +1144,25 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1121
1144
  DrawerContent.displayName = "DrawerContent";
1122
1145
  const DrawerTitle = DialogPrimitive__namespace.Title;
1123
1146
  const DrawerDescription = DialogPrimitive__namespace.Description;
1147
+ const DrawerHeader = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1148
+ const { isCloseButtonRendered } = useDrawerContext();
1149
+ return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
1150
+ ref: ref,
1151
+ "data-show-close-button": domUtils.dataAttr(isCloseButtonRendered),
1152
+ ...props
1153
+ });
1154
+ });
1155
+ DrawerHeader.displayName = "DrawerHeader";
1124
1156
  const DrawerCloseButton = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1125
- const api = useDrawerContext();
1157
+ const { closeButtonRef, setIsOpen } = useDrawerContext();
1158
+ const composedRef = reactComposeRefs.useComposedRefs(ref, closeButtonRef);
1126
1159
  return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.button, {
1127
- ref: ref,
1160
+ ref: composedRef,
1128
1161
  ...props,
1129
1162
  onClick: (e)=>{
1130
1163
  props.onClick?.(e);
1131
1164
  if (e.defaultPrevented) return;
1132
- api.setIsOpen(false);
1165
+ setIsOpen(false);
1133
1166
  }
1134
1167
  });
1135
1168
  });
@@ -1215,6 +1248,7 @@ exports.DrawerCloseButton = DrawerCloseButton;
1215
1248
  exports.DrawerContent = DrawerContent;
1216
1249
  exports.DrawerDescription = DrawerDescription;
1217
1250
  exports.DrawerHandle = DrawerHandle;
1251
+ exports.DrawerHeader = DrawerHeader;
1218
1252
  exports.DrawerPositioner = DrawerPositioner;
1219
1253
  exports.DrawerRoot = DrawerRoot;
1220
1254
  exports.DrawerTitle = DrawerTitle;
package/lib/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- var Drawer12s = require('./Drawer-12s-Yr_NcCoF.cjs');
1
+ var Drawer12s = require('./Drawer-12s-DriAYAR2.cjs');
2
2
 
3
3
  var Drawer_namespace = {
4
4
  __proto__: null,
@@ -7,6 +7,7 @@ var Drawer_namespace = {
7
7
  Content: Drawer12s.DrawerContent,
8
8
  Description: Drawer12s.DrawerDescription,
9
9
  Handle: Drawer12s.DrawerHandle,
10
+ Header: Drawer12s.DrawerHeader,
10
11
  Positioner: Drawer12s.DrawerPositioner,
11
12
  Root: Drawer12s.DrawerRoot,
12
13
  Title: Drawer12s.DrawerTitle,
@@ -18,6 +19,7 @@ exports.DrawerCloseButton = Drawer12s.DrawerCloseButton;
18
19
  exports.DrawerContent = Drawer12s.DrawerContent;
19
20
  exports.DrawerDescription = Drawer12s.DrawerDescription;
20
21
  exports.DrawerHandle = Drawer12s.DrawerHandle;
22
+ exports.DrawerHeader = Drawer12s.DrawerHeader;
21
23
  exports.DrawerPositioner = Drawer12s.DrawerPositioner;
22
24
  exports.DrawerRoot = Drawer12s.DrawerRoot;
23
25
  exports.DrawerTitle = Drawer12s.DrawerTitle;
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as DialogPrimitive from '@radix-ui/react-dialog';
3
3
  import { PrimitiveProps } from '@seed-design/react-primitive';
4
- import * as react from 'react';
4
+ import * as React$1 from 'react';
5
5
 
6
6
  interface UseDrawerProps {
7
7
  activeSnapPoint?: number | string | null;
@@ -55,7 +55,7 @@ interface UseDrawerProps {
55
55
  */
56
56
  direction?: "top" | "bottom" | "left" | "right";
57
57
  /**
58
- * Opened by default, skips initial enter animation. Still reacts to `open` state changes
58
+ * Opened by default. Still reacts to `open` state changes
59
59
  * @default false
60
60
  */
61
61
  defaultOpen?: boolean;
@@ -106,22 +106,21 @@ interface UseDrawerProps {
106
106
  declare function useDrawer(props: UseDrawerProps): {
107
107
  activeSnapPoint: string | number | null;
108
108
  snapPoints: (string | number)[] | undefined;
109
- setActiveSnapPoint: (value: react.SetStateAction<string | number | null>) => void;
110
- drawerRef: react.RefObject<HTMLDivElement | null>;
111
- overlayRef: react.RefObject<HTMLDivElement | null>;
109
+ setActiveSnapPoint: (value: React$1.SetStateAction<string | number | null>) => void;
110
+ drawerRef: React$1.RefObject<HTMLDivElement | null>;
111
+ overlayRef: React$1.RefObject<HTMLDivElement | null>;
112
112
  shouldOverlayAnimate: boolean;
113
113
  onOpenChange: ((open: boolean) => void) | undefined;
114
114
  onPress: (event: React.PointerEvent<HTMLDivElement>) => void;
115
115
  onRelease: (event: React.PointerEvent<HTMLDivElement> | null) => void;
116
116
  onDrag: (event: React.PointerEvent<HTMLDivElement>) => void;
117
117
  dismissible: boolean;
118
- shouldAnimate: react.RefObject<boolean>;
119
118
  handleOnly: boolean;
120
119
  isOpen: boolean;
121
120
  isDragging: boolean;
122
121
  shouldFade: boolean;
123
122
  closeDrawer: (fromWithin?: boolean) => void;
124
- keyboardIsOpen: react.RefObject<boolean>;
123
+ keyboardIsOpen: React$1.RefObject<boolean>;
125
124
  modal: boolean;
126
125
  snapPointsOffset: number[];
127
126
  activeSnapPointIndex: number | null;
@@ -129,62 +128,67 @@ declare function useDrawer(props: UseDrawerProps): {
129
128
  noBodyStyles: boolean;
130
129
  container: HTMLElement | null | undefined;
131
130
  autoFocus: boolean;
132
- setHasBeenOpened: react.Dispatch<react.SetStateAction<boolean>>;
133
- setIsOpen: (value: react.SetStateAction<boolean>) => void;
131
+ setHasBeenOpened: React$1.Dispatch<React$1.SetStateAction<boolean>>;
132
+ setIsOpen: (value: React$1.SetStateAction<boolean>) => void;
134
133
  closeOnInteractOutside: boolean;
135
134
  closeOnEscape: boolean;
135
+ hasAnimationDone: boolean;
136
+ closeButtonRef: (node: HTMLButtonElement | null) => void;
137
+ isCloseButtonRendered: boolean;
136
138
  };
137
139
 
138
140
  interface DrawerRootProps extends UseDrawerProps {
139
- children?: react.ReactNode;
141
+ children?: React$1.ReactNode;
140
142
  }
141
143
  declare const DrawerRoot: (props: DrawerRootProps) => react_jsx_runtime.JSX.Element;
142
144
  interface DrawerTriggerProps extends DialogPrimitive.DialogTriggerProps {
143
145
  }
144
- declare const DrawerTrigger: react.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & react.RefAttributes<HTMLButtonElement>>;
145
- interface DrawerPositionerProps extends PrimitiveProps, react.HTMLAttributes<HTMLDivElement> {
146
+ declare const DrawerTrigger: React$1.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & React$1.RefAttributes<HTMLButtonElement>>;
147
+ interface DrawerPositionerProps extends PrimitiveProps, React$1.HTMLAttributes<HTMLDivElement> {
146
148
  }
147
- declare const DrawerPositioner: react.ForwardRefExoticComponent<DrawerPositionerProps & react.RefAttributes<HTMLDivElement>>;
148
- interface DrawerBackdropProps extends DialogPrimitive.DialogOverlayProps, react.HTMLAttributes<HTMLDivElement> {
149
+ declare const DrawerPositioner: React$1.ForwardRefExoticComponent<DrawerPositionerProps & React$1.RefAttributes<HTMLDivElement>>;
150
+ interface DrawerBackdropProps extends DialogPrimitive.DialogOverlayProps, React$1.HTMLAttributes<HTMLDivElement> {
149
151
  }
150
- declare const DrawerBackdrop: react.ForwardRefExoticComponent<DrawerBackdropProps & react.RefAttributes<HTMLDivElement>>;
151
- interface DrawerContentProps extends DialogPrimitive.DialogContentProps, react.HTMLAttributes<HTMLDivElement> {
152
+ declare const DrawerBackdrop: React$1.ForwardRefExoticComponent<DrawerBackdropProps & React$1.RefAttributes<HTMLDivElement>>;
153
+ interface DrawerContentProps extends DialogPrimitive.DialogContentProps, React$1.HTMLAttributes<HTMLDivElement> {
152
154
  }
153
- declare const DrawerContent: react.ForwardRefExoticComponent<DrawerContentProps & react.RefAttributes<HTMLDivElement>>;
155
+ declare const DrawerContent: React$1.ForwardRefExoticComponent<DrawerContentProps & React$1.RefAttributes<HTMLDivElement>>;
154
156
  interface DrawerTitleProps extends DialogPrimitive.DialogTitleProps {
155
157
  }
156
- declare const DrawerTitle: react.ForwardRefExoticComponent<DialogPrimitive.DialogTitleProps & react.RefAttributes<HTMLHeadingElement>>;
158
+ declare const DrawerTitle: React$1.ForwardRefExoticComponent<DialogPrimitive.DialogTitleProps & React$1.RefAttributes<HTMLHeadingElement>>;
157
159
  interface DrawerDescriptionProps extends DialogPrimitive.DialogDescriptionProps {
158
160
  }
159
- declare const DrawerDescription: react.ForwardRefExoticComponent<DialogPrimitive.DialogDescriptionProps & react.RefAttributes<HTMLParagraphElement>>;
161
+ declare const DrawerDescription: React$1.ForwardRefExoticComponent<DialogPrimitive.DialogDescriptionProps & React$1.RefAttributes<HTMLParagraphElement>>;
162
+ interface DrawerHeaderProps extends PrimitiveProps, React$1.HTMLAttributes<HTMLDivElement> {
163
+ }
164
+ declare const DrawerHeader: React$1.ForwardRefExoticComponent<DrawerHeaderProps & React$1.RefAttributes<HTMLDivElement>>;
160
165
  interface DrawerCloseButtonProps extends DialogPrimitive.DialogCloseProps {
161
166
  }
162
- declare const DrawerCloseButton: react.ForwardRefExoticComponent<DrawerCloseButtonProps & react.RefAttributes<HTMLButtonElement>>;
163
- interface DrawerHandleProps extends react.HTMLAttributes<HTMLDivElement> {
167
+ declare const DrawerCloseButton: React$1.ForwardRefExoticComponent<DrawerCloseButtonProps & React$1.RefAttributes<HTMLButtonElement>>;
168
+ interface DrawerHandleProps extends React$1.HTMLAttributes<HTMLDivElement> {
164
169
  preventCycle?: boolean;
165
170
  }
166
- declare const DrawerHandle: react.ForwardRefExoticComponent<DrawerHandleProps & react.RefAttributes<HTMLDivElement>>;
171
+ declare const DrawerHandle: React$1.ForwardRefExoticComponent<DrawerHandleProps & React$1.RefAttributes<HTMLDivElement>>;
167
172
 
168
173
  type DrawerContextValue = ReturnType<typeof useDrawer>;
169
174
  declare function useDrawerContext(): {
170
175
  activeSnapPoint: string | number | null;
171
176
  snapPoints: (string | number)[] | undefined;
172
- setActiveSnapPoint: (value: react.SetStateAction<string | number | null>) => void;
173
- drawerRef: react.RefObject<HTMLDivElement | null>;
174
- overlayRef: react.RefObject<HTMLDivElement | null>;
177
+ setActiveSnapPoint: (value: React$1.SetStateAction<string | number | null>) => void;
178
+ drawerRef: React$1.RefObject<HTMLDivElement | null>;
179
+ overlayRef: React$1.RefObject<HTMLDivElement | null>;
175
180
  shouldOverlayAnimate: boolean;
176
181
  onOpenChange: ((open: boolean) => void) | undefined;
177
182
  onPress: (event: React.PointerEvent<HTMLDivElement>) => void;
178
183
  onRelease: (event: React.PointerEvent<HTMLDivElement> | null) => void;
179
184
  onDrag: (event: React.PointerEvent<HTMLDivElement>) => void;
180
185
  dismissible: boolean;
181
- shouldAnimate: react.RefObject<boolean>;
182
186
  handleOnly: boolean;
183
187
  isOpen: boolean;
184
188
  isDragging: boolean;
185
189
  shouldFade: boolean;
186
190
  closeDrawer: (fromWithin?: boolean) => void;
187
- keyboardIsOpen: react.RefObject<boolean>;
191
+ keyboardIsOpen: React$1.RefObject<boolean>;
188
192
  modal: boolean;
189
193
  snapPointsOffset: number[];
190
194
  activeSnapPointIndex: number | null;
@@ -192,16 +196,19 @@ declare function useDrawerContext(): {
192
196
  noBodyStyles: boolean;
193
197
  container: HTMLElement | null | undefined;
194
198
  autoFocus: boolean;
195
- setHasBeenOpened: react.Dispatch<react.SetStateAction<boolean>>;
196
- setIsOpen: (value: react.SetStateAction<boolean>) => void;
199
+ setHasBeenOpened: React$1.Dispatch<React$1.SetStateAction<boolean>>;
200
+ setIsOpen: (value: React$1.SetStateAction<boolean>) => void;
197
201
  closeOnInteractOutside: boolean;
198
202
  closeOnEscape: boolean;
203
+ hasAnimationDone: boolean;
204
+ closeButtonRef: (node: HTMLButtonElement | null) => void;
205
+ isCloseButtonRendered: boolean;
199
206
  };
200
207
 
201
208
  declare namespace Drawer_namespace {
202
- export { DrawerBackdrop as Backdrop, DrawerCloseButton as CloseButton, DrawerContent as Content, DrawerDescription as Description, DrawerHandle as Handle, DrawerPositioner as Positioner, DrawerRoot as Root, DrawerTitle as Title, DrawerTrigger as Trigger };
203
- export type { DrawerBackdropProps as BackdropProps, DrawerCloseButtonProps as CloseButtonProps, DrawerContentProps as ContentProps, DrawerDescriptionProps as DescriptionProps, DrawerHandleProps as HandleProps, DrawerPositionerProps as PositionerProps, DrawerRootProps as RootProps, DrawerTitleProps as TitleProps, DrawerTriggerProps as TriggerProps };
209
+ export { DrawerBackdrop as Backdrop, DrawerCloseButton as CloseButton, DrawerContent as Content, DrawerDescription as Description, DrawerHandle as Handle, DrawerHeader as Header, DrawerPositioner as Positioner, DrawerRoot as Root, DrawerTitle as Title, DrawerTrigger as Trigger };
210
+ export type { DrawerBackdropProps as BackdropProps, DrawerCloseButtonProps as CloseButtonProps, DrawerContentProps as ContentProps, DrawerDescriptionProps as DescriptionProps, DrawerHandleProps as HandleProps, DrawerHeaderProps as HeaderProps, DrawerPositionerProps as PositionerProps, DrawerRootProps as RootProps, DrawerTitleProps as TitleProps, DrawerTriggerProps as TriggerProps };
204
211
  }
205
212
 
206
- export { Drawer_namespace as Drawer, DrawerBackdrop, DrawerCloseButton, DrawerContent, DrawerDescription, DrawerHandle, DrawerPositioner, DrawerRoot, DrawerTitle, DrawerTrigger, useDrawer, useDrawerContext };
207
- export type { DrawerBackdropProps, DrawerCloseButtonProps, DrawerContentProps, DrawerContextValue, DrawerDescriptionProps, DrawerHandleProps, DrawerPositionerProps, DrawerRootProps, DrawerTitleProps, DrawerTriggerProps, UseDrawerProps };
213
+ export { Drawer_namespace as Drawer, DrawerBackdrop, DrawerCloseButton, DrawerContent, DrawerDescription, DrawerHandle, DrawerHeader, DrawerPositioner, DrawerRoot, DrawerTitle, DrawerTrigger, useDrawer, useDrawerContext };
214
+ export type { DrawerBackdropProps, DrawerCloseButtonProps, DrawerContentProps, DrawerContextValue, DrawerDescriptionProps, DrawerHandleProps, DrawerHeaderProps, DrawerPositionerProps, DrawerRootProps, DrawerTitleProps, DrawerTriggerProps, UseDrawerProps };
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DrawerBackdrop, a as DrawerCloseButton, b as DrawerContent, c as DrawerDescription, d as DrawerHandle, e as DrawerPositioner, f as DrawerRoot, g as DrawerTitle, h as DrawerTrigger } from './Drawer-12s-BOMSoUDc.js';
2
- export { u as useDrawer, i as useDrawerContext } from './Drawer-12s-BOMSoUDc.js';
1
+ import { D as DrawerBackdrop, a as DrawerCloseButton, b as DrawerContent, c as DrawerDescription, d as DrawerHandle, e as DrawerHeader, f as DrawerPositioner, g as DrawerRoot, h as DrawerTitle, i as DrawerTrigger } from './Drawer-12s-BdeYHia6.js';
2
+ export { u as useDrawer, j as useDrawerContext } from './Drawer-12s-BdeYHia6.js';
3
3
 
4
4
  var Drawer_namespace = {
5
5
  __proto__: null,
@@ -8,10 +8,11 @@ var Drawer_namespace = {
8
8
  Content: DrawerContent,
9
9
  Description: DrawerDescription,
10
10
  Handle: DrawerHandle,
11
+ Header: DrawerHeader,
11
12
  Positioner: DrawerPositioner,
12
13
  Root: DrawerRoot,
13
14
  Title: DrawerTitle,
14
15
  Trigger: DrawerTrigger
15
16
  };
16
17
 
17
- export { Drawer_namespace as Drawer, DrawerBackdrop, DrawerCloseButton, DrawerContent, DrawerDescription, DrawerHandle, DrawerPositioner, DrawerRoot, DrawerTitle, DrawerTrigger };
18
+ export { Drawer_namespace as Drawer, DrawerBackdrop, DrawerCloseButton, DrawerContent, DrawerDescription, DrawerHandle, DrawerHeader, DrawerPositioner, DrawerRoot, DrawerTitle, DrawerTrigger };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/react-drawer",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/daangn/seed-design.git",
@@ -4,6 +4,7 @@ export {
4
4
  DrawerContent as Content,
5
5
  DrawerDescription as Description,
6
6
  DrawerHandle as Handle,
7
+ DrawerHeader as Header,
7
8
  DrawerPositioner as Positioner,
8
9
  DrawerRoot as Root,
9
10
  DrawerTitle as Title,
@@ -13,6 +14,7 @@ export {
13
14
  type DrawerContentProps as ContentProps,
14
15
  type DrawerDescriptionProps as DescriptionProps,
15
16
  type DrawerHandleProps as HandleProps,
17
+ type DrawerHeaderProps as HeaderProps,
16
18
  type DrawerPositionerProps as PositionerProps,
17
19
  type DrawerRootProps as RootProps,
18
20
  type DrawerTitleProps as TitleProps,
package/src/Drawer.tsx CHANGED
@@ -71,14 +71,13 @@ export const DrawerBackdrop = forwardRef<HTMLDivElement, DrawerBackdropProps>((p
71
71
  snapPoints,
72
72
  isOpen,
73
73
  shouldFade,
74
- shouldAnimate,
75
74
  shouldOverlayAnimate,
75
+ hasAnimationDone,
76
76
  } = useDrawerContext();
77
77
  const composedRef = useComposedRefs(ref, overlayRef);
78
78
  const hasSnapPoints = snapPoints && snapPoints.length > 0;
79
79
  const onMouseUp = useCallbackRef((event: React.PointerEvent<HTMLDivElement>) => onRelease(event));
80
80
 
81
- // Overlay is the component that is locking scroll, removing it will unlock the scroll without having to dig into Radix's Dialog library
82
81
  if (!modal) {
83
82
  return null;
84
83
  }
@@ -89,9 +88,9 @@ export const DrawerBackdrop = forwardRef<HTMLDivElement, DrawerBackdropProps>((p
89
88
  onMouseUp={onMouseUp}
90
89
  data-snap-points={isOpen && hasSnapPoints ? "true" : "false"}
91
90
  data-snap-points-overlay={isOpen && shouldFade ? "true" : "false"}
92
- data-animate={shouldAnimate?.current ? "true" : "false"}
93
91
  data-should-overlay-animate={shouldOverlayAnimate ? "true" : "false"}
94
92
  data-open={dataAttr(isOpen)}
93
+ data-animation-done={hasAnimationDone ? "true" : "false"}
95
94
  {...props}
96
95
  />
97
96
  );
@@ -118,12 +117,12 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>((pro
118
117
  snapPoints,
119
118
  container,
120
119
  handleOnly,
121
- shouldAnimate,
122
120
  autoFocus,
123
121
  closeDrawer,
124
122
  closeOnInteractOutside,
125
123
  closeOnEscape,
126
124
  dismissible,
125
+ hasAnimationDone,
127
126
  } = useDrawerContext();
128
127
  // Needed to use transition instead of animations
129
128
  const [delayedSnapPoints, setDelayedSnapPoints] = useState(false);
@@ -180,10 +179,10 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>((pro
180
179
  data-delayed-snap-points={delayedSnapPoints ? "true" : "false"}
181
180
  data-drawer-direction={direction}
182
181
  data-open={dataAttr(isOpen)}
182
+ data-animation-done={hasAnimationDone ? "true" : "false"}
183
183
  data-drawer=""
184
184
  data-snap-points={isOpen && hasSnapPoints ? "true" : "false"}
185
185
  data-custom-container={container ? "true" : "false"}
186
- data-animate={shouldAnimate?.current ? "true" : "false"}
187
186
  {...restProps}
188
187
  ref={composedRef}
189
188
  style={
@@ -220,10 +219,10 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>((pro
220
219
  }
221
220
  }}
222
221
  onFocusOutside={(e) => {
223
- if (!modal) {
224
- e.preventDefault();
225
- return;
226
- }
222
+ props.onFocusOutside?.(e);
223
+ // Always prevent focusOutside to avoid conflicts when focus moves between modals
224
+ // (e.g., when Dialog closes and restores focus while BottomSheet is opening)
225
+ e.preventDefault();
227
226
  }}
228
227
  onPointerMove={(event) => {
229
228
  lastKnownPointerEventRef.current = event;
@@ -262,7 +261,8 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>((pro
262
261
  }
263
262
  }}
264
263
  onInteractOutside={(e) => {
265
- if (dismissible && closeOnInteractOutside) {
264
+ // Only close if event is not prevented (e.g., by onFocusOutside or onPointerDownOutside)
265
+ if (dismissible && closeOnInteractOutside && !e.defaultPrevented) {
266
266
  closeDrawer();
267
267
  }
268
268
  props.onInteractOutside?.(e);
@@ -286,19 +286,30 @@ export interface DrawerDescriptionProps extends DialogPrimitive.DialogDescriptio
286
286
 
287
287
  export const DrawerDescription = DialogPrimitive.Description;
288
288
 
289
+ export interface DrawerHeaderProps extends PrimitiveProps, React.HTMLAttributes<HTMLDivElement> {}
290
+
291
+ export const DrawerHeader = forwardRef<HTMLDivElement, DrawerHeaderProps>((props, ref) => {
292
+ const { isCloseButtonRendered } = useDrawerContext();
293
+ return (
294
+ <Primitive.div ref={ref} data-show-close-button={dataAttr(isCloseButtonRendered)} {...props} />
295
+ );
296
+ });
297
+ DrawerHeader.displayName = "DrawerHeader";
298
+
289
299
  export interface DrawerCloseButtonProps extends DialogPrimitive.DialogCloseProps {}
290
300
 
291
301
  export const DrawerCloseButton = forwardRef<HTMLButtonElement, DrawerCloseButtonProps>(
292
302
  (props, ref) => {
293
- const api = useDrawerContext();
303
+ const { closeButtonRef, setIsOpen } = useDrawerContext();
304
+ const composedRef = useComposedRefs(ref, closeButtonRef);
294
305
  return (
295
306
  <Primitive.button
296
- ref={ref}
307
+ ref={composedRef}
297
308
  {...props}
298
309
  onClick={(e) => {
299
310
  props.onClick?.(e);
300
311
  if (e.defaultPrevented) return;
301
- api.setIsOpen(false);
312
+ setIsOpen(false);
302
313
  }}
303
314
  />
304
315
  );
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export {
4
4
  DrawerContent,
5
5
  DrawerDescription,
6
6
  DrawerHandle,
7
+ DrawerHeader,
7
8
  DrawerPositioner,
8
9
  DrawerRoot,
9
10
  DrawerTitle,
@@ -13,6 +14,7 @@ export {
13
14
  type DrawerContentProps,
14
15
  type DrawerDescriptionProps,
15
16
  type DrawerHandleProps,
17
+ type DrawerHeaderProps,
16
18
  type DrawerPositionerProps,
17
19
  type DrawerRootProps,
18
20
  type DrawerTitleProps,
package/src/useDrawer.ts CHANGED
@@ -65,7 +65,7 @@ export interface UseDrawerProps {
65
65
  */
66
66
  direction?: "top" | "bottom" | "left" | "right";
67
67
  /**
68
- * Opened by default, skips initial enter animation. Still reacts to `open` state changes
68
+ * Opened by default. Still reacts to `open` state changes
69
69
  * @default false
70
70
  */
71
71
  defaultOpen?: boolean;
@@ -178,9 +178,15 @@ export function useDrawer(props: UseDrawerProps) {
178
178
  });
179
179
 
180
180
  const [hasBeenOpened, setHasBeenOpened] = useState<boolean>(false);
181
+ const [hasAnimationDone, setHasAnimationDone] = useState<boolean>(false);
181
182
  const [isDragging, setIsDragging] = useState<boolean>(false);
182
183
  const [shouldOverlayAnimate, setShouldOverlayAnimate] = useState<boolean>(false);
183
184
 
185
+ const [isCloseButtonRendered, setIsCloseButtonRendered] = useState<boolean>(false);
186
+ const closeButtonRef = useCallback((node: HTMLButtonElement | null) => {
187
+ setIsCloseButtonRendered(!!node);
188
+ }, []);
189
+
184
190
  const overlayRef = useRef<HTMLDivElement>(null);
185
191
  const openTime = useRef<Date | null>(null);
186
192
  const dragStartTime = useRef<Date | null>(null);
@@ -189,7 +195,6 @@ export function useDrawer(props: UseDrawerProps) {
189
195
  const isAllowedToDrag = useRef<boolean>(false);
190
196
  const pointerStart = useRef(0);
191
197
  const keyboardIsOpen = useRef(false);
192
- const shouldAnimate = useRef(!defaultOpen);
193
198
  const previousDiffFromInitial = useRef(0);
194
199
  const drawerRef = useRef<HTMLDivElement>(null);
195
200
  const drawerHeightRef = useRef(drawerRef.current?.getBoundingClientRect().height || 0);
@@ -543,12 +548,6 @@ export function useDrawer(props: UseDrawerProps) {
543
548
  };
544
549
  }, [isOpen]);
545
550
 
546
- useEffect(() => {
547
- window.requestAnimationFrame(() => {
548
- shouldAnimate.current = true;
549
- });
550
- }, []);
551
-
552
551
  useEffect(() => {
553
552
  function onVisualViewportChange() {
554
553
  if (!drawerRef.current || !repositionInputs) return;
@@ -614,12 +613,32 @@ export function useDrawer(props: UseDrawerProps) {
614
613
  }
615
614
  }, [modal]);
616
615
 
616
+ // Effect 1: Track drawer open state
617
617
  useEffect(() => {
618
618
  if (isOpen) {
619
619
  setHasBeenOpened(true);
620
620
  }
621
621
  }, [isOpen]);
622
622
 
623
+ // Effect 2: Handle animation state and timer
624
+ useEffect(() => {
625
+ if (isOpen) {
626
+ // Only reset animation state if this is the first open
627
+ if (!hasBeenOpened) {
628
+ setHasAnimationDone(false);
629
+ }
630
+
631
+ const timeoutId = setTimeout(() => {
632
+ setHasAnimationDone(true);
633
+ }, TRANSITIONS.ENTER_DURATION * 1000);
634
+
635
+ return () => clearTimeout(timeoutId);
636
+ }
637
+
638
+ // Reset animation state when drawer closes
639
+ setHasAnimationDone(false);
640
+ }, [isOpen, hasBeenOpened]);
641
+
623
642
  useEffect(() => {
624
643
  if (isOpen && snapPoints && fadeFromIndex === 0) {
625
644
  setShouldOverlayAnimate(true);
@@ -647,7 +666,6 @@ export function useDrawer(props: UseDrawerProps) {
647
666
  onRelease,
648
667
  onDrag,
649
668
  dismissible,
650
- shouldAnimate,
651
669
  handleOnly,
652
670
  isOpen,
653
671
  isDragging,
@@ -665,6 +683,9 @@ export function useDrawer(props: UseDrawerProps) {
665
683
  setIsOpen,
666
684
  closeOnInteractOutside,
667
685
  closeOnEscape,
686
+ hasAnimationDone,
687
+ closeButtonRef,
688
+ isCloseButtonRendered,
668
689
  }),
669
690
  [
670
691
  activeSnapPoint,
@@ -691,6 +712,9 @@ export function useDrawer(props: UseDrawerProps) {
691
712
  onRelease,
692
713
  onDrag,
693
714
  onPress,
715
+ hasAnimationDone,
716
+ closeButtonRef,
717
+ isCloseButtonRendered,
694
718
  ],
695
719
  );
696
720
  }