@tinybigui/react 0.21.0 → 0.22.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.
package/dist/index.js CHANGED
@@ -7716,13 +7716,14 @@ var DialogPanel = ({
7716
7716
  headlineId,
7717
7717
  contentId,
7718
7718
  onClose,
7719
- onTransitionEnd,
7719
+ onAnimationEnd,
7720
7720
  variant,
7721
7721
  isDismissable,
7722
7722
  wrapperClassName,
7723
7723
  className,
7724
7724
  animationState,
7725
7725
  getAnimationClassName,
7726
+ icon,
7726
7727
  children
7727
7728
  }) => {
7728
7729
  const panelRef = useRef(null);
@@ -7745,9 +7746,10 @@ var DialogPanel = ({
7745
7746
  panelRef
7746
7747
  );
7747
7748
  const panelClassName = cn(className, getAnimationClassName?.(animationState));
7749
+ const hasIcon = icon !== void 0 && icon !== null && variant === "basic";
7748
7750
  return (
7749
7751
  // Centering/positioning wrapper — structural only, no ARIA role
7750
- /* @__PURE__ */ jsx("div", { className: wrapperClassName, children: /* @__PURE__ */ jsx(
7752
+ /* @__PURE__ */ jsx("div", { className: wrapperClassName, children: /* @__PURE__ */ jsxs(
7751
7753
  "div",
7752
7754
  {
7753
7755
  ...mergeProps(overlayProps, dialogProps),
@@ -7756,8 +7758,12 @@ var DialogPanel = ({
7756
7758
  className: panelClassName,
7757
7759
  "data-animation-state": animationState,
7758
7760
  "data-variant": variant,
7759
- onTransitionEnd,
7760
- children
7761
+ "data-with-icon": hasIcon ? "" : void 0,
7762
+ onAnimationEnd,
7763
+ children: [
7764
+ hasIcon && icon,
7765
+ children
7766
+ ]
7761
7767
  }
7762
7768
  ) })
7763
7769
  );
@@ -7770,9 +7776,11 @@ var DialogHeadless = forwardRef(
7770
7776
  defaultOpen = false,
7771
7777
  onOpenChange,
7772
7778
  "aria-label": ariaLabel,
7779
+ icon,
7773
7780
  children,
7774
7781
  className,
7775
- scrimClassName,
7782
+ wrapperClassName,
7783
+ getScrimClassName,
7776
7784
  getAnimationClassName
7777
7785
  }, _ref) {
7778
7786
  const state = useOverlayTriggerState({
@@ -7806,7 +7814,7 @@ var DialogHeadless = forwardRef(
7806
7814
  closedRef.current = true;
7807
7815
  setAnimationState("exited");
7808
7816
  }
7809
- }, 150);
7817
+ }, 250);
7810
7818
  }
7811
7819
  }, [isOpen, animationState]);
7812
7820
  useEffect(
@@ -7817,16 +7825,20 @@ var DialogHeadless = forwardRef(
7817
7825
  },
7818
7826
  []
7819
7827
  );
7820
- const handleTransitionEnd = useCallback(() => {
7821
- if (animationState === "exiting" && !closedRef.current) {
7822
- if (exitFallbackRef.current !== null) {
7823
- clearTimeout(exitFallbackRef.current);
7824
- exitFallbackRef.current = null;
7828
+ const handleAnimationEnd = useCallback(
7829
+ (e) => {
7830
+ if (e.target !== e.currentTarget) return;
7831
+ if (animationState === "exiting" && !closedRef.current) {
7832
+ if (exitFallbackRef.current !== null) {
7833
+ clearTimeout(exitFallbackRef.current);
7834
+ exitFallbackRef.current = null;
7835
+ }
7836
+ closedRef.current = true;
7837
+ setAnimationState("exited");
7825
7838
  }
7826
- closedRef.current = true;
7827
- setAnimationState("exited");
7828
- }
7829
- }, [animationState]);
7839
+ },
7840
+ [animationState]
7841
+ );
7830
7842
  const baseId = useId();
7831
7843
  const headlineId = `${baseId}-dialog-headline`;
7832
7844
  const contentId = `${baseId}-dialog-content`;
@@ -7841,6 +7853,8 @@ var DialogHeadless = forwardRef(
7841
7853
  close();
7842
7854
  }
7843
7855
  }, [variant, close]);
7856
+ const resolvedWrapperClass = wrapperClassName ?? (variant === "basic" ? "fixed inset-0 z-50 flex items-center justify-center px-4" : "fixed inset-0 z-50");
7857
+ const resolvedScrimClass = getScrimClassName?.(animationState) ?? "fixed inset-0 z-40 bg-scrim/32";
7844
7858
  if (!isOpen && animationState === "exited") {
7845
7859
  return null;
7846
7860
  }
@@ -7849,7 +7863,7 @@ var DialogHeadless = forwardRef(
7849
7863
  "div",
7850
7864
  {
7851
7865
  "data-testid": "dialog-scrim",
7852
- className: scrimClassName,
7866
+ className: resolvedScrimClass,
7853
7867
  onClick: handleScrimClick,
7854
7868
  "aria-hidden": "true"
7855
7869
  }
@@ -7861,13 +7875,14 @@ var DialogHeadless = forwardRef(
7861
7875
  headlineId,
7862
7876
  contentId,
7863
7877
  onClose: close,
7864
- onTransitionEnd: handleTransitionEnd,
7878
+ onAnimationEnd: handleAnimationEnd,
7865
7879
  variant,
7866
7880
  isDismissable: variant === "basic",
7867
- wrapperClassName: variant === "basic" ? "fixed inset-0 z-50 flex items-center justify-center px-4" : "fixed inset-0 z-50",
7881
+ wrapperClassName: resolvedWrapperClass,
7868
7882
  className,
7869
7883
  animationState,
7870
7884
  getAnimationClassName,
7885
+ icon,
7871
7886
  children
7872
7887
  }
7873
7888
  ) })
@@ -7877,16 +7892,28 @@ var DialogHeadless = forwardRef(
7877
7892
  }
7878
7893
  );
7879
7894
  DialogHeadless.displayName = "DialogHeadless";
7880
- var dialogScrimVariants = cva([
7881
- "fixed",
7882
- "inset-0",
7883
- "z-40",
7884
- "bg-scrim",
7885
- "opacity-32",
7886
- "transition-opacity",
7887
- "duration-medium2",
7888
- "ease-standard"
7889
- ]);
7895
+ var dialogScrimVariants = cva(
7896
+ [
7897
+ "fixed",
7898
+ "inset-0",
7899
+ "z-40",
7900
+ // MD3 scrim: bg-scrim at 32% opacity — always set so instant-show works in reduced-motion
7901
+ "bg-scrim/32"
7902
+ ],
7903
+ {
7904
+ variants: {
7905
+ animationState: {
7906
+ entering: ["opacity-0"],
7907
+ visible: ["animate-md-fade-in"],
7908
+ exiting: ["animate-md-fade-out"],
7909
+ exited: ["opacity-0", "pointer-events-none"]
7910
+ }
7911
+ },
7912
+ defaultVariants: {
7913
+ animationState: "entering"
7914
+ }
7915
+ }
7916
+ );
7890
7917
  var dialogPanelVariants = cva(
7891
7918
  [
7892
7919
  // Stacking above scrim
@@ -7896,9 +7923,10 @@ var dialogPanelVariants = cva(
7896
7923
  // Flex column layout for slots
7897
7924
  "flex",
7898
7925
  "flex-col",
7899
- // Transition for animation state changes
7900
- "transition-[opacity,transform]",
7901
- "will-change-[opacity,transform]"
7926
+ // Compositor hint for keyframe animation
7927
+ "will-change-[opacity,transform]",
7928
+ // group scope — lets child slots consume data-with-icon via group-data-[with-icon]/dialog:
7929
+ "group/dialog"
7902
7930
  ],
7903
7931
  {
7904
7932
  variants: {
@@ -7912,11 +7940,11 @@ var dialogPanelVariants = cva(
7912
7940
  "min-w-70",
7913
7941
  "max-w-dialog-max",
7914
7942
  "w-full",
7915
- // Internal spacing
7943
+ // Internal spacing: 24dp padding, headline mb-4, content mb-6, actions pt-3
7916
7944
  "pt-6",
7917
7945
  "pb-3",
7918
7946
  "px-6",
7919
- // Positioned in viewport center
7947
+ // Positioning (centering wrapper handles the viewport centering)
7920
7948
  "relative"
7921
7949
  ],
7922
7950
  fullscreen: [
@@ -7927,7 +7955,6 @@ var dialogPanelVariants = cva(
7927
7955
  "rounded-none",
7928
7956
  // No elevation shadow on fullscreen
7929
7957
  "shadow-none",
7930
- // Positioned to fill portal
7931
7958
  "relative"
7932
7959
  ]
7933
7960
  }
@@ -7937,7 +7964,7 @@ var dialogPanelVariants = cva(
7937
7964
  }
7938
7965
  }
7939
7966
  );
7940
- cva([], {
7967
+ var dialogWrapperVariants = cva([], {
7941
7968
  variants: {
7942
7969
  variant: {
7943
7970
  basic: ["fixed", "inset-0", "z-50", "flex", "items-center", "justify-center", "px-4"],
@@ -7951,9 +7978,13 @@ cva([], {
7951
7978
  var dialogAnimationVariants = cva("", {
7952
7979
  variants: {
7953
7980
  animationState: {
7981
+ // initial mount frame before the animation starts — rendered invisible
7954
7982
  entering: [],
7983
+ // entry animation active
7955
7984
  visible: [],
7985
+ // exit animation active
7956
7986
  exiting: [],
7987
+ // fully dismissed; portal gate will remove the element
7957
7988
  exited: []
7958
7989
  },
7959
7990
  variant: {
@@ -7962,53 +7993,55 @@ var dialogAnimationVariants = cva("", {
7962
7993
  }
7963
7994
  },
7964
7995
  compoundVariants: [
7965
- // Basic: entering — start scaled down + transparent
7996
+ // ── Basic ────────────────────────────────────────────────────────────────
7997
+ // entering: start invisible (animate-md-scale-in keyframe starts from scale(0.85)/opacity:0)
7966
7998
  {
7967
7999
  animationState: "entering",
7968
8000
  variant: "basic",
7969
- className: ["scale-90", "opacity-0"]
8001
+ className: ["opacity-0"]
7970
8002
  },
7971
- // Basic: visible scale to full + fade in
8003
+ // visible: composite scale-in keyframe (expressive-fast-spatial 350ms)
7972
8004
  {
7973
8005
  animationState: "visible",
7974
8006
  variant: "basic",
7975
- className: ["scale-100", "opacity-100", "duration-medium4", "ease-emphasized-decelerate"]
8007
+ className: ["animate-md-scale-in"]
7976
8008
  },
7977
- // Basic: exiting fade out (scale stays at 1)
8009
+ // exiting: composite scale-out keyframe (emphasized-accelerate 200ms)
7978
8010
  {
7979
8011
  animationState: "exiting",
7980
8012
  variant: "basic",
7981
- className: ["scale-100", "opacity-0", "duration-short2", "ease-emphasized-accelerate"]
8013
+ className: ["animate-md-scale-out"]
7982
8014
  },
7983
- // Basic: exited fully transparent
8015
+ // exited: keep hidden until portal gate removes it
7984
8016
  {
7985
8017
  animationState: "exited",
7986
8018
  variant: "basic",
7987
- className: ["scale-100", "opacity-0"]
8019
+ className: ["opacity-0", "pointer-events-none"]
7988
8020
  },
7989
- // Fullscreen: entering — start below viewport + transparent
8021
+ // ── Fullscreen ───────────────────────────────────────────────────────────
8022
+ // entering: start off-screen below (slide-in-bottom starts from translateY(100%)/opacity:0)
7990
8023
  {
7991
8024
  animationState: "entering",
7992
8025
  variant: "fullscreen",
7993
- className: ["translate-y-full", "opacity-0"]
8026
+ className: ["opacity-0"]
7994
8027
  },
7995
- // Fullscreen: visible slide up + fade in
8028
+ // visible: composite slide-in-bottom keyframe (standard-default-spatial 500ms)
7996
8029
  {
7997
8030
  animationState: "visible",
7998
8031
  variant: "fullscreen",
7999
- className: ["translate-y-0", "opacity-100", "duration-medium4", "ease-emphasized-decelerate"]
8032
+ className: ["animate-md-slide-in-bottom"]
8000
8033
  },
8001
- // Fullscreen: exiting slide down + fade out
8034
+ // exiting: composite slide-out-bottom keyframe (emphasized-accelerate 200ms)
8002
8035
  {
8003
8036
  animationState: "exiting",
8004
8037
  variant: "fullscreen",
8005
- className: ["translate-y-full", "opacity-0", "duration-short2", "ease-emphasized-accelerate"]
8038
+ className: ["animate-md-slide-out-bottom"]
8006
8039
  },
8007
- // Fullscreen: exited fully off-screen
8040
+ // exited: keep hidden until portal gate removes it
8008
8041
  {
8009
8042
  animationState: "exited",
8010
8043
  variant: "fullscreen",
8011
- className: ["translate-y-full", "opacity-0"]
8044
+ className: ["opacity-0", "pointer-events-none"]
8012
8045
  }
8013
8046
  ],
8014
8047
  defaultVariants: {
@@ -8016,10 +8049,26 @@ var dialogAnimationVariants = cva("", {
8016
8049
  variant: "basic"
8017
8050
  }
8018
8051
  });
8052
+ cva([
8053
+ // Center the icon in the panel
8054
+ "flex",
8055
+ "items-center",
8056
+ "justify-center",
8057
+ // Bottom margin separating icon from headline
8058
+ "mb-4",
8059
+ // MD3 spec: icon color = secondary
8060
+ "text-secondary",
8061
+ // 24dp icon size (children — typically an SVG — should be 24×24)
8062
+ "size-6"
8063
+ ]);
8019
8064
  var dialogHeadlineVariants = cva(["text-headline-small", "text-on-surface"], {
8020
8065
  variants: {
8021
8066
  variant: {
8022
- basic: ["mb-4"],
8067
+ basic: [
8068
+ "mb-4",
8069
+ // Center headline text when hero icon is present
8070
+ "group-data-[with-icon]/dialog:text-center"
8071
+ ],
8023
8072
  fullscreen: [
8024
8073
  // Top app bar row in fullscreen: flex, items-center, gap
8025
8074
  "flex",
@@ -8044,7 +8093,19 @@ var dialogHeadlineTitleVariants = cva([
8044
8093
  "truncate"
8045
8094
  ]);
8046
8095
  var dialogContentVariants = cva(
8047
- ["text-body-medium", "text-on-surface-variant", "overflow-y-auto", "flex-1"],
8096
+ [
8097
+ "text-body-medium",
8098
+ "text-on-surface-variant",
8099
+ "overflow-y-auto",
8100
+ "flex-1",
8101
+ // Center supporting text when hero icon is present
8102
+ "group-data-[with-icon]/dialog:text-center",
8103
+ // Scroll dividers — activated by DialogContent's scroll handler
8104
+ "data-[scroll-divider-top]:border-t",
8105
+ "data-[scroll-divider-top]:border-outline-variant",
8106
+ "data-[scroll-divider-bottom]:border-b",
8107
+ "data-[scroll-divider-bottom]:border-outline-variant"
8108
+ ],
8048
8109
  {
8049
8110
  variants: {
8050
8111
  variant: {
@@ -8071,11 +8132,31 @@ var Dialog = forwardRef(function Dialog2({
8071
8132
  defaultOpen = false,
8072
8133
  onOpenChange,
8073
8134
  "aria-label": ariaLabel,
8135
+ icon,
8074
8136
  children,
8075
8137
  className
8076
8138
  }, _ref) {
8077
- const panelClassName = cn(dialogPanelVariants({ variant }), className);
8078
- const scrimClass = dialogScrimVariants();
8139
+ const reducedMotion = useReducedMotion();
8140
+ const panelClassName = cn(
8141
+ dialogPanelVariants({ variant }),
8142
+ reducedMotion && "transition-none",
8143
+ className
8144
+ );
8145
+ const wrapperClassName = dialogWrapperVariants({ variant });
8146
+ const getScrimClassName = useCallback(
8147
+ (state) => {
8148
+ if (reducedMotion) return "fixed inset-0 z-40 bg-scrim/32";
8149
+ return dialogScrimVariants({ animationState: state });
8150
+ },
8151
+ [reducedMotion]
8152
+ );
8153
+ const getAnimationClassName = useCallback(
8154
+ (state) => {
8155
+ if (reducedMotion) return "";
8156
+ return dialogAnimationVariants({ animationState: state, variant });
8157
+ },
8158
+ [reducedMotion, variant]
8159
+ );
8079
8160
  return /* @__PURE__ */ jsx(
8080
8161
  DialogHeadless,
8081
8162
  {
@@ -8084,9 +8165,11 @@ var Dialog = forwardRef(function Dialog2({
8084
8165
  ...defaultOpen !== void 0 ? { defaultOpen } : {},
8085
8166
  ...onOpenChange !== void 0 ? { onOpenChange } : {},
8086
8167
  ...ariaLabel ? { "aria-label": ariaLabel } : {},
8168
+ ...icon !== void 0 ? { icon } : {},
8087
8169
  className: panelClassName,
8088
- scrimClassName: scrimClass,
8089
- getAnimationClassName: (state) => dialogAnimationVariants({ animationState: state, variant }),
8170
+ wrapperClassName,
8171
+ getScrimClassName,
8172
+ getAnimationClassName,
8090
8173
  children
8091
8174
  }
8092
8175
  );
@@ -8097,7 +8180,7 @@ var DialogHeadline = forwardRef(
8097
8180
  const { headlineId, variant } = useDialogContext();
8098
8181
  if (variant === "fullscreen") {
8099
8182
  return (
8100
- // Top app bar row for fullscreen variant
8183
+ // Top app bar row for fullscreen variant — always has border-b border-outline-variant
8101
8184
  /* @__PURE__ */ jsxs("div", { className: cn(dialogHeadlineVariants({ variant: "fullscreen" }), className), children: [
8102
8185
  closeButton,
8103
8186
  /* @__PURE__ */ jsx("h2", { id: headlineId, className: dialogHeadlineTitleVariants(), children }),
@@ -8105,21 +8188,72 @@ var DialogHeadline = forwardRef(
8105
8188
  ] })
8106
8189
  );
8107
8190
  }
8108
- return /* @__PURE__ */ jsx(
8109
- "h2",
8110
- {
8111
- ref,
8112
- id: headlineId,
8113
- className: cn(dialogHeadlineVariants({ variant: "basic" }), className),
8114
- children
8115
- }
8191
+ return (
8192
+ // Basic variant: text-headline-small, text-on-surface, mb-4
8193
+ // group-data-[with-icon]/dialog:text-center is applied via the CVA base classes
8194
+ // when the parent panel root has data-with-icon set by DialogHeadless.
8195
+ /* @__PURE__ */ jsx(
8196
+ "h2",
8197
+ {
8198
+ ref,
8199
+ id: headlineId,
8200
+ className: cn(dialogHeadlineVariants({ variant: "basic" }), className),
8201
+ children
8202
+ }
8203
+ )
8116
8204
  );
8117
8205
  }
8118
8206
  );
8119
8207
  DialogHeadline.displayName = "DialogHeadline";
8120
- var DialogContent = forwardRef(function DialogContent2({ children, className }, ref) {
8208
+ var DialogContent = forwardRef(function DialogContent2({ children, className }, forwardedRef) {
8121
8209
  const { contentId, variant } = useDialogContext();
8122
- return /* @__PURE__ */ jsx("div", { ref, id: contentId, className: cn(dialogContentVariants({ variant }), className), children });
8210
+ const internalRef = useRef(null);
8211
+ const setRef = useCallback(
8212
+ (node) => {
8213
+ internalRef.current = node;
8214
+ if (typeof forwardedRef === "function") {
8215
+ forwardedRef(node);
8216
+ } else if (forwardedRef !== null && forwardedRef !== void 0) {
8217
+ forwardedRef.current = node;
8218
+ }
8219
+ },
8220
+ [forwardedRef]
8221
+ );
8222
+ const updateDividers = useCallback(() => {
8223
+ const el = internalRef.current;
8224
+ if (!el) return;
8225
+ const isScrollable = el.scrollHeight > el.clientHeight;
8226
+ if (!isScrollable) {
8227
+ el.removeAttribute("data-scroll-divider-top");
8228
+ el.removeAttribute("data-scroll-divider-bottom");
8229
+ return;
8230
+ }
8231
+ const scrolledFromTop = el.scrollTop > 1;
8232
+ const scrolledToBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
8233
+ if (scrolledFromTop) {
8234
+ el.setAttribute("data-scroll-divider-top", "");
8235
+ } else {
8236
+ el.removeAttribute("data-scroll-divider-top");
8237
+ }
8238
+ if (!scrolledToBottom) {
8239
+ el.setAttribute("data-scroll-divider-bottom", "");
8240
+ } else {
8241
+ el.removeAttribute("data-scroll-divider-bottom");
8242
+ }
8243
+ }, []);
8244
+ useEffect(() => {
8245
+ const el = internalRef.current;
8246
+ if (!el) return;
8247
+ updateDividers();
8248
+ el.addEventListener("scroll", updateDividers, { passive: true });
8249
+ const observer = new ResizeObserver(updateDividers);
8250
+ observer.observe(el);
8251
+ return () => {
8252
+ el.removeEventListener("scroll", updateDividers);
8253
+ observer.disconnect();
8254
+ };
8255
+ }, [updateDividers]);
8256
+ return /* @__PURE__ */ jsx("div", { ref: setRef, id: contentId, className: cn(dialogContentVariants({ variant }), className), children });
8123
8257
  });
8124
8258
  DialogContent.displayName = "DialogContent";
8125
8259
  var DialogActions = forwardRef(function DialogActions2({ children, className }, ref) {
@@ -8208,7 +8342,8 @@ function TooltipOverlayHeadless({
8208
8342
  }
8209
8343
  var TooltipAnimationContext = createContext({
8210
8344
  isExiting: false,
8211
- onAnimationEnd: () => void 0
8345
+ onAnimationEnd: () => void 0,
8346
+ reducedMotion: false
8212
8347
  });
8213
8348
  function useTooltipAnimation() {
8214
8349
  return useContext(TooltipAnimationContext);
@@ -8218,46 +8353,60 @@ function TooltipTrigger({
8218
8353
  delay = 300,
8219
8354
  isDisabled
8220
8355
  }) {
8356
+ const reducedMotion = useReducedMotion();
8221
8357
  const [isMounted, setIsMounted] = useState(false);
8222
8358
  const [isExiting, setIsExiting] = useState(false);
8223
8359
  const isExitingRef = useRef(false);
8224
8360
  const isPointerOverTooltipRef = useRef(false);
8225
8361
  const pendingCloseRef = useRef(false);
8226
- const handleOpenChange = useCallback((open) => {
8227
- if (open) {
8228
- pendingCloseRef.current = false;
8229
- isExitingRef.current = false;
8230
- setIsMounted(true);
8231
- setIsExiting(false);
8232
- } else if (isPointerOverTooltipRef.current) {
8233
- pendingCloseRef.current = true;
8234
- } else if (!isExitingRef.current) {
8235
- isExitingRef.current = true;
8236
- setIsExiting(true);
8237
- }
8238
- }, []);
8239
- const handleAnimationEnd = useCallback(() => {
8240
- if (!isExitingRef.current) return;
8362
+ const unmountImmediately = useCallback(() => {
8241
8363
  isExitingRef.current = false;
8242
8364
  pendingCloseRef.current = false;
8243
8365
  setIsMounted(false);
8244
8366
  setIsExiting(false);
8245
8367
  }, []);
8368
+ const handleOpenChange = useCallback(
8369
+ (open) => {
8370
+ if (open) {
8371
+ pendingCloseRef.current = false;
8372
+ isExitingRef.current = false;
8373
+ setIsMounted(true);
8374
+ setIsExiting(false);
8375
+ } else if (isPointerOverTooltipRef.current) {
8376
+ pendingCloseRef.current = true;
8377
+ } else if (reducedMotion) {
8378
+ unmountImmediately();
8379
+ } else if (!isExitingRef.current) {
8380
+ isExitingRef.current = true;
8381
+ setIsExiting(true);
8382
+ }
8383
+ },
8384
+ [reducedMotion, unmountImmediately]
8385
+ );
8386
+ const handleAnimationEnd = useCallback(() => {
8387
+ if (!isExitingRef.current) return;
8388
+ unmountImmediately();
8389
+ }, [unmountImmediately]);
8246
8390
  const handleOverlayPointerEnter = useCallback(() => {
8247
8391
  isPointerOverTooltipRef.current = true;
8248
8392
  pendingCloseRef.current = false;
8249
8393
  }, []);
8250
8394
  const handleOverlayPointerLeave = useCallback(() => {
8251
8395
  isPointerOverTooltipRef.current = false;
8252
- if (pendingCloseRef.current && !isExitingRef.current) {
8396
+ if (pendingCloseRef.current) {
8253
8397
  pendingCloseRef.current = false;
8254
- isExitingRef.current = true;
8255
- setIsExiting(true);
8398
+ if (reducedMotion) {
8399
+ unmountImmediately();
8400
+ } else if (!isExitingRef.current) {
8401
+ isExitingRef.current = true;
8402
+ setIsExiting(true);
8403
+ }
8256
8404
  }
8257
- }, []);
8405
+ }, [reducedMotion, unmountImmediately]);
8258
8406
  const contextValue = {
8259
8407
  isExiting,
8260
- onAnimationEnd: handleAnimationEnd
8408
+ onAnimationEnd: handleAnimationEnd,
8409
+ reducedMotion
8261
8410
  };
8262
8411
  const [triggerChild, tooltipChild] = children;
8263
8412
  const placement = isValidElement(tooltipChild) ? tooltipChild.props.placement ?? "top" : "top";
@@ -8286,51 +8435,69 @@ function TooltipTrigger({
8286
8435
  ) });
8287
8436
  }
8288
8437
  var tooltipVariants = cva(
8289
- "z-50 w-fit min-h-6 rounded-xs px-2 py-1 text-body-small bg-inverse-surface text-inverse-on-surface max-w-50",
8438
+ [
8439
+ "z-50 inline-flex items-center w-fit",
8440
+ "min-h-6 rounded-xs px-2 py-1",
8441
+ "text-body-small bg-inverse-surface text-inverse-on-surface",
8442
+ "max-w-50"
8443
+ ],
8290
8444
  {
8291
8445
  variants: {
8292
8446
  /**
8293
- * Drives the MD3 enter/exit animation class.
8447
+ * Controls the enter/exit animation class.
8294
8448
  * Managed by `TooltipTrigger`'s animation state machine.
8295
- * @default true
8449
+ * Set to `"none"` when `prefers-reduced-motion: reduce` is active.
8450
+ * @default "enter"
8296
8451
  */
8297
- isVisible: {
8298
- true: "animate-md-scale-in",
8299
- false: "animate-md-scale-out"
8452
+ animation: {
8453
+ enter: "animate-md-scale-in",
8454
+ exit: "animate-md-scale-out",
8455
+ none: ""
8300
8456
  }
8301
8457
  },
8302
8458
  defaultVariants: {
8303
- isVisible: true
8459
+ animation: "enter"
8304
8460
  }
8305
8461
  }
8306
8462
  );
8307
8463
  var richTooltipVariants = cva(
8308
- "z-50 w-fit min-h-6 rounded-md px-4 py-3 bg-surface-container text-on-surface shadow-elevation-2 max-w-80",
8464
+ [
8465
+ "z-50 flex flex-col w-fit",
8466
+ "min-h-6 rounded-md px-4 py-3",
8467
+ "bg-surface-container text-on-surface shadow-elevation-2",
8468
+ "max-w-80"
8469
+ ],
8309
8470
  {
8310
8471
  variants: {
8311
8472
  /**
8312
- * Drives the MD3 enter/exit animation class.
8473
+ * Controls the enter/exit animation class.
8313
8474
  * Managed by `TooltipTrigger`'s animation state machine.
8314
- * @default true
8475
+ * Set to `"none"` when `prefers-reduced-motion: reduce` is active.
8476
+ * @default "enter"
8315
8477
  */
8316
- isVisible: {
8317
- true: "animate-md-scale-in",
8318
- false: "animate-md-scale-out"
8478
+ animation: {
8479
+ enter: "animate-md-scale-in",
8480
+ exit: "animate-md-scale-out",
8481
+ none: ""
8319
8482
  }
8320
8483
  },
8321
8484
  defaultVariants: {
8322
- isVisible: true
8485
+ animation: "enter"
8323
8486
  }
8324
8487
  }
8325
8488
  );
8489
+ var richTooltipTitleVariants = cva(["text-title-small text-on-surface-variant mb-1"]);
8490
+ var richTooltipSupportingTextVariants = cva(["text-body-medium text-on-surface-variant"]);
8491
+ var richTooltipActionsVariants = cva(["flex items-center justify-start mt-3 -ml-2"]);
8326
8492
  var Tooltip = forwardRef(
8327
8493
  ({ children, className, placement: _placement }, ref) => {
8328
- const { isExiting, onAnimationEnd } = useTooltipAnimation();
8494
+ const { isExiting, onAnimationEnd, reducedMotion } = useTooltipAnimation();
8495
+ const animation = reducedMotion ? "none" : isExiting ? "exit" : "enter";
8329
8496
  return /* @__PURE__ */ jsx(
8330
8497
  "div",
8331
8498
  {
8332
8499
  ref,
8333
- className: cn(tooltipVariants({ isVisible: !isExiting }), className),
8500
+ className: cn(tooltipVariants({ animation }), className),
8334
8501
  onAnimationEnd,
8335
8502
  children
8336
8503
  }
@@ -8340,17 +8507,18 @@ var Tooltip = forwardRef(
8340
8507
  Tooltip.displayName = "Tooltip";
8341
8508
  var RichTooltip = forwardRef(
8342
8509
  ({ title, children, action, className, placement: _placement }, ref) => {
8343
- const { isExiting, onAnimationEnd } = useTooltipAnimation();
8510
+ const { isExiting, onAnimationEnd, reducedMotion } = useTooltipAnimation();
8511
+ const animation = reducedMotion ? "none" : isExiting ? "exit" : "enter";
8344
8512
  return /* @__PURE__ */ jsxs(
8345
8513
  "div",
8346
8514
  {
8347
8515
  ref,
8348
- className: cn(richTooltipVariants({ isVisible: !isExiting }), className),
8516
+ className: cn(richTooltipVariants({ animation }), className),
8349
8517
  onAnimationEnd,
8350
8518
  children: [
8351
- title && /* @__PURE__ */ jsx("p", { className: "text-on-surface text-title-small mb-1", children: title }),
8352
- /* @__PURE__ */ jsx("p", { className: "text-on-surface-variant text-body-medium", children }),
8353
- action
8519
+ title && /* @__PURE__ */ jsx("p", { className: richTooltipTitleVariants(), children: title }),
8520
+ /* @__PURE__ */ jsx("p", { className: richTooltipSupportingTextVariants(), children }),
8521
+ action && /* @__PURE__ */ jsx("div", { className: richTooltipActionsVariants(), children: action })
8354
8522
  ]
8355
8523
  }
8356
8524
  );