@blakesteve/roster 5.3.0 → 5.5.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 +177 -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/MediaPreview/MediaPreview.cjs +1 -0
- package/dist/components/molecules/MediaPreview/MediaPreview.d.ts +27 -0
- package/dist/components/molecules/MediaPreview/MediaPreview.js +55 -0
- package/dist/components/organisms/Dialog/Dialog.cjs +1 -1
- package/dist/components/organisms/Dialog/Dialog.d.ts +9 -1
- package/dist/components/organisms/Dialog/Dialog.js +26 -24
- package/dist/components/organisms/ModerationQueue/ModerationQueue.cjs +1 -0
- package/dist/components/organisms/ModerationQueue/ModerationQueue.d.ts +73 -0
- package/dist/components/organisms/ModerationQueue/ModerationQueue.js +428 -0
- package/dist/components/organisms/ModerationQueue/QueueEditDialog.cjs +1 -0
- package/dist/components/organisms/ModerationQueue/QueueEditDialog.d.ts +8 -0
- package/dist/components/organisms/ModerationQueue/QueueEditDialog.js +37 -0
- package/dist/components/organisms/ModerationQueue/QueueEditSheet.cjs +1 -0
- package/dist/components/organisms/ModerationQueue/QueueEditSheet.d.ts +12 -0
- package/dist/components/organisms/ModerationQueue/QueueEditSheet.js +30 -0
- package/dist/components/organisms/ModerationQueue/queue-actions.cjs +1 -0
- package/dist/components/organisms/ModerationQueue/queue-actions.d.ts +157 -0
- package/dist/components/organisms/ModerationQueue/queue-actions.js +6 -0
- 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 +6 -0
- package/dist/meta.json +3 -3
- package/dist/roster.cjs +1 -1
- package/dist/roster.css +1 -1
- package/dist/roster.js +123 -111
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1208,6 +1208,179 @@ escape the box. Lay the options out across with `orientation` rather than
|
|
|
1208
1208
|
through `optionsClassName`, for the same reason: the horizontal spacing that
|
|
1209
1209
|
keeps two radios 44px apart rides on that prop.
|
|
1210
1210
|
|
|
1211
|
+
### ModerationQueue
|
|
1212
|
+
|
|
1213
|
+
A queue of things waiting for a decision: submissions to approve, reports to
|
|
1214
|
+
resolve, claims to check before they publish. The app owns the data and every
|
|
1215
|
+
request; the queue owns the layout, the states, the steps and the keyboard.
|
|
1216
|
+
**It never fetches.** An action's `run` is the only path from a decision to
|
|
1217
|
+
the network, and the app says an item has left by removing it from `items`.
|
|
1218
|
+
|
|
1219
|
+
```tsx
|
|
1220
|
+
import { ModerationQueue, QueueEditDialog } from "@blakesteve/roster";
|
|
1221
|
+
|
|
1222
|
+
<ModerationQueue
|
|
1223
|
+
label="New recipes"
|
|
1224
|
+
items={recipes}
|
|
1225
|
+
getKey={(r) => r.id}
|
|
1226
|
+
itemLabel={(r) => `${r.title}, from ${r.by}`}
|
|
1227
|
+
renderMeta={(r) => <Badge size="xs">{r.category}</Badge>}
|
|
1228
|
+
renderContent={(r) => <p>{r.method}</p>}
|
|
1229
|
+
status={loading ? "loading" : failed ? "error" : "ready"}
|
|
1230
|
+
onRetry={reload}
|
|
1231
|
+
actions={(a) => [
|
|
1232
|
+
{ id: "list", label: "List it", done: "Listed", tone: "success",
|
|
1233
|
+
run: async (r) => { await api.list(r.id); await reload(); } },
|
|
1234
|
+
a.edit({ id: "edit", label: "Edit and list", done: "Listed with edits",
|
|
1235
|
+
edit: { title: "Tidy it up", submitLabel: "List it", surface: QueueEditDialog,
|
|
1236
|
+
initial: (r) => ({ title: r.title }), Form: TitleForm,
|
|
1237
|
+
validate: (d) => (d.title.trim() ? undefined : "Give it a title.") },
|
|
1238
|
+
run: async (r, { draft }) => { await api.list(r.id, draft); await reload(); } }),
|
|
1239
|
+
{ id: "send-back", label: "Send back", done: "Sent back", tone: "danger",
|
|
1240
|
+
reason: { prompt: "Why? The cook will see this.", required: true,
|
|
1241
|
+
options: [{ value: "duplicate", label: "We already have this one" }] },
|
|
1242
|
+
run: async (r, { reason }) => { await api.sendBack(r.id, reason); await reload(); } },
|
|
1243
|
+
]}
|
|
1244
|
+
/>;
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
**Four kinds of action.** One click (no step). `confirm` asks in place first.
|
|
1248
|
+
`reason` asks why in place: radios, a note, or both. `edit` opens a form the
|
|
1249
|
+
app supplies, inline by default or in a Dialog or Sheet (`QueueEditDialog`,
|
|
1250
|
+
`QueueEditSheet`, separate exports so a queue that doesn't use them doesn't
|
|
1251
|
+
ship them).
|
|
1252
|
+
|
|
1253
|
+
**Drafts are typed.** An edit's draft is whatever its `initial` returns, and
|
|
1254
|
+
`validate`, the form and `run` all get that type. Inside JSX, pass `actions` as
|
|
1255
|
+
a function and write edits with the `edit` it's handed, as above; outside it,
|
|
1256
|
+
`editAction(…)` does the same in a list typed `QueueAction<Item>[]`. A literal
|
|
1257
|
+
`{ edit: … }` doesn't compile, because its draft could only be `any`.
|
|
1258
|
+
|
|
1259
|
+
What it promises:
|
|
1260
|
+
|
|
1261
|
+
- **Only the clicked action is busy, and it keeps focus.** It shows a spinner
|
|
1262
|
+
and is `aria-disabled` rather than `disabled`, which would drop focus to the
|
|
1263
|
+
page. That item's other actions wait; every other item stays live.
|
|
1264
|
+
- **A decision that lands and removes the item** is announced in a polite live
|
|
1265
|
+
region ("Listed: Smoky red lentil soup, from Dana Okafor. 2 left."), and
|
|
1266
|
+
focus moves to the next item, the previous one, or the empty state. Never to
|
|
1267
|
+
the page. If the app keeps the item, the announcement drops the count and
|
|
1268
|
+
focus stays. `run` may resolve with `{ announce }` to say something else
|
|
1269
|
+
("Already decided by someone else.").
|
|
1270
|
+
- **A decision that fails** leaves the item where it was with an Alert saying
|
|
1271
|
+
why, and focus where it was. Inside a step, the step stays open with its
|
|
1272
|
+
draft. A reviewer who moved to another item meanwhile isn't pulled back.
|
|
1273
|
+
- **Several outcomes in one render are said in one sentence**: "Approved:
|
|
1274
|
+
Lentil soup. Approved: Rye crackers. 1 left."
|
|
1275
|
+
- **Steps are per item.** A reason half-written on one item survives another
|
|
1276
|
+
item's decision.
|
|
1277
|
+
- **Steps take focus and give it back.** Escape or Cancel returns focus to the
|
|
1278
|
+
button that opened the step.
|
|
1279
|
+
- **An item that disappears** (another reviewer, a reload) with its step open
|
|
1280
|
+
closes the step without deciding, and is announced once as decided
|
|
1281
|
+
elsewhere. Without a step, it moves focus only if focus was inside it.
|
|
1282
|
+
- **Headings follow the page.** `headingLevel` sets the queue's (2 by default),
|
|
1283
|
+
and items sit one below unless `itemHeadingLevel` says otherwise.
|
|
1284
|
+
- **Phones.** Every button is 44px tall at every width, and nothing scrolls
|
|
1285
|
+
sideways at 320px. Nothing animates as an item leaves.
|
|
1286
|
+
|
|
1287
|
+
Counts for a tab badge need no API: the app holds the items, so it's
|
|
1288
|
+
`items.length`. `MediaPreview` is the thumbnail to use in an item: when the
|
|
1289
|
+
image can't render (an expired link, a format the browser can't draw), it
|
|
1290
|
+
falls back to a link to the original in the same footprint.
|
|
1291
|
+
|
|
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
|
+
|
|
1211
1384
|
### Carousel
|
|
1212
1385
|
|
|
1213
1386
|
A sideways row: a row of cards, a selection strip, or a gallery of one per
|
|
@@ -1349,6 +1522,8 @@ function App() {
|
|
|
1349
1522
|
| `LiquidNav` | `LiquidTabs`' strip as a `nav` of links; takes a router's link component |
|
|
1350
1523
|
| `LiquidTabs` | Tab strip with a sliding pill; arrow keys, roving focus, 44px `lg` size |
|
|
1351
1524
|
| `MatchupCard` | Head-to-head comparison card |
|
|
1525
|
+
| `MediaPreview` | Thumbnail that falls back to "Preview unavailable, open original" when it can't render |
|
|
1526
|
+
| `FileUpload` | Picks files and uploads each through the app's `upload`, with per-file progress, retry and rules |
|
|
1352
1527
|
| `Pullquote` | A line lifted out of prose, as `<figure>` + `<blockquote>` + `<figcaption>` |
|
|
1353
1528
|
| `RadioGroup` | Radio set reporting one `string`; descriptions, error, 44px targets |
|
|
1354
1529
|
| `Toast` | Transient floating notice; `Toaster` + the imperative `toast` handle |
|
|
@@ -1360,8 +1535,9 @@ function App() {
|
|
|
1360
1535
|
| `ActionBar` | Sticky bottom action strip |
|
|
1361
1536
|
| `Countdown` | Live countdown timer |
|
|
1362
1537
|
| `DataTable` | Full-featured table with sorting and pagination via TanStack Table v9. Imported from `@blakesteve/roster/data-table` (see [DataTable columns](#datatable-columns)) |
|
|
1363
|
-
| `Dialog` | Modal dialog
|
|
1538
|
+
| `Dialog` | Modal dialog; `onAfterClose` runs once it has finished closing |
|
|
1364
1539
|
| `Footer` | Site footer |
|
|
1540
|
+
| `ModerationQueue` | Items waiting for a decision: busy per action, confirm, reason and edit steps, focus after an item leaves, a live region. Never fetches (see [ModerationQueue](#moderationqueue)) |
|
|
1365
1541
|
| `Navbar` | Responsive navigation bar with mobile slide-out |
|
|
1366
1542
|
| `Sheet` | Bottom sheet: drag, Escape or backdrop to dismiss; only its content scrolls; returns focus, even from a deep link; the title may be a node |
|
|
1367
1543
|
| `Table` | Static data table |
|
|
@@ -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 };
|