@mediaradar/design-components 0.14.0 → 0.15.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.cjs CHANGED
@@ -1911,6 +1911,19 @@ var import_jsx_runtime19 = require("react/jsx-runtime");
1911
1911
  function cx6(...parts) {
1912
1912
  return parts.filter(Boolean).join(" ");
1913
1913
  }
1914
+ function exitDurationMs(el) {
1915
+ if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return 0;
1916
+ const cs = window.getComputedStyle(el);
1917
+ if (cs.transitionProperty === "none") return 0;
1918
+ const toMs = (v) => {
1919
+ const n = parseFloat(v);
1920
+ if (!Number.isFinite(n)) return 0;
1921
+ return v.trim().endsWith("ms") ? n : n * 1e3;
1922
+ };
1923
+ const durations = cs.transitionDuration.split(",").map(toMs);
1924
+ const delays = cs.transitionDelay.split(",").map(toMs);
1925
+ return Math.max(0, ...durations.map((d, i) => d + (delays[i] ?? delays[0] ?? 0)));
1926
+ }
1914
1927
  function Tooltip({
1915
1928
  label,
1916
1929
  children,
@@ -1930,6 +1943,10 @@ function Tooltip({
1930
1943
  const boxRef = (0, import_react10.useRef)(null);
1931
1944
  const hideTimer = (0, import_react10.useRef)(null);
1932
1945
  const showTimer = (0, import_react10.useRef)(null);
1946
+ const exitTimer = (0, import_react10.useRef)(null);
1947
+ const [shown, setShown] = (0, import_react10.useState)(false);
1948
+ const openRef = (0, import_react10.useRef)(false);
1949
+ const mountedRef = (0, import_react10.useRef)(false);
1933
1950
  const id = (0, import_react10.useId)();
1934
1951
  const clearHideTimer = () => {
1935
1952
  if (hideTimer.current) {
@@ -1943,10 +1960,17 @@ function Tooltip({
1943
1960
  showTimer.current = null;
1944
1961
  }
1945
1962
  };
1963
+ const clearExitTimer = () => {
1964
+ if (exitTimer.current) {
1965
+ clearTimeout(exitTimer.current);
1966
+ exitTimer.current = null;
1967
+ }
1968
+ };
1946
1969
  (0, import_react10.useEffect)(
1947
1970
  () => () => {
1948
1971
  clearHideTimer();
1949
1972
  clearShowTimer();
1973
+ clearExitTimer();
1950
1974
  },
1951
1975
  []
1952
1976
  );
@@ -1954,26 +1978,56 @@ function Tooltip({
1954
1978
  const el = anchorRef ? anchorRef.current : ref.current;
1955
1979
  if (!el) return;
1956
1980
  if (whenTruncated && el.scrollWidth <= el.clientWidth) return;
1981
+ clearExitTimer();
1982
+ openRef.current = true;
1957
1983
  const r = el.getBoundingClientRect();
1958
- setBoxWidth(null);
1984
+ if (!mountedRef.current) setBoxWidth(null);
1985
+ mountedRef.current = true;
1959
1986
  setAnchor({ top: r.top, left: r.left, width: r.width, height: r.height, bottom: r.bottom, right: r.right });
1960
1987
  if (anchorRef) el.setAttribute("aria-describedby", id);
1961
1988
  };
1962
1989
  const show = () => {
1963
1990
  clearHideTimer();
1964
- if (delay > 0) {
1991
+ clearShowTimer();
1992
+ if (delay > 0 && !mountedRef.current) {
1965
1993
  showTimer.current = setTimeout(reveal, delay);
1966
1994
  } else {
1967
1995
  reveal();
1968
1996
  }
1969
1997
  };
1970
- const hideNow = () => {
1971
- clearShowTimer();
1972
- clearHideTimer();
1998
+ const unmount = () => {
1999
+ clearExitTimer();
2000
+ if (openRef.current || !mountedRef.current) return;
2001
+ mountedRef.current = false;
1973
2002
  setAnchor(null);
1974
2003
  setBoxWidth(null);
1975
2004
  if (anchorRef?.current) anchorRef.current.removeAttribute("aria-describedby");
1976
2005
  };
2006
+ const hideNow = () => {
2007
+ clearShowTimer();
2008
+ clearHideTimer();
2009
+ openRef.current = false;
2010
+ if (!mountedRef.current) return;
2011
+ setShown(false);
2012
+ clearExitTimer();
2013
+ const ms = boxRef.current ? exitDurationMs(boxRef.current) : 0;
2014
+ if (ms === 0) {
2015
+ unmount();
2016
+ return;
2017
+ }
2018
+ exitTimer.current = setTimeout(unmount, ms + 50);
2019
+ };
2020
+ const keepOpen = () => {
2021
+ clearHideTimer();
2022
+ if (openRef.current || !mountedRef.current) return;
2023
+ clearExitTimer();
2024
+ openRef.current = true;
2025
+ setShown(true);
2026
+ };
2027
+ const onBoxTransitionEnd = (e) => {
2028
+ if (e.target !== e.currentTarget || e.propertyName !== "opacity") return;
2029
+ if (!openRef.current) unmount();
2030
+ };
1977
2031
  const hide = () => {
1978
2032
  clearShowTimer();
1979
2033
  if (!interactive) {
@@ -2003,6 +2057,7 @@ function Tooltip({
2003
2057
  const el = boxRef.current;
2004
2058
  if (!el) return;
2005
2059
  setBoxWidth(el.getBoundingClientRect().width);
2060
+ if (openRef.current) setShown(true);
2006
2061
  }, [anchor, label]);
2007
2062
  const margin = 8;
2008
2063
  let style = { top: 0, left: 0 };
@@ -2030,10 +2085,19 @@ function Tooltip({
2030
2085
  id,
2031
2086
  "data-testid": testIdPrefix && `${testIdPrefix}-tooltip`,
2032
2087
  style: { top: style.top, left: style.left, right: style.right, visibility: boxWidth == null ? "hidden" : "visible" },
2033
- onMouseEnter: interactive ? clearHideTimer : void 0,
2088
+ onMouseEnter: interactive ? keepOpen : void 0,
2034
2089
  onMouseLeave: interactive ? hide : void 0,
2090
+ onTransitionEnd: onBoxTransitionEnd,
2035
2091
  className: cx6(
2036
2092
  "fixed z-[90] w-max max-w-xs whitespace-normal break-words px-2.5 py-1.5 text-xs leading-snug",
2093
+ // Fade in/out. The sys duration token rather than a ref one:
2094
+ // it is what Button's hover/press transition uses, and a
2095
+ // tooltip's fade is the same kind of interactive feedback.
2096
+ // design-tokens has no in-out curve (ease-interactive is the
2097
+ // decelerating "standard" curve), so ease-in-out is Tailwind's.
2098
+ "transition-opacity duration-(--mr-motion-duration-interactive) ease-in-out motion-reduce:transition-none",
2099
+ shown ? "opacity-100" : "opacity-0",
2100
+ // Non-interactive: never a pointer target, fade-out included.
2037
2101
  interactive ? "pointer-events-auto" : "pointer-events-none",
2038
2102
  variant === "inverted" ? "rounded-control bg-text-primary text-surface" : "rounded-control border border-border-default bg-surface text-text-primary shadow-overlay",
2039
2103
  translate
@@ -2773,21 +2837,19 @@ var buttonVariants = (0, import_class_variance_authority.cva)(
2773
2837
  );
2774
2838
  var Button = (0, import_react13.forwardRef)(
2775
2839
  ({ className, variant, size, asChild = false, loading = false, disabled, children, ...props }, ref) => {
2776
- const Comp = asChild ? import_react_slot.Slot : "button";
2777
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2778
- Comp,
2779
- {
2780
- className: cn(buttonVariants({ variant, size, className })),
2781
- ref,
2782
- disabled: disabled || loading,
2783
- "aria-busy": loading || void 0,
2784
- ...props,
2785
- children: [
2786
- loading && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react15.Loader2, { className: "size-4 shrink-0 animate-spin", "aria-hidden": "true" }),
2787
- children
2788
- ]
2789
- }
2790
- );
2840
+ const shared = {
2841
+ className: cn(buttonVariants({ variant, size, className })),
2842
+ disabled: disabled || loading,
2843
+ "aria-busy": loading || void 0,
2844
+ ...props
2845
+ };
2846
+ if (asChild) {
2847
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_slot.Slot, { ref, ...shared, children });
2848
+ }
2849
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("button", { ref, ...shared, children: [
2850
+ loading && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react15.Loader2, { className: "size-4 shrink-0 animate-spin", "aria-hidden": "true" }),
2851
+ children
2852
+ ] });
2791
2853
  }
2792
2854
  );
2793
2855
  Button.displayName = "Button";