@mediaradar/design-components 0.18.2 → 0.18.4

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
@@ -76,6 +76,9 @@ __export(index_exports, {
76
76
  });
77
77
  module.exports = __toCommonJS(index_exports);
78
78
 
79
+ // <define:__FONT_SIZE_KEYS__>
80
+ var define_FONT_SIZE_KEYS_default = ["micro", "xs", "card-title", "dense", "sm", "panel-title", "base", "md", "lg", "xl", "h1"];
81
+
79
82
  // src/InfoTip.tsx
80
83
  var import_react = require("react");
81
84
  var import_react_dom = require("react-dom");
@@ -85,6 +88,17 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
85
88
  const [open, setOpen] = (0, import_react.useState)(false);
86
89
  const [pos, setPos] = (0, import_react.useState)(null);
87
90
  const btnRef = (0, import_react.useRef)(null);
91
+ const closeTimer = (0, import_react.useRef)(null);
92
+ const textId = (0, import_react.useId)();
93
+ const cancelClose = () => {
94
+ if (closeTimer.current) clearTimeout(closeTimer.current);
95
+ closeTimer.current = null;
96
+ };
97
+ const closeSoon = () => {
98
+ cancelClose();
99
+ closeTimer.current = setTimeout(() => setOpen(false), 120);
100
+ };
101
+ (0, import_react.useEffect)(() => cancelClose, []);
88
102
  (0, import_react.useEffect)(() => {
89
103
  if (!open) return;
90
104
  const onDown = (e) => {
@@ -106,6 +120,7 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
106
120
  setPos({ top: r.bottom + 6, left, width });
107
121
  };
108
122
  const show = () => {
123
+ cancelClose();
109
124
  place();
110
125
  setOpen(true);
111
126
  };
@@ -116,7 +131,7 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
116
131
  };
117
132
  const hoverProps = trigger === "hover" ? {
118
133
  onMouseEnter: show,
119
- onMouseLeave: () => setOpen(false),
134
+ onMouseLeave: closeSoon,
120
135
  onFocus: show,
121
136
  onBlur: () => setOpen(false)
122
137
  } : {};
@@ -129,16 +144,20 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
129
144
  onClick: toggle,
130
145
  "aria-label": label,
131
146
  "aria-expanded": open,
147
+ "aria-describedby": textId,
132
148
  ...hoverProps,
133
149
  className: "inline-flex cursor-pointer items-center text-text-tertiary transition-colors hover:text-text-primary focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
134
150
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Info, { size, "aria-hidden": "true" })
135
151
  }
136
152
  ),
153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: textId, hidden: true, children: text }),
137
154
  open && pos && (0, import_react_dom.createPortal)(
138
155
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
139
156
  "div",
140
157
  {
141
- role: "tooltip",
158
+ "aria-hidden": "true",
159
+ onMouseEnter: trigger === "hover" ? cancelClose : void 0,
160
+ onMouseLeave: trigger === "hover" ? closeSoon : void 0,
142
161
  style: { position: "fixed", top: pos.top, left: pos.left, width: pos.width },
143
162
  className: "z-[90] rounded-control border border-border-default bg-surface px-3 py-2 text-[12px] font-normal not-italic normal-case leading-relaxed tracking-normal text-text-secondary shadow-overlay",
144
163
  children: text
@@ -472,7 +491,7 @@ var import_tailwind_merge = require("tailwind-merge");
472
491
  var twMerge = (0, import_tailwind_merge.extendTailwindMerge)({
473
492
  extend: {
474
493
  classGroups: {
475
- "font-size": [{ text: ["micro", "dense"] }]
494
+ "font-size": [{ text: define_FONT_SIZE_KEYS_default }]
476
495
  }
477
496
  }
478
497
  });
@@ -587,7 +606,7 @@ var import_jsx_runtime6 = require("react/jsx-runtime");
587
606
  function boxClass(on, disabled) {
588
607
  return cn(
589
608
  "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors",
590
- on ? "border-transparent bg-brand-primaryAction text-white" : "border-border-interactive bg-input",
609
+ on ? "border-transparent bg-brand-primaryAction text-brand-onprimary" : "border-border-interactive bg-input",
591
610
  disabled && "cursor-not-allowed opacity-50"
592
611
  );
593
612
  }
@@ -927,7 +946,7 @@ function DateRangeFilter({
927
946
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
928
947
  "span",
929
948
  {
930
- className: `relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-[13px] tabular-nums ${endpoint ? "bg-brand-primaryAction font-semibold text-white" : inMonth ? "text-text-primary hover:bg-subtle" : "text-text-tertiary"}`,
949
+ className: `relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-[13px] tabular-nums ${endpoint ? "bg-brand-primaryAction font-semibold text-brand-onprimary" : inMonth ? "text-text-primary hover:bg-subtle" : "text-text-tertiary"}`,
931
950
  children: d.getDate()
932
951
  }
933
952
  )
@@ -938,7 +957,7 @@ function DateRangeFilter({
938
957
  }) }),
939
958
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "mt-4 flex justify-end gap-2", children: [
940
959
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": testIdPrefix && `${testIdPrefix}-cancel`, onClick: () => setOpen(false), className: "rounded-control border border-border-default bg-surface px-5 py-2 text-[13px] font-semibold text-text-primary transition-colors hover:bg-subtle", children: "Cancel" }),
941
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": testIdPrefix && `${testIdPrefix}-apply`, onClick: apply, className: "rounded-control bg-brand-primaryAction px-5 py-2 text-[13px] font-semibold text-white transition-[filter] hover:brightness-110", children: "Apply" })
960
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", "data-testid": testIdPrefix && `${testIdPrefix}-apply`, onClick: apply, className: "rounded-control bg-brand-primaryAction px-5 py-2 text-[13px] font-semibold text-brand-onprimary transition-[filter] hover:brightness-110", children: "Apply" })
942
961
  ] })
943
962
  ] })
944
963
  ]
@@ -1218,6 +1237,7 @@ function RadioOption({
1218
1237
  }
1219
1238
  function RowInfoTip({ text }) {
1220
1239
  const [open, setOpen] = (0, import_react4.useState)(false);
1240
+ const id = (0, import_react4.useId)();
1221
1241
  const ref = (0, import_react4.useRef)(null);
1222
1242
  (0, import_react4.useEffect)(() => {
1223
1243
  if (!open) return;
@@ -1238,14 +1258,16 @@ function RowInfoTip({ text }) {
1238
1258
  },
1239
1259
  "aria-label": "More information",
1240
1260
  "aria-expanded": open,
1241
- className: "inline-flex text-text-tertiary transition-colors hover:text-text-primary",
1261
+ "aria-describedby": id,
1262
+ className: "inline-flex text-text-tertiary transition-colors hover:text-text-primary focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus",
1242
1263
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react7.AlertCircle, { size: 13, "aria-hidden": "true" })
1243
1264
  }
1244
1265
  ),
1245
- open && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1266
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1246
1267
  "span",
1247
1268
  {
1248
- role: "tooltip",
1269
+ id,
1270
+ hidden: !open,
1249
1271
  className: "absolute left-0 top-[calc(100%+6px)] z-40 w-[220px] rounded-control border border-border-default bg-surface px-2.5 py-1.5 text-[11.5px] font-normal leading-snug text-text-secondary shadow-overlay",
1250
1272
  children: text
1251
1273
  }
@@ -1597,7 +1619,7 @@ var NO_RESULTS_BODY = "Your filters may be too narrow, conflict with each other,
1597
1619
  function noResultsBody(workspaceName) {
1598
1620
  return workspaceName ? `Nothing in the \u201C${workspaceName}\u201D workspace matches. ${NO_RESULTS_BODY}` : NO_RESULTS_BODY;
1599
1621
  }
1600
- var PRIMARY_ACTION_CLASS = "rounded-control bg-brand-primaryAction px-3.5 py-2 text-[13px] font-semibold text-white transition-colors hover:opacity-90";
1622
+ var PRIMARY_ACTION_CLASS = "rounded-control bg-brand-primaryAction px-3.5 py-2 text-[13px] font-semibold text-brand-onprimary transition-colors hover:opacity-90";
1601
1623
  var QUIET_ACTION_CLASS = "rounded-control text-[13px] font-medium text-text-link transition-colors hover:underline focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus";
1602
1624
  function NoResultsEmptyState({
1603
1625
  title = NO_RESULTS_TITLE,
@@ -1938,6 +1960,15 @@ var import_jsx_runtime20 = require("react/jsx-runtime");
1938
1960
  function cx6(...parts) {
1939
1961
  return parts.filter(Boolean).join(" ");
1940
1962
  }
1963
+ function addDescribedBy(el, id) {
1964
+ const ids = (el.getAttribute("aria-describedby") ?? "").split(" ").filter(Boolean);
1965
+ if (!ids.includes(id)) el.setAttribute("aria-describedby", [...ids, id].join(" "));
1966
+ }
1967
+ function removeDescribedBy(el, id) {
1968
+ const ids = (el.getAttribute("aria-describedby") ?? "").split(" ").filter((x) => x && x !== id);
1969
+ if (ids.length) el.setAttribute("aria-describedby", ids.join(" "));
1970
+ else el.removeAttribute("aria-describedby");
1971
+ }
1941
1972
  function exitDurationMs(el) {
1942
1973
  if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return 0;
1943
1974
  const cs = window.getComputedStyle(el);
@@ -1975,6 +2006,7 @@ function Tooltip({
1975
2006
  const openRef = (0, import_react10.useRef)(false);
1976
2007
  const mountedRef = (0, import_react10.useRef)(false);
1977
2008
  const id = (0, import_react10.useId)();
2009
+ const describedRef = (0, import_react10.useRef)(null);
1978
2010
  const clearHideTimer = () => {
1979
2011
  if (hideTimer.current) {
1980
2012
  clearTimeout(hideTimer.current);
@@ -2011,7 +2043,8 @@ function Tooltip({
2011
2043
  if (!mountedRef.current) setBoxWidth(null);
2012
2044
  mountedRef.current = true;
2013
2045
  setAnchor({ top: r.top, left: r.left, width: r.width, height: r.height, bottom: r.bottom, right: r.right });
2014
- if (anchorRef) el.setAttribute("aria-describedby", id);
2046
+ const target = anchorRef ? el : describedRef.current;
2047
+ if (target) addDescribedBy(target, id);
2015
2048
  };
2016
2049
  const show = () => {
2017
2050
  clearHideTimer();
@@ -2028,7 +2061,9 @@ function Tooltip({
2028
2061
  mountedRef.current = false;
2029
2062
  setAnchor(null);
2030
2063
  setBoxWidth(null);
2031
- if (anchorRef?.current) anchorRef.current.removeAttribute("aria-describedby");
2064
+ if (anchorRef?.current) removeDescribedBy(anchorRef.current, id);
2065
+ if (describedRef.current) removeDescribedBy(describedRef.current, id);
2066
+ describedRef.current = null;
2032
2067
  };
2033
2068
  const hideNow = () => {
2034
2069
  clearShowTimer();
@@ -2079,6 +2114,12 @@ function Tooltip({
2079
2114
  el.removeEventListener("blur", hide);
2080
2115
  };
2081
2116
  }, [anchorRef, placement, delay]);
2117
+ (0, import_react10.useEffect)(() => {
2118
+ if (!anchor) return;
2119
+ const onKey = (e) => e.key === "Escape" && hideNow();
2120
+ window.addEventListener("keydown", onKey);
2121
+ return () => window.removeEventListener("keydown", onKey);
2122
+ }, [anchor]);
2082
2123
  (0, import_react10.useLayoutEffect)(() => {
2083
2124
  if (!anchor) return;
2084
2125
  const el = boxRef.current;
@@ -2142,9 +2183,11 @@ function Tooltip({
2142
2183
  className,
2143
2184
  onMouseEnter: show,
2144
2185
  onMouseLeave: hide,
2145
- onFocus: show,
2186
+ onFocus: (e) => {
2187
+ describedRef.current = e.target;
2188
+ show();
2189
+ },
2146
2190
  onBlur: hide,
2147
- "aria-describedby": anchor ? id : void 0,
2148
2191
  children: [
2149
2192
  children,
2150
2193
  floating
@@ -2788,7 +2831,7 @@ function MetricCells({ spendN, yoy }) {
2788
2831
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "flex shrink-0 items-center gap-2 pr-1.5", children: [
2789
2832
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "text-right text-[12px] font-bold tabular-nums text-text-primary", style: { width: COL_CURRENT }, children: fmtM(spendN) }),
2790
2833
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "text-right text-[12px] font-normal tabular-nums text-text-primary", style: { width: COL_PREVIOUS }, children: prior ?? "\u2014" }),
2791
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "flex justify-end", style: { width: COL_YOY }, children: yoy === "NEW" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "rounded-full bg-status-warning/15 px-1.5 text-[10px] font-semibold text-text-warning", children: "NEW" }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: cx9("text-right text-[12px] font-semibold tabular-nums", up ? "text-text-success" : "text-text-danger"), children: [
2834
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "flex justify-end", style: { width: COL_YOY }, children: yoy === "NEW" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "rounded-full bg-status-warning/15 px-1.5 text-micro font-semibold text-text-warning", children: "NEW" }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: cx9("text-right text-[12px] font-semibold tabular-nums", up ? "text-text-success" : "text-text-danger"), children: [
2792
2835
  up ? "+" : "\u2212",
2793
2836
  Math.abs(yoy),
2794
2837
  "%"
@@ -2839,7 +2882,7 @@ var import_class_variance_authority = require("class-variance-authority");
2839
2882
  var import_lucide_react16 = require("lucide-react");
2840
2883
  var import_jsx_runtime26 = require("react/jsx-runtime");
2841
2884
  var buttonVariants = (0, import_class_variance_authority.cva)(
2842
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-control transition-all duration-(--mr-motion-duration-interactive) ease-interactive disabled:pointer-events-none disabled:opacity-40 focus-visible:outline-hidden focus-visible:ring-0",
2885
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-control transition-all duration-(--mr-motion-duration-interactive) ease-interactive disabled:pointer-events-none disabled:opacity-40 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus",
2843
2886
  {
2844
2887
  variants: {
2845
2888
  variant: {
@@ -2955,11 +2998,38 @@ function NameListModal({
2955
2998
  () => Object.fromEntries((options ?? []).map((o) => [o.key, !!o.initial]))
2956
2999
  );
2957
3000
  const toggle = (key) => setChecked((c) => ({ ...c, [key]: !c[key] }));
3001
+ const dialogRef = (0, import_react14.useRef)(null);
3002
+ const titleId = (0, import_react14.useId)();
3003
+ const fieldId = (0, import_react14.useId)();
2958
3004
  (0, import_react14.useEffect)(() => {
2959
3005
  const onKey = (e) => e.key === "Escape" && onCancel();
2960
3006
  window.addEventListener("keydown", onKey);
2961
3007
  return () => window.removeEventListener("keydown", onKey);
2962
3008
  }, [onCancel]);
3009
+ const [opener] = (0, import_react14.useState)(() => typeof document === "undefined" ? null : document.activeElement);
3010
+ (0, import_react14.useEffect)(
3011
+ () => () => {
3012
+ const active = document.activeElement;
3013
+ if (!active || active === document.body) opener?.focus?.();
3014
+ },
3015
+ [opener]
3016
+ );
3017
+ const trapTab = (e) => {
3018
+ if (e.key !== "Tab" || !dialogRef.current) return;
3019
+ const items = [
3020
+ ...dialogRef.current.querySelectorAll("button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex='-1'])")
3021
+ ];
3022
+ if (items.length === 0) return;
3023
+ const first = items[0];
3024
+ const last = items[items.length - 1];
3025
+ if (e.shiftKey && document.activeElement === first) {
3026
+ e.preventDefault();
3027
+ last.focus();
3028
+ } else if (!e.shiftKey && document.activeElement === last) {
3029
+ e.preventDefault();
3030
+ first.focus();
3031
+ }
3032
+ };
2963
3033
  return (0, import_react_dom5.createPortal)(
2964
3034
  // bg-scrim, not a literal: design-tokens gained a scrim token in 2.5.0.
2965
3035
  // No in-data-[theme=dark] override needed any more -- the token redefines
@@ -2972,18 +3042,20 @@ function NameListModal({
2972
3042
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2973
3043
  "div",
2974
3044
  {
3045
+ ref: dialogRef,
2975
3046
  role: "dialog",
2976
3047
  "aria-modal": "true",
2977
- "aria-label": title,
3048
+ "aria-labelledby": titleId,
3049
+ onKeyDown: trapTab,
2978
3050
  className: "w-full max-w-[420px] rounded-card border border-border-default bg-surface p-5 shadow-overlay",
2979
3051
  children: [
2980
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { className: "text-[16px] font-semibold text-text-primary", children: title }),
3052
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { id: titleId, className: "text-[16px] font-semibold text-text-primary", children: title }),
2981
3053
  /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "mt-4 flex flex-col gap-1.5", children: [
2982
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("label", { htmlFor: "account-list-name", className: "text-[12px] font-medium text-text-secondary", children: fieldLabel }),
3054
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("label", { htmlFor: fieldId, className: "text-[12px] font-medium text-text-secondary", children: fieldLabel }),
2983
3055
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2984
3056
  "input",
2985
3057
  {
2986
- id: "account-list-name",
3058
+ id: fieldId,
2987
3059
  autoFocus: true,
2988
3060
  value: name,
2989
3061
  onChange: (e) => setName(e.target.value),
@@ -3141,7 +3213,7 @@ function SortButton({
3141
3213
  // subtly tighter than the fixed headers next to them. A control
3142
3214
  // should carry exactly the properties that do not inherit, and
3143
3215
  // nothing more.
3144
- "group inline-flex items-center gap-1 uppercase transition-colors",
3216
+ "group inline-flex items-center gap-1 uppercase transition-colors focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus",
3145
3217
  ALIGN[align],
3146
3218
  !disabled && "hover:text-text-primary"
3147
3219
  ),