@oprt/react 0.2.0 → 0.3.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/README.md CHANGED
@@ -35,7 +35,8 @@ import "@oprt/tokens";
35
35
  import "./globals.css";
36
36
  ```
37
37
 
38
- Load the tokens from JavaScript or a `<link>`, not with an `@import` inside the Tailwind
38
+ Load the tokens from
39
+ JavaScript or a `<link>`, not with an `@import` inside the Tailwind
39
40
  stylesheet: the Tailwind CLI inlines an imported file without rewriting its font URLs, and the
40
41
  bundled fonts would not load. Inside Tailwind Typography's `prose`, wrap components in an element
41
42
  with the `not-prose` class.
@@ -8,6 +8,7 @@ import { Badge } from "../Badge/Badge.js";
8
8
  import { Button } from "../Button/Button.js";
9
9
  import { useActionFocus } from "../_internal/useActionFocus.js";
10
10
  import { devValidate } from "../_internal/devValidate.js";
11
+ import { useHoldBackNewText } from "../_internal/liveRegion.js";
11
12
  import { ActionStatusManifest } from "./ActionStatus.manifest.js";
12
13
  import { jsx, jsxs } from "react/jsx-runtime";
13
14
  var statusStyles = cva(
@@ -34,6 +35,8 @@ function ActionStatusLine({
34
35
  requestId,
35
36
  children
36
37
  }) {
38
+ const spoken = [`${label}, ${state}.`, requestId && `Request: ${requestId}.`, message].filter(Boolean).join(" ");
39
+ const holdBack = useHoldBackNewText(true);
37
40
  return /* @__PURE__ */ jsxs(
38
41
  "div",
39
42
  {
@@ -42,13 +45,21 @@ function ActionStatusLine({
42
45
  "aria-atomic": "true",
43
46
  className: "flex min-w-0 flex-col gap-1u",
44
47
  children: [
45
- /* @__PURE__ */ jsx("span", { className: "min-w-0 oprt-wrap-anywhere font-medium uppercase tracking-oprt-caps", children: label }),
46
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Badge, { variant: variants[state], indicator: "dot", children: state }) }),
47
- requestId && /* @__PURE__ */ jsxs("span", { className: "min-w-0 oprt-wrap-anywhere font-mono", children: [
48
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: holdBack ? null : spoken }),
49
+ /* @__PURE__ */ jsx(
50
+ "span",
51
+ {
52
+ "aria-hidden": "true",
53
+ className: "min-w-0 oprt-wrap-anywhere font-medium uppercase tracking-oprt-caps",
54
+ children: label
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", children: /* @__PURE__ */ jsx(Badge, { variant: variants[state], indicator: "dot", children: state }) }),
58
+ requestId && /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "min-w-0 oprt-wrap-anywhere font-mono", children: [
48
59
  "Request: ",
49
60
  requestId
50
61
  ] }),
51
- message && /* @__PURE__ */ jsx("p", { className: "m-0 min-w-0 oprt-wrap-anywhere", children: message }),
62
+ message && /* @__PURE__ */ jsx("p", { "aria-hidden": "true", className: "m-0 min-w-0 oprt-wrap-anywhere", children: message }),
52
63
  children
53
64
  ]
54
65
  }
@@ -7,6 +7,7 @@ import { forwardRef } from "react";
7
7
  import clsx from "clsx";
8
8
  import { AlertManifest } from "./Alert.manifest.js";
9
9
  import { devValidate } from "../_internal/devValidate.js";
10
+ import { useHoldBackNewText } from "../_internal/liveRegion.js";
10
11
  import { jsx, jsxs } from "react/jsx-runtime";
11
12
  var ICONS = {
12
13
  info: Info,
@@ -48,6 +49,7 @@ var Alert = forwardRef((allProps, ref) => {
48
49
  ...props
49
50
  } = allProps;
50
51
  const role = variant === "error" || variant === "warning" ? "alert" : "status";
52
+ const holdBack = useHoldBackNewText(role === "status");
51
53
  const Icon = ICONS[variant];
52
54
  const hasBody = children != null && typeof children !== "boolean" && children !== "";
53
55
  if (process.env.NODE_ENV !== "production") {
@@ -55,19 +57,26 @@ var Alert = forwardRef((allProps, ref) => {
55
57
  }
56
58
  return /* @__PURE__ */ jsxs("div", { ref, role, className: clsx(containerStyles({ variant }), className), ...props, children: [
57
59
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: clsx("shrink-0", accentStyles({ variant })), children: /* @__PURE__ */ jsx(Icon, { size: 16 }) }),
58
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1u oprt-wrap-anywhere", children: [
59
- title && /* @__PURE__ */ jsx(
60
- "p",
61
- {
62
- className: clsx(
63
- "m-0 text-oprt-sm uppercase tracking-oprt-sm",
64
- accentStyles({ variant })
60
+ /* @__PURE__ */ jsxs(
61
+ "div",
62
+ {
63
+ className: "flex min-w-0 flex-1 flex-col gap-1u oprt-wrap-anywhere",
64
+ ...holdBack ? { "aria-hidden": true } : {},
65
+ children: [
66
+ title && /* @__PURE__ */ jsx(
67
+ "p",
68
+ {
69
+ className: clsx(
70
+ "m-0 text-oprt-sm uppercase tracking-oprt-sm",
71
+ accentStyles({ variant })
72
+ ),
73
+ children: title
74
+ }
65
75
  ),
66
- children: title
67
- }
68
- ),
69
- hasBody && /* @__PURE__ */ jsx("div", { className: "text-oprt-base normal-case text-oprt-text", children })
70
- ] }),
76
+ hasBody && /* @__PURE__ */ jsx("div", { className: "text-oprt-base normal-case text-oprt-text", children })
77
+ ]
78
+ }
79
+ ),
71
80
  dismissible && /* @__PURE__ */ jsx(
72
81
  "button",
73
82
  {
@@ -77,18 +77,21 @@ var AttachmentPreview = forwardRef(
77
77
  /* @__PURE__ */ jsx("span", { className: "oprt-wrap-anywhere", children: attachment.mediaType })
78
78
  ] })
79
79
  ] }),
80
- preview.kind === "image" && (imageError ? /* @__PURE__ */ jsx("p", { role: "status", className: "m-0 text-oprt-error", children: imageError }) : /* @__PURE__ */ jsx(
81
- "img",
82
- {
83
- src: preview.src,
84
- alt: preview.alt,
85
- onError: () => setImageState(
86
- (current) => current.key === imageKey ? { key: imageKey, failed: true } : current
87
- ),
88
- className: clsx(PREVIEW_HEIGHT, "max-w-full self-start object-contain")
89
- },
90
- imageKey
91
- )),
80
+ preview.kind === "image" && /* @__PURE__ */ jsxs(Fragment, { children: [
81
+ /* @__PURE__ */ jsx("p", { role: "status", className: imageError ? "m-0 text-oprt-error" : "sr-only", children: imageError }),
82
+ !imageError && /* @__PURE__ */ jsx(
83
+ "img",
84
+ {
85
+ src: preview.src,
86
+ alt: preview.alt,
87
+ onError: () => setImageState(
88
+ (current) => current.key === imageKey ? { key: imageKey, failed: true } : current
89
+ ),
90
+ className: clsx(PREVIEW_HEIGHT, "max-w-full self-start object-contain")
91
+ },
92
+ imageKey
93
+ )
94
+ ] }),
92
95
  preview.kind === "text" && /* @__PURE__ */ jsxs(Fragment, { children: [
93
96
  /* @__PURE__ */ jsx(
94
97
  "pre",
@@ -874,7 +874,15 @@ var Chart = forwardRef((allProps, ref) => {
874
874
  onBlur: () => setInspectionFocused(false)
875
875
  }
876
876
  ),
877
- empty ? /* @__PURE__ */ jsx("p", { className: "m-0 py-6u text-oprt-base text-oprt-text-dim", role: "status", children: empty }) : /* @__PURE__ */ jsxs(
877
+ /* @__PURE__ */ jsx(
878
+ "p",
879
+ {
880
+ role: "status",
881
+ className: empty ? "m-0 py-6u text-oprt-base text-oprt-text-dim" : "sr-only",
882
+ children: empty
883
+ }
884
+ ),
885
+ empty ? null : /* @__PURE__ */ jsxs(
878
886
  "svg",
879
887
  {
880
888
  role: "img",
@@ -172,7 +172,15 @@ var EventTimeline = forwardRef(
172
172
  "aria-label": label,
173
173
  className: clsx(timelineStyles(), className),
174
174
  children: [
175
- !validRange ? /* @__PURE__ */ jsx("p", { role: "status", className: "m-0 text-oprt-base text-oprt-text-dim", children: "Choose a valid time range with start before end." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
175
+ /* @__PURE__ */ jsx(
176
+ "p",
177
+ {
178
+ role: "status",
179
+ className: !validRange ? "m-0 text-oprt-base text-oprt-text-dim" : "sr-only",
180
+ children: !validRange ? "Choose a valid time range with start before end." : ""
181
+ }
182
+ ),
183
+ !validRange ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
176
184
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap justify-between gap-1u font-mono text-oprt-base text-oprt-text-dim", children: [
177
185
  /* @__PURE__ */ jsx("time", { dateTime: new Date(start).toISOString(), children: utc(start) }),
178
186
  /* @__PURE__ */ jsx("time", { dateTime: new Date(end).toISOString(), children: utc(end) })
@@ -69,11 +69,12 @@ var Popover = forwardRef(
69
69
  const dialog = useRef(null);
70
70
  const lastDialog = useRef(null);
71
71
  const wasOpen = useRef(false);
72
- const focusFrame = useRef();
72
+ const focusFrame = useRef(void 0);
73
73
  const focusScrollClose = useRef(false);
74
74
  const current = useRef({ isOpen, onCloseAutoFocus, returnFocusRef });
75
75
  current.current = { isOpen, onCloseAutoFocus, returnFocusRef };
76
- const childRef = parseInt(version, 10) < 19 ? children.ref : children.props.ref;
76
+ const triggerChild = children;
77
+ const childRef = parseInt(version, 10) < 19 ? triggerChild.ref : triggerChild.props.ref;
77
78
  const rememberTrigger = useCallback((node) => {
78
79
  trigger.current = node;
79
80
  if (node) lastTrigger.current = node;
@@ -209,10 +210,10 @@ var Popover = forwardRef(
209
210
  document.removeEventListener("pointercancel", cancel, true);
210
211
  };
211
212
  }, [isOpen, requestOpen]);
212
- const triggerElement = cloneElement(children, {
213
+ const triggerElement = cloneElement(triggerChild, {
213
214
  ref: setTrigger,
214
- disabled: disabled || children.props.disabled,
215
- "aria-disabled": disabled || children.props["aria-disabled"],
215
+ disabled: disabled || triggerChild.props.disabled,
216
+ "aria-disabled": disabled || triggerChild.props["aria-disabled"],
216
217
  "aria-controls": isOpen ? id : void 0
217
218
  });
218
219
  const pressable = /* @__PURE__ */ jsx(Pressable, { isDisabled: disabled, children: triggerElement });
@@ -6,7 +6,7 @@ import { createResizeObserver } from "../_internal/useElementWidth.js";
6
6
  import { useIsomorphicLayoutEffect } from "../_internal/useIsomorphicLayoutEffect.js";
7
7
  function useTabsIndicator(list, vertical) {
8
8
  const [geometry, setGeometry] = useState();
9
- const selectedId = useRef();
9
+ const selectedId = useRef(void 0);
10
10
  useIsomorphicLayoutEffect(() => {
11
11
  const node = list.current;
12
12
  if (!node) return;
@@ -165,7 +165,7 @@ var Toast = forwardRef((allProps, ref) => {
165
165
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 select-text flex-col gap-1u oprt-wrap-anywhere", children: [
166
166
  /* @__PURE__ */ jsx(RadixToast.Title, { className: titleStyles({ variant: severity }), children: title }),
167
167
  description && /* @__PURE__ */ jsxs(RadixToast.Description, { className: "m-0 text-oprt-base normal-case text-oprt-text", children: [
168
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: ". " }),
168
+ /* @__PURE__ */ jsx("span", { "data-radix-toast-announce-exclude": "", "data-radix-toast-announce-alt": ". " }),
169
169
  description
170
170
  ] })
171
171
  ] }),
@@ -35,11 +35,11 @@ function warnProbeMissing(origin) {
35
35
  function useHydrationRoot(...forwardedIds) {
36
36
  const probe = useId();
37
37
  const hydrated = useSyncExternalStore(subscribeNoop, getClientSnapshot, getServerSnapshot);
38
- const hydratingMount = useRef();
38
+ const hydratingMount = useRef(void 0);
39
39
  if (hydratingMount.current === void 0) hydratingMount.current = !hydrated;
40
40
  const [disagrees, setDisagrees] = useState(false);
41
41
  const clientOnly = useRef(hydrated);
42
- const origin = useRef();
42
+ const origin = useRef(void 0);
43
43
  if (process.env.NODE_ENV !== "production" && origin.current === void 0) {
44
44
  origin.current = callerOfHook();
45
45
  }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ // src/_internal/liveRegion.ts
4
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
5
+ var neverChanges = () => () => {
6
+ };
7
+ var LIVE_REGION_LEAD_MS = 100;
8
+ function useHoldBackNewText(enabled) {
9
+ const hydrating = useSyncExternalStore(
10
+ neverChanges,
11
+ () => false,
12
+ () => true
13
+ );
14
+ const lateArrival = useRef(null);
15
+ if (lateArrival.current === null) lateArrival.current = !hydrating;
16
+ const [held, setHeld] = useState(Boolean(lateArrival.current));
17
+ useEffect(() => {
18
+ if (!held) return;
19
+ const timer = window.setTimeout(() => setHeld(false), LIVE_REGION_LEAD_MS);
20
+ return () => window.clearTimeout(timer);
21
+ }, [held]);
22
+ return enabled && held;
23
+ }
24
+ export {
25
+ LIVE_REGION_LEAD_MS,
26
+ useHoldBackNewText
27
+ };
@@ -25,8 +25,8 @@ function useMotion(mode = "auto", component) {
25
25
  const [ready, setReady] = useState(false);
26
26
  const [keyboard, setKeyboard] = useState(false);
27
27
  const pointer = useRef(false);
28
- const frame = useRef();
29
- const contactCleanup = useRef();
28
+ const frame = useRef(void 0);
29
+ const contactCleanup = useRef(void 0);
30
30
  const clearContact = useCallback(() => {
31
31
  contactCleanup.current?.();
32
32
  contactCleanup.current = void 0;