@mediaradar/design-components 0.18.1 → 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
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -84,6 +85,17 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
84
85
  const [open, setOpen] = (0, import_react.useState)(false);
85
86
  const [pos, setPos] = (0, import_react.useState)(null);
86
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, []);
87
99
  (0, import_react.useEffect)(() => {
88
100
  if (!open) return;
89
101
  const onDown = (e) => {
@@ -105,6 +117,7 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
105
117
  setPos({ top: r.bottom + 6, left, width });
106
118
  };
107
119
  const show = () => {
120
+ cancelClose();
108
121
  place();
109
122
  setOpen(true);
110
123
  };
@@ -115,7 +128,7 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
115
128
  };
116
129
  const hoverProps = trigger === "hover" ? {
117
130
  onMouseEnter: show,
118
- onMouseLeave: () => setOpen(false),
131
+ onMouseLeave: closeSoon,
119
132
  onFocus: show,
120
133
  onBlur: () => setOpen(false)
121
134
  } : {};
@@ -128,16 +141,20 @@ function InfoTip({ text, size = 11, label = "More information", trigger = "click
128
141
  onClick: toggle,
129
142
  "aria-label": label,
130
143
  "aria-expanded": open,
144
+ "aria-describedby": textId,
131
145
  ...hoverProps,
132
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",
133
147
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Info, { size, "aria-hidden": "true" })
134
148
  }
135
149
  ),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: textId, hidden: true, children: text }),
136
151
  open && pos && (0, import_react_dom.createPortal)(
137
152
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
138
153
  "div",
139
154
  {
140
- role: "tooltip",
155
+ "aria-hidden": "true",
156
+ onMouseEnter: trigger === "hover" ? cancelClose : void 0,
157
+ onMouseLeave: trigger === "hover" ? closeSoon : void 0,
141
158
  style: { position: "fixed", top: pos.top, left: pos.left, width: pos.width },
142
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",
143
160
  children: text
@@ -586,7 +603,7 @@ var import_jsx_runtime6 = require("react/jsx-runtime");
586
603
  function boxClass(on, disabled) {
587
604
  return cn(
588
605
  "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors",
589
- 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",
590
607
  disabled && "cursor-not-allowed opacity-50"
591
608
  );
592
609
  }
@@ -926,7 +943,7 @@ function DateRangeFilter({
926
943
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
927
944
  "span",
928
945
  {
929
- 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"}`,
930
947
  children: d.getDate()
931
948
  }
932
949
  )
@@ -937,7 +954,7 @@ function DateRangeFilter({
937
954
  }) }),
938
955
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "mt-4 flex justify-end gap-2", children: [
939
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" }),
940
- /* @__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" })
941
958
  ] })
942
959
  ] })
943
960
  ]
@@ -1217,6 +1234,7 @@ function RadioOption({
1217
1234
  }
1218
1235
  function RowInfoTip({ text }) {
1219
1236
  const [open, setOpen] = (0, import_react4.useState)(false);
1237
+ const id = (0, import_react4.useId)();
1220
1238
  const ref = (0, import_react4.useRef)(null);
1221
1239
  (0, import_react4.useEffect)(() => {
1222
1240
  if (!open) return;
@@ -1237,14 +1255,16 @@ function RowInfoTip({ text }) {
1237
1255
  },
1238
1256
  "aria-label": "More information",
1239
1257
  "aria-expanded": open,
1240
- 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",
1241
1260
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react7.AlertCircle, { size: 13, "aria-hidden": "true" })
1242
1261
  }
1243
1262
  ),
1244
- open && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1263
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1245
1264
  "span",
1246
1265
  {
1247
- role: "tooltip",
1266
+ id,
1267
+ hidden: !open,
1248
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",
1249
1269
  children: text
1250
1270
  }
@@ -1596,7 +1616,7 @@ var NO_RESULTS_BODY = "Your filters may be too narrow, conflict with each other,
1596
1616
  function noResultsBody(workspaceName) {
1597
1617
  return workspaceName ? `Nothing in the \u201C${workspaceName}\u201D workspace matches. ${NO_RESULTS_BODY}` : NO_RESULTS_BODY;
1598
1618
  }
1599
- 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";
1600
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";
1601
1621
  function NoResultsEmptyState({
1602
1622
  title = NO_RESULTS_TITLE,
@@ -1937,6 +1957,15 @@ var import_jsx_runtime20 = require("react/jsx-runtime");
1937
1957
  function cx6(...parts) {
1938
1958
  return parts.filter(Boolean).join(" ");
1939
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
+ }
1940
1969
  function exitDurationMs(el) {
1941
1970
  if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return 0;
1942
1971
  const cs = window.getComputedStyle(el);
@@ -1974,6 +2003,7 @@ function Tooltip({
1974
2003
  const openRef = (0, import_react10.useRef)(false);
1975
2004
  const mountedRef = (0, import_react10.useRef)(false);
1976
2005
  const id = (0, import_react10.useId)();
2006
+ const describedRef = (0, import_react10.useRef)(null);
1977
2007
  const clearHideTimer = () => {
1978
2008
  if (hideTimer.current) {
1979
2009
  clearTimeout(hideTimer.current);
@@ -2010,7 +2040,8 @@ function Tooltip({
2010
2040
  if (!mountedRef.current) setBoxWidth(null);
2011
2041
  mountedRef.current = true;
2012
2042
  setAnchor({ top: r.top, left: r.left, width: r.width, height: r.height, bottom: r.bottom, right: r.right });
2013
- if (anchorRef) el.setAttribute("aria-describedby", id);
2043
+ const target = anchorRef ? el : describedRef.current;
2044
+ if (target) addDescribedBy(target, id);
2014
2045
  };
2015
2046
  const show = () => {
2016
2047
  clearHideTimer();
@@ -2027,7 +2058,9 @@ function Tooltip({
2027
2058
  mountedRef.current = false;
2028
2059
  setAnchor(null);
2029
2060
  setBoxWidth(null);
2030
- 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;
2031
2064
  };
2032
2065
  const hideNow = () => {
2033
2066
  clearShowTimer();
@@ -2078,6 +2111,12 @@ function Tooltip({
2078
2111
  el.removeEventListener("blur", hide);
2079
2112
  };
2080
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]);
2081
2120
  (0, import_react10.useLayoutEffect)(() => {
2082
2121
  if (!anchor) return;
2083
2122
  const el = boxRef.current;
@@ -2141,9 +2180,11 @@ function Tooltip({
2141
2180
  className,
2142
2181
  onMouseEnter: show,
2143
2182
  onMouseLeave: hide,
2144
- onFocus: show,
2183
+ onFocus: (e) => {
2184
+ describedRef.current = e.target;
2185
+ show();
2186
+ },
2145
2187
  onBlur: hide,
2146
- "aria-describedby": anchor ? id : void 0,
2147
2188
  children: [
2148
2189
  children,
2149
2190
  floating
@@ -2838,7 +2879,7 @@ var import_class_variance_authority = require("class-variance-authority");
2838
2879
  var import_lucide_react16 = require("lucide-react");
2839
2880
  var import_jsx_runtime26 = require("react/jsx-runtime");
2840
2881
  var buttonVariants = (0, import_class_variance_authority.cva)(
2841
- "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",
2842
2883
  {
2843
2884
  variants: {
2844
2885
  variant: {
@@ -2954,11 +2995,38 @@ function NameListModal({
2954
2995
  () => Object.fromEntries((options ?? []).map((o) => [o.key, !!o.initial]))
2955
2996
  );
2956
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)();
2957
3001
  (0, import_react14.useEffect)(() => {
2958
3002
  const onKey = (e) => e.key === "Escape" && onCancel();
2959
3003
  window.addEventListener("keydown", onKey);
2960
3004
  return () => window.removeEventListener("keydown", onKey);
2961
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
+ };
2962
3030
  return (0, import_react_dom5.createPortal)(
2963
3031
  // bg-scrim, not a literal: design-tokens gained a scrim token in 2.5.0.
2964
3032
  // No in-data-[theme=dark] override needed any more -- the token redefines
@@ -2971,18 +3039,20 @@ function NameListModal({
2971
3039
  children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2972
3040
  "div",
2973
3041
  {
3042
+ ref: dialogRef,
2974
3043
  role: "dialog",
2975
3044
  "aria-modal": "true",
2976
- "aria-label": title,
3045
+ "aria-labelledby": titleId,
3046
+ onKeyDown: trapTab,
2977
3047
  className: "w-full max-w-[420px] rounded-card border border-border-default bg-surface p-5 shadow-overlay",
2978
3048
  children: [
2979
- /* @__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 }),
2980
3050
  /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "mt-4 flex flex-col gap-1.5", children: [
2981
- /* @__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 }),
2982
3052
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2983
3053
  "input",
2984
3054
  {
2985
- id: "account-list-name",
3055
+ id: fieldId,
2986
3056
  autoFocus: true,
2987
3057
  value: name,
2988
3058
  onChange: (e) => setName(e.target.value),
@@ -3140,7 +3210,7 @@ function SortButton({
3140
3210
  // subtly tighter than the fixed headers next to them. A control
3141
3211
  // should carry exactly the properties that do not inherit, and
3142
3212
  // nothing more.
3143
- "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",
3144
3214
  ALIGN[align],
3145
3215
  !disabled && "hover:text-text-primary"
3146
3216
  ),