@mediaradar/design-components 0.18.2 → 0.18.3

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
@@ -85,6 +85,17 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
85
85
  const [open, setOpen] = (0, import_react.useState)(false);
86
86
  const [pos, setPos] = (0, import_react.useState)(null);
87
87
  const btnRef = (0, import_react.useRef)(null);
88
+ const closeTimer = (0, import_react.useRef)(null);
89
+ const textId = (0, import_react.useId)();
90
+ const cancelClose = () => {
91
+ if (closeTimer.current) clearTimeout(closeTimer.current);
92
+ closeTimer.current = null;
93
+ };
94
+ const closeSoon = () => {
95
+ cancelClose();
96
+ closeTimer.current = setTimeout(() => setOpen(false), 120);
97
+ };
98
+ (0, import_react.useEffect)(() => cancelClose, []);
88
99
  (0, import_react.useEffect)(() => {
89
100
  if (!open) return;
90
101
  const onDown = (e) => {
@@ -106,6 +117,7 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
106
117
  setPos({ top: r.bottom + 6, left, width });
107
118
  };
108
119
  const show = () => {
120
+ cancelClose();
109
121
  place();
110
122
  setOpen(true);
111
123
  };
@@ -116,7 +128,7 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
116
128
  };
117
129
  const hoverProps = trigger === "hover" ? {
118
130
  onMouseEnter: show,
119
- onMouseLeave: () => setOpen(false),
131
+ onMouseLeave: closeSoon,
120
132
  onFocus: show,
121
133
  onBlur: () => setOpen(false)
122
134
  } : {};
@@ -129,16 +141,20 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
129
141
  onClick: toggle,
130
142
  "aria-label": label,
131
143
  "aria-expanded": open,
144
+ "aria-describedby": textId,
132
145
  ...hoverProps,
133
146
  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
147
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Info, { size, "aria-hidden": "true" })
135
148
  }
136
149
  ),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: textId, hidden: true, children: text }),
137
151
  open && pos && (0, import_react_dom.createPortal)(
138
152
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
139
153
  "div",
140
154
  {
141
- role: "tooltip",
155
+ "aria-hidden": "true",
156
+ onMouseEnter: trigger === "hover" ? cancelClose : void 0,
157
+ onMouseLeave: trigger === "hover" ? closeSoon : void 0,
142
158
  style: { position: "fixed", top: pos.top, left: pos.left, width: pos.width },
143
159
  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
160
  children: text
@@ -587,7 +603,7 @@ var import_jsx_runtime6 = require("react/jsx-runtime");
587
603
  function boxClass(on, disabled) {
588
604
  return cn(
589
605
  "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",
606
+ on ? "border-transparent bg-brand-primaryAction text-brand-onprimary" : "border-border-interactive bg-input",
591
607
  disabled && "cursor-not-allowed opacity-50"
592
608
  );
593
609
  }
@@ -927,7 +943,7 @@ function DateRangeFilter({
927
943
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
928
944
  "span",
929
945
  {
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"}`,
946
+ 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
947
  children: d.getDate()
932
948
  }
933
949
  )
@@ -938,7 +954,7 @@ function DateRangeFilter({
938
954
  }) }),
939
955
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "mt-4 flex justify-end gap-2", children: [
940
956
  /* @__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" })
957
+ /* @__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
958
  ] })
943
959
  ] })
944
960
  ]
@@ -1218,6 +1234,7 @@ function RadioOption({
1218
1234
  }
1219
1235
  function RowInfoTip({ text }) {
1220
1236
  const [open, setOpen] = (0, import_react4.useState)(false);
1237
+ const id = (0, import_react4.useId)();
1221
1238
  const ref = (0, import_react4.useRef)(null);
1222
1239
  (0, import_react4.useEffect)(() => {
1223
1240
  if (!open) return;
@@ -1238,14 +1255,16 @@ function RowInfoTip({ text }) {
1238
1255
  },
1239
1256
  "aria-label": "More information",
1240
1257
  "aria-expanded": open,
1241
- className: "inline-flex text-text-tertiary transition-colors hover:text-text-primary",
1258
+ "aria-describedby": id,
1259
+ 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
1260
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react7.AlertCircle, { size: 13, "aria-hidden": "true" })
1243
1261
  }
1244
1262
  ),
1245
- open && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1263
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1246
1264
  "span",
1247
1265
  {
1248
- role: "tooltip",
1266
+ id,
1267
+ hidden: !open,
1249
1268
  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
1269
  children: text
1251
1270
  }
@@ -1597,7 +1616,7 @@ var NO_RESULTS_BODY = "Your filters may be too narrow, conflict with each other,
1597
1616
  function noResultsBody(workspaceName) {
1598
1617
  return workspaceName ? `Nothing in the \u201C${workspaceName}\u201D workspace matches. ${NO_RESULTS_BODY}` : NO_RESULTS_BODY;
1599
1618
  }
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";
1619
+ 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
1620
  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
1621
  function NoResultsEmptyState({
1603
1622
  title = NO_RESULTS_TITLE,
@@ -1938,6 +1957,15 @@ var import_jsx_runtime20 = require("react/jsx-runtime");
1938
1957
  function cx6(...parts) {
1939
1958
  return parts.filter(Boolean).join(" ");
1940
1959
  }
1960
+ function addDescribedBy(el, id) {
1961
+ const ids = (el.getAttribute("aria-describedby") ?? "").split(" ").filter(Boolean);
1962
+ if (!ids.includes(id)) el.setAttribute("aria-describedby", [...ids, id].join(" "));
1963
+ }
1964
+ function removeDescribedBy(el, id) {
1965
+ const ids = (el.getAttribute("aria-describedby") ?? "").split(" ").filter((x) => x && x !== id);
1966
+ if (ids.length) el.setAttribute("aria-describedby", ids.join(" "));
1967
+ else el.removeAttribute("aria-describedby");
1968
+ }
1941
1969
  function exitDurationMs(el) {
1942
1970
  if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return 0;
1943
1971
  const cs = window.getComputedStyle(el);
@@ -1975,6 +2003,7 @@ function Tooltip({
1975
2003
  const openRef = (0, import_react10.useRef)(false);
1976
2004
  const mountedRef = (0, import_react10.useRef)(false);
1977
2005
  const id = (0, import_react10.useId)();
2006
+ const describedRef = (0, import_react10.useRef)(null);
1978
2007
  const clearHideTimer = () => {
1979
2008
  if (hideTimer.current) {
1980
2009
  clearTimeout(hideTimer.current);
@@ -2011,7 +2040,8 @@ function Tooltip({
2011
2040
  if (!mountedRef.current) setBoxWidth(null);
2012
2041
  mountedRef.current = true;
2013
2042
  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);
2043
+ const target = anchorRef ? el : describedRef.current;
2044
+ if (target) addDescribedBy(target, id);
2015
2045
  };
2016
2046
  const show = () => {
2017
2047
  clearHideTimer();
@@ -2028,7 +2058,9 @@ function Tooltip({
2028
2058
  mountedRef.current = false;
2029
2059
  setAnchor(null);
2030
2060
  setBoxWidth(null);
2031
- if (anchorRef?.current) anchorRef.current.removeAttribute("aria-describedby");
2061
+ if (anchorRef?.current) removeDescribedBy(anchorRef.current, id);
2062
+ if (describedRef.current) removeDescribedBy(describedRef.current, id);
2063
+ describedRef.current = null;
2032
2064
  };
2033
2065
  const hideNow = () => {
2034
2066
  clearShowTimer();
@@ -2079,6 +2111,12 @@ function Tooltip({
2079
2111
  el.removeEventListener("blur", hide);
2080
2112
  };
2081
2113
  }, [anchorRef, placement, delay]);
2114
+ (0, import_react10.useEffect)(() => {
2115
+ if (!anchor) return;
2116
+ const onKey = (e) => e.key === "Escape" && hideNow();
2117
+ window.addEventListener("keydown", onKey);
2118
+ return () => window.removeEventListener("keydown", onKey);
2119
+ }, [anchor]);
2082
2120
  (0, import_react10.useLayoutEffect)(() => {
2083
2121
  if (!anchor) return;
2084
2122
  const el = boxRef.current;
@@ -2142,9 +2180,11 @@ function Tooltip({
2142
2180
  className,
2143
2181
  onMouseEnter: show,
2144
2182
  onMouseLeave: hide,
2145
- onFocus: show,
2183
+ onFocus: (e) => {
2184
+ describedRef.current = e.target;
2185
+ show();
2186
+ },
2146
2187
  onBlur: hide,
2147
- "aria-describedby": anchor ? id : void 0,
2148
2188
  children: [
2149
2189
  children,
2150
2190
  floating
@@ -2839,7 +2879,7 @@ var import_class_variance_authority = require("class-variance-authority");
2839
2879
  var import_lucide_react16 = require("lucide-react");
2840
2880
  var import_jsx_runtime26 = require("react/jsx-runtime");
2841
2881
  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",
2882
+ "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
2883
  {
2844
2884
  variants: {
2845
2885
  variant: {
@@ -2955,11 +2995,38 @@ function NameListModal({
2955
2995
  () => Object.fromEntries((options ?? []).map((o) => [o.key, !!o.initial]))
2956
2996
  );
2957
2997
  const toggle = (key) => setChecked((c) => ({ ...c, [key]: !c[key] }));
2998
+ const dialogRef = (0, import_react14.useRef)(null);
2999
+ const titleId = (0, import_react14.useId)();
3000
+ const fieldId = (0, import_react14.useId)();
2958
3001
  (0, import_react14.useEffect)(() => {
2959
3002
  const onKey = (e) => e.key === "Escape" && onCancel();
2960
3003
  window.addEventListener("keydown", onKey);
2961
3004
  return () => window.removeEventListener("keydown", onKey);
2962
3005
  }, [onCancel]);
3006
+ const [opener] = (0, import_react14.useState)(() => typeof document === "undefined" ? null : document.activeElement);
3007
+ (0, import_react14.useEffect)(
3008
+ () => () => {
3009
+ const active = document.activeElement;
3010
+ if (!active || active === document.body) opener?.focus?.();
3011
+ },
3012
+ [opener]
3013
+ );
3014
+ const trapTab = (e) => {
3015
+ if (e.key !== "Tab" || !dialogRef.current) return;
3016
+ const items = [
3017
+ ...dialogRef.current.querySelectorAll("button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex='-1'])")
3018
+ ];
3019
+ if (items.length === 0) return;
3020
+ const first = items[0];
3021
+ const last = items[items.length - 1];
3022
+ if (e.shiftKey && document.activeElement === first) {
3023
+ e.preventDefault();
3024
+ last.focus();
3025
+ } else if (!e.shiftKey && document.activeElement === last) {
3026
+ e.preventDefault();
3027
+ first.focus();
3028
+ }
3029
+ };
2963
3030
  return (0, import_react_dom5.createPortal)(
2964
3031
  // bg-scrim, not a literal: design-tokens gained a scrim token in 2.5.0.
2965
3032
  // No in-data-[theme=dark] override needed any more -- the token redefines
@@ -2972,18 +3039,20 @@ function NameListModal({
2972
3039
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2973
3040
  "div",
2974
3041
  {
3042
+ ref: dialogRef,
2975
3043
  role: "dialog",
2976
3044
  "aria-modal": "true",
2977
- "aria-label": title,
3045
+ "aria-labelledby": titleId,
3046
+ onKeyDown: trapTab,
2978
3047
  className: "w-full max-w-[420px] rounded-card border border-border-default bg-surface p-5 shadow-overlay",
2979
3048
  children: [
2980
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { className: "text-[16px] font-semibold text-text-primary", children: title }),
3049
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { id: titleId, className: "text-[16px] font-semibold text-text-primary", children: title }),
2981
3050
  /* @__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 }),
3051
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("label", { htmlFor: fieldId, className: "text-[12px] font-medium text-text-secondary", children: fieldLabel }),
2983
3052
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2984
3053
  "input",
2985
3054
  {
2986
- id: "account-list-name",
3055
+ id: fieldId,
2987
3056
  autoFocus: true,
2988
3057
  value: name,
2989
3058
  onChange: (e) => setName(e.target.value),
@@ -3141,7 +3210,7 @@ function SortButton({
3141
3210
  // subtly tighter than the fixed headers next to them. A control
3142
3211
  // should carry exactly the properties that do not inherit, and
3143
3212
  // nothing more.
3144
- "group inline-flex items-center gap-1 uppercase transition-colors",
3213
+ "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
3214
  ALIGN[align],
3146
3215
  !disabled && "hover:text-text-primary"
3147
3216
  ),