@blakesteve/roster 5.3.0 → 5.4.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.
Files changed (25) hide show
  1. package/README.md +84 -1
  2. package/dist/components/molecules/MediaPreview/MediaPreview.cjs +1 -0
  3. package/dist/components/molecules/MediaPreview/MediaPreview.d.ts +27 -0
  4. package/dist/components/molecules/MediaPreview/MediaPreview.js +55 -0
  5. package/dist/components/organisms/Dialog/Dialog.cjs +1 -1
  6. package/dist/components/organisms/Dialog/Dialog.d.ts +9 -1
  7. package/dist/components/organisms/Dialog/Dialog.js +26 -24
  8. package/dist/components/organisms/ModerationQueue/ModerationQueue.cjs +1 -0
  9. package/dist/components/organisms/ModerationQueue/ModerationQueue.d.ts +73 -0
  10. package/dist/components/organisms/ModerationQueue/ModerationQueue.js +430 -0
  11. package/dist/components/organisms/ModerationQueue/QueueEditDialog.cjs +1 -0
  12. package/dist/components/organisms/ModerationQueue/QueueEditDialog.d.ts +8 -0
  13. package/dist/components/organisms/ModerationQueue/QueueEditDialog.js +37 -0
  14. package/dist/components/organisms/ModerationQueue/QueueEditSheet.cjs +1 -0
  15. package/dist/components/organisms/ModerationQueue/QueueEditSheet.d.ts +12 -0
  16. package/dist/components/organisms/ModerationQueue/QueueEditSheet.js +30 -0
  17. package/dist/components/organisms/ModerationQueue/queue-actions.cjs +1 -0
  18. package/dist/components/organisms/ModerationQueue/queue-actions.d.ts +157 -0
  19. package/dist/components/organisms/ModerationQueue/queue-actions.js +6 -0
  20. package/dist/index.d.ts +5 -0
  21. package/dist/meta.json +3 -3
  22. package/dist/roster.cjs +1 -1
  23. package/dist/roster.css +1 -1
  24. package/dist/roster.js +118 -108
  25. package/package.json +1 -1
package/README.md CHANGED
@@ -1208,6 +1208,87 @@ escape the box. Lay the options out across with `orientation` rather than
1208
1208
  through `optionsClassName`, for the same reason: the horizontal spacing that
1209
1209
  keeps two radios 44px apart rides on that prop.
1210
1210
 
1211
+ ### ModerationQueue
1212
+
1213
+ A queue of things waiting for a decision: submissions to approve, reports to
1214
+ resolve, claims to check before they publish. The app owns the data and every
1215
+ request; the queue owns the layout, the states, the steps and the keyboard.
1216
+ **It never fetches.** An action's `run` is the only path from a decision to
1217
+ the network, and the app says an item has left by removing it from `items`.
1218
+
1219
+ ```tsx
1220
+ import { ModerationQueue, QueueEditDialog } from "@blakesteve/roster";
1221
+
1222
+ <ModerationQueue
1223
+ label="New recipes"
1224
+ items={recipes}
1225
+ getKey={(r) => r.id}
1226
+ itemLabel={(r) => `${r.title}, from ${r.by}`}
1227
+ renderMeta={(r) => <Badge size="xs">{r.category}</Badge>}
1228
+ renderContent={(r) => <p>{r.method}</p>}
1229
+ status={loading ? "loading" : failed ? "error" : "ready"}
1230
+ onRetry={reload}
1231
+ actions={(a) => [
1232
+ { id: "list", label: "List it", done: "Listed", tone: "success",
1233
+ run: async (r) => { await api.list(r.id); await reload(); } },
1234
+ a.edit({ id: "edit", label: "Edit and list", done: "Listed with edits",
1235
+ edit: { title: "Tidy it up", submitLabel: "List it", surface: QueueEditDialog,
1236
+ initial: (r) => ({ title: r.title }), Form: TitleForm,
1237
+ validate: (d) => (d.title.trim() ? undefined : "Give it a title.") },
1238
+ run: async (r, { draft }) => { await api.list(r.id, draft); await reload(); } }),
1239
+ { id: "send-back", label: "Send back", done: "Sent back", tone: "danger",
1240
+ reason: { prompt: "Why? The cook will see this.", required: true,
1241
+ options: [{ value: "duplicate", label: "We already have this one" }] },
1242
+ run: async (r, { reason }) => { await api.sendBack(r.id, reason); await reload(); } },
1243
+ ]}
1244
+ />;
1245
+ ```
1246
+
1247
+ **Four kinds of action.** One click (no step). `confirm` asks in place first.
1248
+ `reason` asks why in place: radios, a note, or both. `edit` opens a form the
1249
+ app supplies, inline by default or in a Dialog or Sheet (`QueueEditDialog`,
1250
+ `QueueEditSheet`, separate exports so a queue that doesn't use them doesn't
1251
+ ship them).
1252
+
1253
+ **Drafts are typed.** An edit's draft is whatever its `initial` returns, and
1254
+ `validate`, the form and `run` all get that type. Inside JSX, pass `actions` as
1255
+ a function and write edits with the `edit` it's handed, as above; outside it,
1256
+ `editAction(…)` does the same in a list typed `QueueAction<Item>[]`. A literal
1257
+ `{ edit: … }` doesn't compile, because its draft could only be `any`.
1258
+
1259
+ What it promises:
1260
+
1261
+ - **Only the clicked action is busy, and it keeps focus.** It shows a spinner
1262
+ and is `aria-disabled` rather than `disabled`, which would drop focus to the
1263
+ page. That item's other actions wait; every other item stays live.
1264
+ - **A decision that lands and removes the item** is announced in a polite live
1265
+ region ("Listed: Smoky red lentil soup, from Dana Okafor. 2 left."), and
1266
+ focus moves to the next item, the previous one, or the empty state. Never to
1267
+ the page. If the app keeps the item, the announcement drops the count and
1268
+ focus stays. `run` may resolve with `{ announce }` to say something else
1269
+ ("Already decided by someone else.").
1270
+ - **A decision that fails** leaves the item where it was with an Alert saying
1271
+ why, and focus where it was. Inside a step, the step stays open with its
1272
+ draft. A reviewer who moved to another item meanwhile isn't pulled back.
1273
+ - **Several outcomes in one render are said in one sentence**: "Approved:
1274
+ Lentil soup. Approved: Rye crackers. 1 left."
1275
+ - **Steps are per item.** A reason half-written on one item survives another
1276
+ item's decision.
1277
+ - **Steps take focus and give it back.** Escape or Cancel returns focus to the
1278
+ button that opened the step.
1279
+ - **An item that disappears** (another reviewer, a reload) with its step open
1280
+ closes the step without deciding, and is announced once as decided
1281
+ elsewhere. Without a step, it moves focus only if focus was inside it.
1282
+ - **Headings follow the page.** `headingLevel` sets the queue's (2 by default),
1283
+ and items sit one below unless `itemHeadingLevel` says otherwise.
1284
+ - **Phones.** Every button is 44px tall at every width, and nothing scrolls
1285
+ sideways at 320px. Nothing animates as an item leaves.
1286
+
1287
+ Counts for a tab badge need no API: the app holds the items, so it's
1288
+ `items.length`. `MediaPreview` is the thumbnail to use in an item: when the
1289
+ image can't render (an expired link, a format the browser can't draw), it
1290
+ falls back to a link to the original in the same footprint.
1291
+
1211
1292
  ### Carousel
1212
1293
 
1213
1294
  A sideways row: a row of cards, a selection strip, or a gallery of one per
@@ -1349,6 +1430,7 @@ function App() {
1349
1430
  | `LiquidNav` | `LiquidTabs`' strip as a `nav` of links; takes a router's link component |
1350
1431
  | `LiquidTabs` | Tab strip with a sliding pill; arrow keys, roving focus, 44px `lg` size |
1351
1432
  | `MatchupCard` | Head-to-head comparison card |
1433
+ | `MediaPreview` | Thumbnail that falls back to "Preview unavailable, open original" when it can't render |
1352
1434
  | `Pullquote` | A line lifted out of prose, as `<figure>` + `<blockquote>` + `<figcaption>` |
1353
1435
  | `RadioGroup` | Radio set reporting one `string`; descriptions, error, 44px targets |
1354
1436
  | `Toast` | Transient floating notice; `Toaster` + the imperative `toast` handle |
@@ -1360,8 +1442,9 @@ function App() {
1360
1442
  | `ActionBar` | Sticky bottom action strip |
1361
1443
  | `Countdown` | Live countdown timer |
1362
1444
  | `DataTable` | Full-featured table with sorting and pagination via TanStack Table v9. Imported from `@blakesteve/roster/data-table` (see [DataTable columns](#datatable-columns)) |
1363
- | `Dialog` | Modal dialog |
1445
+ | `Dialog` | Modal dialog; `onAfterClose` runs once it has finished closing |
1364
1446
  | `Footer` | Site footer |
1447
+ | `ModerationQueue` | Items waiting for a decision: busy per action, confirm, reason and edit steps, focus after an item leaves, a live region. Never fetches (see [ModerationQueue](#moderationqueue)) |
1365
1448
  | `Navbar` | Responsive navigation bar with mobile slide-out |
1366
1449
  | `Sheet` | Bottom sheet: drag, Escape or backdrop to dismiss; only its content scrolls; returns focus, even from a deep link; the title may be a node |
1367
1450
  | `Table` | Static data table |
@@ -0,0 +1 @@
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),m=require("react"),i=require("../../../lib/utils.cjs");function h({src:e,href:t,alt:s,unavailableLabel:n="Preview unavailable",openLabel:a="Open original",onUnavailable:u,className:o,...g}){const[x,b]=m.useState(null),f=!e||x===e,c="rst:font-ui rst:h-28 rst:w-36 rst:shrink-0 rst:rounded-md rst:border rst:border-[var(--roster-card-border)]";if(f){const d=r.jsxs(r.Fragment,{children:[r.jsx("span",{children:n}),t?r.jsxs("span",{className:"rst:font-semibold rst:underline",children:[a,r.jsx("span",{"aria-hidden":"true",children:" ↗"})]}):null]}),l=i.cn(c,"rst:flex rst:flex-col rst:items-center rst:justify-center rst:gap-1 rst:border-dashed rst:px-2 rst:text-center rst:text-xs rst:text-gray-600 rst:dark:text-gray-300",o);return t?r.jsx("a",{href:t,target:"_blank",rel:"noreferrer",className:i.cn(l,"rst:hover:bg-gray-50 rst:dark:hover:bg-gray-800 rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring"),"aria-label":`${n}. ${a}, in a new tab: ${s}`,children:d}):r.jsx("span",{role:"img","aria-label":`${n}: ${s}`,className:l,children:d})}return r.jsx("img",{...g,src:e,alt:s,className:i.cn(c,"rst:object-cover",o),onError:()=>{b(e),u?.()}})}exports.MediaPreview=h;
@@ -0,0 +1,27 @@
1
+ import { ImgHTMLAttributes } from 'react';
2
+ export interface MediaPreviewProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt" | "onError"> {
3
+ /** The thumbnail. Missing or failing to load, the preview falls back. */
4
+ src?: string | null;
5
+ /** The original, opened from the fallback. Without it the fallback says so. */
6
+ href?: string | null;
7
+ /** What the image shows. Required: a preview in a queue is content. */
8
+ alt: string;
9
+ /** What the fallback says before "Open original". */
10
+ unavailableLabel?: string;
11
+ /** The link's own words. */
12
+ openLabel?: string;
13
+ /** Called once when the thumbnail fails, for an app that keeps a count. */
14
+ onUnavailable?: () => void;
15
+ }
16
+ /**
17
+ * A thumbnail that admits when it can't render.
18
+ *
19
+ * Previews fail in ordinary ways: a short-lived signed link that expired, or a
20
+ * format the browser can't draw, such as a photo straight off a phone. The
21
+ * browser's answer is a broken-image glyph, which tells a reviewer nothing
22
+ * and hides the one thing they may need to see before deciding. This swaps it
23
+ * for a card that says the preview is unavailable and links to the original,
24
+ * in the same footprint, so a row of previews doesn't jump.
25
+ */
26
+ declare function MediaPreview({ src, href, alt, unavailableLabel, openLabel, onUnavailable, className, ...props }: MediaPreviewProps): import("react/jsx-runtime").JSX.Element;
27
+ export { MediaPreview };
@@ -0,0 +1,55 @@
1
+ "use client";
2
+ import { jsx as r, jsxs as m, Fragment as x } from "react/jsx-runtime";
3
+ import { useState as p } from "react";
4
+ import { cn as i } from "../../../lib/utils.js";
5
+ function $({
6
+ src: e,
7
+ href: t,
8
+ alt: s,
9
+ unavailableLabel: n = "Preview unavailable",
10
+ openLabel: o = "Open original",
11
+ onUnavailable: g,
12
+ className: a,
13
+ ...u
14
+ }) {
15
+ const [f, b] = p(null), h = !e || f === e, d = "rst:font-ui rst:h-28 rst:w-36 rst:shrink-0 rst:rounded-md rst:border rst:border-[var(--roster-card-border)]";
16
+ if (h) {
17
+ const l = /* @__PURE__ */ m(x, { children: [
18
+ /* @__PURE__ */ r("span", { children: n }),
19
+ t ? /* @__PURE__ */ m("span", { className: "rst:font-semibold rst:underline", children: [
20
+ o,
21
+ /* @__PURE__ */ r("span", { "aria-hidden": "true", children: " ↗" })
22
+ ] }) : null
23
+ ] }), c = i(
24
+ d,
25
+ "rst:flex rst:flex-col rst:items-center rst:justify-center rst:gap-1 rst:border-dashed rst:px-2 rst:text-center rst:text-xs rst:text-gray-600 rst:dark:text-gray-300",
26
+ a
27
+ );
28
+ return t ? /* @__PURE__ */ r(
29
+ "a",
30
+ {
31
+ href: t,
32
+ target: "_blank",
33
+ rel: "noreferrer",
34
+ className: i(c, "rst:hover:bg-gray-50 rst:dark:hover:bg-gray-800 rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring"),
35
+ "aria-label": `${n}. ${o}, in a new tab: ${s}`,
36
+ children: l
37
+ }
38
+ ) : /* @__PURE__ */ r("span", { role: "img", "aria-label": `${n}: ${s}`, className: c, children: l });
39
+ }
40
+ return /* @__PURE__ */ r(
41
+ "img",
42
+ {
43
+ ...u,
44
+ src: e,
45
+ alt: s,
46
+ className: i(d, "rst:object-cover", a),
47
+ onError: () => {
48
+ b(e), g?.();
49
+ }
50
+ }
51
+ );
52
+ }
53
+ export {
54
+ $ as MediaPreview
55
+ };
@@ -1 +1 @@
1
- "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),t=require("react"),y=require("../../../node_modules/@fortawesome/react-fontawesome/dist/index.cjs"),h=require("../../../node_modules/@fortawesome/free-solid-svg-icons/index.cjs"),o=require("../../../node_modules/class-variance-authority/dist/index.cjs"),s=require("../../../lib/utils.cjs"),k=require("../../../internal/page-inert.cjs"),a=require("../../../node_modules/@headlessui/react/dist/components/dialog/dialog.cjs"),d=require("../../../node_modules/@headlessui/react/dist/components/transition/transition.cjs"),j=o.cva("rst:relative rst:w-full rst:transform rst:overflow-hidden rst:rounded-2xl rst:p-6 rst:text-left rst:align-middle rst:elevation-overlay rst:transition-all rst:border",{variants:{size:{xs:"rst:max-w-xs",sm:"rst:max-w-sm",md:"rst:max-w-md",lg:"rst:max-w-lg",xl:"rst:max-w-xl","2xl":"rst:max-w-2xl","3xl":"rst:max-w-3xl",full:"rst:max-w-[95vw] rst:m-4"},variant:{white:"rst:bg-[var(--roster-popover-bg)] rst:border-[var(--roster-popover-border)] rst:text-[var(--roster-popover-text)]",slate:"rst:bg-gray-700 rst:border-gray-600 rst:text-gray-100 rst:dark:bg-gray-900 rst:dark:border-gray-800 rst:[--roster-ring:var(--roster-primary-400,#5ea3de)] rst:[--roster-ring-offset:var(--roster-gray-700,#44403c)] rst:dark:[--roster-ring-offset:var(--roster-gray-900,#1c1917)] rst:[--roster-control-text:var(--roster-gray-100,#f5f5f4)] rst:[--roster-control-border:var(--roster-gray-400,#a8a29e)] rst:[--roster-control-border-focus:var(--roster-primary-400,#5ea3de)]",primary:"rst:bg-primary-700 rst:border-primary-600 rst:text-primary-700-ink rst:dark:text-white rst:dark:bg-primary-950 rst:dark:border-primary-900 rst:[--roster-ring:var(--roster-primary-400,#5ea3de)] rst:[--roster-ring-offset:var(--roster-primary-700,#084063)] rst:dark:[--roster-ring-offset:var(--roster-primary-950,#021724)] rst:[--roster-control-text:var(--roster-gray-100,#f5f5f4)] rst:[--roster-control-border:var(--roster-gray-400,#a8a29e)] rst:[--roster-control-border-focus:var(--roster-primary-400,#5ea3de)]",glass:"rst:bg-white/80 rst:border-white/20 rst:backdrop-blur-xl rst:text-gray-900 rst:dark:bg-slate-900/80 rst:dark:border-slate-700/50 rst:dark:text-white"},status:{default:"",destructive:"rst:border-t-4 rst:border-t-error-500",success:"rst:border-t-4 rst:border-t-success-500"}},defaultVariants:{size:"md",variant:"white",status:"default"}}),w=o.cva("rst:text-xl rst:font-bold rst:leading-6 rst:text-inherit"),N=o.cva("rst:mt-1 rst:text-sm rst:text-inherit rst:opacity-75"),_=o.cva("rst:inline-flex rst:size-11 rst:shrink-0 rst:items-center rst:justify-center rst:-m-3 rst:cursor-pointer rst:rounded-md rst:bg-transparent rst:text-inherit rst:opacity-50 rst:hover:opacity-100 rst:focus:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-offset-2 rst:ring-offset-background rst:transition-opacity"),q=({isOpen:u,onClose:i,title:m,description:n,size:x,variant:l,status:g,children:b,className:f})=>{const[v,p]=t.useState(!1);t.useLayoutEffect(()=>{p(!0)},[]);const e=u&&v,c=t.useRef(null);return t.useLayoutEffect(()=>{if(e)return k.lockPage(c.current)},[e]),r.jsxs(r.Fragment,{children:[e&&r.jsx("span",{ref:c,hidden:!0}),r.jsxs(a.Dialog,{open:e,as:"div",className:"rst:font-ui rst:relative rst:z-50",onClose:i,children:[r.jsx(d.TransitionChild,{as:t.Fragment,enter:"rst:ease-out rst:duration-300",enterFrom:"rst:opacity-0",enterTo:"rst:opacity-100",leave:"rst:ease-in rst:duration-200",leaveFrom:"rst:opacity-100",leaveTo:"rst:opacity-0",children:r.jsx(a.DialogBackdrop,{className:s.cn("rst:fixed rst:inset-0 rst:transition-opacity",l==="glass"?"rst:bg-[var(--roster-backdrop,color-mix(in_oklab,oklch(20.8%_0.042_265.755)_40%,transparent))] rst:dark:bg-[var(--roster-backdrop,color-mix(in_oklab,var(--roster-black,#1e1c1a)_60%,transparent))]":"rst:bg-[var(--roster-backdrop,color-mix(in_oklab,oklch(20.8%_0.042_265.755)_60%,transparent))] rst:dark:bg-[var(--roster-backdrop,color-mix(in_oklab,var(--roster-black,#1e1c1a)_80%,transparent))] rst:backdrop-blur-backdrop")})}),r.jsx("div",{className:"rst:fixed rst:inset-0 rst:overflow-y-auto",children:r.jsx("div",{className:"rst:flex rst:min-h-full rst:items-center rst:justify-center rst:p-4 rst:text-center",children:r.jsx(d.TransitionChild,{as:t.Fragment,enter:"rst:ease-out rst:duration-300",enterFrom:"rst:opacity-0 rst:scale-95",enterTo:"rst:opacity-100 rst:scale-100",leave:"rst:ease-in rst:duration-200",leaveFrom:"rst:opacity-100 rst:scale-100",leaveTo:"rst:opacity-0 rst:scale-95",children:r.jsxs(a.DialogPanel,{className:s.cn(j({size:x,variant:l,status:g}),f),children:[r.jsxs("div",{className:"rst:flex rst:items-start rst:justify-between",children:[r.jsxs("div",{children:[r.jsx(a.DialogTitle,{as:"h2",className:s.cn(w()),children:m}),n&&r.jsx("p",{className:s.cn(N()),children:n})]}),r.jsx("div",{className:"rst:ml-4 rst:flex rst:shrink-0",children:r.jsx("button",{type:"button",onClick:i,className:s.cn(_()),"aria-label":"Close dialog",children:r.jsx(y.FontAwesomeIcon,{icon:h.faXmark,className:"rst:h-5 rst:w-5"})})})]}),r.jsx("div",{className:"rst:mt-6",children:b})]})})})})]})]})};exports.Dialog=q;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),t=require("react"),h=require("../../../node_modules/@fortawesome/react-fontawesome/dist/index.cjs"),k=require("../../../node_modules/@fortawesome/free-solid-svg-icons/index.cjs"),o=require("../../../node_modules/class-variance-authority/dist/index.cjs"),s=require("../../../lib/utils.cjs"),j=require("../../../internal/page-inert.cjs"),a=require("../../../node_modules/@headlessui/react/dist/components/dialog/dialog.cjs"),d=require("../../../node_modules/@headlessui/react/dist/components/transition/transition.cjs"),w=o.cva("rst:relative rst:w-full rst:transform rst:overflow-hidden rst:rounded-2xl rst:p-6 rst:text-left rst:align-middle rst:elevation-overlay rst:transition-all rst:border",{variants:{size:{xs:"rst:max-w-xs",sm:"rst:max-w-sm",md:"rst:max-w-md",lg:"rst:max-w-lg",xl:"rst:max-w-xl","2xl":"rst:max-w-2xl","3xl":"rst:max-w-3xl",full:"rst:max-w-[95vw] rst:m-4"},variant:{white:"rst:bg-[var(--roster-popover-bg)] rst:border-[var(--roster-popover-border)] rst:text-[var(--roster-popover-text)]",slate:"rst:bg-gray-700 rst:border-gray-600 rst:text-gray-100 rst:dark:bg-gray-900 rst:dark:border-gray-800 rst:[--roster-ring:var(--roster-primary-400,#5ea3de)] rst:[--roster-ring-offset:var(--roster-gray-700,#44403c)] rst:dark:[--roster-ring-offset:var(--roster-gray-900,#1c1917)] rst:[--roster-control-text:var(--roster-gray-100,#f5f5f4)] rst:[--roster-control-border:var(--roster-gray-400,#a8a29e)] rst:[--roster-control-border-focus:var(--roster-primary-400,#5ea3de)]",primary:"rst:bg-primary-700 rst:border-primary-600 rst:text-primary-700-ink rst:dark:text-white rst:dark:bg-primary-950 rst:dark:border-primary-900 rst:[--roster-ring:var(--roster-primary-400,#5ea3de)] rst:[--roster-ring-offset:var(--roster-primary-700,#084063)] rst:dark:[--roster-ring-offset:var(--roster-primary-950,#021724)] rst:[--roster-control-text:var(--roster-gray-100,#f5f5f4)] rst:[--roster-control-border:var(--roster-gray-400,#a8a29e)] rst:[--roster-control-border-focus:var(--roster-primary-400,#5ea3de)]",glass:"rst:bg-white/80 rst:border-white/20 rst:backdrop-blur-xl rst:text-gray-900 rst:dark:bg-slate-900/80 rst:dark:border-slate-700/50 rst:dark:text-white"},status:{default:"",destructive:"rst:border-t-4 rst:border-t-error-500",success:"rst:border-t-4 rst:border-t-success-500"}},defaultVariants:{size:"md",variant:"white",status:"default"}}),N=o.cva("rst:text-xl rst:font-bold rst:leading-6 rst:text-inherit"),_=o.cva("rst:mt-1 rst:text-sm rst:text-inherit rst:opacity-75"),q=o.cva("rst:inline-flex rst:size-11 rst:shrink-0 rst:items-center rst:justify-center rst:-m-3 rst:cursor-pointer rst:rounded-md rst:bg-transparent rst:text-inherit rst:opacity-50 rst:hover:opacity-100 rst:focus:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-offset-2 rst:ring-offset-background rst:transition-opacity"),F=({isOpen:u,onClose:i,title:m,description:n,size:x,variant:l,status:g,children:f,className:v,onAfterClose:b})=>{const[p,y]=t.useState(!1);t.useLayoutEffect(()=>{y(!0)},[]);const e=u&&p,c=t.useRef(null);return t.useLayoutEffect(()=>{if(e)return j.lockPage(c.current)},[e]),r.jsxs(r.Fragment,{children:[e&&r.jsx("span",{ref:c,hidden:!0}),r.jsxs(a.Dialog,{open:e,as:"div",className:"rst:font-ui rst:relative rst:z-50",onClose:i,children:[r.jsx(d.TransitionChild,{as:t.Fragment,enter:"rst:ease-out rst:duration-300",enterFrom:"rst:opacity-0",enterTo:"rst:opacity-100",leave:"rst:ease-in rst:duration-200",leaveFrom:"rst:opacity-100",leaveTo:"rst:opacity-0",children:r.jsx(a.DialogBackdrop,{className:s.cn("rst:fixed rst:inset-0 rst:transition-opacity",l==="glass"?"rst:bg-[var(--roster-backdrop,color-mix(in_oklab,oklch(20.8%_0.042_265.755)_40%,transparent))] rst:dark:bg-[var(--roster-backdrop,color-mix(in_oklab,var(--roster-black,#1e1c1a)_60%,transparent))]":"rst:bg-[var(--roster-backdrop,color-mix(in_oklab,oklch(20.8%_0.042_265.755)_60%,transparent))] rst:dark:bg-[var(--roster-backdrop,color-mix(in_oklab,var(--roster-black,#1e1c1a)_80%,transparent))] rst:backdrop-blur-backdrop")})}),r.jsx("div",{className:"rst:fixed rst:inset-0 rst:overflow-y-auto",children:r.jsx("div",{className:"rst:flex rst:min-h-full rst:items-center rst:justify-center rst:p-4 rst:text-center",children:r.jsx(d.TransitionChild,{as:t.Fragment,enter:"rst:ease-out rst:duration-300",enterFrom:"rst:opacity-0 rst:scale-95",enterTo:"rst:opacity-100 rst:scale-100",leave:"rst:ease-in rst:duration-200",leaveFrom:"rst:opacity-100 rst:scale-100",leaveTo:"rst:opacity-0 rst:scale-95",afterLeave:b,children:r.jsxs(a.DialogPanel,{className:s.cn(w({size:x,variant:l,status:g}),v),children:[r.jsxs("div",{className:"rst:flex rst:items-start rst:justify-between",children:[r.jsxs("div",{children:[r.jsx(a.DialogTitle,{as:"h2",className:s.cn(N()),children:m}),n&&r.jsx("p",{className:s.cn(_()),children:n})]}),r.jsx("div",{className:"rst:ml-4 rst:flex rst:shrink-0",children:r.jsx("button",{type:"button",onClick:i,className:s.cn(q()),"aria-label":"Close dialog",children:r.jsx(h.FontAwesomeIcon,{icon:k.faXmark,className:"rst:h-5 rst:w-5"})})})]}),r.jsx("div",{className:"rst:mt-6",children:f})]})})})})]})]})};exports.Dialog=F;
@@ -12,6 +12,14 @@ export interface DialogProps extends VariantProps<typeof dialogVariants> {
12
12
  description?: string;
13
13
  children: ReactNode;
14
14
  className?: string;
15
+ /**
16
+ * Called once the dialog has finished closing, after its leave transition.
17
+ * The place to move focus somewhere other than where it was before the
18
+ * dialog opened, for instance when that element no longer exists. Not
19
+ * called for a close cut short by reopening, nor when the dialog unmounts
20
+ * while open.
21
+ */
22
+ onAfterClose?: () => void;
15
23
  }
16
- declare const Dialog: ({ isOpen, onClose, title, description, size, variant, status, children, className, }: DialogProps) => import("react/jsx-runtime").JSX.Element;
24
+ declare const Dialog: ({ isOpen, onClose, title, description, size, variant, status, children, className, onAfterClose, }: DialogProps) => import("react/jsx-runtime").JSX.Element;
17
25
  export { Dialog };
@@ -1,14 +1,14 @@
1
1
  "use client";
2
- import { jsxs as t, Fragment as h, jsx as r } from "react/jsx-runtime";
3
- import { useState as k, useLayoutEffect as c, useRef as w, Fragment as d } from "react";
4
- import { FontAwesomeIcon as N } from "../../../node_modules/@fortawesome/react-fontawesome/dist/index.js";
5
- import { faXmark as _ } from "../../../node_modules/@fortawesome/free-solid-svg-icons/index.js";
2
+ import { jsxs as t, Fragment as k, jsx as r } from "react/jsx-runtime";
3
+ import { useState as w, useLayoutEffect as c, useRef as N, Fragment as d } from "react";
4
+ import { FontAwesomeIcon as _ } from "../../../node_modules/@fortawesome/react-fontawesome/dist/index.js";
5
+ import { faXmark as F } from "../../../node_modules/@fortawesome/free-solid-svg-icons/index.js";
6
6
  import { cva as a } from "../../../node_modules/class-variance-authority/dist/index.js";
7
7
  import { cn as s } from "../../../lib/utils.js";
8
- import { lockPage as F } from "../../../internal/page-inert.js";
9
- import { Dialog as T, DialogBackdrop as j, DialogPanel as z, DialogTitle as D } from "../../../node_modules/@headlessui/react/dist/components/dialog/dialog.js";
8
+ import { lockPage as T } from "../../../internal/page-inert.js";
9
+ import { Dialog as j, DialogBackdrop as z, DialogPanel as D, DialogTitle as V } from "../../../node_modules/@headlessui/react/dist/components/dialog/dialog.js";
10
10
  import { TransitionChild as m } from "../../../node_modules/@headlessui/react/dist/components/transition/transition.js";
11
- const V = /* @__PURE__ */ a(
11
+ const L = /* @__PURE__ */ a(
12
12
  "rst:relative rst:w-full rst:transform rst:overflow-hidden rst:rounded-2xl rst:p-6 rst:text-left rst:align-middle rst:elevation-overlay rst:transition-all rst:border",
13
13
  {
14
14
  variants: {
@@ -77,9 +77,9 @@ const V = /* @__PURE__ */ a(
77
77
  status: "default"
78
78
  }
79
79
  }
80
- ), C = /* @__PURE__ */ a("rst:text-xl rst:font-bold rst:leading-6 rst:text-inherit"), L = /* @__PURE__ */ a("rst:mt-1 rst:text-sm rst:text-inherit rst:opacity-75"), P = /* @__PURE__ */ a(
80
+ ), C = /* @__PURE__ */ a("rst:text-xl rst:font-bold rst:leading-6 rst:text-inherit"), P = /* @__PURE__ */ a("rst:mt-1 rst:text-sm rst:text-inherit rst:opacity-75"), R = /* @__PURE__ */ a(
81
81
  "rst:inline-flex rst:size-11 rst:shrink-0 rst:items-center rst:justify-center rst:-m-3 rst:cursor-pointer rst:rounded-md rst:bg-transparent rst:text-inherit rst:opacity-50 rst:hover:opacity-100 rst:focus:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-offset-2 rst:ring-offset-background rst:transition-opacity"
82
- ), X = ({
82
+ ), $ = ({
83
83
  isOpen: f,
84
84
  onClose: o,
85
85
  title: p,
@@ -88,20 +88,21 @@ const V = /* @__PURE__ */ a(
88
88
  variant: n,
89
89
  status: g,
90
90
  children: v,
91
- className: u
91
+ className: u,
92
+ onAfterClose: x
92
93
  }) => {
93
- const [x, y] = k(!1);
94
+ const [y, h] = w(!1);
94
95
  c(() => {
95
- y(!0);
96
+ h(!0);
96
97
  }, []);
97
- const e = f && x, l = w(null);
98
+ const e = f && y, l = N(null);
98
99
  return c(() => {
99
100
  if (e)
100
- return F(l.current);
101
- }, [e]), /* @__PURE__ */ t(h, { children: [
101
+ return T(l.current);
102
+ }, [e]), /* @__PURE__ */ t(k, { children: [
102
103
  e && /* @__PURE__ */ r("span", { ref: l, hidden: !0 }),
103
104
  /* @__PURE__ */ t(
104
- T,
105
+ j,
105
106
  {
106
107
  open: e,
107
108
  as: "div",
@@ -119,7 +120,7 @@ const V = /* @__PURE__ */ a(
119
120
  leaveFrom: "rst:opacity-100",
120
121
  leaveTo: "rst:opacity-0",
121
122
  children: /* @__PURE__ */ r(
122
- j,
123
+ z,
123
124
  {
124
125
  className: s(
125
126
  "rst:fixed rst:inset-0 rst:transition-opacity",
@@ -139,27 +140,28 @@ const V = /* @__PURE__ */ a(
139
140
  leave: "rst:ease-in rst:duration-200",
140
141
  leaveFrom: "rst:opacity-100 rst:scale-100",
141
142
  leaveTo: "rst:opacity-0 rst:scale-95",
143
+ afterLeave: x,
142
144
  children: /* @__PURE__ */ t(
143
- z,
145
+ D,
144
146
  {
145
147
  className: s(
146
- V({ size: b, variant: n, status: g }),
148
+ L({ size: b, variant: n, status: g }),
147
149
  u
148
150
  ),
149
151
  children: [
150
152
  /* @__PURE__ */ t("div", { className: "rst:flex rst:items-start rst:justify-between", children: [
151
153
  /* @__PURE__ */ t("div", { children: [
152
- /* @__PURE__ */ r(D, { as: "h2", className: s(C()), children: p }),
153
- i && /* @__PURE__ */ r("p", { className: s(L()), children: i })
154
+ /* @__PURE__ */ r(V, { as: "h2", className: s(C()), children: p }),
155
+ i && /* @__PURE__ */ r("p", { className: s(P()), children: i })
154
156
  ] }),
155
157
  /* @__PURE__ */ r("div", { className: "rst:ml-4 rst:flex rst:shrink-0", children: /* @__PURE__ */ r(
156
158
  "button",
157
159
  {
158
160
  type: "button",
159
161
  onClick: o,
160
- className: s(P()),
162
+ className: s(R()),
161
163
  "aria-label": "Close dialog",
162
- children: /* @__PURE__ */ r(N, { icon: _, className: "rst:h-5 rst:w-5" })
164
+ children: /* @__PURE__ */ r(_, { icon: F, className: "rst:h-5 rst:w-5" })
163
165
  }
164
166
  ) })
165
167
  ] }),
@@ -175,5 +177,5 @@ const V = /* @__PURE__ */ a(
175
177
  ] });
176
178
  };
177
179
  export {
178
- X as Dialog
180
+ $ as Dialog
179
181
  };
@@ -0,0 +1 @@
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react/jsx-runtime"),a=require("react"),z=require("../../../lib/utils.cjs"),xe=require("../../atoms/Button/Button.cjs"),Oe=require("../../atoms/Card/Card.cjs"),he=require("../../atoms/Spinner/Spinner.cjs"),Pe=require("../../atoms/Textarea/Textarea.cjs"),Z=require("../../molecules/Alert/Alert.cjs"),ze=require("../../molecules/EmptyState/EmptyState.cjs"),De=require("../../molecules/RadioGroup/RadioGroup.cjs"),He=require("./queue-actions.cjs"),pe={primary:{colorScheme:"primary",variant:"solid"},success:{colorScheme:"success",variant:"solid"},danger:{colorScheme:"error",variant:"soft"},neutral:{colorScheme:"neutral",variant:"outline"}},Ke={edit:He.editAction},R=o=>o.edit?o:null,Qe=o=>o===0?"None left.":`${o} left.`,Ge=o=>Math.min(6,Math.max(1,o)),We=o=>o instanceof Error&&o.message?o.message:"Something went wrong.";function B(o,f){const p={...o};return delete p[f],p}function _({busy:o=!1,blocked:f=!1,onClick:p,children:j,className:v,...y}){const g=o||f;return n.jsx(xe.Button,{size:"lg",...y,"aria-disabled":g||void 0,"aria-busy":o||void 0,startIcon:o?n.jsx(he.Spinner,{size:"sm",variant:"current"}):void 0,className:z.cn(f&&!o&&"rst:cursor-not-allowed rst:opacity-50",v),onClick:T=>{if(g){T.preventDefault();return}p?.(T)},children:j})}function Je({label:o,hideLabel:f=!1,headingLevel:p=2,itemHeadingLevel:j,items:v,getKey:y,itemLabel:g,renderMeta:T,renderContent:ve,actions:D,status:ee="ready",error:be,onRetry:te,empty:re={title:"Nothing waiting"},density:ne="comfortable",footer:ye,className:Se}){const je=typeof D=="function"?D(Ke):D,N=a.useId(),q=a.useRef(null),k=v.map(y),E=a.useRef(new Map);E.current=new Map(v.map((e,t)=>[k[t],e]));const[H,qe]=a.useState({}),$=a.useRef({}),se=e=>{$.current=e($.current),qe($.current)},[oe,F]=a.useState({}),[ce,ke]=a.useState({}),x=a.useRef({}),h=(e,t)=>{x.current=t?{...x.current,[e]:t}:B(x.current,e),ke(x.current)},[K,$e]=a.useState({text:"",n:0}),Q=a.useRef([]),w=a.useRef(null),ie=a.useCallback(e=>{Q.current.push(e),!w.current&&(w.current=setTimeout(()=>{w.current=null;const t=Q.current.join(" ");Q.current=[],$e(r=>({text:t,n:r.n+1}))},0))},[]);a.useLayoutEffect(()=>()=>{w.current&&clearTimeout(w.current)},[]);const[Re,Ne]=a.useState(0),G=a.useRef(null),S=a.useCallback(e=>{G.current=e,Ne(t=>t+1)},[]),ae=a.useCallback(e=>{const t=q.current;if(!t)return null;const r="key"in e?CSS.escape(e.key):"";switch(e.kind){case"item":return t.querySelector(`[data-queue-item="${r}"]`);case"empty":return t.querySelector("[data-queue-empty]");case"trigger":return t.querySelector(`[data-queue-item="${r}"] [data-queue-action="${CSS.escape(e.actionId)}"]`);case"step":return t.querySelector(`[data-queue-item="${r}"] [data-queue-step]`)}},[]);a.useLayoutEffect(()=>{const e=G.current;if(!e)return;G.current=null,(ae(e)??q.current?.querySelector("[data-queue-item]")??q.current?.querySelector("[data-queue-empty]"))?.focus()},[Re,ae]);const W=a.useRef(null),we=e=>()=>{W.current=e},Ce=e=>{e.relatedTarget&&!q.current?.contains(e.relatedTarget)&&(W.current=null)},A=e=>{if(W.current!==e)return!1;const t=document.activeElement;return!t||t===document.body?!0:q.current?.contains(t)?!!t.closest(`[data-queue-item="${CSS.escape(e)}"]`):!1},C=a.useRef(null),M=(e,t)=>{C.current=t,h(e.key,{...e,open:!1})},le=a.useRef(k),J=a.useRef(new Map),[,Ie]=a.useState(0),U=a.useRef(new Set),ue=(e,t,r)=>{const c=t.indexOf(e),d=t.slice(c+1).find(i=>r.has(i));if(d)return{kind:"item",key:d};const s=t.slice(0,c).reverse().find(i=>r.has(i));return s?{kind:"item",key:s}:{kind:"empty"}};a.useLayoutEffect(()=>{const e=le.current;le.current=k;const t=new Set(k),r=J.current;J.current=new Map;const c=[];let d=!1;for(const[s,i]of r){const l=t.has(s);c.push(i.announce??i.sentence),l||(d=!0),U.current.delete(s);const u=l?{kind:"trigger",key:s,actionId:i.actionId}:e.includes(s)?ue(s,e,t):null,m=x.current[s];if(m?.surface)M(m,u??{kind:"empty"});else{const I=A(s);m&&h(s,null),u&&I&&S(u)}}for(const s of e.filter(i=>!t.has(i))){if(r.has(s))continue;F(m=>s in m?B(m,s):m);const i=ue(s,e,t),l=C.current;if(l&&"key"in l&&l.key===s&&(C.current=i),$.current[s]){U.current.add(s),A(s)&&S(i);continue}const u=x.current[s];if(u&&!(u.surface&&!u.open))if(c.push(`${u.label} was decided elsewhere.`),d=!0,u.surface)M(u,i);else{const m=A(s);h(s,null),m&&S(i)}else!u&&A(s)&&S(i)}c.length&&ie(`${c.join(" ")}${d?` ${Qe(k.length)}`:""}`)});const L=async(e,t,r,c)=>{const d=E.current.get(e)??t,s=g(d),i=r.edit?R(r).run:r.run;se(l=>({...l,[e]:r.id})),F(l=>B(l,e));try{const l=await i(d,c);J.current.set(e,{actionId:r.id,sentence:`${r.done??r.label}: ${s}.`,announce:l?l.announce:void 0}),Ie(u=>u+1)}catch(l){const u=We(l);ie(`${r.label} didn't go through for ${s}. ${u}`);const m=x.current[e];U.current.delete(e)?m?.surface?M(m,{kind:"empty"}):m&&h(e,null):m?h(e,{...m,error:u}):F(I=>({...I,[e]:u}))}finally{se(l=>B(l,e))}},Be=(e,t)=>{const r=y(e),c=R(t);if(!t.confirm&&!t.reason&&!c){L(r,e,t,{});return}F(d=>B(d,r)),h(r,{key:r,label:g(e),item:e,action:t,draft:c?c.edit.initial(e):void 0,reason:{},surface:!!c?.edit.surface,open:!0}),c?.edit.surface||S({kind:"step",key:r})},V=e=>{const t=x.current[e];if(!t||$.current[e])return;const r={kind:"trigger",key:e,actionId:t.action.id};t.surface?M(t,r):(h(e,null),S(r))},Te=e=>{const t=x.current[e];!t||$.current[e]||ge(t,E.current.get(e)??t.item)||(t.action.edit?L(e,t.item,t.action,{draft:t.draft}):t.action.reason?L(e,t.item,t.action,{reason:Ue(t.reason)}):L(e,t.item,t.action,{}))},Ee=e=>{const t=C.current;C.current=null,h(e,null),t&&S(t)},Fe=`h${p}`,de=j??Ge(p+1),Ae=`h${de}`,X=`${N}-label`,fe=(e,t)=>{const{action:r}=e,c=R(r),d=E.current.get(e.key)??e.item,s=!!H[e.key],i=ge(e,d),l=`${N}-step-${t}-message`,u=n.jsx(_,{...pe[r.tone??"neutral"],busy:s,blocked:!!i,"aria-describedby":i?l:void 0,onClick:()=>Te(e.key),"data-queue-submit":"",children:c?c.edit.submitLabel:r.label}),m=n.jsx(_,{variant:"ghost",colorScheme:"neutral",blocked:s,onClick:()=>V(e.key),children:"Cancel"}),I=n.jsxs(n.Fragment,{children:[r.reason&&n.jsx(Ve,{id:`${N}-step-${t}`,step:r.reason,value:e.reason,disabled:s,onChange:Y=>h(e.key,{...x.current[e.key],reason:Y})}),c&&n.jsx(c.edit.Form,{item:d,value:e.draft,onChange:Y=>h(e.key,{...x.current[e.key],draft:Y})}),i&&n.jsx("p",{id:l,className:"rst:text-sm rst:text-error-600 rst:dark:text-error-400","data-queue-validation":"",children:i}),e.error&&n.jsx(Z.Alert,{colorScheme:"error",role:"group","aria-live":"off",children:e.error})]});return{submitButton:u,cancelButton:m,fields:I}},Me=(e,t)=>{const{submitButton:r,cancelButton:c,fields:d}=fe(e,t),s=`${N}-step-${t}-prompt`,i=R(e.action),l=e.action.confirm?.prompt??e.action.reason?.prompt??i?.edit.title;return n.jsxs("div",{role:"group","aria-labelledby":s,tabIndex:-1,"data-queue-step":"",onKeyDown:u=>{u.key==="Escape"&&(u.stopPropagation(),V(e.key))},className:"rst:flex rst:flex-col rst:gap-3 rst:rounded-lg rst:border rst:border-[var(--roster-card-border)] rst:p-4 rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring",children:[n.jsx("p",{id:s,className:"rst:text-sm rst:font-medium rst:text-[var(--roster-control-text)]",children:l}),i?.edit.description&&n.jsx("p",{className:"rst:text-sm rst:text-gray-600 rst:dark:text-gray-300",children:i.edit.description}),d,n.jsxs("div",{className:"rst:flex rst:flex-wrap rst:gap-2",children:[r,c]})]})},Le=(e,t)=>{const r=H[t];return n.jsx("div",{role:"group","aria-label":`Decide: ${g(e)}`,className:"rst:flex rst:flex-wrap rst:gap-2",children:je.filter(c=>!c.hidden?.(e)).map(c=>n.jsx(_,{...pe[c.tone??"neutral"],"data-queue-action":c.id,busy:r===c.id,blocked:!!r&&r!==c.id,onClick:()=>Be(e,c),children:c.label},c.id))})},b=Object.values(ce).find(e=>e.surface)??null,O=b?R(b.action):null,me=O?.edit.surface,P=b?fe(b,-1):null;return n.jsxs("section",{ref:q,"aria-labelledby":X,onBlur:Ce,className:z.cn("rst:font-ui rst:flex rst:flex-col rst:gap-4",Se),children:[n.jsx(Fe,{id:X,className:z.cn("rst:text-lg rst:font-semibold rst:text-[var(--roster-control-text)]",f&&"rst:sr-only"),children:o}),n.jsxs("div",{role:"status","aria-live":"polite","aria-atomic":"true",className:"rst:sr-only","data-queue-live":"","data-queue-live-n":K.n,children:[K.text,K.n%2?" ":""]}),ee==="loading"?n.jsx("div",{className:"rst:flex rst:justify-center rst:py-12","aria-busy":"true",children:n.jsx(he.Spinner,{size:"lg",label:`Loading ${o}`})}):ee==="error"?n.jsx(Z.Alert,{colorScheme:"error",children:n.jsxs("div",{className:"rst:flex rst:flex-col rst:items-start rst:gap-3",children:[n.jsx("div",{children:be??"The queue couldn't load."}),te&&n.jsx(xe.Button,{size:"lg",variant:"outline",colorScheme:"neutral",onClick:te,children:"Retry"})]})}):v.length===0?n.jsx(ze.EmptyState,{title:re.title,description:re.description,headingLevel:de,tabIndex:-1,"data-queue-empty":"",className:"rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring"}):n.jsx("ul",{role:"list","aria-labelledby":X,className:"rst:m-0 rst:flex rst:list-none rst:flex-col rst:gap-3 rst:p-0",children:v.map((e,t)=>{const r=k[t],c=`${N}-item-${t}`,d=T?.(e),s=ce[r];return n.jsx("li",{children:n.jsx("article",{"data-queue-item":r,tabIndex:-1,"aria-labelledby":c,onFocus:we(r),className:"rst:rounded-xl rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-offset-2 rst:ring-offset-background",children:n.jsx(Oe.Card,{padding:ne==="compact"?"sm":"md",children:n.jsxs("div",{className:z.cn("rst:flex rst:flex-col",ne==="compact"?"rst:gap-3":"rst:gap-4"),children:[n.jsxs("div",{className:"rst:flex rst:flex-wrap rst:items-baseline rst:gap-x-3 rst:gap-y-1",children:[n.jsx(Ae,{id:c,className:"rst:text-base rst:font-semibold",children:g(e)}),d&&n.jsx("div",{className:"rst:flex rst:flex-wrap rst:items-center rst:gap-2 rst:text-sm rst:text-gray-600 rst:dark:text-gray-300",children:d})]}),n.jsx("div",{className:"rst:min-w-0",children:ve(e)}),oe[r]&&n.jsx(Z.Alert,{colorScheme:"error",role:"group","aria-live":"off",children:oe[r]}),s&&!s.surface?Me(s,t):Le(e,r)]})})})},r)})}),ye,me&&b&&O&&P&&n.jsx(me,{open:b.open,title:O.edit.title,description:O.edit.description,busy:!!H[b.key],onCancel:()=>V(b.key),onAfterClose:()=>Ee(b.key),submitButton:P.submitButton,cancelButton:P.cancelButton,children:n.jsx("div",{className:"rst:flex rst:flex-col rst:gap-4",children:P.fields})})]})}function ge(o,f){const p=R(o.action);if(p)return p.edit.validate?.(o.draft,f);if(o.action.reason){if(o.action.reason.required&&!o.reason.value)return"Pick a reason.";if(o.action.reason.note==="required"&&!o.reason.note?.trim())return"Add a note."}}function Ue(o){const f=o.note?.trim();return{...o.value?{value:o.value}:{},...f?{note:f}:{}}}function Ve({id:o,step:f,value:p,disabled:j,onChange:v}){const y=f.note??"optional";return n.jsxs(n.Fragment,{children:[f.options&&f.options.length>0&&n.jsx(De.RadioGroup,{label:f.required?"Reason":"Reason (optional)",options:f.options,value:p.value??"",onChange:g=>v({...p,value:g}),disabled:j}),y!=="none"&&n.jsx(Pe.Textarea,{id:`${o}-note`,label:f.noteLabel??(y==="required"?"Note":"Note (optional)"),rows:3,value:p.note??"",onChange:g=>v({...p,note:g.target.value}),disabled:j})]})}exports.ModerationQueue=Je;
@@ -0,0 +1,73 @@
1
+ import { ReactNode } from 'react';
2
+ import { QueueAction, QueueActionKit, QueueCheckedEditAction, QueueHeadingLevel } from './queue-actions';
3
+ export interface ModerationQueueProps<T> {
4
+ /** The queue's heading, and the list's accessible name. */
5
+ label: string;
6
+ /** Keep the heading for assistive technology only, when the page already titles the queue. */
7
+ hideLabel?: boolean;
8
+ /** The queue heading's level. `2` by default. */
9
+ headingLevel?: QueueHeadingLevel;
10
+ /** Each item's heading level. One below `headingLevel` by default. */
11
+ itemHeadingLevel?: QueueHeadingLevel;
12
+ /** What's waiting. Remove an item once its decision lands; the queue notices. */
13
+ items: readonly T[];
14
+ getKey: (item: T) => string;
15
+ /** The item's heading, and what announcements call it: "Recipe from Dana". */
16
+ itemLabel: (item: T) => string;
17
+ /** Beside the heading: badges, a time, who sent it. */
18
+ renderMeta?: (item: T) => ReactNode;
19
+ /** The item's body: its text, its previews, links out. */
20
+ renderContent: (item: T) => ReactNode;
21
+ /**
22
+ * What a reviewer can decide. A list, or a function returning one: the
23
+ * function form is handed `edit`, which types an edit action's draft, and
24
+ * is the one to use inline in JSX.
25
+ *
26
+ * actions={(a) => [
27
+ * { id: "approve", label: "Approve", run: (r) => approve(r.id) },
28
+ * a.edit({ id: "edit", label: "Edit", edit: { initial: (r) => ({ … }), … },
29
+ * run: (r, { draft }) => save(r.id, draft) }),
30
+ * ]}
31
+ */
32
+ actions: readonly QueueAction<T>[] | ((kit: QueueActionKit<T>) => readonly QueueAction<T>[]);
33
+ /** `"loading"` before the first answer, `"error"` when the queue itself can't load. */
34
+ status?: "ready" | "loading" | "error";
35
+ /** The whole-queue error, shown with a Retry when `onRetry` is passed. */
36
+ error?: ReactNode;
37
+ onRetry?: () => void;
38
+ empty?: {
39
+ title: string;
40
+ description?: string;
41
+ };
42
+ density?: "comfortable" | "compact";
43
+ /** After the list: "Load more", if the app pages. */
44
+ footer?: ReactNode;
45
+ className?: string;
46
+ }
47
+ /**
48
+ * A queue of things waiting for a decision: submissions to approve, reports to
49
+ * resolve, claims to check before they publish.
50
+ *
51
+ * The app owns the data and every request; this owns the layout, the states,
52
+ * the steps and the keyboard. It never fetches. An action's `run` is the only
53
+ * path from a decision to the network, and the app says whether the item has
54
+ * left by removing it from `items`.
55
+ *
56
+ * What it promises, each pinned in ModerationQueue.checks.stories.tsx:
57
+ * - Only the clicked action is busy, and it keeps focus while it is. That
58
+ * item's other actions wait; every other item stays live.
59
+ * - A decision that lands and removes the item is announced with how many
60
+ * are left, and focus moves to the next item, the previous one, or the
61
+ * empty state. Never to the page.
62
+ * - A decision that fails leaves the item where it was, says why, and leaves
63
+ * focus on the button that tried. A step stays open with its draft.
64
+ * - Confirm, reason and edit steps open in place (an edit may open in a
65
+ * Dialog or Sheet instead), take focus, and give it back on Escape.
66
+ * - An item that disappears for any other reason, with its step open,
67
+ * closes the step without deciding and is announced once as decided
68
+ * elsewhere. Focus moves only if it was inside the item.
69
+ * - Several outcomes in one render are said in one sentence.
70
+ */
71
+ declare function ModerationQueue<T>({ label, hideLabel, headingLevel, itemHeadingLevel, items, getKey, itemLabel, renderMeta, renderContent, actions: actionsProp, status, error, onRetry, empty, density, footer, className, }: ModerationQueueProps<T>): import("react/jsx-runtime").JSX.Element;
72
+ export type { QueueCheckedEditAction };
73
+ export { ModerationQueue };