@scalewing/react 1.48.0 → 1.50.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/CHANGELOG.md +22 -0
- package/README.md +75 -2
- package/dist/components/EmptyState.d.ts +23 -0
- package/dist/components/EmptyState.js +40 -0
- package/dist/components/Grid.d.ts +11 -3
- package/dist/components/Grid.js +13 -4
- package/dist/components/Hero.d.ts +26 -0
- package/dist/components/Hero.js +23 -0
- package/dist/components/ScreenPreview.d.ts +25 -0
- package/dist/components/ScreenPreview.js +20 -0
- package/dist/components/Stepper.d.ts +11 -1
- package/dist/components/Stepper.js +10 -8
- package/dist/components/Text.js +3 -0
- package/dist/components/screen-preview/PreviewGroup.d.ts +8 -0
- package/dist/components/screen-preview/PreviewGroup.js +8 -0
- package/dist/components/screen-preview/PreviewRow.d.ts +15 -0
- package/dist/components/screen-preview/PreviewRow.js +11 -0
- package/dist/components/screen-preview/PreviewTiles.d.ts +16 -0
- package/dist/components/screen-preview/PreviewTiles.js +15 -0
- package/dist/components/stepper/StepperCountFrame.d.ts +15 -0
- package/dist/components/stepper/StepperCountFrame.js +25 -0
- package/dist/css/css-components.js +9 -0
- package/dist/css/css-document.js +3 -0
- package/dist/css/css-empty-state.d.ts +2 -0
- package/dist/css/css-empty-state.js +77 -0
- package/dist/css/css-grid-span.js +5 -2
- package/dist/css/css-grid.d.ts +13 -0
- package/dist/css/css-grid.js +17 -1
- package/dist/css/css-hero.d.ts +2 -0
- package/dist/css/css-hero.js +62 -0
- package/dist/css/css-screen-preview.d.ts +10 -0
- package/dist/css/css-screen-preview.js +232 -0
- package/dist/css/css-stepper-unit.d.ts +9 -0
- package/dist/css/css-stepper-unit.js +102 -0
- package/dist/css/css-stepper.js +5 -1
- package/dist/css/css-theme-frame.d.ts +10 -0
- package/dist/css/css-theme-frame.js +26 -0
- package/dist/css/stylesheet.js +6 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +8 -1
- package/dist/stepper-count.d.ts +8 -0
- package/dist/stepper-count.js +17 -0
- package/dist/styles.css +393 -1
- package/dist/theme/ThemeProvider.d.ts +10 -1
- package/dist/theme/ThemeProvider.js +4 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.50.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d707587: `ScreenPreview`: a drawn stand-in for an app's screen on a public page, a `figure` whose screen is one image (`role="img"`) named by `label`, with an optional `eyebrow`, `title` and visible `caption`. `frame="card"` (default) or `"device"`, a 20rem phone from `md` and the card below it. Its content is `PreviewGroup`s of `PreviewRow`s (`kind` `plain`, `total`, `note`, `filled`, `alert`) and `PreviewTiles` (three to a row on a phone, six from `md`). New classes `sw-screen-preview*`, `sw-preview-*`, with `screenPreviewFrames` and `previewRowKinds`; a blank label, an unknown frame or kind, or bad tiles throw a `RangeError`. No new tokens or dependencies. Request: `docs/requests/teisoro-screen-preview.md`.
|
|
8
|
+
- 7a647d3: `ThemeProvider` takes `frame` (`none` by default, or `site`): `site` writes `data-frame="site"` and makes the themed root a column at least the window's height whose last child (the footer) is pushed to its foot, so a short page's footer meets the window's foot. `themeFrames` and `ThemeFrame` list the frames; an unknown one throws a `RangeError`. No new tokens or dependencies. Request: `docs/requests/teisoro-site-frame.md`.
|
|
9
|
+
|
|
10
|
+
## 1.49.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- 2ae2974: `Hero`: a public page's opening, a `section` named by its title. An optional `eyebrow` (muted label words), the `title` (the page's `h1` in the `hero` size), an optional `lead` and `actions`, and an optional `media` picture that keeps its own width beside the words from `md` and sits under them below it, where the actions stack to the full width. With no picture the words keep the reading width. New classes `sw-hero*`; a blank `title` throws a `RangeError`. No new tokens or dependencies. Request: `docs/requests/teisoro-hero.md`.
|
|
15
|
+
- dc0c896: `EmptyState` (new): where a list or a page has nothing yet, a centred panel on the surface with a dashed hairline edge that fills its column (a page column or a Card). `title` is a heading at `titleAs` (`h2` by default, or `h3`; `emptyStateTitleElements`), `description` a muted line, `icon` a decorative glyph in a quiet square, `actions` a centred row that wraps (44 px Buttons on a coarse pointer). Forced colours keep the dashed edge. A blank `title` or `description` or an unknown `titleAs` throws a `RangeError`. New classes `sw-empty-state`, `sw-empty-state-icon`, `sw-empty-state-title`, `sw-empty-state-description`, `sw-empty-state-actions`. No new tokens or dependencies. Request: `docs/requests/futmas-empty-state.md`.
|
|
16
|
+
- 6d3cbe8: `Grid` takes `fit` (`narrow` 15rem, `medium` 16.25rem, `wide` 18.75rem): as many columns as the grid's width holds at that minimum, sharing the row, one when the grid is narrower, so a row of cards reflows with no breakpoint. Not with `columns` or `columnsBelow`; either, or an unknown `fit`, throws a `RangeError`. New classes `sw-grid-fit-*` and the `gridFits` list and `GridFit` type. No new tokens or dependencies. Request: `docs/requests/teisoro-grid-fit.md`.
|
|
17
|
+
- b466bbb: `Text` takes the tokens' new variants `hero` (an `h1` by default), `headline` (an `h2`) and `lead` (a `p`), each stepping down to its compact size below `md` like `display` and `heading`. New classes `sw-text-hero`, `sw-text-headline`, `sw-text-lead`, generated from the tokens. Needs `@scalewing/tokens` 1.7.0. Request: `docs/requests/teisoro-showcase-type.md`.
|
|
18
|
+
- 7b57979: `Stepper` takes `unit` (opt-in), a short muted word after the count inside its frame, such as "min" or "players". The count is read with it (`aria-valuetext` "90 min") unless `valueText` says it in words; the unit itself is hidden from assistive technology. The frame draws the control surface, focus ring and the invalid, changed and disabled states; the count keeps its few-digit width, so the frame never follows it. A blank `unit` throws a `RangeError`. New classes `sw-stepper-count`, `sw-stepper-unit`. No new tokens or dependencies. Request: `docs/requests/futmas-stepper-unit.md`.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [b466bbb]
|
|
23
|
+
- @scalewing/tokens@1.7.0
|
|
24
|
+
|
|
3
25
|
## 1.48.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -35,10 +35,16 @@ import {
|
|
|
35
35
|
InfoTip,
|
|
36
36
|
Separator,
|
|
37
37
|
Band,
|
|
38
|
+
Hero,
|
|
39
|
+
ScreenPreview,
|
|
40
|
+
PreviewGroup,
|
|
41
|
+
PreviewRow,
|
|
42
|
+
PreviewTiles,
|
|
38
43
|
SiteFooter,
|
|
39
44
|
SiteHeader,
|
|
40
45
|
DescriptionList,
|
|
41
46
|
DescriptionItem,
|
|
47
|
+
EmptyState,
|
|
42
48
|
FixtureGroup,
|
|
43
49
|
FixtureRow,
|
|
44
50
|
ListGroup,
|
|
@@ -79,6 +85,16 @@ The accent does not keep 4.5:1 on the grey in every palette, so keep links and a
|
|
|
79
85
|
</ThemeProvider>
|
|
80
86
|
```
|
|
81
87
|
|
|
88
|
+
`frame="site"` (opt-in; `themeFrames`, default `"none"`) makes the themed node a public site's page: a column at least the window's height (the canvas already keeps that height) whose last child is pushed to its foot, so a page shorter than the window still has its footer at the window's foot. It writes `data-frame="site"` on the themed node. Put the header, `main` and footer as the provider's direct children, the footer last: anything rendered after it as a direct child (a live region, a toast host) would take the footer's place at the foot, so put those inside `main`. An unknown frame throws a `RangeError`.
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<ThemeProvider frame="site" colorScheme="light">
|
|
92
|
+
<SiteHeader … />
|
|
93
|
+
<main>…</main>
|
|
94
|
+
<SiteFooter … />
|
|
95
|
+
</ThemeProvider>
|
|
96
|
+
```
|
|
97
|
+
|
|
82
98
|
```html
|
|
83
99
|
<div class="sw-padding-top-4 sw-padding-x-4"></div>
|
|
84
100
|
```
|
|
@@ -189,7 +205,7 @@ A chosen photo can show itself: `value.preview` is the address of a small image
|
|
|
189
205
|
/>
|
|
190
206
|
```
|
|
191
207
|
|
|
192
|
-
`Stepper` is a whole-number count changed with − and + or typed. The count is a native text input with `role="spinbutton"` and `inputmode="numeric"`, named by `label` (visible above it, or `labelVisuallyHidden` where table headers say it) and the one tab stop: ArrowUp and ArrowDown step it by `step` (default 1), Home goes to `min` and End to a finite `max` (a range below zero takes `inputmode="text"`, since a phone's numeric pad has no minus key). The − and + buttons are pointer targets outside the tab order, named by `decrementLabel` and `incrementLabel` (your copy), and a press on one leaves focus where it was; the count and both buttons sit in a `group` named by the label. − is disabled at `min` (default 0) and + at `max` (no upper bound when left out). `value` is a count or `null` for an empty box, which the buttons treat as `min` and `placeholder` can fill. Typed text is refused unless it is digits (with a leading minus when `min` is below zero), so `onChange` only receives a whole number or `null`; a typed count is reported as typed even outside the bounds, so say why in `error` (a polite message, `aria-invalid` and the danger border, as `Field`'s), and the buttons then move it back inside them. `aria-describedby` takes ids of elements that describe the count, such as the value it adds up to, read before an `error`'s message. `changed` takes `Field`'s changed look; `readOnly` keeps the count focusable and hides the buttons in their own room; `disabled` stops all three. `limitAppearance="plain"` (default `"faded"`) keeps − at `min` and + at `max` in their usual look instead of faded, for a count sheet where a faded − reads as broken; they are still disabled, a disabled or read-only Stepper is unchanged, forced colors still grey them, and an unknown value throws a `RangeError`. The count is a few digits wide and keeps that width (`width="fixed"`), or fills its container (`width="full"`, for a table cell). Bounds that are not whole numbers, `min` above `max`, or a `step` that is not above zero throw a `RangeError`.
|
|
208
|
+
`Stepper` is a whole-number count changed with − and + or typed. The count is a native text input with `role="spinbutton"` and `inputmode="numeric"`, named by `label` (visible above it, or `labelVisuallyHidden` where table headers say it) and the one tab stop: ArrowUp and ArrowDown step it by `step` (default 1), Home goes to `min` and End to a finite `max` (a range below zero takes `inputmode="text"`, since a phone's numeric pad has no minus key). The − and + buttons are pointer targets outside the tab order, named by `decrementLabel` and `incrementLabel` (your copy), and a press on one leaves focus where it was; the count and both buttons sit in a `group` named by the label. − is disabled at `min` (default 0) and + at `max` (no upper bound when left out). `value` is a count or `null` for an empty box, which the buttons treat as `min` and `placeholder` can fill. Typed text is refused unless it is digits (with a leading minus when `min` is below zero), so `onChange` only receives a whole number or `null`; a typed count is reported as typed even outside the bounds, so say why in `error` (a polite message, `aria-invalid` and the danger border, as `Field`'s), and the buttons then move it back inside them. `aria-describedby` takes ids of elements that describe the count, such as the value it adds up to, read before an `error`'s message. `changed` takes `Field`'s changed look; `readOnly` keeps the count focusable and hides the buttons in their own room; `disabled` stops all three. `limitAppearance="plain"` (default `"faded"`) keeps − at `min` and + at `max` in their usual look instead of faded, for a count sheet where a faded − reads as broken; they are still disabled, a disabled or read-only Stepper is unchanged, forced colors still grey them, and an unknown value throws a `RangeError`. The count is a few digits wide and keeps that width (`width="fixed"`), or fills its container (`width="full"`, for a table cell). `unit` (opt-in) is a short muted word after the count inside its frame, such as "min" or "players": the frame (`sw-stepper-count`) draws the control surface, focus ring and the invalid, changed and disabled states, a press on the word focuses the count, and the count keeps its few-digit width with its digits against the word, so the frame never follows it. The word (`sw-stepper-unit`) is hidden from assistive technology; the count is read with it (`aria-valuetext` "90 min", none while the box is empty), and a `valueText` you pass wins. The default joins the count and the word as given, so a word that changes with the count ("players") reads "1 players": pass `valueText` in your language's right form ("1 player", "11 players"). A blank `unit` throws a `RangeError`. Bounds that are not whole numbers, `min` above `max`, or a `step` that is not above zero throw a `RangeError`.
|
|
193
209
|
|
|
194
210
|
```tsx
|
|
195
211
|
<Stepper
|
|
@@ -840,6 +856,24 @@ The consumer needs no PDF.js dependency of its own. With another bundler, build
|
|
|
840
856
|
<Skeleton label="Loading rangers" variant="rows" lines={4} />
|
|
841
857
|
```
|
|
842
858
|
|
|
859
|
+
`EmptyState` is where a list or a page has nothing yet: a centred panel on the surface with a dashed hairline edge (the md radius) that fills its column, so it sits in a page column or inside a `Card`. `title` (required) says what is empty ("No teams in Spring 2027 yet"), a heading at `titleAs` (`emptyStateTitleElements`: `"h2"` by default in a page, `"h3"` inside a card under its own heading) in title type. `description` is a muted line under it, usually what to do next. `icon` is a decorative glyph (Lucide in your app) in a quiet subtle-fill square over the title, hidden from assistive technology. `actions` are the ways out, such as a primary and a neutral `Button`, in a centred row that wraps (a 44 px target on a touch screen). The panel is a plain `div`, not a landmark or a live region: if it replaces a list after a person's action, say so yourself. An empty slot (nothing, an empty Fragment or array, or blank words) draws no box. Inside a bordered `Card` (the default glass or `outlined`) the dashed edge sits just inside the card's own hairline and reads as a double edge: where the empty panel is the card's whole content, use it in place of the card, or in an `elevated` or `filled` Card, which draw no hairline. Forced colours keep the dashed edge. A blank `title` or `description` or an unknown `titleAs` throws a `RangeError`.
|
|
860
|
+
|
|
861
|
+
```tsx
|
|
862
|
+
<EmptyState
|
|
863
|
+
icon={<Users />}
|
|
864
|
+
title="No nests counted yet"
|
|
865
|
+
description="Bring back last spring's nests, or paste a list."
|
|
866
|
+
actions={
|
|
867
|
+
<>
|
|
868
|
+
<Button onPress={copy}>Copy last spring's 12 nests</Button>
|
|
869
|
+
<Button variant="neutral" onPress={paste}>
|
|
870
|
+
Paste a list
|
|
871
|
+
</Button>
|
|
872
|
+
</>
|
|
873
|
+
}
|
|
874
|
+
/>
|
|
875
|
+
```
|
|
876
|
+
|
|
843
877
|
`GlyphTile` is a small rounded square in a categorical colour with the consumer's glyph in white: the mark of a kind beside its name, such as a service in a list. `hue` is one of the `@scalewing/tokens` categorical colours (`green`, `blue`, `orange`, `purple`, `teal`, `rose`, or `neutral`); which kind takes which hue is yours. `size` is `sm` (24), `md` (28, the default) or `lg` (32); the glyph is drawn at spacing step 4 in white, which keeps 3:1 on every fill; pass the SVG (a Lucide component) as the tile's direct child, since only a direct-child SVG is sized. It is always `aria-hidden`, so put the kind's name in visible text beside it. An unknown `hue` or `size` throws a `RangeError`. It may hold up to three letters in place of the glyph, such as initials (`<GlyphTile hue="neutral" size="sm">NSL</GlyphTile>`): words given as its children are drawn in one line at weight 600 and sized from the tile, not the text round it (one letter at half its side, two at 0.42, three at a third), so they fit every size; blank words or more than three letters throw a `RangeError`.
|
|
844
878
|
|
|
845
879
|
```tsx
|
|
@@ -1018,6 +1052,35 @@ const { headings, omissions } = inspectMarkdown(page.body, {
|
|
|
1018
1052
|
/>
|
|
1019
1053
|
```
|
|
1020
1054
|
|
|
1055
|
+
`Hero` is a public page's opening, a `section` named by its title: an optional `eyebrow` (muted label words), the `title` (the page's one `h1`, in the `hero` size), an optional `lead` (the `lead` size) and `actions` (links or buttons in a wrapping row, stacked to the full width below `md`), with an optional `media` picture. From `md` the picture keeps its own width at the row's end and the words take the rest; below `md` it sits centred under the words. With no picture the words keep the reading width (`--sw-reading-max`). Put it in the page's column (`sw-container`); it sets no gutter or fill of its own. The title always names the section (`aria-labelledby` is not a prop). Leave out `actions` or `media` rather than passing an empty fragment, which keeps its row or column. A blank `title` throws a `RangeError`.
|
|
1056
|
+
|
|
1057
|
+
```tsx
|
|
1058
|
+
<Hero
|
|
1059
|
+
eyebrow="For birdwatchers"
|
|
1060
|
+
title="Every heron counted."
|
|
1061
|
+
lead="The north marsh in every season, from the hide to the shore."
|
|
1062
|
+
actions={<ButtonLink href="/visit">Plan a visit</ButtonLink>}
|
|
1063
|
+
media={<img alt="The heron hide at dawn" src="/hide.jpg" />}
|
|
1064
|
+
/>
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
`ScreenPreview` is a drawn stand-in for an app's screen on a public page, until a real screenshot replaces it: a `figure` whose screen is one image (`role="img"`) named by `label`, its alternative text, so a screen reader hears that sentence instead of the rows; an optional `caption` shows under it as the `figcaption`. An optional `eyebrow` and `title` head the screen. Its content is `PreviewGroup`s of `PreviewRow`s (`label` at the start, `value` at the end in tabular numerals; `kind` `plain`, `total` in bold past a hairline, `note` a small label over its words, `filled` on the quiet fill, or `alert` on the danger tint) and `PreviewTiles` (`items` of `{ id, label, value }`, three to a row on a phone, six from `md`). `frame="card"` (default) is a surface card whose groups are parted by hairlines; `frame="device"` draws a 20rem phone from `md` (a bezel in the text colour round a quiet screen with a surface header and each group a card) and the card below `md`. A blank `label`, an unknown `frame` or `kind`, no tiles, or a blank or repeated tile id throws a `RangeError`.
|
|
1068
|
+
|
|
1069
|
+
```tsx
|
|
1070
|
+
<ScreenPreview
|
|
1071
|
+
frame="device"
|
|
1072
|
+
label="Today's census: 14 herons, 6 egrets, 20 birds in all"
|
|
1073
|
+
eyebrow="Census · Today"
|
|
1074
|
+
title="Birds on the marsh"
|
|
1075
|
+
caption="A photo of the real board goes here."
|
|
1076
|
+
>
|
|
1077
|
+
<PreviewGroup>
|
|
1078
|
+
<PreviewRow label="Grey herons" value="14" />
|
|
1079
|
+
<PreviewRow kind="total" label="All birds" value="20" />
|
|
1080
|
+
</PreviewGroup>
|
|
1081
|
+
</ScreenPreview>
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1021
1084
|
`SiteFooter` is a public site's footer on the dark inverse band (`Band tone="inverse"`, so a brand moves it with a theme overlay of `inverse`). Supply `brand` (such as a logo for a dark background), an optional `tagline`, and `groups`: each `{ id, label, links }` is a `nav` named by its label, which shows above its links (hidden from assistive technology, since the `nav` already carries it). Each link is `{ id, label, href, current?, lang?, hrefLang? }`; `lang` and `hrefLang` pass through to the link unchecked; `current` sets `aria-current="page"` and draws it in the band's words at label weight, the others in its muted words, underlined on hover and focus. `legal` and `end` fill the bottom row past a hairline, such as the copyright and a language switch. Below `md` the brand takes the full width and the groups two columns; from `md` the brand and the groups share one spread row. Links get the 44 px target on a coarse pointer. Put it last in the page's `body` (outside a `section` or `article`) so it is the page's `contentinfo` landmark. No groups, a group with no links, blank or repeated ids, blank labels or a blank `href` throw a `RangeError`.
|
|
1022
1085
|
|
|
1023
1086
|
```tsx
|
|
@@ -1181,7 +1244,7 @@ A row can also carry three trailing parts. `unread` marks it new for the person,
|
|
|
1181
1244
|
|
|
1182
1245
|
`--sw-color-accentSubtle` is the accent tint from `@scalewing/tokens`: a quiet fill behind accent text or glyphs that keeps 4.5:1. The stylesheet and every palette file set it, and a palette that moves the accent or surface retints it.
|
|
1183
1246
|
|
|
1184
|
-
`Text` steps `display` and `
|
|
1247
|
+
`Text` steps `hero`, `display`, `headline`, `heading` and `lead` down to the compact token sizes below the `md` breakpoint; consumers do not size type per viewport. `hero` (an `h1`), `headline` (an `h2`) and `lead` (a `p`) are a public page's sizes: its one opening title, its section titles and the sentence under them; an app's screens keep `display` and `heading`.
|
|
1185
1248
|
|
|
1186
1249
|
`sw-container-reading` centres a column at the reading width (`--sw-reading-max`, 46rem, `Article`'s), as `sw-container` centres a page's column at the container width (72rem): `width: 100%`, the max width and auto inline margins, full width below it. Use it for a form or text that reads better narrower than the page, such as an open form of one column. As with `sw-container`, the width is the content box's, so put padding or a border on a child.
|
|
1187
1250
|
|
|
@@ -1193,6 +1256,16 @@ A row can also carry three trailing parts. `unread` marks it new for the person,
|
|
|
1193
1256
|
|
|
1194
1257
|
`Grid` places children in one to four (or six) equal-width columns with a token `gap` step. `columnsBelow={{ md: 2 }}` drops to fewer columns below the `md` breakpoint so tiles and stat cards stay readable on a phone. It accepts every `Box` prop.
|
|
1195
1258
|
|
|
1259
|
+
`fit` (`narrow` 15rem, `medium` 16.25rem, `wide` 18.75rem) fits the columns to the grid's width instead of a fixed count: as many columns as fit at that minimum, sharing the row, and one column when the grid is narrower than it, so a row of cards reflows on a tablet and stacks on a phone with no breakpoint. A fitted grid takes no `columns` or `columnsBelow` (either throws a `RangeError`, as an unknown `fit` does); keep `columnSpan` for fixed-column grids. `gridFits` lists the names.
|
|
1260
|
+
|
|
1261
|
+
```tsx
|
|
1262
|
+
<Grid fit="narrow" gap={4}>
|
|
1263
|
+
{habitats.map((habitat) => (
|
|
1264
|
+
<Card key={habitat.id}>…</Card>
|
|
1265
|
+
))}
|
|
1266
|
+
</Grid>
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1196
1269
|
Every `Box`-based component (`Box`, `Stack`, `Inline`, `Card`, `Grid`, …) takes `columnSpan` (1, 2, 3, 4 or 6) to cover several columns of the `Grid` it sits in directly, through the generated `sw-grid-span-*` classes. A span is capped at the columns the grid has at the current width, so it never adds an implicit column, and `columnsBelow` still decides the phone layout. A form about twice the width of a side panel, stacking below `md`:
|
|
1197
1270
|
|
|
1198
1271
|
```tsx
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export declare const emptyStateTitleElements: readonly ["h2", "h3"];
|
|
3
|
+
/** The title's heading level: `h2` (the default) in a page, `h3` in a card under its own heading. */
|
|
4
|
+
export type EmptyStateTitleElement = (typeof emptyStateTitleElements)[number];
|
|
5
|
+
export type EmptyStateProps = {
|
|
6
|
+
/** What is empty, in the consumer's words ("No teams in Spring 2027 yet"). */
|
|
7
|
+
title: string;
|
|
8
|
+
/** `h2` (the default) in a page column; `h3` inside a card under its own heading. */
|
|
9
|
+
titleAs?: EmptyStateTitleElement;
|
|
10
|
+
/** A muted line under the title: what to do next. Not blank. */
|
|
11
|
+
description?: string;
|
|
12
|
+
/** A decorative glyph over the title (Lucide in the consumer), hidden from assistive technology. */
|
|
13
|
+
icon?: ReactNode;
|
|
14
|
+
/** The ways out, such as Buttons, in a centred row that wraps. */
|
|
15
|
+
actions?: ReactNode;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Where a list or a page has nothing yet: a centred panel on the surface
|
|
19
|
+
* with a dashed hairline edge, an optional decorative glyph, a title, a
|
|
20
|
+
* muted line and the actions that fill it. It fills its column, so it fits
|
|
21
|
+
* a page column or a Card.
|
|
22
|
+
*/
|
|
23
|
+
export declare function EmptyState({ title, titleAs, description, icon, actions, }: EmptyStateProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, Fragment, isValidElement } from 'react';
|
|
3
|
+
import { isOneOf } from '../is-one-of.js';
|
|
4
|
+
import { Text } from './Text.js';
|
|
5
|
+
export const emptyStateTitleElements = ['h2', 'h3'];
|
|
6
|
+
/** Fails closed on blank words or an unknown heading level. */
|
|
7
|
+
function assertEmptyState({ title, titleAs, description }) {
|
|
8
|
+
if (typeof title !== 'string' || !title.trim())
|
|
9
|
+
throw new RangeError('EmptyState title must be nonempty');
|
|
10
|
+
if (titleAs !== undefined && !isOneOf(emptyStateTitleElements, titleAs))
|
|
11
|
+
throw new RangeError(`Unknown EmptyState titleAs: ${String(titleAs)}`);
|
|
12
|
+
if (description !== undefined &&
|
|
13
|
+
(typeof description !== 'string' || !description.trim()))
|
|
14
|
+
throw new RangeError('EmptyState description must be nonempty');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Whether a slot draws anything: nothing, a boolean, blank words, an empty
|
|
18
|
+
* array or a Fragment holding only those is no content, so the slot's box
|
|
19
|
+
* (and its gap) is left out.
|
|
20
|
+
*/
|
|
21
|
+
function hasContent(node) {
|
|
22
|
+
return Children.toArray(node).some((child) => {
|
|
23
|
+
if (typeof child === 'string')
|
|
24
|
+
return child.trim() !== '';
|
|
25
|
+
if (isValidElement(child) &&
|
|
26
|
+
child.type === Fragment)
|
|
27
|
+
return hasContent(child.props.children);
|
|
28
|
+
return true;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Where a list or a page has nothing yet: a centred panel on the surface
|
|
33
|
+
* with a dashed hairline edge, an optional decorative glyph, a title, a
|
|
34
|
+
* muted line and the actions that fill it. It fills its column, so it fits
|
|
35
|
+
* a page column or a Card.
|
|
36
|
+
*/
|
|
37
|
+
export function EmptyState({ title, titleAs = 'h2', description, icon, actions, }) {
|
|
38
|
+
assertEmptyState({ title, titleAs, description });
|
|
39
|
+
return (_jsxs("div", { className: "sw-empty-state", children: [hasContent(icon) ? (_jsx("span", { "aria-hidden": "true", className: "sw-empty-state-icon", children: icon })) : null, _jsx(Text, { as: titleAs, className: "sw-empty-state-title", variant: "title", children: title }), description !== undefined ? (_jsx(Text, { as: "p", className: "sw-empty-state-description", color: "muted", children: description })) : null, hasContent(actions) ? (_jsx("div", { className: "sw-empty-state-actions", children: actions })) : null] }));
|
|
40
|
+
}
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { type SpacingStep } from '@scalewing/tokens';
|
|
2
2
|
import { type Breakpoint } from '../css/breakpoints.js';
|
|
3
|
-
import { type GridColumns } from '../css/css-grid.js';
|
|
3
|
+
import { type GridColumns, type GridFit, gridFits } from '../css/css-grid.js';
|
|
4
4
|
import { type BoxProps } from './Box.js';
|
|
5
5
|
import { type Align } from './Stack.js';
|
|
6
|
-
export type
|
|
6
|
+
export { gridFits, type GridColumns, type GridFit };
|
|
7
7
|
export type GridProps = BoxProps & {
|
|
8
8
|
/** Where each child sits in the height of its row; unset, children stretch. */
|
|
9
9
|
align?: Align;
|
|
10
10
|
columns?: GridColumns;
|
|
11
11
|
columnsBelow?: Partial<Record<Breakpoint, GridColumns>>;
|
|
12
|
+
/**
|
|
13
|
+
* Fits the columns to the grid's width instead of a fixed count: as many
|
|
14
|
+
* as fit at the named minimum (`narrow` 15rem, `medium` 16.25rem, `wide`
|
|
15
|
+
* 18.75rem), sharing the row, one column when narrower. Not with
|
|
16
|
+
* `columns` or `columnsBelow`; a child's `columnSpan` is ignored in a
|
|
17
|
+
* fitted grid, where every child takes one column.
|
|
18
|
+
*/
|
|
19
|
+
fit?: GridFit;
|
|
12
20
|
gap?: SpacingStep;
|
|
13
21
|
};
|
|
14
|
-
export declare function Grid({ align, className, columns, columnsBelow, gap, ...rest }: GridProps): import("react").JSX.Element;
|
|
22
|
+
export declare function Grid({ align, className, columns, columnsBelow, fit, gap, ...rest }: GridProps): import("react").JSX.Element;
|
package/dist/components/Grid.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from '../class-names.js';
|
|
3
3
|
import { breakpoints } from '../css/breakpoints.js';
|
|
4
|
-
import { gridColumnsBelowClass, gridColumnsClass, } from '../css/css-grid.js';
|
|
4
|
+
import { gridColumnsBelowClass, gridColumnsClass, gridFitClass, gridFits, } from '../css/css-grid.js';
|
|
5
5
|
import { spacingClass } from '../css/spacing-classes.js';
|
|
6
6
|
import { assertGridCount } from '../grid-counts.js';
|
|
7
|
+
import { isOneOf } from '../is-one-of.js';
|
|
7
8
|
import { Box } from './Box.js';
|
|
8
|
-
export
|
|
9
|
-
|
|
9
|
+
export { gridFits };
|
|
10
|
+
export function Grid({ align, className, columns, columnsBelow, fit, gap = 0, ...rest }) {
|
|
11
|
+
if (fit !== undefined) {
|
|
12
|
+
if (!isOneOf(gridFits, fit))
|
|
13
|
+
throw new RangeError(`Unknown Grid fit: ${String(fit)}`);
|
|
14
|
+
if (columns !== undefined || columnsBelow !== undefined)
|
|
15
|
+
throw new RangeError('A fitted Grid takes no columns or columnsBelow; fit sets them');
|
|
16
|
+
}
|
|
17
|
+
const fixedColumns = columns ?? 1;
|
|
18
|
+
assertGridCount('columns', fixedColumns);
|
|
10
19
|
const belowClasses = breakpoints.flatMap((breakpoint) => {
|
|
11
20
|
const count = columnsBelow?.[breakpoint];
|
|
12
21
|
if (count === undefined)
|
|
@@ -14,5 +23,5 @@ export function Grid({ align, className, columns = 1, columnsBelow, gap = 0, ...
|
|
|
14
23
|
assertGridCount('columnsBelow', count);
|
|
15
24
|
return [gridColumnsBelowClass(breakpoint, count)];
|
|
16
25
|
});
|
|
17
|
-
return (_jsx(Box, { className: cx('sw-grid', gridColumnsClass(
|
|
26
|
+
return (_jsx(Box, { className: cx('sw-grid', fit ? gridFitClass(fit) : gridColumnsClass(fixedColumns), ...belowClasses, spacingClass('gap', 'all', gap), align && `sw-align-${align}`, className), ...rest }));
|
|
18
27
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export type HeroProps = Omit<HTMLAttributes<HTMLElement>, 'aria-labelledby' | 'children' | 'style' | 'title'> & {
|
|
3
|
+
/** The page's title, its one `h1`, in the `hero` size. */
|
|
4
|
+
title: ReactNode;
|
|
5
|
+
/** A short line above the title saying who or what the page is for. */
|
|
6
|
+
eyebrow?: ReactNode;
|
|
7
|
+
/** The sentence under the title, in the `lead` size. */
|
|
8
|
+
lead?: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Links or buttons in a row, stacked to the full width below `md`. Leave
|
|
11
|
+
* it out rather than passing an empty fragment, which keeps the row.
|
|
12
|
+
*/
|
|
13
|
+
actions?: ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* A picture beside the words from `md`, under them below it. Leave it out
|
|
16
|
+
* rather than passing an empty fragment, which keeps its column.
|
|
17
|
+
*/
|
|
18
|
+
media?: ReactNode;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A public page's opening, as a `section` named by its title: an optional
|
|
22
|
+
* eyebrow, the title (`h1`, `hero` size), an optional lead (`lead` size)
|
|
23
|
+
* and actions, with an optional picture beside them from `md`. The title
|
|
24
|
+
* always names the section. A blank title throws a `RangeError`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function Hero({ actions, className, eyebrow, lead, media, title, ...rest }: HeroProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
import { cx } from '../class-names.js';
|
|
4
|
+
import { Text } from './Text.js';
|
|
5
|
+
function isBlank(node) {
|
|
6
|
+
return (node === undefined ||
|
|
7
|
+
node === null ||
|
|
8
|
+
node === false ||
|
|
9
|
+
(typeof node === 'string' && node.trim() === ''));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A public page's opening, as a `section` named by its title: an optional
|
|
13
|
+
* eyebrow, the title (`h1`, `hero` size), an optional lead (`lead` size)
|
|
14
|
+
* and actions, with an optional picture beside them from `md`. The title
|
|
15
|
+
* always names the section. A blank title throws a `RangeError`.
|
|
16
|
+
*/
|
|
17
|
+
export function Hero({ actions, className, eyebrow, lead, media, title, ...rest }) {
|
|
18
|
+
const titleId = useId();
|
|
19
|
+
if (isBlank(title))
|
|
20
|
+
throw new RangeError('A Hero needs a title');
|
|
21
|
+
const withMedia = !isBlank(media);
|
|
22
|
+
return (_jsxs("section", { ...rest, "aria-labelledby": titleId, className: cx('sw-hero', withMedia && 'sw-hero-with-media', className), children: [_jsxs("div", { className: "sw-hero-text", children: [isBlank(eyebrow) ? null : (_jsx(Text, { color: "muted", as: "p", variant: "label", children: eyebrow })), _jsx(Text, { id: titleId, variant: "hero", children: title }), isBlank(lead) ? null : _jsx(Text, { variant: "lead", children: lead }), isBlank(actions) ? null : (_jsx("div", { className: "sw-hero-actions", children: actions }))] }), withMedia ? _jsx("div", { className: "sw-hero-media", children: media }) : null] }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import { type ScreenPreviewFrame, screenPreviewFrames } from '../css/css-screen-preview.js';
|
|
3
|
+
export { screenPreviewFrames, type ScreenPreviewFrame };
|
|
4
|
+
export type ScreenPreviewProps = Omit<HTMLAttributes<HTMLElement>, 'children' | 'role' | 'style' | 'title'> & {
|
|
5
|
+
/** What the drawn screen shows, said in words: its alternative text. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** `card` (default), or `device`, a phone's bezel from `md`. */
|
|
8
|
+
frame?: ScreenPreviewFrame;
|
|
9
|
+
/** A small line above the screen's title, such as where in the app it is. */
|
|
10
|
+
eyebrow?: ReactNode;
|
|
11
|
+
/** The screen's own title. */
|
|
12
|
+
title?: ReactNode;
|
|
13
|
+
/** A visible caption under the screen, such as what will replace it. */
|
|
14
|
+
caption?: ReactNode;
|
|
15
|
+
/** `PreviewGroup`s, `PreviewRow`s and `PreviewTiles`. */
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A drawn stand-in for an app's screen on a public page: a `figure` whose
|
|
20
|
+
* screen is one image (`role="img"`) named by `label`, so its rows are
|
|
21
|
+
* read as that one sentence, with an optional visible `caption`. Its
|
|
22
|
+
* content is `PreviewGroup`s of `PreviewRow`s and `PreviewTiles`. A blank
|
|
23
|
+
* label or an unknown frame throws a `RangeError`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ScreenPreview({ caption, children, className, eyebrow, frame, label, title, ...rest }: ScreenPreviewProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../class-names.js';
|
|
3
|
+
import { screenPreviewFrameClass, screenPreviewFrames, } from '../css/css-screen-preview.js';
|
|
4
|
+
import { isOneOf } from '../is-one-of.js';
|
|
5
|
+
export { screenPreviewFrames };
|
|
6
|
+
/**
|
|
7
|
+
* A drawn stand-in for an app's screen on a public page: a `figure` whose
|
|
8
|
+
* screen is one image (`role="img"`) named by `label`, so its rows are
|
|
9
|
+
* read as that one sentence, with an optional visible `caption`. Its
|
|
10
|
+
* content is `PreviewGroup`s of `PreviewRow`s and `PreviewTiles`. A blank
|
|
11
|
+
* label or an unknown frame throws a `RangeError`.
|
|
12
|
+
*/
|
|
13
|
+
export function ScreenPreview({ caption, children, className, eyebrow, frame = 'card', label, title, ...rest }) {
|
|
14
|
+
if (!label.trim())
|
|
15
|
+
throw new RangeError('A ScreenPreview needs a label, its alternative text');
|
|
16
|
+
if (!isOneOf(screenPreviewFrames, frame))
|
|
17
|
+
throw new RangeError(`Unknown ScreenPreview frame: ${String(frame)}`);
|
|
18
|
+
const header = eyebrow || title ? (_jsxs("div", { className: "sw-screen-preview-header", children: [eyebrow ? (_jsx("span", { className: "sw-screen-preview-eyebrow", children: eyebrow })) : null, title ? (_jsx("span", { className: "sw-screen-preview-title", children: title })) : null] })) : null;
|
|
19
|
+
return (_jsxs("figure", { ...rest, className: cx('sw-screen-preview', screenPreviewFrameClass(frame), className), children: [_jsx("div", { className: "sw-screen-preview-bezel", children: _jsxs("div", { "aria-label": label, className: "sw-screen-preview-screen", role: "img", children: [header, _jsx("div", { className: "sw-screen-preview-body", children: children })] }) }), caption ? (_jsx("figcaption", { className: "sw-screen-preview-caption", children: caption })) : null] }));
|
|
20
|
+
}
|
|
@@ -36,8 +36,18 @@ export type StepperProps = {
|
|
|
36
36
|
disabled?: boolean;
|
|
37
37
|
/** Readable and focusable, never changed: the buttons are hidden and keep their room. */
|
|
38
38
|
readOnly?: boolean;
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Says the count in words for assistive technology (`aria-valuetext`),
|
|
41
|
+
* such as "12 straps". It wins over the count and `unit` read together.
|
|
42
|
+
*/
|
|
40
43
|
valueText?: string;
|
|
44
|
+
/**
|
|
45
|
+
* A short muted word after the count, inside its frame, such as "min".
|
|
46
|
+
* Without `valueText` the count is read with it ("90 min"), word for
|
|
47
|
+
* word: a unit that changes with the count ("players") reads "1 players",
|
|
48
|
+
* so pass `valueText` with the right form ("1 player"). Not blank.
|
|
49
|
+
*/
|
|
50
|
+
unit?: string;
|
|
41
51
|
/**
|
|
42
52
|
* Ids of elements that describe the count, such as the value it adds up
|
|
43
53
|
* to; read before an `error`'s message.
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef, useId, useState, } from 'react';
|
|
4
4
|
import { cx } from '../class-names.js';
|
|
5
|
-
import { assertStepperBounds, readStepperEntry, stepperMoves, stepperText, } from '../stepper-count.js';
|
|
5
|
+
import { assertStepperBounds, assertStepperUnit, readStepperEntry, stepperMoves, stepperText, stepperValueText, } from '../stepper-count.js';
|
|
6
6
|
import { FieldErrorRegion } from './FieldErrorRegion.js';
|
|
7
7
|
import { FieldLabelText } from './FieldLabelText.js';
|
|
8
|
+
import { StepperCountFrame } from './stepper/StepperCountFrame.js';
|
|
8
9
|
export const stepperLimitAppearances = ['faded', 'plain'];
|
|
9
10
|
/** Keeps focus where it was (the count, or nowhere) when a button is pressed. */
|
|
10
11
|
function keepFocus(event) {
|
|
@@ -15,9 +16,10 @@ function keepFocus(event) {
|
|
|
15
16
|
* tab stop (the count, a spin button that ArrowUp and ArrowDown step), with
|
|
16
17
|
* the buttons as pointer targets beside it. − stops at `min`, + at `max`.
|
|
17
18
|
*/
|
|
18
|
-
export const Stepper = forwardRef(function Stepper({ label, labelVisuallyHidden = false, value, onChange, min = 0, max = Number.POSITIVE_INFINITY, step = 1, decrementLabel, incrementLabel, error, changed = false, disabled = false, readOnly = false, valueText, 'aria-describedby': describedBy, placeholder, name, width = 'fixed', limitAppearance = 'faded', }, ref) {
|
|
19
|
+
export const Stepper = forwardRef(function Stepper({ label, labelVisuallyHidden = false, value, onChange, min = 0, max = Number.POSITIVE_INFINITY, step = 1, decrementLabel, incrementLabel, error, changed = false, disabled = false, readOnly = false, valueText, unit, 'aria-describedby': describedBy, placeholder, name, width = 'fixed', limitAppearance = 'faded', }, ref) {
|
|
19
20
|
const bounds = { min, max, step };
|
|
20
21
|
assertStepperBounds(bounds);
|
|
22
|
+
assertStepperUnit(unit);
|
|
21
23
|
if (!stepperLimitAppearances.includes(limitAppearance))
|
|
22
24
|
throw new RangeError(`Unknown Stepper limitAppearance: ${String(limitAppearance)}`);
|
|
23
25
|
if (value !== null && !Number.isSafeInteger(value))
|
|
@@ -76,10 +78,10 @@ export const Stepper = forwardRef(function Stepper({ label, labelVisuallyHidden
|
|
|
76
78
|
return (_jsxs("div", { className: cx('sw-stepper', width === 'full' && 'sw-stepper-full', invalid && 'sw-stepper-invalid', changed && !invalid && 'sw-stepper-changed', readOnly && 'sw-stepper-readonly', limitAppearance === 'plain' &&
|
|
77
79
|
!disabled &&
|
|
78
80
|
!readOnly &&
|
|
79
|
-
'sw-stepper-limit-plain'), children: [_jsx("label", { className: labelVisuallyHidden ? 'sw-sr-only' : undefined, htmlFor: inputId, id: labelId, children: _jsx(FieldLabelText, { label: label }) }), _jsxs("div", { "aria-labelledby": labelId, className: "sw-stepper-track", role: "group", children: [_jsx("button", { "aria-controls": inputId, "aria-label": decrementLabel, className: "sw-stepper-button", disabled: moves.down === null, onClick: () => move(moves.down), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: '−' }) }), _jsx("input", { ref: ref, "aria-describedby": [describedBy?.trim() || null, invalid ? errorId : null]
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
'sw-stepper-limit-plain'), children: [_jsx("label", { className: labelVisuallyHidden ? 'sw-sr-only' : undefined, htmlFor: inputId, id: labelId, children: _jsx(FieldLabelText, { label: label }) }), _jsxs("div", { "aria-labelledby": labelId, className: "sw-stepper-track", role: "group", children: [_jsx("button", { "aria-controls": inputId, "aria-label": decrementLabel, className: "sw-stepper-button", disabled: moves.down === null, onClick: () => move(moves.down), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: '−' }) }), _jsx(StepperCountFrame, { unit: unit, children: _jsx("input", { ref: ref, "aria-describedby": [describedBy?.trim() || null, invalid ? errorId : null]
|
|
82
|
+
.filter((id) => id !== null)
|
|
83
|
+
.join(' ') || undefined, "aria-invalid": invalid || undefined, "aria-valuemax": Number.isFinite(max) ? max : undefined, "aria-valuemin": min, "aria-valuenow": value ?? undefined, "aria-valuetext": stepperValueText(value, unit, valueText), autoComplete: "off", className: "sw-stepper-input", disabled: disabled, id: inputId,
|
|
84
|
+
// iOS's numeric pad has no minus key, so a range below zero takes
|
|
85
|
+
// the text keyboard.
|
|
86
|
+
inputMode: allowsNegative ? 'text' : 'numeric', name: name, onBlur: () => setDraft(null), onChange: onType, onKeyDown: onKeyDown, placeholder: placeholder, readOnly: readOnly, role: "spinbutton", type: "text", value: text }) }), _jsx("button", { "aria-controls": inputId, "aria-label": incrementLabel, className: "sw-stepper-button", disabled: moves.up === null, onClick: () => move(moves.up), onMouseDown: keepFocus, tabIndex: -1, type: "button", children: _jsx("span", { "aria-hidden": "true", children: "+" }) })] }), _jsx(FieldErrorRegion, { className: "sw-field-error", id: errorId, message: error })] }));
|
|
85
87
|
});
|
package/dist/components/Text.js
CHANGED
|
@@ -7,7 +7,10 @@ const defaultElement = {
|
|
|
7
7
|
data: 'span',
|
|
8
8
|
display: 'h1',
|
|
9
9
|
heading: 'h2',
|
|
10
|
+
headline: 'h2',
|
|
11
|
+
hero: 'h1',
|
|
10
12
|
label: 'span',
|
|
13
|
+
lead: 'p',
|
|
11
14
|
title: 'h3',
|
|
12
15
|
};
|
|
13
16
|
export const Text = forwardRef(function Text({ align, as, className, color = 'text', style, truncate = false, variant = 'body', ...rest }, ref) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Rows of a `ScreenPreview` that belong together: parted from the next
|
|
4
|
+
* group by a hairline on a card, each a card of its own in a device frame.
|
|
5
|
+
*/
|
|
6
|
+
export declare function PreviewGroup({ children }: {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Rows of a `ScreenPreview` that belong together: parted from the next
|
|
4
|
+
* group by a hairline on a card, each a card of its own in a device frame.
|
|
5
|
+
*/
|
|
6
|
+
export function PreviewGroup({ children }) {
|
|
7
|
+
return _jsx("div", { className: "sw-preview-group", children: children });
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type PreviewRowKind, previewRowKinds } from '../../css/css-screen-preview.js';
|
|
3
|
+
export { previewRowKinds, type PreviewRowKind };
|
|
4
|
+
export type PreviewRowProps = {
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
value: ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* `plain` (default) a label and its figure; `total` the same in bold past
|
|
9
|
+
* a hairline; `note` a small label over its words; `filled` on the quiet
|
|
10
|
+
* fill; `alert` on the danger tint.
|
|
11
|
+
*/
|
|
12
|
+
kind?: PreviewRowKind;
|
|
13
|
+
};
|
|
14
|
+
/** One row of a `ScreenPreview`: a label at its start and a value at its end. */
|
|
15
|
+
export declare function PreviewRow({ kind, label, value }: PreviewRowProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from '../../class-names.js';
|
|
3
|
+
import { previewRowKindClass, previewRowKinds, } from '../../css/css-screen-preview.js';
|
|
4
|
+
import { isOneOf } from '../../is-one-of.js';
|
|
5
|
+
export { previewRowKinds };
|
|
6
|
+
/** One row of a `ScreenPreview`: a label at its start and a value at its end. */
|
|
7
|
+
export function PreviewRow({ kind = 'plain', label, value }) {
|
|
8
|
+
if (!isOneOf(previewRowKinds, kind))
|
|
9
|
+
throw new RangeError(`Unknown PreviewRow kind: ${String(kind)}`);
|
|
10
|
+
return (_jsxs("div", { className: cx('sw-preview-row', previewRowKindClass(kind)), children: [_jsx("span", { className: "sw-preview-row-label", children: label }), _jsx("span", { className: "sw-preview-row-value", children: value })] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** One tile: `id` keys it and is never shown; `label` sits over `value`. */
|
|
3
|
+
export type PreviewTile = {
|
|
4
|
+
id: string;
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
value: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export type PreviewTilesProps = {
|
|
9
|
+
items: readonly PreviewTile[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A row of small tiles in a `ScreenPreview`, each a label over its value,
|
|
13
|
+
* such as counts by kind: three to a row on a phone, six from `md`. No
|
|
14
|
+
* items, or a blank or repeated id, throws a `RangeError`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function PreviewTiles({ items }: PreviewTilesProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A row of small tiles in a `ScreenPreview`, each a label over its value,
|
|
4
|
+
* such as counts by kind: three to a row on a phone, six from `md`. No
|
|
5
|
+
* items, or a blank or repeated id, throws a `RangeError`.
|
|
6
|
+
*/
|
|
7
|
+
export function PreviewTiles({ items }) {
|
|
8
|
+
if (items.length === 0)
|
|
9
|
+
throw new RangeError('PreviewTiles needs an item');
|
|
10
|
+
if (items.some((item) => !item.id.trim()))
|
|
11
|
+
throw new RangeError('PreviewTiles items need nonempty ids');
|
|
12
|
+
if (new Set(items.map((item) => item.id)).size !== items.length)
|
|
13
|
+
throw new RangeError('PreviewTiles items need unique ids');
|
|
14
|
+
return (_jsx("div", { className: "sw-preview-tiles", children: items.map((item) => (_jsxs("div", { className: "sw-preview-tile", children: [_jsx("span", { className: "sw-preview-tile-label", children: item.label }), _jsx("span", { className: "sw-preview-tile-value", children: item.value })] }, item.id))) }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export type StepperCountFrameProps = {
|
|
3
|
+
/** The count's input. */
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
/** A short word after the count; no frame is drawn without one. */
|
|
6
|
+
unit?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The count with its unit inside one control frame ("90 min"), as Field's
|
|
10
|
+
* suffix: the frame draws the control surface and focus ring, and the input
|
|
11
|
+
* inside it drops its own. The unit is hidden from assistive technology,
|
|
12
|
+
* which hears it in the count's `aria-valuetext` instead. Without a unit the
|
|
13
|
+
* input is the frame, unchanged.
|
|
14
|
+
*/
|
|
15
|
+
export declare function StepperCountFrame({ children, unit }: StepperCountFrameProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* A press on the frame's own chrome (the unit or the padding) focuses the
|
|
4
|
+
* count, as the text cursor over it promises. A press on the input itself
|
|
5
|
+
* keeps the browser's own caret placement and selection.
|
|
6
|
+
*/
|
|
7
|
+
function focusCount(event) {
|
|
8
|
+
const input = event.currentTarget.querySelector('input');
|
|
9
|
+
if (!input || input.disabled || event.target === input)
|
|
10
|
+
return;
|
|
11
|
+
event.preventDefault();
|
|
12
|
+
input.focus();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The count with its unit inside one control frame ("90 min"), as Field's
|
|
16
|
+
* suffix: the frame draws the control surface and focus ring, and the input
|
|
17
|
+
* inside it drops its own. The unit is hidden from assistive technology,
|
|
18
|
+
* which hears it in the count's `aria-valuetext` instead. Without a unit the
|
|
19
|
+
* input is the frame, unchanged.
|
|
20
|
+
*/
|
|
21
|
+
export function StepperCountFrame({ children, unit }) {
|
|
22
|
+
if (unit === undefined)
|
|
23
|
+
return _jsx(_Fragment, { children: children });
|
|
24
|
+
return (_jsxs("span", { className: "sw-stepper-count", onMouseDown: focusCount, children: [children, _jsx("span", { "aria-hidden": "true", className: "sw-stepper-unit", children: unit.trim() })] }));
|
|
25
|
+
}
|