@blakesteve/roster 5.5.0 → 5.7.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 +183 -2
- package/dist/components/atoms/LoadingDots/LoadingDots.cjs +1 -0
- package/dist/components/atoms/LoadingDots/LoadingDots.d.ts +20 -0
- package/dist/components/atoms/LoadingDots/LoadingDots.js +43 -0
- package/dist/components/atoms/Progress/Progress.cjs +1 -0
- package/dist/components/atoms/Progress/Progress.d.ts +61 -0
- package/dist/components/atoms/Progress/Progress.js +106 -0
- package/dist/components/atoms/Progress/progress-values.cjs +1 -0
- package/dist/components/atoms/Progress/progress-values.d.ts +26 -0
- package/dist/components/atoms/Progress/progress-values.js +12 -0
- package/dist/components/atoms/Skeleton/Skeleton.cjs +1 -0
- package/dist/components/atoms/Skeleton/Skeleton.d.ts +79 -0
- package/dist/components/atoms/Skeleton/Skeleton.js +80 -0
- package/dist/components/atoms/Stat/Stat.cjs +1 -1
- package/dist/components/atoms/Stat/Stat.js +19 -19
- package/dist/components/atoms/Stat/stat-variants.cjs +1 -1
- package/dist/components/atoms/Stat/stat-variants.d.ts +1 -0
- package/dist/components/atoms/Stat/stat-variants.js +5 -4
- package/dist/components/molecules/FileUpload/FileUpload.cjs +1 -1
- package/dist/components/molecules/FileUpload/FileUpload.js +120 -141
- 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/molecules/ProgressField/ProgressField.cjs +1 -0
- package/dist/components/molecules/ProgressField/ProgressField.d.ts +23 -0
- package/dist/components/molecules/ProgressField/ProgressField.js +32 -0
- package/dist/components/molecules/SkeletonPresets/SkeletonPresets.cjs +1 -0
- package/dist/components/molecules/SkeletonPresets/SkeletonPresets.d.ts +55 -0
- package/dist/components/molecules/SkeletonPresets/SkeletonPresets.js +45 -0
- package/dist/components/molecules/StepProgress/StepProgress.cjs +1 -0
- package/dist/components/molecules/StepProgress/StepProgress.d.ts +36 -0
- package/dist/components/molecules/StepProgress/StepProgress.js +89 -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/Dialog/Dialog.d.ts +1 -1
- package/dist/index.d.ts +7 -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 +178 -159
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -591,6 +591,14 @@ per glyph, and holding it to 3:1 would flatten the effect.
|
|
|
591
591
|
`Spinner` holds still under reduced motion: the arc still reads as loading, and
|
|
592
592
|
its `label` (default "loading") says so.
|
|
593
593
|
|
|
594
|
+
The loading system has its own: `--roster-skeleton` and
|
|
595
|
+
`--roster-skeleton-highlight` are the skeleton's fill and the light that
|
|
596
|
+
crosses it, and the empty part of every progress bar (gray-200 and gray-100,
|
|
597
|
+
gray-700 and gray-600 in dark). Three durations, unset by default, change the
|
|
598
|
+
pace: `--roster-skeleton-duration` (1.8s), `--roster-progress-travel-duration`
|
|
599
|
+
(1.4s, the indeterminate pill) and `--roster-dots-duration` (1.2s). Under
|
|
600
|
+
reduced motion all three stop on a resting frame.
|
|
601
|
+
|
|
594
602
|
### Scrollbars
|
|
595
603
|
|
|
596
604
|
`custom-scrollbar` gives any scrollable surface a slim themed scrollbar.
|
|
@@ -1381,6 +1389,172 @@ submit button from sending its form twice. It no longer dims, since
|
|
|
1381
1389
|
the spinner says it's busy. `loadingLabel` names the spinner ("loading" by
|
|
1382
1390
|
default). `disabled` is unchanged and wins over `isLoading`.
|
|
1383
1391
|
|
|
1392
|
+
### The loading system
|
|
1393
|
+
|
|
1394
|
+
Five components for the time between asking and having, chosen by what the
|
|
1395
|
+
wait is:
|
|
1396
|
+
|
|
1397
|
+
| The wait | Use |
|
|
1398
|
+
| --- | --- |
|
|
1399
|
+
| Content is coming, and you know its shape | `Skeleton`, or a preset, inside `SkeletonRegion` |
|
|
1400
|
+
| A task with a fraction (bytes, pages, items) | `ProgressField` (a `Progress` with its label and value) |
|
|
1401
|
+
| A count of discrete things | `ProgressField` or `Progress`, `variant="segmented"` |
|
|
1402
|
+
| Named stages of a flow | `StepProgress` |
|
|
1403
|
+
| A model generating, before its first token | `LoadingDots` |
|
|
1404
|
+
| A short wait with nothing to show | `Spinner` |
|
|
1405
|
+
|
|
1406
|
+
`SegmentBar` isn't one of these: it shows how a whole divides into parts (a
|
|
1407
|
+
vote split, a budget), not how much of a task is done.
|
|
1408
|
+
|
|
1409
|
+
**Skeleton.** A placeholder in the shape of what's loading, so a page keeps
|
|
1410
|
+
its layout and nothing moves when data lands.
|
|
1411
|
+
|
|
1412
|
+
```tsx
|
|
1413
|
+
import { SkeletonRegion, SkeletonCard } from "@blakesteve/roster";
|
|
1414
|
+
|
|
1415
|
+
<SkeletonRegion loading={!trails} label="Loading trails" skeleton={<SkeletonCard media={140} lines={2} />}>
|
|
1416
|
+
{trails && <TrailCard trail={trails[0]} />}
|
|
1417
|
+
</SkeletonRegion>;
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
- A `line` is one line box of its text size: `sm` (12/16), `md` (14/20), `lg`
|
|
1421
|
+
(16/24), or `inherit`. It's a no-break space set in that size with a bar
|
|
1422
|
+
drawn over it, so `lines={3}` is exactly the height of three lines of that
|
|
1423
|
+
text. A `block` takes a `height`; a `circle` takes Avatar's sizes.
|
|
1424
|
+
- Presets draw the real component's box: `SkeletonCard` (a Card with optional
|
|
1425
|
+
media, a `text-base` title and `text-sm` body lines 8px under it),
|
|
1426
|
+
`SkeletonTableRow` (Table's own cells), `SkeletonAvatar`, `SkeletonStat`.
|
|
1427
|
+
- `SkeletonRegion` sets `aria-busy` while loading, hides the skeleton from
|
|
1428
|
+
screen readers and says `label` once (a polite status beside the region,
|
|
1429
|
+
empty until a moment after loading starts). For a grid of cards, wrap the
|
|
1430
|
+
grid in one region, or pass `announce={false}` to all but one. Skeletons are
|
|
1431
|
+
always `aria-hidden`. `SkeletonTableRow` is a `tr`, so it can't sit in a
|
|
1432
|
+
region: set `aria-busy` on the table yourself.
|
|
1433
|
+
- `SkeletonRegion` renders two siblings, the hidden status and then the
|
|
1434
|
+
region, so the status counts in a parent's `first:` and `nth-child`
|
|
1435
|
+
selectors. It takes no room.
|
|
1436
|
+
- The fill is `--roster-skeleton` (gray-200, gray-700 in dark), with
|
|
1437
|
+
`--roster-skeleton-highlight` crossing it. Neutral rather than the brand
|
|
1438
|
+
shimmer Countdown uses, and its own pair, so retinting one never recolors the
|
|
1439
|
+
other. Under reduced motion it holds still.
|
|
1440
|
+
|
|
1441
|
+
**Progress and ProgressField.** `ProgressField` is the form to reach for: the
|
|
1442
|
+
label and value on one baseline above the bar, an optional detail line below.
|
|
1443
|
+
|
|
1444
|
+
```tsx
|
|
1445
|
+
<ProgressField label="Uploading photos" value={sent} max={total} detail={`${mb(sent)} of ${mb(total)}`} />
|
|
1446
|
+
<ProgressField label="Picks" variant="segmented" segments={["done", "done", "na", "current", "remaining"]} />
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
- Leave `value` out for indeterminate (a pill crossing the track). Set `busy`
|
|
1450
|
+
when a value stands but work goes on: the bar breathes in place.
|
|
1451
|
+
- Four statuses (`default`, `success`, `warning`, `error`), solid fills only:
|
|
1452
|
+
each clears 3:1 against the track and the page in both themes, where a
|
|
1453
|
+
translucent fill would fade into whatever is behind it. Two sizes, 6px and
|
|
1454
|
+
10px.
|
|
1455
|
+
- Segments take `done`, `current`, `remaining` and `na` (not part of this
|
|
1456
|
+
one: dashed, and not counted). They're capsules unless `rounded={false}`.
|
|
1457
|
+
- The bar is `aria-hidden`. A status region beside it says the value as text,
|
|
1458
|
+
at most every 1.5 seconds and the end at once, because a changed
|
|
1459
|
+
`aria-label` isn't reliably announced and a `progressbar` may beep or speak
|
|
1460
|
+
on every update. `announce={false}` when something else already says it.
|
|
1461
|
+
- On dark glass in the light theme, set `--roster-skeleton` on that surface to
|
|
1462
|
+
`var(--roster-primary-200)`: the empty cells take it and the bar keeps its
|
|
1463
|
+
extent (primary-600 against primary-200 is 6.4:1). Not in the dark theme,
|
|
1464
|
+
where the fill is primary-400 and that pair is about 2:1; there the default
|
|
1465
|
+
gray-700 track keeps done against remaining at 3.8:1, though its edge is
|
|
1466
|
+
faint on the glass.
|
|
1467
|
+
- In forced colors the fill keeps the system highlight color, the track and
|
|
1468
|
+
empty cells an outline, and the current cell its border.
|
|
1469
|
+
|
|
1470
|
+
**StepProgress.** Named stages: `variant="stepper"` shows every step with a
|
|
1471
|
+
numbered marker and connectors that fill behind the current one;
|
|
1472
|
+
`"segmented"` is the compact form, always with "Step 2 of 5". The current
|
|
1473
|
+
step is `aria-current="step"`, finished ones say "done", and the position is
|
|
1474
|
+
said when it changes.
|
|
1475
|
+
|
|
1476
|
+
**LoadingDots.** Only for a model generating before its first token. It takes
|
|
1477
|
+
the surrounding text's size and color, rests at 1, 0.6 and 0.3 opacity (also
|
|
1478
|
+
what reduced motion shows), and is replaced by the text once it starts: never
|
|
1479
|
+
shown beside it.
|
|
1480
|
+
|
|
1481
|
+
Every string in all five is a prop with an English default.
|
|
1482
|
+
|
|
1483
|
+
### Pagination
|
|
1484
|
+
|
|
1485
|
+
Moves through pages of a list, built for a long list browsed on a phone.
|
|
1486
|
+
Controlled: you hold `page` and `pageCount`.
|
|
1487
|
+
|
|
1488
|
+
```tsx
|
|
1489
|
+
import { Pagination } from "@blakesteve/roster";
|
|
1490
|
+
|
|
1491
|
+
// Pages in your own state: every control is a button.
|
|
1492
|
+
<Pagination page={page} pageCount={79} onPageChange={setPage} />
|
|
1493
|
+
|
|
1494
|
+
// Pages that are URLs: every control is a real link.
|
|
1495
|
+
<Pagination
|
|
1496
|
+
page={page}
|
|
1497
|
+
pageCount={79}
|
|
1498
|
+
getPageHref={(p) => `/catalog?page=${p}`}
|
|
1499
|
+
linkComponent={NextLink} // from a "use client" wrapper
|
|
1500
|
+
navigate={router.push} // for the compact form's page picker
|
|
1501
|
+
/>
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
**Buttons or links.** Without `getPageHref`, controls are buttons and
|
|
1505
|
+
`onPageChange(page)` is the only thing that changes the page. With it, every
|
|
1506
|
+
control is an `<a href>` (crawlable, shareable, openable in a new tab) rendered
|
|
1507
|
+
through `linkComponent` (a plain `<a>` by default; it gets `href` and `to`, so
|
|
1508
|
+
`next/link` and React Router's `Link` both work). `onPageChange` is then called
|
|
1509
|
+
on a plain click before the link is followed. The compact form's page picker
|
|
1510
|
+
has no link to follow, so it calls `navigate(href)`, or loads the page.
|
|
1511
|
+
|
|
1512
|
+
**Two forms, chosen by the pager's own width** (a container query, so it's
|
|
1513
|
+
right in a sidebar and needs no measuring):
|
|
1514
|
+
|
|
1515
|
+
- **448px and up:** previous, the first page, the current page with a
|
|
1516
|
+
neighbor each side, the last page, gaps between, and next. That's always
|
|
1517
|
+
seven equal slots, so the buttons don't shift under the pointer as you page.
|
|
1518
|
+
The words "Previous" and "Next" join the arrows from 576px.
|
|
1519
|
+
- **Under 448px:** previous, a page picker ("Page 40 of 79"), and next, with
|
|
1520
|
+
the first and last pages joining from 384px. Any page is two taps away, and
|
|
1521
|
+
on a phone the picker is the system's own list.
|
|
1522
|
+
|
|
1523
|
+
`layout` picks one form (`"pages"`, `"compact"`), or `"arrows"` for first,
|
|
1524
|
+
previous, next and last beside a count the app shows itself, which is what
|
|
1525
|
+
DataTable uses.
|
|
1526
|
+
|
|
1527
|
+
What it promises:
|
|
1528
|
+
|
|
1529
|
+
- **Every control is a 44px target,** at every width.
|
|
1530
|
+
- **The current page is `aria-current="page"`,** the pager is a `nav` named by
|
|
1531
|
+
`labels.nav`, and every control's name says the page it goes to ("Next page
|
|
1532
|
+
(41)"). A new page is said in a polite live region ("Page 41 of 79").
|
|
1533
|
+
- **Focus stays on the control that was pressed.** A page number is the same
|
|
1534
|
+
element afterwards, and an arrow that reaches the end stays focusable,
|
|
1535
|
+
`aria-disabled` rather than `disabled`. To take a reader to the top of the
|
|
1536
|
+
new page, focus your list's heading in `onPageChange`.
|
|
1537
|
+
- **Every word is a `labels` entry,** page numbers included (`labels.number`).
|
|
1538
|
+
- **No Font Awesome:** the chevrons are inline SVG.
|
|
1539
|
+
- **The picker waits for the keyboard.** A pick from its list goes at once.
|
|
1540
|
+
Arrow keys on the closed picker (which step it on Windows and Linux) hold the
|
|
1541
|
+
choice until Enter or leaving it; Escape puts it back.
|
|
1542
|
+
|
|
1543
|
+
Things to know:
|
|
1544
|
+
|
|
1545
|
+
- **Give the pager a width.** It sizes its form by the width it's given, so in
|
|
1546
|
+
a slot that sizes to its content (an `ml-auto` box beside a heading, an `auto`
|
|
1547
|
+
grid column) it falls back to 18rem, the compact form. For numbers there,
|
|
1548
|
+
give the slot a width (`flex-1`, a `1fr` column) or pass `layout="pages"`.
|
|
1549
|
+
- **`siblings` above 1** needs more room than the 448px switch allows for;
|
|
1550
|
+
force `layout` if you widen it.
|
|
1551
|
+
- **When you move focus yourself,** pass `announce={false}`: the heading you
|
|
1552
|
+
focus says the new page, and the live region would say it again.
|
|
1553
|
+
- **In jsdom tests,** where no CSS applies, `layout="auto"` renders both forms;
|
|
1554
|
+
pass a `layout`, or query inside `[data-pagination-layout="pages"]`.
|
|
1555
|
+
- **Wire it fully.** With neither `onPageChange` nor `getPageHref` nothing
|
|
1556
|
+
pages, and in link mode without `navigate` the picker does a full page load.
|
|
1557
|
+
|
|
1384
1558
|
### Carousel
|
|
1385
1559
|
|
|
1386
1560
|
A sideways row: a row of cards, a selection strip, or a gallery of one per
|
|
@@ -1498,7 +1672,10 @@ function App() {
|
|
|
1498
1672
|
| `AvatarStrip` | Stacked avatar row with overflow chip, dismiss button, trailing slot, and label area |
|
|
1499
1673
|
| `CollapsibleSection` | Clamps any content (prose, chips, image grids) to a fixed height with a fade and expand/collapse toggle |
|
|
1500
1674
|
| `Select` | Dropdown selector, on the same size scale as `Button` and `Input` |
|
|
1501
|
-
| `SegmentBar` | Proportional horizontal bar divided into colored segments with optional legend
|
|
1675
|
+
| `SegmentBar` | Proportional horizontal bar divided into colored segments with optional legend (a split, not progress) |
|
|
1676
|
+
| `Skeleton` | Line, block or circle placeholder at the content's exact size; `SkeletonRegion` sets `aria-busy` |
|
|
1677
|
+
| `Progress` | Continuous or segmented progress bar: determinate, indeterminate or busy; per-segment state |
|
|
1678
|
+
| `LoadingDots` | Three dots for a model generating before its first token; replaced by the text |
|
|
1502
1679
|
| `Stat` | A figure with its label and, optionally, its source; a valid `<dl>` group, or `semantics="standalone"` |
|
|
1503
1680
|
| `Spinner` | Loading indicator |
|
|
1504
1681
|
| `Switch` | Toggle switch; its label toggles it, and every size is a 44px target |
|
|
@@ -1524,6 +1701,10 @@ function App() {
|
|
|
1524
1701
|
| `MatchupCard` | Head-to-head comparison card |
|
|
1525
1702
|
| `MediaPreview` | Thumbnail that falls back to "Preview unavailable, open original" when it can't render |
|
|
1526
1703
|
| `FileUpload` | Picks files and uploads each through the app's `upload`, with per-file progress, retry and rules |
|
|
1704
|
+
| `Pagination` | Page numbers with gaps, or a compact page picker on narrow screens; buttons or real links, 44px targets |
|
|
1705
|
+
| `SkeletonCard`, `SkeletonTableRow`, `SkeletonAvatar`, `SkeletonStat` | Skeleton presets in the real component's box |
|
|
1706
|
+
| `ProgressField` | Progress with a label and value on one baseline and a detail line: the form to reach for |
|
|
1707
|
+
| `StepProgress` | Named steps: a stepper with markers and connectors, or a compact segmented "Step x of y" |
|
|
1527
1708
|
| `Pullquote` | A line lifted out of prose, as `<figure>` + `<blockquote>` + `<figcaption>` |
|
|
1528
1709
|
| `RadioGroup` | Radio set reporting one `string`; descriptions, error, 44px targets |
|
|
1529
1710
|
| `Toast` | Transient floating notice; `Toaster` + the imperative `toast` handle |
|
|
@@ -1534,7 +1715,7 @@ function App() {
|
|
|
1534
1715
|
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1535
1716
|
| `ActionBar` | Sticky bottom action strip |
|
|
1536
1717
|
| `Countdown` | Live countdown timer |
|
|
1537
|
-
| `DataTable` | Full-featured table with sorting and pagination via TanStack Table v9
|
|
1718
|
+
| `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)) |
|
|
1538
1719
|
| `Dialog` | Modal dialog; `onAfterClose` runs once it has finished closing |
|
|
1539
1720
|
| `Footer` | Site footer |
|
|
1540
1721
|
| `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)) |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),a=require("react"),o=require("../../../lib/utils.cjs"),l=[{rest:"rst:opacity-100",delay:"0s"},{rest:"rst:opacity-60",delay:"0.2s"},{rest:"rst:opacity-30",delay:"0.4s"}];function u({label:s="Loading",announce:r=!0,className:i}){const[n,c]=a.useState("");return a.useEffect(()=>{const t=setTimeout(()=>c(s),50);return()=>clearTimeout(t)},[s]),e.jsxs("span",{role:r?"status":void 0,className:o.cn("rst:inline-flex rst:items-center rst:gap-[0.25em] rst:align-middle",i),"data-loading-dots":"",children:[l.map((t,d)=>e.jsx("span",{"aria-hidden":"true",className:o.cn("rst:block rst:h-[0.4em] rst:w-[0.4em] rst:rounded-full rst:bg-current rst:animate-dot","rst:forced-colors:forced-color-adjust-none rst:forced-colors:bg-[CanvasText]",t.rest),style:{animationDelay:t.delay,animationFillMode:"backwards"}},d)),e.jsx("span",{className:"rst:sr-only",children:r?n:s})]})}exports.LoadingDots=u;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface LoadingDotsProps {
|
|
2
|
+
/** What a screen reader hears: "Writing a reply". */
|
|
3
|
+
label?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Say `label` in a polite status of its own (the default). Off inside a
|
|
6
|
+
* chat log or any live region that already announces what's added to it,
|
|
7
|
+
* where it would be said twice; the label is still there to be read.
|
|
8
|
+
*/
|
|
9
|
+
announce?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Three dots for a model that's generating and hasn't sent anything yet.
|
|
14
|
+
*
|
|
15
|
+
* It stands in for text, so it's sized and colored by the text around it (em
|
|
16
|
+
* and `currentColor`), and it goes the moment the first token arrives: render
|
|
17
|
+
* the text instead of it, never beside it.
|
|
18
|
+
*/
|
|
19
|
+
declare function LoadingDots({ label, announce, className }: LoadingDotsProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export { LoadingDots };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as c, jsx as e } from "react/jsx-runtime";
|
|
3
|
+
import { useState as l, useEffect as m } from "react";
|
|
4
|
+
import { cn as a } from "../../../lib/utils.js";
|
|
5
|
+
const u = [
|
|
6
|
+
{ rest: "rst:opacity-100", delay: "0s" },
|
|
7
|
+
{ rest: "rst:opacity-60", delay: "0.2s" },
|
|
8
|
+
{ rest: "rst:opacity-30", delay: "0.4s" }
|
|
9
|
+
];
|
|
10
|
+
function g({ label: s = "Loading", announce: r = !0, className: o }) {
|
|
11
|
+
const [i, n] = l("");
|
|
12
|
+
return m(() => {
|
|
13
|
+
const t = setTimeout(() => n(s), 50);
|
|
14
|
+
return () => clearTimeout(t);
|
|
15
|
+
}, [s]), /* @__PURE__ */ c(
|
|
16
|
+
"span",
|
|
17
|
+
{
|
|
18
|
+
role: r ? "status" : void 0,
|
|
19
|
+
className: a("rst:inline-flex rst:items-center rst:gap-[0.25em] rst:align-middle", o),
|
|
20
|
+
"data-loading-dots": "",
|
|
21
|
+
children: [
|
|
22
|
+
u.map((t, d) => /* @__PURE__ */ e(
|
|
23
|
+
"span",
|
|
24
|
+
{
|
|
25
|
+
"aria-hidden": "true",
|
|
26
|
+
className: a(
|
|
27
|
+
"rst:block rst:h-[0.4em] rst:w-[0.4em] rst:rounded-full rst:bg-current rst:animate-dot",
|
|
28
|
+
/* Forced colors drop backgrounds; these keep the text's color. */
|
|
29
|
+
"rst:forced-colors:forced-color-adjust-none rst:forced-colors:bg-[CanvasText]",
|
|
30
|
+
t.rest
|
|
31
|
+
),
|
|
32
|
+
style: { animationDelay: t.delay, animationFillMode: "backwards" }
|
|
33
|
+
},
|
|
34
|
+
d
|
|
35
|
+
)),
|
|
36
|
+
/* @__PURE__ */ e("span", { className: "rst:sr-only", children: r ? i : s })
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
g as LoadingDots
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),u=require("react"),c=require("../../../lib/utils.cjs"),G=require("./progress-values.cjs"),H={percent:r=>`${r}%`,count:(r,s)=>`${r} of ${s}`,indeterminate:"Loading",status:(r,s)=>r?`${r}: ${s}`:s},b={default:"rst:bg-primary-600 rst:dark:bg-primary-400",success:"rst:bg-success-600 rst:dark:bg-success-400",warning:"rst:bg-amber-700 rst:dark:bg-amber-400",error:"rst:bg-error-600 rst:dark:bg-error-400"},O={default:"rst:border-primary-600 rst:dark:border-primary-400",success:"rst:border-success-600 rst:dark:border-success-400",warning:"rst:border-amber-700 rst:dark:border-amber-400",error:"rst:border-error-600 rst:dark:border-error-400"},p="rst:bg-[var(--roster-skeleton)]",R="rst:forced-colors:outline-solid rst:forced-colors:outline-1",h="rst:forced-colors:forced-color-adjust-none rst:forced-colors:bg-[Highlight]",T={sm:"rst:h-1.5",md:"rst:h-2.5"};function P(r,s,t){const[m,n]=u.useState(r),e=u.useRef(r),l=u.useRef(Number.NEGATIVE_INFINITY),a=u.useRef(r);return u.useEffect(()=>{if(a.current=r,r===e.current)return;const f=t?0:Math.max(0,l.current+s-Date.now()),g=setTimeout(()=>{a.current!==e.current&&(l.current=Date.now(),e.current=a.current,n(a.current))},f);return()=>clearTimeout(g)},[r,s,t]),m}function V({value:r,max:s=100,variant:t="continuous",segments:m,status:n="default",size:e="md",rounded:l=!0,busy:a=!1,label:f,valueText:g,announce:j=!0,announceEvery:y=1500,labels:v,className:x}){const i={...H,...v},{known:w,fraction:k,percent:I,states:E,done:L,applicable:S,complete:$}=G.progressValues({value:r,max:s,variant:t,segments:m}),F=g??(t==="segmented"?i.count(L,S):w?i.percent(I):i.indeterminate),q=$,A=P(i.status(f,F),y,q),N=a&&"rst:animate-pulse rst:motion-reduce:animate-none",C=t==="segmented"?o.jsx("div",{"aria-hidden":"true",className:c.cn("rst:flex rst:w-full",e==="sm"?"rst:gap-0.5":"rst:gap-1",T[e],N),"data-progress-track":"",children:E.map((d,D)=>o.jsx("span",{className:c.cn("rst:h-full rst:min-w-0 rst:flex-1",l?"rst:rounded-full":"rst:rounded-sm",d==="done"&&[b[n],h],d==="current"&&[p,"rst:border-2",O[n]],d==="remaining"&&[p,R],d==="na"&&"rst:border rst:border-dashed rst:border-gray-500 rst:dark:border-gray-400"),"data-segment":d},D))}):o.jsx("div",{"aria-hidden":"true",className:c.cn("rst:relative rst:w-full rst:overflow-hidden rst:rounded-full",T[e],p,R,N),"data-progress-track":"",children:w?o.jsx("div",{className:c.cn("rst:h-full rst:rounded-full rst:transition-[width] rst:motion-reduce:transition-none",b[n],h),style:{width:`${k*100}%`,minWidth:k>0?e==="sm"?"0.375rem":"0.625rem":void 0},"data-progress-fill":""}):o.jsx("div",{className:c.cn("rst:absolute rst:inset-y-0 rst:left-0 rst:w-[30%] rst:rounded-full rst:animate-progress-travel",b[n],h),"data-progress-fill":"indeterminate"})});return o.jsxs("div",{className:c.cn("rst:w-full",x),"data-progress":t,"data-status":n,children:[C,j&&o.jsx("span",{role:"status",className:"rst:sr-only","data-progress-status":"",children:A})]})}exports.Progress=V;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { SegmentState } from './progress-values';
|
|
2
|
+
export type ProgressStatus = "default" | "success" | "warning" | "error";
|
|
3
|
+
/** A segment's state: finished, under way, still to come, or not part of this one. */
|
|
4
|
+
export type ProgressSegmentState = SegmentState;
|
|
5
|
+
/** Every word Progress says. Pass any of them in `labels` to replace it. */
|
|
6
|
+
export interface ProgressLabels {
|
|
7
|
+
/** A continuous value as said: "45%". */
|
|
8
|
+
percent: (percent: number) => string;
|
|
9
|
+
/** A segmented value as said: "3 of 8". Segments that don't apply aren't counted. */
|
|
10
|
+
count: (done: number, total: number) => string;
|
|
11
|
+
/** Said when there's no value to give. */
|
|
12
|
+
indeterminate: string;
|
|
13
|
+
/** The label and the value together: "Uploading photos: 45%". */
|
|
14
|
+
status: (label: string | undefined, value: string) => string;
|
|
15
|
+
}
|
|
16
|
+
export interface ProgressProps {
|
|
17
|
+
/**
|
|
18
|
+
* How far, from 0 to `max`. Leave it out for indeterminate: work under way
|
|
19
|
+
* with no figure, drawn as a pill crossing the track.
|
|
20
|
+
*/
|
|
21
|
+
value?: number | null;
|
|
22
|
+
/** The whole: 100 by default; for segmented, the number of segments. */
|
|
23
|
+
max?: number;
|
|
24
|
+
/**
|
|
25
|
+
* - `"continuous"`: one filled track, for a fraction (bytes, percent).
|
|
26
|
+
* - `"segmented"`: one cell per unit, for a count of discrete things:
|
|
27
|
+
* "twelve of sixteen" reads without a number beside it.
|
|
28
|
+
*/
|
|
29
|
+
variant?: "continuous" | "segmented";
|
|
30
|
+
/** Segmented: each segment's state, when there's more to say than done and not done. */
|
|
31
|
+
segments?: ProgressSegmentState[];
|
|
32
|
+
status?: ProgressStatus;
|
|
33
|
+
size?: "sm" | "md";
|
|
34
|
+
/** Segmented: round each cell into a capsule (the default), or keep them square. */
|
|
35
|
+
rounded?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The value stands but work goes on (a server finishing what it was sent):
|
|
38
|
+
* the bar breathes in place. Stops under reduced motion.
|
|
39
|
+
*/
|
|
40
|
+
busy?: boolean;
|
|
41
|
+
/** What's progressing, for the status a screen reader hears: "Uploading photos". */
|
|
42
|
+
label?: string;
|
|
43
|
+
/** The value as said, in place of the percent or the count: "3 of 8 files". */
|
|
44
|
+
valueText?: string;
|
|
45
|
+
/** Give the value to screen readers in a polite status region. Off when something else already says it. */
|
|
46
|
+
announce?: boolean;
|
|
47
|
+
/** The least time between two announcements, in milliseconds. */
|
|
48
|
+
announceEvery?: number;
|
|
49
|
+
labels?: Partial<ProgressLabels>;
|
|
50
|
+
className?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* How far through something is: a continuous bar, or a segmented one.
|
|
54
|
+
*
|
|
55
|
+
* The bar is a picture and is hidden from screen readers; a status region
|
|
56
|
+
* beside it carries the value as text, because a changed `aria-label` isn't
|
|
57
|
+
* reliably announced and a `progressbar` may beep or speak on every update.
|
|
58
|
+
* Most screens want ProgressField, which adds a visible label and value.
|
|
59
|
+
*/
|
|
60
|
+
declare function Progress({ value, max, variant, segments, status, size, rounded, busy, label, valueText: valueOverride, announce, announceEvery, labels: overrides, className, }: ProgressProps): import("react/jsx-runtime").JSX.Element;
|
|
61
|
+
export { Progress };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as a, jsxs as H } from "react/jsx-runtime";
|
|
3
|
+
import { useState as _, useRef as g, useEffect as K } from "react";
|
|
4
|
+
import { cn as d } from "../../../lib/utils.js";
|
|
5
|
+
import { progressValues as O } from "./progress-values.js";
|
|
6
|
+
const V = {
|
|
7
|
+
percent: (r) => `${r}%`,
|
|
8
|
+
count: (r, s) => `${r} of ${s}`,
|
|
9
|
+
indeterminate: "Loading",
|
|
10
|
+
status: (r, s) => r ? `${r}: ${s}` : s
|
|
11
|
+
}, b = {
|
|
12
|
+
default: "rst:bg-primary-600 rst:dark:bg-primary-400",
|
|
13
|
+
success: "rst:bg-success-600 rst:dark:bg-success-400",
|
|
14
|
+
warning: "rst:bg-amber-700 rst:dark:bg-amber-400",
|
|
15
|
+
error: "rst:bg-error-600 rst:dark:bg-error-400"
|
|
16
|
+
}, B = {
|
|
17
|
+
default: "rst:border-primary-600 rst:dark:border-primary-400",
|
|
18
|
+
success: "rst:border-success-600 rst:dark:border-success-400",
|
|
19
|
+
warning: "rst:border-amber-700 rst:dark:border-amber-400",
|
|
20
|
+
error: "rst:border-error-600 rst:dark:border-error-400"
|
|
21
|
+
}, p = "rst:bg-[var(--roster-skeleton)]", T = "rst:forced-colors:outline-solid rst:forced-colors:outline-1", h = "rst:forced-colors:forced-color-adjust-none rst:forced-colors:bg-[Highlight]", y = { sm: "rst:h-1.5", md: "rst:h-2.5" };
|
|
22
|
+
function M(r, s, t) {
|
|
23
|
+
const [u, o] = _(r), e = g(r), l = g(Number.NEGATIVE_INFINITY), n = g(r);
|
|
24
|
+
return K(() => {
|
|
25
|
+
if (n.current = r, r === e.current) return;
|
|
26
|
+
const m = t ? 0 : Math.max(0, l.current + s - Date.now()), f = setTimeout(() => {
|
|
27
|
+
n.current !== e.current && (l.current = Date.now(), e.current = n.current, o(n.current));
|
|
28
|
+
}, m);
|
|
29
|
+
return () => clearTimeout(f);
|
|
30
|
+
}, [r, s, t]), u;
|
|
31
|
+
}
|
|
32
|
+
function J({
|
|
33
|
+
value: r,
|
|
34
|
+
max: s = 100,
|
|
35
|
+
variant: t = "continuous",
|
|
36
|
+
segments: u,
|
|
37
|
+
status: o = "default",
|
|
38
|
+
size: e = "md",
|
|
39
|
+
rounded: l = !0,
|
|
40
|
+
busy: n = !1,
|
|
41
|
+
label: m,
|
|
42
|
+
valueText: f,
|
|
43
|
+
announce: I = !0,
|
|
44
|
+
announceEvery: v = 1500,
|
|
45
|
+
labels: E,
|
|
46
|
+
className: L
|
|
47
|
+
}) {
|
|
48
|
+
const i = { ...V, ...E }, { known: w, fraction: k, percent: R, states: $, done: F, applicable: A, complete: C } = O({ value: r, max: s, variant: t, segments: u }), D = f ?? (t === "segmented" ? i.count(F, A) : w ? i.percent(R) : i.indeterminate), S = C, j = M(i.status(m, D), v, S), N = n && "rst:animate-pulse rst:motion-reduce:animate-none", x = t === "segmented" ? /* @__PURE__ */ a(
|
|
49
|
+
"div",
|
|
50
|
+
{
|
|
51
|
+
"aria-hidden": "true",
|
|
52
|
+
className: d("rst:flex rst:w-full", e === "sm" ? "rst:gap-0.5" : "rst:gap-1", y[e], N),
|
|
53
|
+
"data-progress-track": "",
|
|
54
|
+
children: $.map((c, G) => /* @__PURE__ */ a(
|
|
55
|
+
"span",
|
|
56
|
+
{
|
|
57
|
+
className: d(
|
|
58
|
+
"rst:h-full rst:min-w-0 rst:flex-1",
|
|
59
|
+
l ? "rst:rounded-full" : "rst:rounded-sm",
|
|
60
|
+
c === "done" && [b[o], h],
|
|
61
|
+
/* Under way: the track inside a border of the fill's color. */
|
|
62
|
+
c === "current" && [p, "rst:border-2", B[o]],
|
|
63
|
+
c === "remaining" && [p, T],
|
|
64
|
+
/* Not part of this one: a dashed outline, neither filled nor to
|
|
65
|
+
fill. Gray-500 on light and gray-400 on dark clear 3:1. */
|
|
66
|
+
c === "na" && "rst:border rst:border-dashed rst:border-gray-500 rst:dark:border-gray-400"
|
|
67
|
+
),
|
|
68
|
+
"data-segment": c
|
|
69
|
+
},
|
|
70
|
+
G
|
|
71
|
+
))
|
|
72
|
+
}
|
|
73
|
+
) : /* @__PURE__ */ a(
|
|
74
|
+
"div",
|
|
75
|
+
{
|
|
76
|
+
"aria-hidden": "true",
|
|
77
|
+
className: d("rst:relative rst:w-full rst:overflow-hidden rst:rounded-full", y[e], p, T, N),
|
|
78
|
+
"data-progress-track": "",
|
|
79
|
+
children: w ? /* @__PURE__ */ a(
|
|
80
|
+
"div",
|
|
81
|
+
{
|
|
82
|
+
className: d(
|
|
83
|
+
"rst:h-full rst:rounded-full rst:transition-[width] rst:motion-reduce:transition-none",
|
|
84
|
+
b[o],
|
|
85
|
+
h
|
|
86
|
+
),
|
|
87
|
+
style: { width: `${k * 100}%`, minWidth: k > 0 ? e === "sm" ? "0.375rem" : "0.625rem" : void 0 },
|
|
88
|
+
"data-progress-fill": ""
|
|
89
|
+
}
|
|
90
|
+
) : /* @__PURE__ */ a(
|
|
91
|
+
"div",
|
|
92
|
+
{
|
|
93
|
+
className: d("rst:absolute rst:inset-y-0 rst:left-0 rst:w-[30%] rst:rounded-full rst:animate-progress-travel", b[o], h),
|
|
94
|
+
"data-progress-fill": "indeterminate"
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
return /* @__PURE__ */ H("div", { className: d("rst:w-full", L), "data-progress": t, "data-status": o, children: [
|
|
100
|
+
x,
|
|
101
|
+
I && /* @__PURE__ */ a("span", { role: "status", className: "rst:sr-only", "data-progress-status": "", children: j })
|
|
102
|
+
] });
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
J as Progress
|
|
106
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function u({value:t,max:c=100,variant:a="continuous",segments:h}){const s=Number.isFinite(c)?Math.max(0,c):100,e=t!=null&&Number.isFinite(t),n=e&&s>0?Math.min(1,Math.max(0,t/s)):0,m=n>=1?100:n<=0?0:Math.min(99,Math.max(1,Math.round(n*100))),r=a==="segmented"?h??Array.from({length:Math.floor(s)},(o,d)=>e&&d<Math.floor(t)?"done":"remaining"):[],l=r.filter(o=>o==="done").length,i=r.filter(o=>o!=="na").length,g=a==="segmented"?i>0&&l===i:e&&n>=1;return{known:e,fraction:n,percent:m,states:r,done:l,applicable:i,complete:g}}exports.progressValues=u;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The numbers a Progress shows and says, worked out once so the bar, the
|
|
3
|
+
* status and a ProgressField's visible value can't disagree.
|
|
4
|
+
*/
|
|
5
|
+
export type SegmentState = "done" | "current" | "remaining" | "na";
|
|
6
|
+
export interface ProgressValues {
|
|
7
|
+
/** Whether there's a figure at all; without one, the bar is indeterminate. */
|
|
8
|
+
known: boolean;
|
|
9
|
+
/** 0 to 1. */
|
|
10
|
+
fraction: number;
|
|
11
|
+
/** The percent as said and shown: 1 to 99 while under way, never rounded to 0 or 100. */
|
|
12
|
+
percent: number;
|
|
13
|
+
/** Segmented: every segment's state. */
|
|
14
|
+
states: SegmentState[];
|
|
15
|
+
done: number;
|
|
16
|
+
/** Segments that count: all but `na`. */
|
|
17
|
+
applicable: number;
|
|
18
|
+
/** Nothing left: the value is at the whole, or every applicable segment is done. */
|
|
19
|
+
complete: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function progressValues({ value, max, variant, segments, }: {
|
|
22
|
+
value?: number | null;
|
|
23
|
+
max?: number;
|
|
24
|
+
variant?: "continuous" | "segmented";
|
|
25
|
+
segments?: SegmentState[];
|
|
26
|
+
}): ProgressValues;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function f({
|
|
2
|
+
value: n,
|
|
3
|
+
max: c = 100,
|
|
4
|
+
variant: a = "continuous",
|
|
5
|
+
segments: m
|
|
6
|
+
}) {
|
|
7
|
+
const s = Number.isFinite(c) ? Math.max(0, c) : 100, o = n != null && Number.isFinite(n), t = o && s > 0 ? Math.min(1, Math.max(0, n / s)) : 0, l = t >= 1 ? 100 : t <= 0 ? 0 : Math.min(99, Math.max(1, Math.round(t * 100))), r = a === "segmented" ? m ?? Array.from({ length: Math.floor(s) }, (e, d) => o && d < Math.floor(n) ? "done" : "remaining") : [], h = r.filter((e) => e === "done").length, i = r.filter((e) => e !== "na").length, M = a === "segmented" ? i > 0 && h === i : o && t >= 1;
|
|
8
|
+
return { known: o, fraction: t, percent: l, states: r, done: h, applicable: i, complete: M };
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
f as progressValues
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),x=require("react"),c=require("../../../lib/utils.cjs"),k={sm:"rst:text-xs",md:"rst:text-sm",lg:"rst:text-base",inherit:""},f={xs:"rst:h-6 rst:w-6",sm:"rst:h-8 rst:w-8",md:"rst:h-10 rst:w-10",lg:"rst:h-12 rst:w-12",xl:"rst:h-16 rst:w-16"},b={sm:"rst:rounded-sm",md:"rst:rounded-md",lg:"rst:rounded-lg",xl:"rst:rounded-xl",full:"rst:rounded-full"},h="rst:animate-skeleton rst:forced-colors:outline-solid rst:forced-colors:outline-1";function S({shape:s="line",size:r,lines:a=1,width:e,height:u,radius:i,className:l}){if(s==="circle"){const o=r&&r in f?r:"md";return t.jsx("span",{"aria-hidden":"true",className:c.cn("rst:block rst:shrink-0 rst:rounded-full rst:border rst:border-transparent",f[o],h,l),"data-skeleton":"circle"})}if(s==="block")return t.jsx("span",{"aria-hidden":"true",className:c.cn("rst:block rst:w-full",b[i??"lg"],h,l),style:{height:u??"8rem",width:e},"data-skeleton":"block"});const m=r&&r in k?r:"md",n=Math.max(1,Math.floor(a)),j=o=>{const d=o===n-1;return t.jsxs("span",{className:c.cn("rst:relative rst:block",n>1&&!d&&"rst:w-full"),style:{width:n>1?d?e??"60%":void 0:e},children:[" ",t.jsx("span",{className:c.cn("rst:absolute rst:inset-x-0 rst:top-1/2 rst:h-[0.7em] rst:-translate-y-1/2",b[i??"sm"],h)})]},o)};return t.jsx("span",{"aria-hidden":"true",className:c.cn("rst:block",k[m],l),"data-skeleton":"line",children:Array.from({length:n},(o,d)=>j(d))})}function g({loading:s,skeleton:r,label:a="Loading",announce:e=!0,children:u,className:i}){const[l,m]=x.useState("");return x.useEffect(()=>{const n=setTimeout(()=>m(s&&e?a:""),s?50:0);return()=>clearTimeout(n)},[s,a,e]),t.jsxs(t.Fragment,{children:[t.jsx("span",{role:"status",className:"rst:sr-only","data-skeleton-status":"",children:l}),t.jsx("div",{"aria-busy":s||void 0,className:i,"data-skeleton-region":s?"loading":"ready",children:s?t.jsx("div",{"aria-hidden":"true",children:r}):u})]})}exports.Skeleton=S;exports.SkeletonRegion=g;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* A text size a line stands in for: its font size and line height, so one
|
|
4
|
+
* line of skeleton is exactly one line of the text that replaces it.
|
|
5
|
+
* `inherit` takes whatever the surrounding text is.
|
|
6
|
+
*/
|
|
7
|
+
declare const LINE_SIZE: {
|
|
8
|
+
readonly sm: "rst:text-xs";
|
|
9
|
+
readonly md: "rst:text-sm";
|
|
10
|
+
readonly lg: "rst:text-base";
|
|
11
|
+
readonly inherit: "";
|
|
12
|
+
};
|
|
13
|
+
declare const CIRCLE_SIZE: {
|
|
14
|
+
readonly xs: "rst:h-6 rst:w-6";
|
|
15
|
+
readonly sm: "rst:h-8 rst:w-8";
|
|
16
|
+
readonly md: "rst:h-10 rst:w-10";
|
|
17
|
+
readonly lg: "rst:h-12 rst:w-12";
|
|
18
|
+
readonly xl: "rst:h-16 rst:w-16";
|
|
19
|
+
};
|
|
20
|
+
declare const RADIUS: {
|
|
21
|
+
readonly sm: "rst:rounded-sm";
|
|
22
|
+
readonly md: "rst:rounded-md";
|
|
23
|
+
readonly lg: "rst:rounded-lg";
|
|
24
|
+
readonly xl: "rst:rounded-xl";
|
|
25
|
+
readonly full: "rst:rounded-full";
|
|
26
|
+
};
|
|
27
|
+
export interface SkeletonProps {
|
|
28
|
+
/**
|
|
29
|
+
* - `"line"`: a line of text, one line box tall at its `size`.
|
|
30
|
+
* - `"block"`: an image, a chart, any box: `height` sets it.
|
|
31
|
+
* - `"circle"`: an avatar, at Avatar's sizes.
|
|
32
|
+
*/
|
|
33
|
+
shape?: "line" | "block" | "circle";
|
|
34
|
+
/**
|
|
35
|
+
* For a line, the text it stands in for: `sm` (12px on 16), `md` (14px on
|
|
36
|
+
* 20), `lg` (16px on 24), or `inherit`. For a circle, Avatar's sizes.
|
|
37
|
+
*/
|
|
38
|
+
size?: keyof typeof LINE_SIZE | keyof typeof CIRCLE_SIZE;
|
|
39
|
+
/** How many lines, for a paragraph. The last is shorter, as a last line is. */
|
|
40
|
+
lines?: number;
|
|
41
|
+
/** The width: a line's, or the last line's when there are several. */
|
|
42
|
+
width?: CSSProperties["width"];
|
|
43
|
+
/** A block's height. */
|
|
44
|
+
height?: CSSProperties["height"];
|
|
45
|
+
/** A block's corners, from the radius scale. */
|
|
46
|
+
radius?: keyof typeof RADIUS;
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A placeholder in the shape of what's loading.
|
|
51
|
+
*
|
|
52
|
+
* Hidden from screen readers, always: a skeleton is a picture of content that
|
|
53
|
+
* isn't there yet. The region it stands in for says it's busy; use
|
|
54
|
+
* `SkeletonRegion`, which does that, or set `aria-busy` on it yourself.
|
|
55
|
+
*/
|
|
56
|
+
declare function Skeleton({ shape, size, lines, width, height, radius, className }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
export interface SkeletonRegionProps {
|
|
58
|
+
/** While true, the skeleton shows and the region says it's busy. */
|
|
59
|
+
loading: boolean;
|
|
60
|
+
/** What shows while loading. */
|
|
61
|
+
skeleton: ReactNode;
|
|
62
|
+
/** What a screen reader hears while loading: "Loading the schedule". */
|
|
63
|
+
label?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Say `label` (the default). Off for all but one when several regions load
|
|
66
|
+
* together, a grid of cards say, or wrap them in one region instead.
|
|
67
|
+
*/
|
|
68
|
+
announce?: boolean;
|
|
69
|
+
children?: ReactNode;
|
|
70
|
+
className?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The region a skeleton stands in for: `aria-busy` while loading, the
|
|
74
|
+
* skeleton hidden from screen readers, and `label` said once, politely.
|
|
75
|
+
* When `loading` turns false the content replaces the skeleton in the same
|
|
76
|
+
* box, so a skeleton drawn at the content's size means nothing moves.
|
|
77
|
+
*/
|
|
78
|
+
declare function SkeletonRegion({ loading, skeleton, label, announce, children, className }: SkeletonRegionProps): import("react/jsx-runtime").JSX.Element;
|
|
79
|
+
export { Skeleton, SkeletonRegion };
|