@blakesteve/roster 5.4.0 → 5.6.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 (34) hide show
  1. package/README.md +170 -1
  2. package/dist/components/atoms/Button/Button.cjs +1 -1
  3. package/dist/components/atoms/Button/Button.d.ts +11 -0
  4. package/dist/components/atoms/Button/Button.js +42 -32
  5. package/dist/components/atoms/Button/button-variants.cjs +1 -1
  6. package/dist/components/atoms/Button/button-variants.js +3 -1
  7. package/dist/components/molecules/FileUpload/FileUpload.cjs +1 -0
  8. package/dist/components/molecules/FileUpload/FileUpload.d.ts +111 -0
  9. package/dist/components/molecules/FileUpload/FileUpload.js +403 -0
  10. package/dist/components/molecules/FileUpload/file-rules.cjs +1 -0
  11. package/dist/components/molecules/FileUpload/file-rules.d.ts +22 -0
  12. package/dist/components/molecules/FileUpload/file-rules.js +43 -0
  13. package/dist/components/molecules/Pagination/Pagination.cjs +1 -0
  14. package/dist/components/molecules/Pagination/Pagination.d.ts +116 -0
  15. package/dist/components/molecules/Pagination/Pagination.js +247 -0
  16. package/dist/components/molecules/Pagination/page-items.cjs +1 -0
  17. package/dist/components/molecules/Pagination/page-items.d.ts +13 -0
  18. package/dist/components/molecules/Pagination/page-items.js +11 -0
  19. package/dist/components/organisms/DataTable/DataTable.cjs +1 -1
  20. package/dist/components/organisms/DataTable/DataTable.d.ts +8 -1
  21. package/dist/components/organisms/DataTable/DataTable.js +61 -98
  22. package/dist/components/organisms/ModerationQueue/ModerationQueue.cjs +1 -1
  23. package/dist/components/organisms/ModerationQueue/ModerationQueue.js +190 -192
  24. package/dist/components/organisms/Navbar/Navbar.cjs +1 -1
  25. package/dist/components/organisms/Navbar/Navbar.d.ts +2 -0
  26. package/dist/components/organisms/Navbar/Navbar.js +49 -49
  27. package/dist/index.d.ts +2 -0
  28. package/dist/meta.json +3 -3
  29. package/dist/node_modules/@fortawesome/free-solid-svg-icons/index.cjs +1 -1
  30. package/dist/node_modules/@fortawesome/free-solid-svg-icons/index.js +30 -50
  31. package/dist/roster.cjs +1 -1
  32. package/dist/roster.css +1 -1
  33. package/dist/roster.js +102 -98
  34. package/package.json +4 -3
package/README.md CHANGED
@@ -1289,6 +1289,173 @@ Counts for a tab badge need no API: the app holds the items, so it's
1289
1289
  image can't render (an expired link, a format the browser can't draw), it
1290
1290
  falls back to a link to the original in the same footprint.
1291
1291
 
1292
+ ### FileUpload
1293
+
1294
+ Picks files, uploads each one through the app, and shows where each stands.
1295
+ **It never sends anything itself.** The app passes `upload`, which is called
1296
+ once per file and returns a promise; FileUpload owns picking, the rules, the
1297
+ list, each file's state, the keyboard and what a screen reader hears.
1298
+
1299
+ ```tsx
1300
+ import { FileUpload } from "@blakesteve/roster";
1301
+
1302
+ <FileUpload
1303
+ label="Photos"
1304
+ helperText="JPEG or PNG, up to 10 photos, 15 MB each."
1305
+ types={["image/jpeg", "image/png"]}
1306
+ maxFiles={10}
1307
+ maxSize={15 * 1024 * 1024}
1308
+ validate={(file) => (/\.hei[cf]$/i.test(file.name) ? "Export it as JPEG first." : undefined)}
1309
+ upload={async (file, { onProgress, onProcessing, signal }) => {
1310
+ const { id, url } = await api.presignPhoto(file.type, { signal });
1311
+ await putWithProgress(url, file, { onProgress, signal });
1312
+ onProcessing();
1313
+ await api.completePhoto(id, { signal });
1314
+ return id;
1315
+ }}
1316
+ onChange={(items) => setPhotoIds(items.filter((i) => i.status === "done").map((i) => i.result!))}
1317
+ />;
1318
+ ```
1319
+
1320
+ `upload(file, { onProgress, onProcessing, signal })`:
1321
+
1322
+ - **`onProgress(fraction)`** from 0 to 1, if the request can report it
1323
+ (`fetch` can't; an `XMLHttpRequest`'s `upload.onprogress` can). Without it the
1324
+ file reads Uploading with a bar that doesn't give a figure.
1325
+ - **`onProcessing()`** once the bytes are up and the app's server is working on
1326
+ them. The file reads Finishing up until the promise settles.
1327
+ - **`signal`** aborts when the person removes the file, or the field unmounts.
1328
+ Pass it to the request.
1329
+ - **Resolve** with what the form needs later, an id say; it comes back as that
1330
+ file's `result` in `onChange`. **Reject** with an Error whose message says
1331
+ why; it's shown, with Try again.
1332
+
1333
+ Each file is `queued`, `uploading`, `processing`, `done`, `failed` or
1334
+ `rejected`. A few upload at once (`concurrency`, 3 by default) and the rest
1335
+ wait.
1336
+
1337
+ **Rules** are checked as files arrive, picked or dropped: the same file picked
1338
+ twice is turned away first; then `validate(file)`, the app's own reason; then
1339
+ `types` (`image/jpeg`, `image/*`, `.png`,
1340
+ defaulting to `accept`); then `maxSize` in bytes; then `maxFiles`. A file
1341
+ turned away stays in the list with its reason and is never sent. A file that
1342
+ arrives with no type, as some cloud-backed picks on Android do, is judged by
1343
+ its extension.
1344
+
1345
+ **Phones first.** `accept` defaults to `"image/*"`, with `multiple` and no
1346
+ `capture`. That combination is what opens Android's system photo picker, with
1347
+ albums and cloud photos; a list of exact types or an extension gets a file
1348
+ browser instead. Keep `accept` broad and narrow with `types`.
1349
+
1350
+ What it promises:
1351
+
1352
+ - **Disabled, it keeps focus.** Its buttons are `aria-disabled` and ignore
1353
+ presses, so a form that disables the field while it sends doesn't drop
1354
+ whoever was on Remove to the top of the page.
1355
+ - **Choosing is a button** that opens the picker. Dragging files onto the
1356
+ field is an addition where there's a pointer, and the hint for it shows only
1357
+ there. A file let go over the field never replaces the page, even while
1358
+ disabled.
1359
+ - **Removing a file cancels its upload** and moves focus to the next file's
1360
+ Remove, the previous one's, or the Choose button. Never to the page. Try
1361
+ again moves focus to that file's Remove.
1362
+ - **A polite live region** says what happened, one sentence per moment: "3
1363
+ photos added. big.jpg wasn't added. It's over 15 MB.", "2 of 3 uploaded.",
1364
+ "c.jpg didn't upload. The server is busy." Of several counts at once, only
1365
+ the latest is said.
1366
+ - **Thumbnails** come from object URLs, revoked when the file goes. A picture
1367
+ the browser can't draw (HEIC outside Safari) shows a plain mark.
1368
+ - **Every word is replaceable** through `strings`, on screen and spoken.
1369
+ - **Phones.** Every button is a 44px target at 375px, and a long file name
1370
+ truncates rather than pushing Remove off its row.
1371
+
1372
+ ### Buttons that load
1373
+
1374
+ `isLoading` shows a spinner and ignores presses, and since 5.5.0 the button
1375
+ keeps focus while it does. It used to set `disabled`, and a browser drops focus
1376
+ from a disabled element to the page, so a keyboard or screen reader user who
1377
+ pressed Save was sent back to the top. A loading button is now `aria-disabled`
1378
+ and `aria-busy` instead, and ignores presses itself (clicks, Enter, Space, and the
1379
+ app's own press handlers, while hover ones still run), which also keeps a
1380
+ submit button from sending its form twice. It no longer dims, since
1381
+ the spinner says it's busy. `loadingLabel` names the spinner ("loading" by
1382
+ default). `disabled` is unchanged and wins over `isLoading`.
1383
+
1384
+ ### Pagination
1385
+
1386
+ Moves through pages of a list, built for a long list browsed on a phone.
1387
+ Controlled: you hold `page` and `pageCount`.
1388
+
1389
+ ```tsx
1390
+ import { Pagination } from "@blakesteve/roster";
1391
+
1392
+ // Pages in your own state: every control is a button.
1393
+ <Pagination page={page} pageCount={79} onPageChange={setPage} />
1394
+
1395
+ // Pages that are URLs: every control is a real link.
1396
+ <Pagination
1397
+ page={page}
1398
+ pageCount={79}
1399
+ getPageHref={(p) => `/catalog?page=${p}`}
1400
+ linkComponent={NextLink} // from a "use client" wrapper
1401
+ navigate={router.push} // for the compact form's page picker
1402
+ />
1403
+ ```
1404
+
1405
+ **Buttons or links.** Without `getPageHref`, controls are buttons and
1406
+ `onPageChange(page)` is the only thing that changes the page. With it, every
1407
+ control is an `<a href>` (crawlable, shareable, openable in a new tab) rendered
1408
+ through `linkComponent` (a plain `<a>` by default; it gets `href` and `to`, so
1409
+ `next/link` and React Router's `Link` both work). `onPageChange` is then called
1410
+ on a plain click before the link is followed. The compact form's page picker
1411
+ has no link to follow, so it calls `navigate(href)`, or loads the page.
1412
+
1413
+ **Two forms, chosen by the pager's own width** (a container query, so it's
1414
+ right in a sidebar and needs no measuring):
1415
+
1416
+ - **448px and up:** previous, the first page, the current page with a
1417
+ neighbor each side, the last page, gaps between, and next. That's always
1418
+ seven equal slots, so the buttons don't shift under the pointer as you page.
1419
+ The words "Previous" and "Next" join the arrows from 576px.
1420
+ - **Under 448px:** previous, a page picker ("Page 40 of 79"), and next, with
1421
+ the first and last pages joining from 384px. Any page is two taps away, and
1422
+ on a phone the picker is the system's own list.
1423
+
1424
+ `layout` picks one form (`"pages"`, `"compact"`), or `"arrows"` for first,
1425
+ previous, next and last beside a count the app shows itself, which is what
1426
+ DataTable uses.
1427
+
1428
+ What it promises:
1429
+
1430
+ - **Every control is a 44px target,** at every width.
1431
+ - **The current page is `aria-current="page"`,** the pager is a `nav` named by
1432
+ `labels.nav`, and every control's name says the page it goes to ("Next page
1433
+ (41)"). A new page is said in a polite live region ("Page 41 of 79").
1434
+ - **Focus stays on the control that was pressed.** A page number is the same
1435
+ element afterwards, and an arrow that reaches the end stays focusable,
1436
+ `aria-disabled` rather than `disabled`. To take a reader to the top of the
1437
+ new page, focus your list's heading in `onPageChange`.
1438
+ - **Every word is a `labels` entry,** page numbers included (`labels.number`).
1439
+ - **No Font Awesome:** the chevrons are inline SVG.
1440
+ - **The picker waits for the keyboard.** A pick from its list goes at once.
1441
+ Arrow keys on the closed picker (which step it on Windows and Linux) hold the
1442
+ choice until Enter or leaving it; Escape puts it back.
1443
+
1444
+ Things to know:
1445
+
1446
+ - **Give the pager a width.** It sizes its form by the width it's given, so in
1447
+ a slot that sizes to its content (an `ml-auto` box beside a heading, an `auto`
1448
+ grid column) it falls back to 18rem, the compact form. For numbers there,
1449
+ give the slot a width (`flex-1`, a `1fr` column) or pass `layout="pages"`.
1450
+ - **`siblings` above 1** needs more room than the 448px switch allows for;
1451
+ force `layout` if you widen it.
1452
+ - **When you move focus yourself,** pass `announce={false}`: the heading you
1453
+ focus says the new page, and the live region would say it again.
1454
+ - **In jsdom tests,** where no CSS applies, `layout="auto"` renders both forms;
1455
+ pass a `layout`, or query inside `[data-pagination-layout="pages"]`.
1456
+ - **Wire it fully.** With neither `onPageChange` nor `getPageHref` nothing
1457
+ pages, and in link mode without `navigate` the picker does a full page load.
1458
+
1292
1459
  ### Carousel
1293
1460
 
1294
1461
  A sideways row: a row of cards, a selection strip, or a gallery of one per
@@ -1431,6 +1598,8 @@ function App() {
1431
1598
  | `LiquidTabs` | Tab strip with a sliding pill; arrow keys, roving focus, 44px `lg` size |
1432
1599
  | `MatchupCard` | Head-to-head comparison card |
1433
1600
  | `MediaPreview` | Thumbnail that falls back to "Preview unavailable, open original" when it can't render |
1601
+ | `FileUpload` | Picks files and uploads each through the app's `upload`, with per-file progress, retry and rules |
1602
+ | `Pagination` | Page numbers with gaps, or a compact page picker on narrow screens; buttons or real links, 44px targets |
1434
1603
  | `Pullquote` | A line lifted out of prose, as `<figure>` + `<blockquote>` + `<figcaption>` |
1435
1604
  | `RadioGroup` | Radio set reporting one `string`; descriptions, error, 44px targets |
1436
1605
  | `Toast` | Transient floating notice; `Toaster` + the imperative `toast` handle |
@@ -1441,7 +1610,7 @@ function App() {
1441
1610
  | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1442
1611
  | `ActionBar` | Sticky bottom action strip |
1443
1612
  | `Countdown` | Live countdown timer |
1444
- | `DataTable` | Full-featured table with sorting and pagination via TanStack Table v9. Imported from `@blakesteve/roster/data-table` (see [DataTable columns](#datatable-columns)) |
1613
+ | `DataTable` | Full-featured table with sorting and pagination via TanStack Table v9; its pager is `Pagination`'s arrows, worded through `paginationLabels`. Imported from `@blakesteve/roster/data-table` (see [DataTable columns](#datatable-columns)) |
1445
1614
  | `Dialog` | Modal dialog; `onAfterClose` runs once it has finished closing |
1446
1615
  | `Footer` | Site footer |
1447
1616
  | `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)) |
@@ -1 +1 @@
1
- "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),f=require("react"),h=require("../../../lib/utils.cjs"),j=require("../Spinner/Spinner.cjs"),p=require("./button-variants.cjs"),q=require("../../../node_modules/@headlessui/react/dist/components/button/button.cjs"),n=f.forwardRef(({className:i,colorScheme:a,variant:c,size:l,isLoading:t=!1,startIcon:s,endIcon:r,surface:u="auto",disabled:o,children:m,...d},x)=>{const b=o||t;return e.jsxs(q.Button,{className:h.cn(p.buttonVariants({colorScheme:a,variant:c,size:l}),u==="dark"&&"dark",i),ref:x,disabled:b,...d,children:[t&&e.jsx("span",{className:"rst:mr-2 rst:flex rst:shrink-0 rst:items-center",children:e.jsx(j.Spinner,{size:"sm",variant:"current"})}),!t&&s&&e.jsx("span",{className:"rst:mr-2 rst:inline-flex rst:shrink-0 rst:items-center",children:s}),m,!t&&r&&e.jsx("span",{className:"rst:ml-2 rst:inline-flex rst:shrink-0 rst:items-center",children:r})]})});n.displayName="Button";exports.Button=n;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),h=require("react"),j=require("../../../lib/utils.cjs"),p=require("../Spinner/Spinner.cjs"),y=require("./button-variants.cjs"),S=require("../../../node_modules/@headlessui/react/dist/components/button/button.cjs"),q=/^on(?:Click|DoubleClick|AuxClick|(?:Pointer|Mouse)(?:Down|Up)|Key(?:Down|Up|Press)|Touch(?:Start|End))(?:Capture)?$/;function P(u){const r={};for(const[e,a]of Object.entries(u))q.test(e)||(r[e]=a);return{...r,onClick:e=>e.preventDefault()}}const m=h.forwardRef(({className:u,colorScheme:r,variant:e,size:a,isLoading:s=!1,loadingLabel:b,startIcon:c,endIcon:o,surface:f="auto",disabled:l,children:k,...n},x)=>{const i=s&&!l,d=i?P(n):n;return t.jsxs(S.Button,{className:j.cn(y.buttonVariants({colorScheme:r,variant:e,size:a}),f==="dark"&&"dark",i&&"rst:cursor-progress",u),ref:x,disabled:l,...d,"aria-disabled":i?!0:n["aria-disabled"],"aria-busy":i?!0:n["aria-busy"],children:[s&&t.jsx("span",{className:"rst:mr-2 rst:flex rst:shrink-0 rst:items-center",children:t.jsx(p.Spinner,{size:"sm",variant:"current",label:b})}),!s&&c&&t.jsx("span",{className:"rst:mr-2 rst:inline-flex rst:shrink-0 rst:items-center",children:c}),k,!s&&o&&t.jsx("span",{className:"rst:ml-2 rst:inline-flex rst:shrink-0 rst:items-center",children:o})]})});m.displayName="Button";exports.Button=m;
@@ -2,7 +2,18 @@ import { default as React } from 'react';
2
2
  import { VariantProps } from 'class-variance-authority';
3
3
  import { buttonVariants } from './button-variants';
4
4
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
5
+ /**
6
+ * Shows a spinner and ignores presses while something the button started is
7
+ * in flight. Unlike `disabled`, it keeps the button focusable: a native
8
+ * `disabled` drops focus to the page the moment it lands, so a keyboard or
9
+ * screen reader user who just pressed Save is thrown back to the top. A
10
+ * loading button stays where it is, says it's busy (`aria-busy`) and
11
+ * unavailable (`aria-disabled`), and swallows clicks, Enter and Space, which
12
+ * also stops a submit button sending its form twice.
13
+ */
5
14
  isLoading?: boolean;
15
+ /** What a screen reader hears for the spinner while loading: "loading" by default. */
16
+ loadingLabel?: string;
6
17
  startIcon?: React.ReactNode;
7
18
  endIcon?: React.ReactNode;
8
19
  /**
@@ -1,50 +1,60 @@
1
1
  "use client";
2
- import { jsxs as d, jsx as t } from "react/jsx-runtime";
3
- import h from "react";
2
+ import { jsxs as b, jsx as a } from "react/jsx-runtime";
3
+ import d from "react";
4
4
  import { cn as x } from "../../../lib/utils.js";
5
- import { Spinner as k } from "../Spinner/Spinner.js";
6
- import { buttonVariants as N } from "./button-variants.js";
7
- import { Button as b } from "../../../node_modules/@headlessui/react/dist/components/button/button.js";
8
- const B = h.forwardRef(
5
+ import { Spinner as y } from "../Spinner/Spinner.js";
6
+ import { buttonVariants as C } from "./button-variants.js";
7
+ import { Button as N } from "../../../node_modules/@headlessui/react/dist/components/button/button.js";
8
+ const P = /^on(?:Click|DoubleClick|AuxClick|(?:Pointer|Mouse)(?:Down|Up)|Key(?:Down|Up|Press)|Touch(?:Start|End))(?:Capture)?$/;
9
+ function w(i) {
10
+ const t = {};
11
+ for (const [r, o] of Object.entries(i)) P.test(r) || (t[r] = o);
12
+ return { ...t, onClick: (r) => r.preventDefault() };
13
+ }
14
+ const D = d.forwardRef(
9
15
  ({
10
- className: n,
11
- colorScheme: m,
12
- variant: a,
13
- size: i,
14
- isLoading: r = !1,
15
- startIcon: s,
16
- endIcon: e,
17
- surface: l = "auto",
18
- disabled: o,
19
- children: c,
20
- ...f
21
- }, p) => {
22
- const u = o || r;
23
- return /* @__PURE__ */ d(
24
- b,
16
+ className: i,
17
+ colorScheme: t,
18
+ variant: r,
19
+ size: o,
20
+ isLoading: e = !1,
21
+ loadingLabel: m,
22
+ startIcon: l,
23
+ endIcon: c,
24
+ surface: f = "auto",
25
+ disabled: u,
26
+ children: p,
27
+ ...s
28
+ }, k) => {
29
+ const n = e && !u, h = n ? w(s) : s;
30
+ return /* @__PURE__ */ b(
31
+ N,
25
32
  {
26
33
  className: x(
27
- N({ colorScheme: m, variant: a, size: i }),
34
+ C({ colorScheme: t, variant: r, size: o }),
28
35
  // Roster's dark variant is `&:where(.dark, .dark *)`, so an element
29
36
  // carrying `.dark` itself satisfies it. That is all forcing the
30
37
  // dark surface requires.
31
- l === "dark" && "dark",
32
- n
38
+ f === "dark" && "dark",
39
+ n && "rst:cursor-progress",
40
+ i
33
41
  ),
34
- ref: p,
42
+ ref: k,
35
43
  disabled: u,
36
- ...f,
44
+ ...h,
45
+ "aria-disabled": n ? !0 : s["aria-disabled"],
46
+ "aria-busy": n ? !0 : s["aria-busy"],
37
47
  children: [
38
- r && /* @__PURE__ */ t("span", { className: "rst:mr-2 rst:flex rst:shrink-0 rst:items-center", children: /* @__PURE__ */ t(k, { size: "sm", variant: "current" }) }),
39
- !r && s && /* @__PURE__ */ t("span", { className: "rst:mr-2 rst:inline-flex rst:shrink-0 rst:items-center", children: s }),
40
- c,
41
- !r && e && /* @__PURE__ */ t("span", { className: "rst:ml-2 rst:inline-flex rst:shrink-0 rst:items-center", children: e })
48
+ e && /* @__PURE__ */ a("span", { className: "rst:mr-2 rst:flex rst:shrink-0 rst:items-center", children: /* @__PURE__ */ a(y, { size: "sm", variant: "current", label: m }) }),
49
+ !e && l && /* @__PURE__ */ a("span", { className: "rst:mr-2 rst:inline-flex rst:shrink-0 rst:items-center", children: l }),
50
+ p,
51
+ !e && c && /* @__PURE__ */ a("span", { className: "rst:ml-2 rst:inline-flex rst:shrink-0 rst:items-center", children: c })
42
52
  ]
43
53
  }
44
54
  );
45
55
  }
46
56
  );
47
- B.displayName = "Button";
57
+ D.displayName = "Button";
48
58
  export {
49
- B as Button
59
+ D as Button
50
60
  };
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("../../../node_modules/class-variance-authority/dist/index.cjs"),t=r.cva("rst:font-ui rst:inline-flex rst:items-center rst:justify-center rst:cursor-pointer rst:whitespace-nowrap rst:rounded-md rst:text-sm rst:font-medium rst:ring-offset-background rst:transition-colors rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-offset-2 rst:disabled:pointer-events-none rst:disabled:opacity-50",{variants:{variant:{solid:"rst:border rst:border-transparent rst:elevation-control",soft:"rst:border-transparent rst:shadow-none",outline:"rst:border rst:bg-transparent rst:elevation-control",ghost:"rst:border rst:border-transparent rst:bg-transparent",link:"rst:bg-transparent rst:underline-offset-4 rst:hover:underline"},colorScheme:{primary:"",orange:"",teal:"",purple:"",amber:"",success:"",error:"",neutral:""},size:{xs:"rst:h-7 rst:rounded-sm rst:px-2 rst:text-xs",sm:"rst:h-9 rst:rounded-md rst:px-3",default:"rst:h-10 rst:px-4 rst:py-2",lg:"rst:h-11 rst:rounded-md rst:px-8",icon:"rst:h-10 rst:w-10"}},compoundVariants:[{variant:"solid",colorScheme:"primary",className:"rst:bg-primary-600 rst:hover:bg-primary-700 rst:dark:bg-primary-600 rst:dark:hover:bg-primary-500 rst:text-primary-600-ink rst:hover:text-primary-700-ink rst:dark:hover:text-primary-500-ink"},{variant:"solid",colorScheme:"orange",className:"rst:bg-orange-600 rst:hover:bg-orange-700 rst:dark:bg-orange-600 rst:dark:hover:bg-orange-700 rst:text-orange-600-ink rst:hover:text-orange-700-ink rst:dark:hover:text-orange-700-ink"},{variant:"solid",colorScheme:"teal",className:"rst:bg-teal-600 rst:hover:bg-teal-500 rst:dark:bg-teal-600 rst:dark:hover:bg-teal-500 rst:text-teal-600-ink rst:hover:text-teal-500-ink rst:dark:hover:text-teal-500-ink"},{variant:"solid",colorScheme:"purple",className:"rst:bg-purple-600 rst:hover:bg-purple-700 rst:dark:bg-purple-600 rst:dark:hover:bg-purple-700 rst:text-purple-600-ink rst:hover:text-purple-700-ink rst:dark:hover:text-purple-700-ink"},{variant:"solid",colorScheme:"amber",className:"rst:bg-amber-400 rst:hover:bg-amber-500 rst:dark:bg-amber-500 rst:dark:hover:bg-amber-400 rst:text-amber-400-ink rst:hover:text-amber-500-ink rst:dark:text-amber-500-ink rst:dark:hover:text-amber-400-ink"},{variant:"solid",colorScheme:"success",className:"rst:bg-success-600 rst:hover:bg-success-700 rst:dark:bg-success-600 rst:dark:hover:bg-success-700 rst:text-success-600-ink rst:hover:text-success-700-ink rst:dark:hover:text-success-700-ink"},{variant:"solid",colorScheme:"error",className:"rst:bg-error-600 rst:hover:bg-error-700 rst:dark:bg-error-600 rst:dark:hover:bg-error-500 rst:text-error-600-ink rst:hover:text-error-700-ink rst:dark:hover:text-error-500-ink"},{variant:"solid",colorScheme:"neutral",className:"rst:bg-gray-600 rst:hover:bg-gray-700 rst:dark:bg-gray-600 rst:dark:hover:bg-gray-500 rst:text-gray-600-ink rst:hover:text-gray-700-ink rst:dark:hover:text-gray-500-ink"},{variant:"soft",colorScheme:"primary",className:"rst:bg-primary-50 rst:text-primary-700 rst:hover:bg-primary-100 rst:dark:bg-primary-900/40 rst:dark:text-primary-300 rst:dark:hover:bg-primary-900/60"},{variant:"soft",colorScheme:"orange",className:"rst:bg-orange-50 rst:text-orange-700 rst:hover:bg-orange-100 rst:dark:bg-orange-900/30 rst:dark:text-orange-300 rst:dark:hover:bg-orange-900/50"},{variant:"soft",colorScheme:"teal",className:"rst:bg-teal-100 rst:text-teal-800 rst:hover:bg-teal-200 rst:dark:bg-teal-900/30 rst:dark:text-teal-300 rst:dark:hover:bg-teal-900/50"},{variant:"soft",colorScheme:"purple",className:"rst:bg-purple-50 rst:text-purple-700 rst:hover:bg-purple-100 rst:dark:bg-purple-900/30 rst:dark:text-purple-300 rst:dark:hover:bg-purple-900/50"},{variant:"soft",colorScheme:"amber",className:"rst:bg-amber-50 rst:text-amber-800 rst:hover:bg-amber-100 rst:dark:bg-amber-900/30 rst:dark:text-amber-300 rst:dark:hover:bg-amber-900/50"},{variant:"soft",colorScheme:"success",className:"rst:bg-success-50 rst:text-success-700 rst:hover:bg-success-100 rst:dark:bg-success-900/30 rst:dark:text-success-300 rst:dark:hover:bg-success-900/50"},{variant:"soft",colorScheme:"error",className:"rst:bg-error-50 rst:text-error-700 rst:hover:bg-error-100 rst:dark:bg-error-900/30 rst:dark:text-error-300 rst:dark:hover:bg-error-900/50"},{variant:"soft",colorScheme:"neutral",className:"rst:bg-gray-100 rst:text-gray-700 rst:hover:bg-gray-200 rst:dark:bg-gray-800/50 rst:dark:text-gray-300 rst:dark:hover:bg-gray-800"},{variant:"outline",colorScheme:"primary",className:"rst:border-primary-600 rst:text-primary-600 rst:hover:bg-primary-50 rst:dark:border-primary-500 rst:dark:text-primary-400 rst:dark:hover:bg-primary-500/10"},{variant:"outline",colorScheme:"orange",className:"rst:border-orange-600 rst:text-orange-600 rst:hover:bg-orange-50 rst:dark:border-orange-500 rst:dark:text-orange-400 rst:dark:hover:bg-orange-500/10"},{variant:"outline",colorScheme:"teal",className:"rst:border-teal-600 rst:text-teal-600 rst:hover:bg-teal-50 rst:dark:border-teal-500 rst:dark:text-teal-400 rst:dark:hover:bg-teal-500/10"},{variant:"outline",colorScheme:"purple",className:"rst:border-purple-600 rst:text-purple-600 rst:hover:bg-purple-50 rst:dark:border-purple-500 rst:dark:text-purple-400 rst:dark:hover:bg-purple-500/10"},{variant:"outline",colorScheme:"amber",className:"rst:border-amber-600 rst:text-amber-600 rst:hover:bg-amber-50 rst:dark:border-amber-500 rst:dark:text-amber-400 rst:dark:hover:bg-amber-500/10"},{variant:"outline",colorScheme:"success",className:"rst:border-success-600 rst:text-success-600 rst:hover:bg-success-50 rst:dark:border-success-500 rst:dark:text-success-400 rst:dark:hover:bg-success-500/10"},{variant:"outline",colorScheme:"error",className:"rst:border-error-600 rst:text-error-600 rst:hover:bg-error-50 rst:dark:border-error-500 rst:dark:text-error-400 rst:dark:hover:bg-error-500/10"},{variant:"outline",colorScheme:"neutral",className:"rst:border-gray-300 rst:text-gray-700 rst:hover:bg-gray-50 rst:dark:border-gray-600 rst:dark:text-gray-300 rst:dark:hover:bg-gray-800/50"},{variant:"ghost",colorScheme:"primary",className:"rst:text-primary-600 rst:hover:bg-primary-500/10 rst:dark:text-primary-400 rst:dark:hover:bg-primary-400/15"},{variant:"ghost",colorScheme:"orange",className:"rst:text-orange-600 rst:hover:bg-orange-500/10 rst:dark:text-orange-400 rst:dark:hover:bg-orange-400/15"},{variant:"ghost",colorScheme:"teal",className:"rst:text-teal-600 rst:hover:bg-teal-500/10 rst:dark:text-teal-400 rst:dark:hover:bg-teal-400/15"},{variant:"ghost",colorScheme:"purple",className:"rst:text-purple-600 rst:hover:bg-purple-500/10 rst:dark:text-purple-400 rst:dark:hover:bg-purple-400/15"},{variant:"ghost",colorScheme:"amber",className:"rst:text-amber-600 rst:hover:bg-amber-500/10 rst:dark:text-amber-400 rst:dark:hover:bg-amber-400/15"},{variant:"ghost",colorScheme:"success",className:"rst:text-success-600 rst:hover:bg-success-500/10 rst:dark:text-success-400 rst:dark:hover:bg-success-400/15"},{variant:"ghost",colorScheme:"error",className:"rst:text-error-600 rst:hover:bg-error-500/10 rst:dark:text-error-400 rst:dark:hover:bg-error-400/15"},{variant:"ghost",colorScheme:"neutral",className:"rst:text-gray-600 rst:hover:bg-gray-500/10 rst:dark:text-gray-400 rst:dark:hover:bg-gray-400/15"},{variant:"link",colorScheme:"primary",className:"rst:text-primary-600 rst:hover:text-primary-700 rst:dark:text-primary-400 rst:dark:hover:text-primary-300"},{variant:"link",colorScheme:"orange",className:"rst:text-orange-600 rst:hover:text-orange-700 rst:dark:text-orange-400 rst:dark:hover:text-orange-300"},{variant:"link",colorScheme:"teal",className:"rst:text-teal-600 rst:hover:text-teal-700 rst:dark:text-teal-400 rst:dark:hover:text-teal-300"},{variant:"link",colorScheme:"purple",className:"rst:text-purple-600 rst:hover:text-purple-700 rst:dark:text-purple-400 rst:dark:hover:text-purple-300"},{variant:"link",colorScheme:"amber",className:"rst:text-amber-600 rst:hover:text-amber-700 rst:dark:text-amber-400 rst:dark:hover:text-amber-300"},{variant:"link",colorScheme:"success",className:"rst:text-success-600 rst:hover:text-success-700 rst:dark:text-success-400 rst:dark:hover:text-success-300"},{variant:"link",colorScheme:"error",className:"rst:text-error-600 rst:hover:text-error-700 rst:dark:text-error-400 rst:dark:hover:text-error-300"},{variant:"link",colorScheme:"neutral",className:"rst:text-gray-600 rst:hover:text-gray-900 rst:dark:text-gray-400 rst:dark:hover:text-gray-100"}],defaultVariants:{colorScheme:"primary",variant:"solid",size:"default"}});exports.buttonVariants=t;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("../../../node_modules/class-variance-authority/dist/index.cjs"),t=r.cva("rst:font-ui rst:inline-flex rst:items-center rst:justify-center rst:cursor-pointer rst:whitespace-nowrap rst:rounded-md rst:text-sm rst:font-medium rst:ring-offset-background rst:transition-colors rst:focus-visible:outline-hidden rst:focus-visible:ring-2 rst:focus-visible:ring-ring rst:focus-visible:ring-offset-2 rst:disabled:pointer-events-none rst:disabled:opacity-50",{variants:{variant:{solid:"rst:border rst:border-transparent rst:elevation-control",soft:"rst:border-transparent rst:shadow-none",outline:"rst:border rst:bg-transparent rst:elevation-control",ghost:"rst:border rst:border-transparent rst:bg-transparent",link:"rst:border-0 rst:bg-transparent rst:underline-offset-4 rst:hover:underline"},colorScheme:{primary:"",orange:"",teal:"",purple:"",amber:"",success:"",error:"",neutral:""},size:{xs:"rst:h-7 rst:rounded-sm rst:px-2 rst:text-xs",sm:"rst:h-9 rst:rounded-md rst:px-3",default:"rst:h-10 rst:px-4 rst:py-2",lg:"rst:h-11 rst:rounded-md rst:px-8",icon:"rst:h-10 rst:w-10"}},compoundVariants:[{variant:"solid",colorScheme:"primary",className:"rst:bg-primary-600 rst:hover:bg-primary-700 rst:dark:bg-primary-600 rst:dark:hover:bg-primary-500 rst:text-primary-600-ink rst:hover:text-primary-700-ink rst:dark:hover:text-primary-500-ink"},{variant:"solid",colorScheme:"orange",className:"rst:bg-orange-600 rst:hover:bg-orange-700 rst:dark:bg-orange-600 rst:dark:hover:bg-orange-700 rst:text-orange-600-ink rst:hover:text-orange-700-ink rst:dark:hover:text-orange-700-ink"},{variant:"solid",colorScheme:"teal",className:"rst:bg-teal-600 rst:hover:bg-teal-500 rst:dark:bg-teal-600 rst:dark:hover:bg-teal-500 rst:text-teal-600-ink rst:hover:text-teal-500-ink rst:dark:hover:text-teal-500-ink"},{variant:"solid",colorScheme:"purple",className:"rst:bg-purple-600 rst:hover:bg-purple-700 rst:dark:bg-purple-600 rst:dark:hover:bg-purple-700 rst:text-purple-600-ink rst:hover:text-purple-700-ink rst:dark:hover:text-purple-700-ink"},{variant:"solid",colorScheme:"amber",className:"rst:bg-amber-400 rst:hover:bg-amber-500 rst:dark:bg-amber-500 rst:dark:hover:bg-amber-400 rst:text-amber-400-ink rst:hover:text-amber-500-ink rst:dark:text-amber-500-ink rst:dark:hover:text-amber-400-ink"},{variant:"solid",colorScheme:"success",className:"rst:bg-success-600 rst:hover:bg-success-700 rst:dark:bg-success-600 rst:dark:hover:bg-success-700 rst:text-success-600-ink rst:hover:text-success-700-ink rst:dark:hover:text-success-700-ink"},{variant:"solid",colorScheme:"error",className:"rst:bg-error-600 rst:hover:bg-error-700 rst:dark:bg-error-600 rst:dark:hover:bg-error-500 rst:text-error-600-ink rst:hover:text-error-700-ink rst:dark:hover:text-error-500-ink"},{variant:"solid",colorScheme:"neutral",className:"rst:bg-gray-600 rst:hover:bg-gray-700 rst:dark:bg-gray-600 rst:dark:hover:bg-gray-500 rst:text-gray-600-ink rst:hover:text-gray-700-ink rst:dark:hover:text-gray-500-ink"},{variant:"soft",colorScheme:"primary",className:"rst:bg-primary-50 rst:text-primary-700 rst:hover:bg-primary-100 rst:dark:bg-primary-900/40 rst:dark:text-primary-300 rst:dark:hover:bg-primary-900/60"},{variant:"soft",colorScheme:"orange",className:"rst:bg-orange-50 rst:text-orange-700 rst:hover:bg-orange-100 rst:dark:bg-orange-900/30 rst:dark:text-orange-300 rst:dark:hover:bg-orange-900/50"},{variant:"soft",colorScheme:"teal",className:"rst:bg-teal-100 rst:text-teal-800 rst:hover:bg-teal-200 rst:dark:bg-teal-900/30 rst:dark:text-teal-300 rst:dark:hover:bg-teal-900/50"},{variant:"soft",colorScheme:"purple",className:"rst:bg-purple-50 rst:text-purple-700 rst:hover:bg-purple-100 rst:dark:bg-purple-900/30 rst:dark:text-purple-300 rst:dark:hover:bg-purple-900/50"},{variant:"soft",colorScheme:"amber",className:"rst:bg-amber-50 rst:text-amber-800 rst:hover:bg-amber-100 rst:dark:bg-amber-900/30 rst:dark:text-amber-300 rst:dark:hover:bg-amber-900/50"},{variant:"soft",colorScheme:"success",className:"rst:bg-success-50 rst:text-success-700 rst:hover:bg-success-100 rst:dark:bg-success-900/30 rst:dark:text-success-300 rst:dark:hover:bg-success-900/50"},{variant:"soft",colorScheme:"error",className:"rst:bg-error-50 rst:text-error-700 rst:hover:bg-error-100 rst:dark:bg-error-900/30 rst:dark:text-error-300 rst:dark:hover:bg-error-900/50"},{variant:"soft",colorScheme:"neutral",className:"rst:bg-gray-100 rst:text-gray-700 rst:hover:bg-gray-200 rst:dark:bg-gray-800/50 rst:dark:text-gray-300 rst:dark:hover:bg-gray-800"},{variant:"outline",colorScheme:"primary",className:"rst:border-primary-600 rst:text-primary-600 rst:hover:bg-primary-50 rst:dark:border-primary-500 rst:dark:text-primary-400 rst:dark:hover:bg-primary-500/10"},{variant:"outline",colorScheme:"orange",className:"rst:border-orange-600 rst:text-orange-600 rst:hover:bg-orange-50 rst:dark:border-orange-500 rst:dark:text-orange-400 rst:dark:hover:bg-orange-500/10"},{variant:"outline",colorScheme:"teal",className:"rst:border-teal-600 rst:text-teal-600 rst:hover:bg-teal-50 rst:dark:border-teal-500 rst:dark:text-teal-400 rst:dark:hover:bg-teal-500/10"},{variant:"outline",colorScheme:"purple",className:"rst:border-purple-600 rst:text-purple-600 rst:hover:bg-purple-50 rst:dark:border-purple-500 rst:dark:text-purple-400 rst:dark:hover:bg-purple-500/10"},{variant:"outline",colorScheme:"amber",className:"rst:border-amber-600 rst:text-amber-600 rst:hover:bg-amber-50 rst:dark:border-amber-500 rst:dark:text-amber-400 rst:dark:hover:bg-amber-500/10"},{variant:"outline",colorScheme:"success",className:"rst:border-success-600 rst:text-success-600 rst:hover:bg-success-50 rst:dark:border-success-500 rst:dark:text-success-400 rst:dark:hover:bg-success-500/10"},{variant:"outline",colorScheme:"error",className:"rst:border-error-600 rst:text-error-600 rst:hover:bg-error-50 rst:dark:border-error-500 rst:dark:text-error-400 rst:dark:hover:bg-error-500/10"},{variant:"outline",colorScheme:"neutral",className:"rst:border-gray-300 rst:text-gray-700 rst:hover:bg-gray-50 rst:dark:border-gray-600 rst:dark:text-gray-300 rst:dark:hover:bg-gray-800/50"},{variant:"ghost",colorScheme:"primary",className:"rst:text-primary-600 rst:hover:bg-primary-500/10 rst:dark:text-primary-400 rst:dark:hover:bg-primary-400/15"},{variant:"ghost",colorScheme:"orange",className:"rst:text-orange-600 rst:hover:bg-orange-500/10 rst:dark:text-orange-400 rst:dark:hover:bg-orange-400/15"},{variant:"ghost",colorScheme:"teal",className:"rst:text-teal-600 rst:hover:bg-teal-500/10 rst:dark:text-teal-400 rst:dark:hover:bg-teal-400/15"},{variant:"ghost",colorScheme:"purple",className:"rst:text-purple-600 rst:hover:bg-purple-500/10 rst:dark:text-purple-400 rst:dark:hover:bg-purple-400/15"},{variant:"ghost",colorScheme:"amber",className:"rst:text-amber-600 rst:hover:bg-amber-500/10 rst:dark:text-amber-400 rst:dark:hover:bg-amber-400/15"},{variant:"ghost",colorScheme:"success",className:"rst:text-success-600 rst:hover:bg-success-500/10 rst:dark:text-success-400 rst:dark:hover:bg-success-400/15"},{variant:"ghost",colorScheme:"error",className:"rst:text-error-600 rst:hover:bg-error-500/10 rst:dark:text-error-400 rst:dark:hover:bg-error-400/15"},{variant:"ghost",colorScheme:"neutral",className:"rst:text-gray-600 rst:hover:bg-gray-500/10 rst:dark:text-gray-400 rst:dark:hover:bg-gray-400/15"},{variant:"link",colorScheme:"primary",className:"rst:text-primary-600 rst:hover:text-primary-700 rst:dark:text-primary-400 rst:dark:hover:text-primary-300"},{variant:"link",colorScheme:"orange",className:"rst:text-orange-600 rst:hover:text-orange-700 rst:dark:text-orange-400 rst:dark:hover:text-orange-300"},{variant:"link",colorScheme:"teal",className:"rst:text-teal-600 rst:hover:text-teal-700 rst:dark:text-teal-400 rst:dark:hover:text-teal-300"},{variant:"link",colorScheme:"purple",className:"rst:text-purple-600 rst:hover:text-purple-700 rst:dark:text-purple-400 rst:dark:hover:text-purple-300"},{variant:"link",colorScheme:"amber",className:"rst:text-amber-600 rst:hover:text-amber-700 rst:dark:text-amber-400 rst:dark:hover:text-amber-300"},{variant:"link",colorScheme:"success",className:"rst:text-success-600 rst:hover:text-success-700 rst:dark:text-success-400 rst:dark:hover:text-success-300"},{variant:"link",colorScheme:"error",className:"rst:text-error-600 rst:hover:text-error-700 rst:dark:text-error-400 rst:dark:hover:text-error-300"},{variant:"link",colorScheme:"neutral",className:"rst:text-gray-600 rst:hover:text-gray-900 rst:dark:text-gray-400 rst:dark:hover:text-gray-100"}],defaultVariants:{colorScheme:"primary",variant:"solid",size:"default"}});exports.buttonVariants=t;
@@ -9,7 +9,9 @@ const e = /* @__PURE__ */ r(
9
9
  soft: "rst:border-transparent rst:shadow-none",
10
10
  outline: "rst:border rst:bg-transparent rst:elevation-control",
11
11
  ghost: "rst:border rst:border-transparent rst:bg-transparent",
12
- link: "rst:bg-transparent rst:underline-offset-4 rst:hover:underline"
12
+ // `border-0`: a <button> draws the browser's own border unless told
13
+ // otherwise, and an app without a reset would see it round a link.
14
+ link: "rst:border-0 rst:bg-transparent rst:underline-offset-4 rst:hover:underline"
13
15
  },
14
16
  colorScheme: {
15
17
  primary: "",
@@ -0,0 +1 @@
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),o=require("react"),p=require("../../../lib/utils.cjs"),P=require("../../atoms/Button/Button.cjs"),R=require("./file-rules.cjs"),pt={choose:"Choose photos",dropHint:"or drag them here",dropHere:"Drop to add them",queued:"Waiting",uploading:n=>n===null?"Uploading":`Uploading, ${n}%`,processing:"Finishing up",done:"Uploaded",failed:"Didn't upload.",rejected:"Not added.",remove:n=>`Remove ${n}`,retry:n=>`Try ${n} again`,wrongType:()=>"That type of file isn't accepted.",tooLarge:(n,l)=>`It's over ${R.formatBytes(l)}.`,tooMany:n=>`Only ${n} can be added.`,duplicate:()=>"It's already added.",uploadError:n=>n instanceof Error&&n.message?n.message:"Something went wrong.",size:R.formatBytes,added:n=>`${n} ${n===1?"photo":"photos"} added.`,progress:(n,l)=>`${n} of ${l} uploaded.`,failedNotice:(n,l)=>`${n} didn't upload. ${l}`,rejectedNotice:(n,l)=>`${n} wasn't added. ${l}`,removed:n=>`${n} removed.`};function gt({file:n,status:l}){const[M,q]=o.useState(!1),B=!M&&(n.type.startsWith("image/")||/\.(jpe?g|png|gif|webp|avif|heic|heif)$/i.test(n.name)),N=o.useCallback(L=>{if(!L)return;const w=URL.createObjectURL(n);return L.src=w,()=>URL.revokeObjectURL(w)},[n]),x="rst:h-14 rst:w-14 rst:shrink-0 rst:rounded-md rst:border rst:border-[var(--roster-card-border)]";return B?e.jsx("img",{ref:N,alt:"",onError:()=>q(!0),className:p.cn(x,"rst:object-cover",l==="rejected"&&"rst:opacity-50"),"data-file-thumb":"image"}):e.jsx("span",{"aria-hidden":"true",className:p.cn(x,"rst:flex rst:items-center rst:justify-center rst:text-gray-500 rst:dark:text-gray-400"),"data-file-thumb":"placeholder",children:e.jsxs("svg",{viewBox:"0 0 24 24",width:"24",height:"24",fill:"none",stroke:"currentColor",strokeWidth:"1.6",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("rect",{x:"3",y:"5",width:"18",height:"14",rx:"2"}),e.jsx("circle",{cx:"9",cy:"10",r:"1.6"}),e.jsx("path",{d:"m21 15-4.2-4.2a1.6 1.6 0 0 0-2.3 0L7 18"})]})})}const W="rst:cursor-not-allowed rst:opacity-50",S={width:20,height:20,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};function ht({label:n,upload:l,onChange:M,accept:q="image/*",types:B,maxSize:N,maxFiles:x,validate:L,concurrency:w=3,helperText:E,errorMessage:T,disabled:u=!1,strings:K,className:Q}){const c={...pt,...K},D=o.useId(),I=`${D}-label`,G=`${D}-help`,V=`${D}-error`,[U,Y]=o.useState([]),d=o.useRef(U),Z=o.useRef(0),m=o.useRef(new Map),F=o.useRef(null),X=o.useRef(null),g=o.useRef({upload:l,onChange:M,concurrency:w,t:c,pump:()=>{}}),[O,tt]=o.useState({text:"",n:0}),v=o.useRef([]),y=o.useRef(null),$=o.useCallback((t,s=!1)=>{s&&(v.current=v.current.filter(r=>!r.count)),v.current.push({text:t,count:s}),!y.current&&(y.current=setTimeout(()=>{y.current=null;const r=v.current.map(i=>i.text).join(" ");v.current=[],tt(i=>({text:r,n:i.n+1}))},0))},[]),z=o.useRef(null),[rt,et]=o.useState(0),_=t=>{z.current=t,et(s=>s+1)};o.useLayoutEffect(()=>{const t=z.current;if(!t)return;z.current=null,(F.current?.querySelector(t)??F.current?.querySelector("[data-file-choose]"))?.focus()},[rt]);const j=o.useCallback(t=>{d.current=t,Y(t),g.current.onChange?.(t)},[]),b=o.useCallback((t,s)=>{d.current.some(r=>r.id===t)&&j(d.current.map(r=>r.id===t?{...r,...s}:r))},[j]);function k(){const{concurrency:t}=g.current,s=d.current.filter(i=>i.status==="uploading"||i.status==="processing").length,r=d.current.filter(i=>i.status==="queued").slice(0,Math.max(0,Math.max(1,t)-s));for(const i of r)st(i)}function st(t){const s=new AbortController;m.current.set(t.id,s),b(t.id,{status:"uploading",progress:null,reason:void 0});const r=()=>!s.signal.aborted&&m.current.get(t.id)===s,i={onProgress:a=>{if(!r())return;const f=Math.round(Math.min(1,Math.max(0,Number.isFinite(a)?a:0))*100)/100,h=d.current.find(ft=>ft.id===t.id);h?.status==="uploading"&&h.progress!==f&&b(t.id,{progress:f})},onProcessing:()=>{r()&&b(t.id,{status:"processing"})},signal:s.signal};new Promise(a=>a(g.current.upload(t.file,i))).then(a=>{if(!r())return;m.current.delete(t.id),b(t.id,{status:"done",progress:1,result:a});const f=d.current.filter(h=>h.status!=="rejected");$(g.current.t.progress(f.filter(h=>h.status==="done").length,f.length),!0),k()},a=>{if(!r())return;m.current.delete(t.id);const f=g.current.t.uploadError(a);b(t.id,{status:"failed",reason:f}),$(g.current.t.failedNotice(t.file.name,f)),k()})}o.useLayoutEffect(()=>{g.current={upload:l,onChange:M,concurrency:w,t:c,pump:k}}),o.useEffect(()=>{const t=m.current;return d.current.some(r=>(r.status==="uploading"||r.status==="processing")&&!t.has(r.id))&&(j(d.current.map(r=>(r.status==="uploading"||r.status==="processing")&&!t.has(r.id)?{...r,status:"queued",progress:null}:r)),g.current.pump()),()=>{for(const r of t.values())r.abort();t.clear(),y.current&&clearTimeout(y.current),y.current=null,v.current=[]}},[j]);const nt=B??R.acceptList(q),at=(t,s)=>{if(s.some(i=>R.sameFile(i.file,t)))return c.duplicate(t);const r=L?.(t);if(r)return r;if(!R.matchesType(t,nt))return c.wrongType(t);if(N!==void 0&&t.size>N)return c.tooLarge(t,N);if(x!==void 0&&s.length>=x)return c.tooMany(x)},J=t=>{if(u||t.length===0)return;const s=d.current.filter(a=>a.status!=="rejected"),r=[];for(const a of t){const f=at(a,[...s,...r.filter(h=>h.status!=="rejected")]);r.push({id:`${D}-file-${Z.current++}`,file:a,status:f?"rejected":"queued",progress:null,reason:f})}j([...d.current,...r]);const i=r.filter(a=>a.status==="queued").length;i&&$(c.added(i));for(const a of r)a.reason&&$(c.rejectedNotice(a.file.name,a.reason));k()},ot=t=>{if(u)return;const s=d.current,r=s.findIndex(a=>a.id===t);if(r<0)return;m.current.get(t)?.abort(),m.current.delete(t);const i=s[r+1]??s[r-1];j(s.filter(a=>a.id!==t)),$(c.removed(s[r].file.name)),_(i?`[data-file-remove="${i.id}"]`:"[data-file-choose]"),k()},ct=t=>{u||(b(t,{status:"queued",progress:null,reason:void 0}),_(`[data-file-remove="${t}"]`),k())},[it,A]=o.useState(0),C=t=>Array.from(t.dataTransfer?.types??[]).includes("Files"),ut={onDragEnter:t=>{C(t)&&(t.preventDefault(),A(s=>s+1))},onDragOver:t=>{C(t)&&(t.preventDefault(),t.dataTransfer.dropEffect=u?"none":"copy")},onDragLeave:t=>{C(t)&&A(s=>Math.max(0,s-1))},onDrop:t=>{C(t)&&(t.preventDefault(),A(0),J(Array.from(t.dataTransfer.files)))}},H=it>0&&!u,lt=t=>{switch(t.status){case"queued":return c.queued;case"uploading":return c.uploading(t.progress===null?null:Math.round(t.progress*100));case"processing":return c.processing;case"done":return c.done;case"failed":return`${c.failed} ${t.reason??""}`.trim();case"rejected":return`${c.rejected} ${t.reason??""}`.trim()}},dt=[E?G:null,T?V:null].filter(Boolean).join(" ")||void 0;return e.jsxs("div",{ref:F,role:"group","aria-labelledby":I,className:p.cn("rst:font-ui rst:w-full",Q),"data-file-upload":"",children:[e.jsx("p",{id:I,className:p.cn("rst:mb-1.5 rst:text-left rst:text-sm rst:font-medium rst:leading-none rst:text-[var(--roster-control-text)]",u&&"rst:opacity-70"),children:n}),e.jsxs("div",{...ut,"data-file-drop":"","data-dragging":H?"":void 0,className:p.cn("rst:flex rst:flex-col rst:items-center rst:justify-center rst:gap-2 rst:rounded-lg rst:border-2 rst:border-dashed rst:px-4 rst:py-6 rst:text-center rst:transition-colors rst:motion-reduce:transition-none",H?"rst:border-primary-500 rst:bg-primary-500/10":T?"rst:border-error-500 rst:dark:border-error-400":"rst:border-gray-300 rst:dark:border-gray-600"),children:[e.jsx(P.Button,{type:"button",variant:"outline",colorScheme:"neutral",size:"lg",className:p.cn("rst:px-5",u&&W),"aria-disabled":u||void 0,"aria-describedby":dt,onClick:()=>X.current?.click(),"data-file-choose":"",startIcon:e.jsx("svg",{...S,"aria-hidden":"true",children:e.jsx("path",{d:"M12 16V4M7 9l5-5 5 5M5 20h14"})}),children:c.choose}),e.jsx("p",{className:"rst:hidden rst:text-xs rst:text-gray-600 rst:pointer-fine:block rst:dark:text-gray-400","data-file-drop-hint":"",children:H?c.dropHere:c.dropHint}),e.jsx("input",{ref:X,type:"file",hidden:!0,tabIndex:-1,accept:q,multiple:x!==1,disabled:u,onChange:t=>{J(Array.from(t.currentTarget.files??[])),t.currentTarget.value=""},"data-file-input":""})]}),E&&e.jsx("p",{id:G,className:"rst:mt-1.5 rst:text-left rst:text-xs rst:text-gray-500 rst:dark:text-gray-400",children:E}),T&&e.jsx("p",{id:V,className:"rst:mt-1.5 rst:text-left rst:text-xs rst:font-medium rst:text-error-600 rst:dark:text-error-400",children:T}),U.length>0&&e.jsx("ul",{className:"rst:mt-3 rst:flex rst:flex-col rst:gap-2","aria-labelledby":I,children:U.map(t=>{const s=t.status==="failed"||t.status==="rejected",r=t.status==="uploading"||t.status==="processing"||t.status==="queued";return e.jsxs("li",{"data-file-item":t.id,"data-status":t.status,className:"rst:flex rst:items-center rst:gap-3 rst:rounded-md rst:border rst:border-[var(--roster-card-border)] rst:p-2",children:[e.jsx(gt,{file:t.file,status:t.status}),e.jsxs("div",{className:"rst:min-w-0 rst:flex-1 rst:text-left",children:[e.jsx("p",{className:"rst:truncate rst:text-sm rst:font-medium rst:text-[var(--roster-control-text)]",title:t.file.name,children:t.file.name}),e.jsxs("p",{className:p.cn("rst:mt-0.5 rst:flex rst:items-start rst:gap-1 rst:text-xs",s?"rst:text-error-600 rst:dark:text-error-400":"rst:text-gray-600 rst:dark:text-gray-400"),"data-file-status":"",children:[t.status==="done"&&e.jsx("svg",{...S,width:14,height:14,"aria-hidden":"true",className:"rst:mt-px rst:shrink-0 rst:text-success-600 rst:dark:text-success-400",children:e.jsx("path",{d:"M20 6 9 17l-5-5"})}),e.jsxs("span",{children:[e.jsx("span",{className:"rst:tabular-nums",children:c.size(t.file.size)}),e.jsx("span",{"aria-hidden":"true",children:" · "}),lt(t)]})]}),r&&e.jsx("div",{role:"progressbar","aria-label":t.file.name,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":t.status==="uploading"&&t.progress!==null?Math.round(t.progress*100):void 0,className:p.cn("rst:mt-1.5 rst:h-1.5 rst:overflow-hidden rst:rounded-full rst:bg-gray-200 rst:dark:bg-gray-700",t.status==="uploading"&&t.progress===null&&"rst:animate-pulse rst:motion-reduce:animate-none"),"data-file-progress":"",children:e.jsx("div",{className:p.cn("rst:h-full rst:origin-left rst:rounded-full rst:bg-primary-600 rst:transition-transform rst:motion-reduce:transition-none rst:dark:bg-primary-400",t.status==="processing"&&"rst:animate-pulse rst:motion-reduce:animate-none"),style:{transform:`scaleX(${t.status==="processing"?1:t.status==="uploading"?t.progress??0:0})`}})})]}),t.status==="failed"&&e.jsx(P.Button,{type:"button",variant:"ghost",colorScheme:"neutral",size:"icon",className:p.cn("rst:h-11 rst:w-11",u&&W),"aria-disabled":u||void 0,"aria-label":c.retry(t.file.name),onClick:()=>ct(t.id),"data-file-retry":t.id,children:e.jsxs("svg",{...S,"aria-hidden":"true",children:[e.jsx("path",{d:"M3 12a9 9 0 1 0 3-6.7L3 8"}),e.jsx("path",{d:"M3 3v5h5"})]})}),e.jsx(P.Button,{type:"button",variant:"ghost",colorScheme:"neutral",size:"icon",className:p.cn("rst:h-11 rst:w-11",u&&W),"aria-disabled":u||void 0,"aria-label":c.remove(t.file.name),onClick:()=>ot(t.id),"data-file-remove":t.id,children:e.jsx("svg",{...S,"aria-hidden":"true",children:e.jsx("path",{d:"M18 6 6 18M6 6l12 12"})})})]},t.id)})}),e.jsxs("div",{role:"status","aria-live":"polite","aria-atomic":"true",className:"rst:sr-only","data-file-live":"","data-file-live-n":O.n,children:[O.text,O.n%2?" ":""]})]})}exports.FileUpload=ht;
@@ -0,0 +1,111 @@
1
+ import { ReactNode } from 'react';
2
+ /** Where a file is: waiting its turn, on its way, being handled by the app's server, or finished one way or another. */
3
+ export type FileUploadStatus = "queued" | "uploading" | "processing" | "done" | "failed" | "rejected";
4
+ /** What FileUpload hands `upload` with each file. */
5
+ export interface FileUploadContext {
6
+ /** Report how much is sent, from 0 to 1. Without it the bar runs without a figure. */
7
+ onProgress: (fraction: number) => void;
8
+ /** Say the bytes are up and the app's server is working on them. */
9
+ onProcessing: () => void;
10
+ /** Aborts when the person removes the file, or the component goes away. */
11
+ signal: AbortSignal;
12
+ }
13
+ /** A file in the list, as `onChange` reports it. */
14
+ export interface FileUploadItem<R = unknown> {
15
+ id: string;
16
+ file: File;
17
+ status: FileUploadStatus;
18
+ /** From 0 to 1 once the app reports any; `null` before. */
19
+ progress: number | null;
20
+ /** What `upload` resolved with, once `done`: an id to send with the form, say. */
21
+ result?: R;
22
+ /** Why it `failed`, or why it was `rejected`. */
23
+ reason?: string;
24
+ }
25
+ /** Every word FileUpload shows or says. Pass any of them in `strings` to replace it. */
26
+ export interface FileUploadStrings {
27
+ choose: string;
28
+ /** Beside the button where a pointer can drag; hidden on touch screens. */
29
+ dropHint: string;
30
+ /** While files are dragged over. */
31
+ dropHere: string;
32
+ queued: string;
33
+ uploading: (percent: number | null) => string;
34
+ processing: string;
35
+ done: string;
36
+ failed: string;
37
+ rejected: string;
38
+ remove: (name: string) => string;
39
+ retry: (name: string) => string;
40
+ wrongType: (file: File) => string;
41
+ tooLarge: (file: File, maxSize: number) => string;
42
+ tooMany: (maxFiles: number) => string;
43
+ duplicate: (file: File) => string;
44
+ /** The reason shown when `upload` rejects. */
45
+ uploadError: (error: unknown) => string;
46
+ size: (bytes: number) => string;
47
+ added: (count: number) => string;
48
+ progress: (done: number, total: number) => string;
49
+ failedNotice: (name: string, reason: string) => string;
50
+ rejectedNotice: (name: string, reason: string) => string;
51
+ removed: (name: string) => string;
52
+ }
53
+ export interface FileUploadProps<R = unknown> {
54
+ /** What the files are: "Photos". Names the group. */
55
+ label: string;
56
+ /**
57
+ * Sends one file, and resolves when it's done with whatever the app needs
58
+ * later (an id, a URL). Rejects with an Error whose message says why, and
59
+ * that message is shown. FileUpload never sends anything itself.
60
+ */
61
+ upload: (file: File, context: FileUploadContext) => Promise<R>;
62
+ /** Every change to the list, with each file's status and result. */
63
+ onChange?: (items: FileUploadItem<R>[]) => void;
64
+ /**
65
+ * What the picker offers. Defaults to `"image/*"`, and on phones it should
66
+ * stay that way. Android's Chrome opens the system photo picker, with its
67
+ * albums and cloud photos, only when every accepted type is an image (or
68
+ * video) type and there's no `capture`; an extension among them gets a plain
69
+ * file browser. Exact types are riskier than they look: a picker that
70
+ * filters by the type a photo reports can hide photos whose reported type
71
+ * doesn't match. Narrow what's allowed with `types`, which is checked after
72
+ * picking.
73
+ */
74
+ accept?: string;
75
+ /**
76
+ * The types allowed, checked on every picked or dropped file: `image/jpeg`,
77
+ * `image/*` or `.png`. Defaults to `accept`. A file that arrives without a
78
+ * type is judged by its extension.
79
+ */
80
+ types?: string[];
81
+ /** The largest file allowed, in bytes. */
82
+ maxSize?: number;
83
+ /** How many files the list may hold, not counting the ones turned away. */
84
+ maxFiles?: number;
85
+ /**
86
+ * The app's own rule: return why a file can't be added, or nothing. Runs
87
+ * before the type and size rules, so its reason is the one shown.
88
+ */
89
+ validate?: (file: File) => string | null | undefined;
90
+ /** How many files upload at once. The rest wait. */
91
+ concurrency?: number;
92
+ helperText?: ReactNode;
93
+ /** A problem with the field as a whole, from the form: "Add at least one photo." */
94
+ errorMessage?: string;
95
+ disabled?: boolean;
96
+ strings?: Partial<FileUploadStrings>;
97
+ className?: string;
98
+ }
99
+ /**
100
+ * Picks files, uploads each one through the app, and shows where each stands.
101
+ *
102
+ * The app owns every request: `upload` is called once per file, with a way to
103
+ * report progress, a way to say the server is working on it, and a signal
104
+ * that aborts when the file is removed. This owns picking, the rules, the
105
+ * list, each file's state, the keyboard and what a screen reader hears.
106
+ *
107
+ * Choosing is a button, which works everywhere; dragging files onto the field
108
+ * is an addition for a pointer, never the only way in.
109
+ */
110
+ declare function FileUpload<R = unknown>({ label, upload, onChange, accept, types, maxSize, maxFiles, validate, concurrency, helperText, errorMessage, disabled, strings: overrides, className, }: FileUploadProps<R>): import("react/jsx-runtime").JSX.Element;
111
+ export { FileUpload };