@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.
- package/README.md +170 -1
- package/dist/components/atoms/Button/Button.cjs +1 -1
- package/dist/components/atoms/Button/Button.d.ts +11 -0
- package/dist/components/atoms/Button/Button.js +42 -32
- package/dist/components/atoms/Button/button-variants.cjs +1 -1
- package/dist/components/atoms/Button/button-variants.js +3 -1
- package/dist/components/molecules/FileUpload/FileUpload.cjs +1 -0
- package/dist/components/molecules/FileUpload/FileUpload.d.ts +111 -0
- package/dist/components/molecules/FileUpload/FileUpload.js +403 -0
- package/dist/components/molecules/FileUpload/file-rules.cjs +1 -0
- package/dist/components/molecules/FileUpload/file-rules.d.ts +22 -0
- package/dist/components/molecules/FileUpload/file-rules.js +43 -0
- package/dist/components/molecules/Pagination/Pagination.cjs +1 -0
- package/dist/components/molecules/Pagination/Pagination.d.ts +116 -0
- package/dist/components/molecules/Pagination/Pagination.js +247 -0
- package/dist/components/molecules/Pagination/page-items.cjs +1 -0
- package/dist/components/molecules/Pagination/page-items.d.ts +13 -0
- package/dist/components/molecules/Pagination/page-items.js +11 -0
- package/dist/components/organisms/DataTable/DataTable.cjs +1 -1
- package/dist/components/organisms/DataTable/DataTable.d.ts +8 -1
- package/dist/components/organisms/DataTable/DataTable.js +61 -98
- package/dist/components/organisms/ModerationQueue/ModerationQueue.cjs +1 -1
- package/dist/components/organisms/ModerationQueue/ModerationQueue.js +190 -192
- package/dist/components/organisms/Navbar/Navbar.cjs +1 -1
- package/dist/components/organisms/Navbar/Navbar.d.ts +2 -0
- package/dist/components/organisms/Navbar/Navbar.js +49 -49
- package/dist/index.d.ts +2 -0
- package/dist/meta.json +3 -3
- package/dist/node_modules/@fortawesome/free-solid-svg-icons/index.cjs +1 -1
- package/dist/node_modules/@fortawesome/free-solid-svg-icons/index.js +30 -50
- package/dist/roster.cjs +1 -1
- package/dist/roster.css +1 -1
- package/dist/roster.js +102 -98
- 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
|
|
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
|
|
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
|
|
3
|
-
import
|
|
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
|
|
6
|
-
import { buttonVariants as
|
|
7
|
-
import { Button as
|
|
8
|
-
const
|
|
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:
|
|
11
|
-
colorScheme:
|
|
12
|
-
variant:
|
|
13
|
-
size:
|
|
14
|
-
isLoading:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
n
|
|
38
|
+
f === "dark" && "dark",
|
|
39
|
+
n && "rst:cursor-progress",
|
|
40
|
+
i
|
|
33
41
|
),
|
|
34
|
-
ref:
|
|
42
|
+
ref: k,
|
|
35
43
|
disabled: u,
|
|
36
|
-
...
|
|
44
|
+
...h,
|
|
45
|
+
"aria-disabled": n ? !0 : s["aria-disabled"],
|
|
46
|
+
"aria-busy": n ? !0 : s["aria-busy"],
|
|
37
47
|
children: [
|
|
38
|
-
|
|
39
|
-
!
|
|
40
|
-
|
|
41
|
-
!
|
|
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
|
-
|
|
57
|
+
D.displayName = "Button";
|
|
48
58
|
export {
|
|
49
|
-
|
|
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
|
-
|
|
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 };
|