@mediaradar/design-components 0.13.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
@@ -517,17 +517,22 @@ var TONE_STYLE = {
517
517
  }
518
518
  };
519
519
  var KIND = {
520
- label: "px-2 py-0.5",
520
+ // Twice the vertical room the first version had. At py-0.5 the pill hugged
521
+ // its text and read as a tight little tag; at py-1 it reads as a deliberate
522
+ // object. Taken from the status pills the product had already arrived at by
523
+ // hand, which were noticeably more comfortable than anything else -- the
524
+ // breathing room was what made them so, not their extra pixel of type.
525
+ label: "px-2.5 py-1",
521
526
  // Square-ish and centred, with a floor width so a 1-digit and a 3-digit
522
527
  // count are the same shape, and lining figures so the width does not shift
523
528
  // as the number changes.
524
529
  count: "h-[18px] min-w-[18px] justify-center px-1.5 tabular-nums",
525
530
  // The only kind that shouts. Caps need the letterspacing -- caps set tight
526
531
  // are harder to read than the sentence case they replaced.
527
- eyebrow: "px-2 py-0.5 uppercase tracking-wider"
532
+ eyebrow: "px-2.5 py-1 uppercase tracking-wider"
528
533
  };
529
- function Badge({ tone = "neutral", kind = "label", children, className }) {
530
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
534
+ function Badge({ tone = "neutral", kind = "label", dot = false, children, className }) {
535
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
531
536
  "span",
532
537
  {
533
538
  style: TONE_STYLE[tone],
@@ -541,7 +546,10 @@ function Badge({ tone = "neutral", kind = "label", children, className }) {
541
546
  KIND[kind],
542
547
  className
543
548
  ),
544
- children
549
+ children: [
550
+ dot && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { "aria-hidden": "true", className: "size-1.5 shrink-0 rounded-full bg-current" }),
551
+ children
552
+ ]
545
553
  }
546
554
  );
547
555
  }
@@ -1903,6 +1911,19 @@ var import_jsx_runtime19 = require("react/jsx-runtime");
1903
1911
  function cx6(...parts) {
1904
1912
  return parts.filter(Boolean).join(" ");
1905
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
+ }
1906
1927
  function Tooltip({
1907
1928
  label,
1908
1929
  children,
@@ -1922,6 +1943,10 @@ function Tooltip({
1922
1943
  const boxRef = (0, import_react10.useRef)(null);
1923
1944
  const hideTimer = (0, import_react10.useRef)(null);
1924
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);
1925
1950
  const id = (0, import_react10.useId)();
1926
1951
  const clearHideTimer = () => {
1927
1952
  if (hideTimer.current) {
@@ -1935,10 +1960,17 @@ function Tooltip({
1935
1960
  showTimer.current = null;
1936
1961
  }
1937
1962
  };
1963
+ const clearExitTimer = () => {
1964
+ if (exitTimer.current) {
1965
+ clearTimeout(exitTimer.current);
1966
+ exitTimer.current = null;
1967
+ }
1968
+ };
1938
1969
  (0, import_react10.useEffect)(
1939
1970
  () => () => {
1940
1971
  clearHideTimer();
1941
1972
  clearShowTimer();
1973
+ clearExitTimer();
1942
1974
  },
1943
1975
  []
1944
1976
  );
@@ -1946,26 +1978,56 @@ function Tooltip({
1946
1978
  const el = anchorRef ? anchorRef.current : ref.current;
1947
1979
  if (!el) return;
1948
1980
  if (whenTruncated && el.scrollWidth <= el.clientWidth) return;
1981
+ clearExitTimer();
1982
+ openRef.current = true;
1949
1983
  const r = el.getBoundingClientRect();
1950
- setBoxWidth(null);
1984
+ if (!mountedRef.current) setBoxWidth(null);
1985
+ mountedRef.current = true;
1951
1986
  setAnchor({ top: r.top, left: r.left, width: r.width, height: r.height, bottom: r.bottom, right: r.right });
1952
1987
  if (anchorRef) el.setAttribute("aria-describedby", id);
1953
1988
  };
1954
1989
  const show = () => {
1955
1990
  clearHideTimer();
1956
- if (delay > 0) {
1991
+ clearShowTimer();
1992
+ if (delay > 0 && !mountedRef.current) {
1957
1993
  showTimer.current = setTimeout(reveal, delay);
1958
1994
  } else {
1959
1995
  reveal();
1960
1996
  }
1961
1997
  };
1962
- const hideNow = () => {
1963
- clearShowTimer();
1964
- clearHideTimer();
1998
+ const unmount = () => {
1999
+ clearExitTimer();
2000
+ if (openRef.current || !mountedRef.current) return;
2001
+ mountedRef.current = false;
1965
2002
  setAnchor(null);
1966
2003
  setBoxWidth(null);
1967
2004
  if (anchorRef?.current) anchorRef.current.removeAttribute("aria-describedby");
1968
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
+ };
1969
2031
  const hide = () => {
1970
2032
  clearShowTimer();
1971
2033
  if (!interactive) {
@@ -1995,6 +2057,7 @@ function Tooltip({
1995
2057
  const el = boxRef.current;
1996
2058
  if (!el) return;
1997
2059
  setBoxWidth(el.getBoundingClientRect().width);
2060
+ if (openRef.current) setShown(true);
1998
2061
  }, [anchor, label]);
1999
2062
  const margin = 8;
2000
2063
  let style = { top: 0, left: 0 };
@@ -2022,10 +2085,19 @@ function Tooltip({
2022
2085
  id,
2023
2086
  "data-testid": testIdPrefix && `${testIdPrefix}-tooltip`,
2024
2087
  style: { top: style.top, left: style.left, right: style.right, visibility: boxWidth == null ? "hidden" : "visible" },
2025
- onMouseEnter: interactive ? clearHideTimer : void 0,
2088
+ onMouseEnter: interactive ? keepOpen : void 0,
2026
2089
  onMouseLeave: interactive ? hide : void 0,
2090
+ onTransitionEnd: onBoxTransitionEnd,
2027
2091
  className: cx6(
2028
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.
2029
2101
  interactive ? "pointer-events-auto" : "pointer-events-none",
2030
2102
  variant === "inverted" ? "rounded-control bg-text-primary text-surface" : "rounded-control border border-border-default bg-surface text-text-primary shadow-overlay",
2031
2103
  translate
@@ -2765,21 +2837,19 @@ var buttonVariants = (0, import_class_variance_authority.cva)(
2765
2837
  );
2766
2838
  var Button = (0, import_react13.forwardRef)(
2767
2839
  ({ className, variant, size, asChild = false, loading = false, disabled, children, ...props }, ref) => {
2768
- const Comp = asChild ? import_react_slot.Slot : "button";
2769
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2770
- Comp,
2771
- {
2772
- className: cn(buttonVariants({ variant, size, className })),
2773
- ref,
2774
- disabled: disabled || loading,
2775
- "aria-busy": loading || void 0,
2776
- ...props,
2777
- children: [
2778
- loading && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react15.Loader2, { className: "size-4 shrink-0 animate-spin", "aria-hidden": "true" }),
2779
- children
2780
- ]
2781
- }
2782
- );
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
+ ] });
2783
2853
  }
2784
2854
  );
2785
2855
  Button.displayName = "Button";