@scalewing/react 1.48.0 → 1.49.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 +15 -0
- package/README.md +44 -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/Stepper.d.ts +11 -1
- package/dist/components/Stepper.js +10 -8
- package/dist/components/Text.js +3 -0
- package/dist/components/stepper/StepperCountFrame.d.ts +15 -0
- package/dist/components/stepper/StepperCountFrame.js +25 -0
- package/dist/css/css-components.js +6 -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-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/stylesheet.js +4 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -1
- package/dist/stepper-count.d.ts +8 -0
- package/dist/stepper-count.js +17 -0
- package/dist/styles.css +212 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @scalewing/react
|
|
2
2
|
|
|
3
|
+
## 1.49.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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`.
|
|
8
|
+
- 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`.
|
|
9
|
+
- 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`.
|
|
10
|
+
- 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`.
|
|
11
|
+
- 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`.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [b466bbb]
|
|
16
|
+
- @scalewing/tokens@1.7.0
|
|
17
|
+
|
|
3
18
|
## 1.48.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -35,10 +35,12 @@ import {
|
|
|
35
35
|
InfoTip,
|
|
36
36
|
Separator,
|
|
37
37
|
Band,
|
|
38
|
+
Hero,
|
|
38
39
|
SiteFooter,
|
|
39
40
|
SiteHeader,
|
|
40
41
|
DescriptionList,
|
|
41
42
|
DescriptionItem,
|
|
43
|
+
EmptyState,
|
|
42
44
|
FixtureGroup,
|
|
43
45
|
FixtureRow,
|
|
44
46
|
ListGroup,
|
|
@@ -189,7 +191,7 @@ A chosen photo can show itself: `value.preview` is the address of a small image
|
|
|
189
191
|
/>
|
|
190
192
|
```
|
|
191
193
|
|
|
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`.
|
|
194
|
+
`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
195
|
|
|
194
196
|
```tsx
|
|
195
197
|
<Stepper
|
|
@@ -840,6 +842,24 @@ The consumer needs no PDF.js dependency of its own. With another bundler, build
|
|
|
840
842
|
<Skeleton label="Loading rangers" variant="rows" lines={4} />
|
|
841
843
|
```
|
|
842
844
|
|
|
845
|
+
`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`.
|
|
846
|
+
|
|
847
|
+
```tsx
|
|
848
|
+
<EmptyState
|
|
849
|
+
icon={<Users />}
|
|
850
|
+
title="No nests counted yet"
|
|
851
|
+
description="Bring back last spring's nests, or paste a list."
|
|
852
|
+
actions={
|
|
853
|
+
<>
|
|
854
|
+
<Button onPress={copy}>Copy last spring's 12 nests</Button>
|
|
855
|
+
<Button variant="neutral" onPress={paste}>
|
|
856
|
+
Paste a list
|
|
857
|
+
</Button>
|
|
858
|
+
</>
|
|
859
|
+
}
|
|
860
|
+
/>
|
|
861
|
+
```
|
|
862
|
+
|
|
843
863
|
`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
864
|
|
|
845
865
|
```tsx
|
|
@@ -1018,6 +1038,18 @@ const { headings, omissions } = inspectMarkdown(page.body, {
|
|
|
1018
1038
|
/>
|
|
1019
1039
|
```
|
|
1020
1040
|
|
|
1041
|
+
`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`.
|
|
1042
|
+
|
|
1043
|
+
```tsx
|
|
1044
|
+
<Hero
|
|
1045
|
+
eyebrow="For birdwatchers"
|
|
1046
|
+
title="Every heron counted."
|
|
1047
|
+
lead="The north marsh in every season, from the hide to the shore."
|
|
1048
|
+
actions={<ButtonLink href="/visit">Plan a visit</ButtonLink>}
|
|
1049
|
+
media={<img alt="The heron hide at dawn" src="/hide.jpg" />}
|
|
1050
|
+
/>
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1021
1053
|
`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
1054
|
|
|
1023
1055
|
```tsx
|
|
@@ -1181,7 +1213,7 @@ A row can also carry three trailing parts. `unread` marks it new for the person,
|
|
|
1181
1213
|
|
|
1182
1214
|
`--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
1215
|
|
|
1184
|
-
`Text` steps `display` and `
|
|
1216
|
+
`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
1217
|
|
|
1186
1218
|
`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
1219
|
|
|
@@ -1193,6 +1225,16 @@ A row can also carry three trailing parts. `unread` marks it new for the person,
|
|
|
1193
1225
|
|
|
1194
1226
|
`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
1227
|
|
|
1228
|
+
`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.
|
|
1229
|
+
|
|
1230
|
+
```tsx
|
|
1231
|
+
<Grid fit="narrow" gap={4}>
|
|
1232
|
+
{habitats.map((habitat) => (
|
|
1233
|
+
<Card key={habitat.id}>…</Card>
|
|
1234
|
+
))}
|
|
1235
|
+
</Grid>
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1196
1238
|
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
1239
|
|
|
1198
1240
|
```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
|
+
}
|
|
@@ -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,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
|
+
}
|
|
@@ -43,6 +43,7 @@ import { cssSwitchClasses } from './css-switch.js';
|
|
|
43
43
|
import { cssTextClasses } from './css-text.js';
|
|
44
44
|
import { cssFontMonoClasses } from './css-font-mono.js';
|
|
45
45
|
import { cssSkeletonClasses } from './css-skeleton.js';
|
|
46
|
+
import { cssEmptyStateClasses } from './css-empty-state.js';
|
|
46
47
|
import { cssSpinnerClasses } from './css-spinner.js';
|
|
47
48
|
import { cssProgressClasses } from './css-progress.js';
|
|
48
49
|
import { cssNoticeClasses } from './css-notice.js';
|
|
@@ -51,6 +52,7 @@ import { cssTooltipClasses } from './css-tooltip.js';
|
|
|
51
52
|
import { cssInfoTipClasses } from './css-info-tip.js';
|
|
52
53
|
import { cssSeparatorClasses } from './css-separator.js';
|
|
53
54
|
import { cssBandClasses } from './css-band.js';
|
|
55
|
+
import { cssHeroClasses } from './css-hero.js';
|
|
54
56
|
import { cssSiteFooterClasses } from './css-site-footer.js';
|
|
55
57
|
import { cssSiteHeaderClasses } from './css-site-header.js';
|
|
56
58
|
import { cssStatTileClasses } from './css-stat-tile.js';
|
|
@@ -200,6 +202,8 @@ ${cssSectionNavClasses()}
|
|
|
200
202
|
|
|
201
203
|
${cssBandClasses()}
|
|
202
204
|
|
|
205
|
+
${cssHeroClasses()}
|
|
206
|
+
|
|
203
207
|
${cssSiteFooterClasses()}
|
|
204
208
|
|
|
205
209
|
${cssSiteHeaderClasses()}
|
|
@@ -237,6 +241,8 @@ ${cssSpinnerClasses()}
|
|
|
237
241
|
|
|
238
242
|
${cssSkeletonClasses()}
|
|
239
243
|
|
|
244
|
+
${cssEmptyStateClasses()}
|
|
245
|
+
|
|
240
246
|
${cssProgressClasses()}
|
|
241
247
|
|
|
242
248
|
${cssNoticeClasses()}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
2
|
+
/*
|
|
3
|
+
* EmptyState: a centred column on the surface inside a dashed hairline
|
|
4
|
+
* (the md radius), spacing step 8 above and below and step 4 at the sides,
|
|
5
|
+
* its parts a step 3 apart. The glyph sits in a decorative step-8 square of
|
|
6
|
+
* the subtle fill in the muted colour, a direct-child SVG drawn at step 5.
|
|
7
|
+
* The title and line keep a readable measure and wrap; a long word breaks.
|
|
8
|
+
* The actions are a centred row that wraps, a step 2 apart, each Button a
|
|
9
|
+
* 44 px target on a coarse pointer. Forced colours keep the dashed edge in
|
|
10
|
+
* the system's border colour and draw the glyph in CanvasText.
|
|
11
|
+
*/
|
|
12
|
+
export function cssEmptyStateClasses() {
|
|
13
|
+
return `.sw-empty-state {
|
|
14
|
+
align-items: center;
|
|
15
|
+
background: var(--sw-color-surface);
|
|
16
|
+
border: 1px dashed var(--sw-color-border);
|
|
17
|
+
border-radius: var(--sw-radius-md);
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: var(--sw-space-3);
|
|
22
|
+
min-width: 0;
|
|
23
|
+
overflow-wrap: anywhere;
|
|
24
|
+
padding: var(--sw-space-8) var(--sw-space-4);
|
|
25
|
+
text-align: center;
|
|
26
|
+
width: 100%;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.sw-empty-state-icon {
|
|
30
|
+
align-items: center;
|
|
31
|
+
background: var(--sw-color-subtle);
|
|
32
|
+
border-radius: var(--sw-radius-md);
|
|
33
|
+
color: var(--sw-color-muted);
|
|
34
|
+
display: inline-flex;
|
|
35
|
+
flex: none;
|
|
36
|
+
height: var(--sw-space-8);
|
|
37
|
+
justify-content: center;
|
|
38
|
+
width: var(--sw-space-8);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sw-empty-state-icon > svg {
|
|
42
|
+
height: var(--sw-space-5);
|
|
43
|
+
width: var(--sw-space-5);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.sw-empty-state-title,
|
|
47
|
+
.sw-empty-state-description {
|
|
48
|
+
margin: 0;
|
|
49
|
+
max-width: var(--sw-reading-max);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sw-empty-state-actions {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-wrap: wrap;
|
|
55
|
+
gap: var(--sw-space-2);
|
|
56
|
+
justify-content: center;
|
|
57
|
+
margin-top: var(--sw-space-1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media ${coarsePointerQuery} {
|
|
61
|
+
.sw-empty-state-actions .sw-button { min-height: ${touchTarget}; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@media (forced-colors: active) {
|
|
65
|
+
.sw-empty-state { border-color: CanvasText; }
|
|
66
|
+
.sw-empty-state-icon { color: CanvasText; }
|
|
67
|
+
}`;
|
|
68
|
+
}
|
|
69
|
+
export function emptyStateClassCatalog() {
|
|
70
|
+
return [
|
|
71
|
+
'sw-empty-state',
|
|
72
|
+
'sw-empty-state-icon',
|
|
73
|
+
'sw-empty-state-title',
|
|
74
|
+
'sw-empty-state-description',
|
|
75
|
+
'sw-empty-state-actions',
|
|
76
|
+
];
|
|
77
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { breakpointQuery, breakpoints } from './breakpoints.js';
|
|
2
|
-
import { gridColumnCounts, gridColumnsBelowClass, gridColumnsClass, } from './css-grid.js';
|
|
2
|
+
import { gridColumnCounts, gridColumnsBelowClass, gridColumnsClass, gridFitClass, gridFits, } from './css-grid.js';
|
|
3
3
|
export const gridColumnSpans = gridColumnCounts;
|
|
4
4
|
export function gridSpanClass(span) {
|
|
5
5
|
return `sw-grid-span-${span}`;
|
|
@@ -21,6 +21,9 @@ function widerSpans(columns) {
|
|
|
21
21
|
export function cssGridSpanClasses() {
|
|
22
22
|
const spans = gridColumnSpans.map((span) => spanRule(`.${gridSpanClass(span)}`, span));
|
|
23
23
|
const caps = gridColumnCounts.flatMap((columns) => widerSpans(columns).map((span) => capRule(gridColumnsClass(columns), columns, span)));
|
|
24
|
+
// A fitted grid's column count follows its width, so a span there would
|
|
25
|
+
// add implicit columns: its children each take one column.
|
|
26
|
+
const fitted = gridFits.flatMap((fit) => widerSpans(1).map((span) => `:where(.${gridFitClass(fit)}) > .${gridSpanClass(span)} { grid-column: auto; }`));
|
|
24
27
|
// Below a breakpoint every span wider than one is restated for the phone
|
|
25
28
|
// column count, which also undoes a desktop cap when that count is larger.
|
|
26
29
|
const below = breakpoints.map((breakpoint) => `@media ${breakpointQuery('below', breakpoint)} {
|
|
@@ -28,7 +31,7 @@ ${gridColumnCounts
|
|
|
28
31
|
.flatMap((columns) => widerSpans(1).map((span) => ` ${capRule(gridColumnsBelowClass(breakpoint, columns), columns, span)}`))
|
|
29
32
|
.join('\n')}
|
|
30
33
|
}`);
|
|
31
|
-
return [...spans, ...caps, ...below].join('\n');
|
|
34
|
+
return [...spans, ...caps, ...fitted, ...below].join('\n');
|
|
32
35
|
}
|
|
33
36
|
export function gridSpanClassCatalog() {
|
|
34
37
|
return gridColumnSpans.map((span) => gridSpanClass(span));
|
package/dist/css/css-grid.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { type Breakpoint } from './breakpoints.js';
|
|
2
2
|
export declare const gridColumnCounts: readonly [1, 2, 3, 4, 6];
|
|
3
3
|
export type GridColumns = (typeof gridColumnCounts)[number];
|
|
4
|
+
/**
|
|
5
|
+
* A fitted grid's narrowest column: as many columns as fit the grid's width
|
|
6
|
+
* at this minimum, each stretched to share the row, and one column when
|
|
7
|
+
* the grid is narrower than the minimum.
|
|
8
|
+
*/
|
|
9
|
+
export declare const gridFitWidths: {
|
|
10
|
+
readonly narrow: "15rem";
|
|
11
|
+
readonly medium: "16.25rem";
|
|
12
|
+
readonly wide: "18.75rem";
|
|
13
|
+
};
|
|
14
|
+
export type GridFit = keyof typeof gridFitWidths;
|
|
15
|
+
export declare const gridFits: GridFit[];
|
|
16
|
+
export declare function gridFitClass(fit: GridFit): string;
|
|
4
17
|
export declare function gridColumnsClass(columns: GridColumns): string;
|
|
5
18
|
export declare function gridColumnsBelowClass(breakpoint: Breakpoint, columns: GridColumns): string;
|
|
6
19
|
export declare function cssGridClasses(): string;
|
package/dist/css/css-grid.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { breakpointQuery, breakpoints, } from './breakpoints.js';
|
|
2
2
|
export const gridColumnCounts = [1, 2, 3, 4, 6];
|
|
3
|
+
/**
|
|
4
|
+
* A fitted grid's narrowest column: as many columns as fit the grid's width
|
|
5
|
+
* at this minimum, each stretched to share the row, and one column when
|
|
6
|
+
* the grid is narrower than the minimum.
|
|
7
|
+
*/
|
|
8
|
+
export const gridFitWidths = {
|
|
9
|
+
narrow: '15rem',
|
|
10
|
+
medium: '16.25rem',
|
|
11
|
+
wide: '18.75rem',
|
|
12
|
+
};
|
|
13
|
+
export const gridFits = Object.keys(gridFitWidths);
|
|
14
|
+
export function gridFitClass(fit) {
|
|
15
|
+
return `sw-grid-fit-${fit}`;
|
|
16
|
+
}
|
|
3
17
|
export function gridColumnsClass(columns) {
|
|
4
18
|
return `sw-grid-cols-${columns}`;
|
|
5
19
|
}
|
|
@@ -20,12 +34,14 @@ ${gridColumnCounts
|
|
|
20
34
|
.map((count) => columnsRule(gridColumnsBelowClass(breakpoint, count), count))
|
|
21
35
|
.join('\n')}
|
|
22
36
|
}`);
|
|
23
|
-
|
|
37
|
+
const fitted = gridFits.map((fit) => `.${gridFitClass(fit)} { grid-template-columns: repeat(auto-fit, minmax(min(100%, ${gridFitWidths[fit]}), 1fr)); }`);
|
|
38
|
+
return [base, ...columns, ...below, ...fitted].join('\n');
|
|
24
39
|
}
|
|
25
40
|
export function gridClassCatalog() {
|
|
26
41
|
return [
|
|
27
42
|
'sw-grid',
|
|
28
43
|
...gridColumnCounts.map((count) => gridColumnsClass(count)),
|
|
29
44
|
...breakpoints.flatMap((breakpoint) => gridColumnCounts.map((count) => gridColumnsBelowClass(breakpoint, count))),
|
|
45
|
+
...gridFits.map(gridFitClass),
|
|
30
46
|
];
|
|
31
47
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
2
|
+
/*
|
|
3
|
+
* A public page's opening: the words in one column (an eyebrow, the hero
|
|
4
|
+
* title, the lead and the actions) and an optional picture beside them.
|
|
5
|
+
* From `md` the picture keeps its own width at the row's end and the words
|
|
6
|
+
* take the rest; below `md` the picture sits centred under the words and
|
|
7
|
+
* the actions stack to the full width, as a phone's thumb expects. With no
|
|
8
|
+
* picture the words keep the reading width, so a long title or lead does
|
|
9
|
+
* not run the page's full width. The actions sit one spacing step further
|
|
10
|
+
* from the lead than the text's gap, so the buttons read as their own row.
|
|
11
|
+
*/
|
|
12
|
+
export function cssHeroClasses() {
|
|
13
|
+
return `.sw-hero {
|
|
14
|
+
align-items: center;
|
|
15
|
+
display: grid;
|
|
16
|
+
gap: var(--sw-space-8);
|
|
17
|
+
min-width: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.sw-hero-text {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: var(--sw-space-4);
|
|
24
|
+
min-width: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.sw-hero:not(.sw-hero-with-media) .sw-hero-text {
|
|
28
|
+
max-width: var(--sw-reading-max);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.sw-hero-actions {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-wrap: wrap;
|
|
34
|
+
gap: var(--sw-space-3);
|
|
35
|
+
padding-top: var(--sw-space-1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.sw-hero-media {
|
|
39
|
+
justify-self: center;
|
|
40
|
+
max-width: 100%;
|
|
41
|
+
min-width: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@media ${breakpointQuery('from', 'md')} {
|
|
45
|
+
.sw-hero-text { gap: var(--sw-space-5); }
|
|
46
|
+
.sw-hero-with-media { grid-template-columns: minmax(0, 1fr) auto; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@media ${breakpointQuery('below', 'md')} {
|
|
50
|
+
.sw-hero { gap: var(--sw-space-6); }
|
|
51
|
+
.sw-hero-actions { align-items: stretch; flex-direction: column; }
|
|
52
|
+
}`;
|
|
53
|
+
}
|
|
54
|
+
export function heroClassCatalog() {
|
|
55
|
+
return [
|
|
56
|
+
'sw-hero',
|
|
57
|
+
'sw-hero-with-media',
|
|
58
|
+
'sw-hero-text',
|
|
59
|
+
'sw-hero-actions',
|
|
60
|
+
'sw-hero-media',
|
|
61
|
+
];
|
|
62
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Stepper with a `unit`: the count and its muted word ("90 min") inside
|
|
3
|
+
* one control frame, as Field's suffix. The frame draws the canvas control
|
|
4
|
+
* surface, the focus ring and the states; the input inside drops its own and
|
|
5
|
+
* keeps its few-digit width, its digits against the unit, so the frame never
|
|
6
|
+
* follows the count. The pair is centred, also in a full-width stepper.
|
|
7
|
+
*/
|
|
8
|
+
export declare function cssStepperUnitClasses(): string;
|
|
9
|
+
export declare function stepperUnitClassCatalog(): string[];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { typographyVariants } from '@scalewing/tokens';
|
|
2
|
+
import { controlSurface, disabledControlForcedColors, disabledControlSurface, } from './css-document.js';
|
|
3
|
+
import { changedTintLayer } from './css-field-changed.js';
|
|
4
|
+
const body = typographyVariants.body;
|
|
5
|
+
/*
|
|
6
|
+
* Outranks the stepper's own input rules and state rules (the invalid
|
|
7
|
+
* border, the changed shadow and tint), which the frame draws instead.
|
|
8
|
+
*/
|
|
9
|
+
const framedInput = '[data-theme] .sw-stepper .sw-stepper-count > input.sw-stepper-input';
|
|
10
|
+
const tintedFrame = '.sw-stepper-changed .sw-stepper-count:not(:has(> input:placeholder-shown))';
|
|
11
|
+
/**
|
|
12
|
+
* A Stepper with a `unit`: the count and its muted word ("90 min") inside
|
|
13
|
+
* one control frame, as Field's suffix. The frame draws the canvas control
|
|
14
|
+
* surface, the focus ring and the states; the input inside drops its own and
|
|
15
|
+
* keeps its few-digit width, its digits against the unit, so the frame never
|
|
16
|
+
* follows the count. The pair is centred, also in a full-width stepper.
|
|
17
|
+
*/
|
|
18
|
+
export function cssStepperUnitClasses() {
|
|
19
|
+
return `.sw-stepper-count {
|
|
20
|
+
${controlSurface}
|
|
21
|
+
align-items: center;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
cursor: text;
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
gap: var(--sw-space-1);
|
|
26
|
+
justify-content: center;
|
|
27
|
+
min-height: var(--sw-control-md-min-height);
|
|
28
|
+
min-width: 0;
|
|
29
|
+
padding-inline: var(--sw-space-2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.sw-stepper-full .sw-stepper-count { display: flex; }
|
|
33
|
+
|
|
34
|
+
${framedInput} {
|
|
35
|
+
align-self: stretch;
|
|
36
|
+
background: transparent;
|
|
37
|
+
background-image: none;
|
|
38
|
+
border: 0;
|
|
39
|
+
border-radius: 0;
|
|
40
|
+
box-shadow: none;
|
|
41
|
+
inline-size: 5ch;
|
|
42
|
+
min-height: 0;
|
|
43
|
+
padding-inline: 0;
|
|
44
|
+
text-align: end;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
${framedInput}:focus-visible { outline: none; }
|
|
48
|
+
|
|
49
|
+
.sw-stepper-count:focus-within {
|
|
50
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
51
|
+
outline-offset: var(--sw-focus-ring-offset);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.sw-stepper-unit {
|
|
55
|
+
color: var(--sw-color-muted);
|
|
56
|
+
flex: none;
|
|
57
|
+
font-size: ${body.fontSize}px;
|
|
58
|
+
line-height: ${body.lineHeight}px;
|
|
59
|
+
user-select: none;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sw-stepper-invalid .sw-stepper-count { border-color: var(--sw-color-danger); }
|
|
64
|
+
|
|
65
|
+
.sw-stepper-changed .sw-stepper-count {
|
|
66
|
+
border-color: var(--sw-color-accent);
|
|
67
|
+
box-shadow: inset 0 0 0 1px var(--sw-color-accent);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
${tintedFrame} { background-image: ${changedTintLayer}; }
|
|
71
|
+
|
|
72
|
+
${tintedFrame} > .sw-stepper-unit { color: var(--sw-color-text); }
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* The frame draws the surface, so it carries the disabled look for its count,
|
|
76
|
+
* and only that look: a locked count drops the invalid and changed edges and
|
|
77
|
+
* tint, which name nothing a person can act on.
|
|
78
|
+
*/
|
|
79
|
+
.sw-stepper .sw-stepper-count:has(> input:disabled) {
|
|
80
|
+
${disabledControlSurface}
|
|
81
|
+
background-image: none;
|
|
82
|
+
border-color: var(--sw-color-border);
|
|
83
|
+
box-shadow: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.sw-stepper .sw-stepper-count:has(> input:disabled) > .sw-stepper-unit {
|
|
87
|
+
color: var(--sw-color-muted);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@media (forced-colors: active) {
|
|
91
|
+
.sw-stepper-unit { color: CanvasText; }
|
|
92
|
+
.sw-stepper-invalid .sw-stepper-count { border-color: Mark; }
|
|
93
|
+
.sw-stepper-changed .sw-stepper-count { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; background-image: none; }
|
|
94
|
+
.sw-stepper .sw-stepper-count:has(> input:disabled) {
|
|
95
|
+
${disabledControlForcedColors}
|
|
96
|
+
box-shadow: none;
|
|
97
|
+
}
|
|
98
|
+
}`;
|
|
99
|
+
}
|
|
100
|
+
export function stepperUnitClassCatalog() {
|
|
101
|
+
return ['sw-stepper-count', 'sw-stepper-unit'];
|
|
102
|
+
}
|
package/dist/css/css-stepper.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { changedTintLayer } from './css-field-changed.js';
|
|
3
|
+
import { cssStepperUnitClasses, stepperUnitClassCatalog, } from './css-stepper-unit.js';
|
|
3
4
|
const body = typographyVariants.body;
|
|
4
5
|
const label = typographyVariants.label;
|
|
5
6
|
const title = typographyVariants.title;
|
|
@@ -116,7 +117,9 @@ ${input} {
|
|
|
116
117
|
|
|
117
118
|
.sw-stepper-full input.sw-stepper-input { inline-size: 100%; }
|
|
118
119
|
|
|
119
|
-
${stateRules()}
|
|
120
|
+
${stateRules()}
|
|
121
|
+
|
|
122
|
+
${cssStepperUnitClasses()}`;
|
|
120
123
|
}
|
|
121
124
|
export function stepperClassCatalog() {
|
|
122
125
|
return [
|
|
@@ -129,5 +132,6 @@ export function stepperClassCatalog() {
|
|
|
129
132
|
'sw-stepper-changed',
|
|
130
133
|
'sw-stepper-readonly',
|
|
131
134
|
'sw-stepper-limit-plain',
|
|
135
|
+
...stepperUnitClassCatalog(),
|
|
132
136
|
];
|
|
133
137
|
}
|
package/dist/css/stylesheet.js
CHANGED
|
@@ -19,6 +19,7 @@ import { countPillClassCatalog } from './css-count-pill.js';
|
|
|
19
19
|
import { checkboxClassCatalog } from './css-checkbox.js';
|
|
20
20
|
import { radioGroupClassCatalog } from './css-radio-group.js';
|
|
21
21
|
import { skeletonClassCatalog } from './css-skeleton.js';
|
|
22
|
+
import { emptyStateClassCatalog } from './css-empty-state.js';
|
|
22
23
|
import { spinnerClassCatalog } from './css-spinner.js';
|
|
23
24
|
import { progressClassCatalog } from './css-progress.js';
|
|
24
25
|
import { noticeClassCatalog } from './css-notice.js';
|
|
@@ -27,6 +28,7 @@ import { tooltipClassCatalog } from './css-tooltip.js';
|
|
|
27
28
|
import { infoTipClassCatalog } from './css-info-tip.js';
|
|
28
29
|
import { separatorClassCatalog } from './css-separator.js';
|
|
29
30
|
import { bandClassCatalog } from './css-band.js';
|
|
31
|
+
import { heroClassCatalog } from './css-hero.js';
|
|
30
32
|
import { siteFooterClassCatalog } from './css-site-footer.js';
|
|
31
33
|
import { siteHeaderClassCatalog } from './css-site-header.js';
|
|
32
34
|
import { statTileClassCatalog } from './css-stat-tile.js';
|
|
@@ -132,6 +134,7 @@ export function utilityClassCatalog() {
|
|
|
132
134
|
...radioGroupClassCatalog(),
|
|
133
135
|
...spinnerClassCatalog(),
|
|
134
136
|
...skeletonClassCatalog(),
|
|
137
|
+
...emptyStateClassCatalog(),
|
|
135
138
|
...progressClassCatalog(),
|
|
136
139
|
...noticeClassCatalog(),
|
|
137
140
|
...glyphTileClassCatalog(),
|
|
@@ -139,6 +142,7 @@ export function utilityClassCatalog() {
|
|
|
139
142
|
...infoTipClassCatalog(),
|
|
140
143
|
...separatorClassCatalog(),
|
|
141
144
|
...bandClassCatalog(),
|
|
145
|
+
...heroClassCatalog(),
|
|
142
146
|
...siteFooterClassCatalog(),
|
|
143
147
|
...siteHeaderClassCatalog(),
|
|
144
148
|
...statTileClassCatalog(),
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { listTileColumnCounts, type ListGroupTileColumns, } from './css/css-list
|
|
|
17
17
|
export { Checkbox, type CheckboxProps } from './components/Checkbox.js';
|
|
18
18
|
export { Spinner, type SpinnerProps, type SpinnerSize, } from './components/Spinner.js';
|
|
19
19
|
export { Skeleton, skeletonVariants, type SkeletonProps, type SkeletonVariant, } from './components/Skeleton.js';
|
|
20
|
+
export { EmptyState, emptyStateTitleElements, type EmptyStateProps, type EmptyStateTitleElement, } from './components/EmptyState.js';
|
|
20
21
|
export { GlyphTile, glyphTileSizes, type GlyphTileHue, type GlyphTileProps, type GlyphTileSize, } from './components/GlyphTile.js';
|
|
21
22
|
export { Notice, noticeTones, noticeVariants, type NoticeProps, type NoticeRole, type NoticeTone, type NoticeVariant, } from './components/Notice.js';
|
|
22
23
|
export { Progress, progressTones, type ProgressProps, type ProgressTone, } from './components/Progress.js';
|
|
@@ -79,14 +80,16 @@ export { Band, bandElements, bandTones, bandWidths, } from './components/Band.js
|
|
|
79
80
|
export type { BandElement, BandProps, BandTone, BandWidth, } from './components/Band.js';
|
|
80
81
|
export { SiteHeader } from './components/SiteHeader.js';
|
|
81
82
|
export type { SiteHeaderItem, SiteHeaderLabels, SiteHeaderProps, } from './components/SiteHeader.js';
|
|
83
|
+
export { Hero } from './components/Hero.js';
|
|
84
|
+
export type { HeroProps } from './components/Hero.js';
|
|
82
85
|
export { SiteFooter } from './components/SiteFooter.js';
|
|
83
86
|
export type { SiteFooterGroup, SiteFooterLink, SiteFooterProps, } from './components/SiteFooter.js';
|
|
84
87
|
export { Separator } from './components/Separator.js';
|
|
85
88
|
export type { SeparatorOrientation, SeparatorProps, } from './components/Separator.js';
|
|
86
89
|
export { DescriptionItem, DescriptionList, descriptionListLayouts, } from './components/DescriptionList.js';
|
|
87
90
|
export type { DescriptionItemProps, DescriptionListLayout, DescriptionListProps, } from './components/DescriptionList.js';
|
|
88
|
-
export { Grid } from './components/Grid.js';
|
|
89
|
-
export type { GridColumns, GridProps } from './components/Grid.js';
|
|
91
|
+
export { Grid, gridFits } from './components/Grid.js';
|
|
92
|
+
export type { GridColumns, GridFit, GridProps } from './components/Grid.js';
|
|
90
93
|
export type { GridColumnSpan } from './css/css-grid-span.js';
|
|
91
94
|
export type { ColumnSpanProps } from './column-span-classes.js';
|
|
92
95
|
export { DenominationGrid, denominationCellToneAppearances, denominationGridFrames, denominationLabelAppearances, denominationNegativeAppearances, denominationTotalEmphases, denominationTransposeBreakpoints, } from './components/DenominationGrid.js';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { listTileColumnCounts, } from './css/css-list-tiles.js';
|
|
|
16
16
|
export { Checkbox } from './components/Checkbox.js';
|
|
17
17
|
export { Spinner, } from './components/Spinner.js';
|
|
18
18
|
export { Skeleton, skeletonVariants, } from './components/Skeleton.js';
|
|
19
|
+
export { EmptyState, emptyStateTitleElements, } from './components/EmptyState.js';
|
|
19
20
|
export { GlyphTile, glyphTileSizes, } from './components/GlyphTile.js';
|
|
20
21
|
export { Notice, noticeTones, noticeVariants, } from './components/Notice.js';
|
|
21
22
|
export { Progress, progressTones, } from './components/Progress.js';
|
|
@@ -71,10 +72,11 @@ export { utilityClassCatalog } from './css/stylesheet.js';
|
|
|
71
72
|
export { breakpointQuery } from './css/breakpoints.js';
|
|
72
73
|
export { Band, bandElements, bandTones, bandWidths, } from './components/Band.js';
|
|
73
74
|
export { SiteHeader } from './components/SiteHeader.js';
|
|
75
|
+
export { Hero } from './components/Hero.js';
|
|
74
76
|
export { SiteFooter } from './components/SiteFooter.js';
|
|
75
77
|
export { Separator } from './components/Separator.js';
|
|
76
78
|
export { DescriptionItem, DescriptionList, descriptionListLayouts, } from './components/DescriptionList.js';
|
|
77
|
-
export { Grid } from './components/Grid.js';
|
|
79
|
+
export { Grid, gridFits } from './components/Grid.js';
|
|
78
80
|
export { DenominationGrid, denominationCellToneAppearances, denominationGridFrames, denominationLabelAppearances, denominationNegativeAppearances, denominationTotalEmphases, denominationTransposeBreakpoints, } from './components/DenominationGrid.js';
|
|
79
81
|
export { FilterChips, filterChipCountAppearances, filterChipsOverflows, filterChipTones, } from './components/FilterChips.js';
|
|
80
82
|
export { FilterChipsRow } from './components/FilterChipsRow.js';
|
package/dist/stepper-count.d.ts
CHANGED
|
@@ -35,3 +35,11 @@ export declare function stepperMoves(value: number | null, bounds: StepperBounds
|
|
|
35
35
|
export declare function readStepperEntry(text: string, allowsNegative: boolean): StepperEntry;
|
|
36
36
|
/** The box's text for a count: the plain number, or empty for none. */
|
|
37
37
|
export declare function stepperText(value: number | null): string;
|
|
38
|
+
/** A consumer bug: a unit, when given, is a word to show after the count. */
|
|
39
|
+
export declare function assertStepperUnit(unit: string | undefined): void;
|
|
40
|
+
/**
|
|
41
|
+
* The count in words for assistive technology: the consumer's own words
|
|
42
|
+
* when given, otherwise the count and its unit ("90 min"), or nothing extra
|
|
43
|
+
* (the bare count is `aria-valuenow`) when there is no unit or no count.
|
|
44
|
+
*/
|
|
45
|
+
export declare function stepperValueText(value: number | null, unit: string | undefined, valueText: string | undefined): string | undefined;
|
package/dist/stepper-count.js
CHANGED
|
@@ -48,3 +48,20 @@ export function readStepperEntry(text, allowsNegative) {
|
|
|
48
48
|
export function stepperText(value) {
|
|
49
49
|
return value === null ? '' : String(value);
|
|
50
50
|
}
|
|
51
|
+
/** A consumer bug: a unit, when given, is a word to show after the count. */
|
|
52
|
+
export function assertStepperUnit(unit) {
|
|
53
|
+
if (unit !== undefined && unit.trim() === '')
|
|
54
|
+
throw new RangeError('Stepper unit must not be blank');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The count in words for assistive technology: the consumer's own words
|
|
58
|
+
* when given, otherwise the count and its unit ("90 min"), or nothing extra
|
|
59
|
+
* (the bare count is `aria-valuenow`) when there is no unit or no count.
|
|
60
|
+
*/
|
|
61
|
+
export function stepperValueText(value, unit, valueText) {
|
|
62
|
+
if (valueText !== undefined)
|
|
63
|
+
return valueText;
|
|
64
|
+
if (unit === undefined || value === null)
|
|
65
|
+
return undefined;
|
|
66
|
+
return `${value} ${unit.trim()}`;
|
|
67
|
+
}
|
package/dist/styles.css
CHANGED
|
@@ -894,19 +894,25 @@ body {
|
|
|
894
894
|
.sw-text-align-center { text-align: center; }
|
|
895
895
|
.sw-text-align-end { text-align: end; }
|
|
896
896
|
|
|
897
|
-
:where(.sw-text-display, .sw-text-heading, .sw-text-title, .sw-text-body, .sw-text-label, .sw-text-caption, .sw-text-data) { margin: 0; }
|
|
897
|
+
:where(.sw-text-hero, .sw-text-display, .sw-text-headline, .sw-text-heading, .sw-text-title, .sw-text-lead, .sw-text-body, .sw-text-label, .sw-text-caption, .sw-text-data) { margin: 0; }
|
|
898
898
|
|
|
899
|
+
.sw-text-hero { font-family: var(--sw-font-sans); font-size: 56px; line-height: 60px; font-weight: 700; letter-spacing: -1.7px; }
|
|
899
900
|
.sw-text-display { font-family: var(--sw-font-sans); font-size: 40px; line-height: 48px; font-weight: 600; letter-spacing: -0.8px; }
|
|
901
|
+
.sw-text-headline { font-family: var(--sw-font-sans); font-size: 36px; line-height: 40px; font-weight: 700; letter-spacing: -0.7px; }
|
|
900
902
|
.sw-text-heading { font-family: var(--sw-font-sans); font-size: 28px; line-height: 34px; font-weight: 600; letter-spacing: -0.45px; }
|
|
901
903
|
.sw-text-title { font-family: var(--sw-font-sans); font-size: 20px; line-height: 28px; font-weight: 600; letter-spacing: -0.3px; }
|
|
904
|
+
.sw-text-lead { font-family: var(--sw-font-sans); font-size: 20px; line-height: 29px; font-weight: 400; letter-spacing: -0.3px; }
|
|
902
905
|
.sw-text-body { font-family: var(--sw-font-sans); font-size: 17px; line-height: 25px; font-weight: 400; letter-spacing: -0.2px; }
|
|
903
906
|
.sw-text-label { font-family: var(--sw-font-sans); font-size: 15px; line-height: 20px; font-weight: 600; letter-spacing: -0.2px; }
|
|
904
907
|
.sw-text-caption { font-family: var(--sw-font-sans); font-size: 13px; line-height: 18px; font-weight: 400; letter-spacing: -0.08px; }
|
|
905
908
|
.sw-text-data { font-family: var(--sw-font-sans); font-size: 13px; line-height: 18px; font-weight: 500; letter-spacing: -0.08px; font-variant-numeric: tabular-nums; }
|
|
906
909
|
|
|
907
910
|
@media not all and (min-width: 48rem) {
|
|
911
|
+
.sw-text-hero { font-size: 36px; line-height: 40px; letter-spacing: -0.7px; }
|
|
908
912
|
.sw-text-display { font-size: 32px; line-height: 38px; letter-spacing: -0.6px; }
|
|
913
|
+
.sw-text-headline { font-size: 26px; line-height: 30px; letter-spacing: -0.4px; }
|
|
909
914
|
.sw-text-heading { font-size: 24px; line-height: 30px; letter-spacing: -0.4px; }
|
|
915
|
+
.sw-text-lead { font-size: 17px; line-height: 25px; letter-spacing: -0.2px; }
|
|
910
916
|
}
|
|
911
917
|
|
|
912
918
|
[data-theme] .sw-font-mono,
|
|
@@ -4600,6 +4606,47 @@ body {
|
|
|
4600
4606
|
.sw-band-inverse { border-block: 1px solid CanvasText; }
|
|
4601
4607
|
}
|
|
4602
4608
|
|
|
4609
|
+
.sw-hero {
|
|
4610
|
+
align-items: center;
|
|
4611
|
+
display: grid;
|
|
4612
|
+
gap: var(--sw-space-8);
|
|
4613
|
+
min-width: 0;
|
|
4614
|
+
}
|
|
4615
|
+
|
|
4616
|
+
.sw-hero-text {
|
|
4617
|
+
display: flex;
|
|
4618
|
+
flex-direction: column;
|
|
4619
|
+
gap: var(--sw-space-4);
|
|
4620
|
+
min-width: 0;
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
.sw-hero:not(.sw-hero-with-media) .sw-hero-text {
|
|
4624
|
+
max-width: var(--sw-reading-max);
|
|
4625
|
+
}
|
|
4626
|
+
|
|
4627
|
+
.sw-hero-actions {
|
|
4628
|
+
display: flex;
|
|
4629
|
+
flex-wrap: wrap;
|
|
4630
|
+
gap: var(--sw-space-3);
|
|
4631
|
+
padding-top: var(--sw-space-1);
|
|
4632
|
+
}
|
|
4633
|
+
|
|
4634
|
+
.sw-hero-media {
|
|
4635
|
+
justify-self: center;
|
|
4636
|
+
max-width: 100%;
|
|
4637
|
+
min-width: 0;
|
|
4638
|
+
}
|
|
4639
|
+
|
|
4640
|
+
@media (min-width: 48rem) {
|
|
4641
|
+
.sw-hero-text { gap: var(--sw-space-5); }
|
|
4642
|
+
.sw-hero-with-media { grid-template-columns: minmax(0, 1fr) auto; }
|
|
4643
|
+
}
|
|
4644
|
+
|
|
4645
|
+
@media not all and (min-width: 48rem) {
|
|
4646
|
+
.sw-hero { gap: var(--sw-space-6); }
|
|
4647
|
+
.sw-hero-actions { align-items: stretch; flex-direction: column; }
|
|
4648
|
+
}
|
|
4649
|
+
|
|
4603
4650
|
.sw-site-footer-top {
|
|
4604
4651
|
display: grid;
|
|
4605
4652
|
gap: var(--sw-space-6) var(--sw-space-5);
|
|
@@ -7141,6 +7188,62 @@ body {
|
|
|
7141
7188
|
.sw-skeleton-row + .sw-skeleton-row { border-color: GrayText; }
|
|
7142
7189
|
}
|
|
7143
7190
|
|
|
7191
|
+
.sw-empty-state {
|
|
7192
|
+
align-items: center;
|
|
7193
|
+
background: var(--sw-color-surface);
|
|
7194
|
+
border: 1px dashed var(--sw-color-border);
|
|
7195
|
+
border-radius: var(--sw-radius-md);
|
|
7196
|
+
box-sizing: border-box;
|
|
7197
|
+
display: flex;
|
|
7198
|
+
flex-direction: column;
|
|
7199
|
+
gap: var(--sw-space-3);
|
|
7200
|
+
min-width: 0;
|
|
7201
|
+
overflow-wrap: anywhere;
|
|
7202
|
+
padding: var(--sw-space-8) var(--sw-space-4);
|
|
7203
|
+
text-align: center;
|
|
7204
|
+
width: 100%;
|
|
7205
|
+
}
|
|
7206
|
+
|
|
7207
|
+
.sw-empty-state-icon {
|
|
7208
|
+
align-items: center;
|
|
7209
|
+
background: var(--sw-color-subtle);
|
|
7210
|
+
border-radius: var(--sw-radius-md);
|
|
7211
|
+
color: var(--sw-color-muted);
|
|
7212
|
+
display: inline-flex;
|
|
7213
|
+
flex: none;
|
|
7214
|
+
height: var(--sw-space-8);
|
|
7215
|
+
justify-content: center;
|
|
7216
|
+
width: var(--sw-space-8);
|
|
7217
|
+
}
|
|
7218
|
+
|
|
7219
|
+
.sw-empty-state-icon > svg {
|
|
7220
|
+
height: var(--sw-space-5);
|
|
7221
|
+
width: var(--sw-space-5);
|
|
7222
|
+
}
|
|
7223
|
+
|
|
7224
|
+
.sw-empty-state-title,
|
|
7225
|
+
.sw-empty-state-description {
|
|
7226
|
+
margin: 0;
|
|
7227
|
+
max-width: var(--sw-reading-max);
|
|
7228
|
+
}
|
|
7229
|
+
|
|
7230
|
+
.sw-empty-state-actions {
|
|
7231
|
+
display: flex;
|
|
7232
|
+
flex-wrap: wrap;
|
|
7233
|
+
gap: var(--sw-space-2);
|
|
7234
|
+
justify-content: center;
|
|
7235
|
+
margin-top: var(--sw-space-1);
|
|
7236
|
+
}
|
|
7237
|
+
|
|
7238
|
+
@media (pointer: coarse) {
|
|
7239
|
+
.sw-empty-state-actions .sw-button { min-height: var(--sw-control-md-min-height); }
|
|
7240
|
+
}
|
|
7241
|
+
|
|
7242
|
+
@media (forced-colors: active) {
|
|
7243
|
+
.sw-empty-state { border-color: CanvasText; }
|
|
7244
|
+
.sw-empty-state-icon { color: CanvasText; }
|
|
7245
|
+
}
|
|
7246
|
+
|
|
7144
7247
|
.sw-progress {
|
|
7145
7248
|
display: flex;
|
|
7146
7249
|
flex-direction: column;
|
|
@@ -7868,6 +7971,9 @@ body {
|
|
|
7868
7971
|
.sw-grid-cols-below-md-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
7869
7972
|
.sw-grid-cols-below-md-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
7870
7973
|
}
|
|
7974
|
+
.sw-grid-fit-narrow { grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }
|
|
7975
|
+
.sw-grid-fit-medium { grid-template-columns: repeat(auto-fit, minmax(min(100%, 16.25rem), 1fr)); }
|
|
7976
|
+
.sw-grid-fit-wide { grid-template-columns: repeat(auto-fit, minmax(min(100%, 18.75rem), 1fr)); }
|
|
7871
7977
|
.sw-grid-span-1 { grid-column: span 1; }
|
|
7872
7978
|
.sw-grid-span-2 { grid-column: span 2; }
|
|
7873
7979
|
.sw-grid-span-3 { grid-column: span 3; }
|
|
@@ -7883,6 +7989,18 @@ body {
|
|
|
7883
7989
|
:where(.sw-grid-cols-3) > .sw-grid-span-4 { grid-column: span 3; }
|
|
7884
7990
|
:where(.sw-grid-cols-3) > .sw-grid-span-6 { grid-column: span 3; }
|
|
7885
7991
|
:where(.sw-grid-cols-4) > .sw-grid-span-6 { grid-column: span 4; }
|
|
7992
|
+
:where(.sw-grid-fit-narrow) > .sw-grid-span-2 { grid-column: auto; }
|
|
7993
|
+
:where(.sw-grid-fit-narrow) > .sw-grid-span-3 { grid-column: auto; }
|
|
7994
|
+
:where(.sw-grid-fit-narrow) > .sw-grid-span-4 { grid-column: auto; }
|
|
7995
|
+
:where(.sw-grid-fit-narrow) > .sw-grid-span-6 { grid-column: auto; }
|
|
7996
|
+
:where(.sw-grid-fit-medium) > .sw-grid-span-2 { grid-column: auto; }
|
|
7997
|
+
:where(.sw-grid-fit-medium) > .sw-grid-span-3 { grid-column: auto; }
|
|
7998
|
+
:where(.sw-grid-fit-medium) > .sw-grid-span-4 { grid-column: auto; }
|
|
7999
|
+
:where(.sw-grid-fit-medium) > .sw-grid-span-6 { grid-column: auto; }
|
|
8000
|
+
:where(.sw-grid-fit-wide) > .sw-grid-span-2 { grid-column: auto; }
|
|
8001
|
+
:where(.sw-grid-fit-wide) > .sw-grid-span-3 { grid-column: auto; }
|
|
8002
|
+
:where(.sw-grid-fit-wide) > .sw-grid-span-4 { grid-column: auto; }
|
|
8003
|
+
:where(.sw-grid-fit-wide) > .sw-grid-span-6 { grid-column: auto; }
|
|
7886
8004
|
@media not all and (min-width: 48rem) {
|
|
7887
8005
|
:where(.sw-grid-cols-below-md-1) > .sw-grid-span-2 { grid-column: span 1; }
|
|
7888
8006
|
:where(.sw-grid-cols-below-md-1) > .sw-grid-span-3 { grid-column: span 1; }
|
|
@@ -8915,6 +9033,99 @@ body {
|
|
|
8915
9033
|
[data-theme] .sw-stepper-changed input.sw-stepper-input { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; background-image: none; }
|
|
8916
9034
|
}
|
|
8917
9035
|
|
|
9036
|
+
.sw-stepper-count {
|
|
9037
|
+
background-color: var(--sw-glass-fill);
|
|
9038
|
+
border: 1px solid var(--sw-color-border);
|
|
9039
|
+
border-radius: var(--sw-radius-sm);
|
|
9040
|
+
color: inherit;
|
|
9041
|
+
font-family: inherit;
|
|
9042
|
+
font-size: inherit;
|
|
9043
|
+
padding-inline: var(--sw-control-md-padding-inline);
|
|
9044
|
+
align-items: center;
|
|
9045
|
+
box-sizing: border-box;
|
|
9046
|
+
cursor: text;
|
|
9047
|
+
display: inline-flex;
|
|
9048
|
+
gap: var(--sw-space-1);
|
|
9049
|
+
justify-content: center;
|
|
9050
|
+
min-height: var(--sw-control-md-min-height);
|
|
9051
|
+
min-width: 0;
|
|
9052
|
+
padding-inline: var(--sw-space-2);
|
|
9053
|
+
}
|
|
9054
|
+
|
|
9055
|
+
.sw-stepper-full .sw-stepper-count { display: flex; }
|
|
9056
|
+
|
|
9057
|
+
[data-theme] .sw-stepper .sw-stepper-count > input.sw-stepper-input {
|
|
9058
|
+
align-self: stretch;
|
|
9059
|
+
background: transparent;
|
|
9060
|
+
background-image: none;
|
|
9061
|
+
border: 0;
|
|
9062
|
+
border-radius: 0;
|
|
9063
|
+
box-shadow: none;
|
|
9064
|
+
inline-size: 5ch;
|
|
9065
|
+
min-height: 0;
|
|
9066
|
+
padding-inline: 0;
|
|
9067
|
+
text-align: end;
|
|
9068
|
+
}
|
|
9069
|
+
|
|
9070
|
+
[data-theme] .sw-stepper .sw-stepper-count > input.sw-stepper-input:focus-visible { outline: none; }
|
|
9071
|
+
|
|
9072
|
+
.sw-stepper-count:focus-within {
|
|
9073
|
+
outline: var(--sw-focus-ring-width) solid var(--sw-color-accent);
|
|
9074
|
+
outline-offset: var(--sw-focus-ring-offset);
|
|
9075
|
+
}
|
|
9076
|
+
|
|
9077
|
+
.sw-stepper-unit {
|
|
9078
|
+
color: var(--sw-color-muted);
|
|
9079
|
+
flex: none;
|
|
9080
|
+
font-size: 17px;
|
|
9081
|
+
line-height: 25px;
|
|
9082
|
+
user-select: none;
|
|
9083
|
+
white-space: nowrap;
|
|
9084
|
+
}
|
|
9085
|
+
|
|
9086
|
+
.sw-stepper-invalid .sw-stepper-count { border-color: var(--sw-color-danger); }
|
|
9087
|
+
|
|
9088
|
+
.sw-stepper-changed .sw-stepper-count {
|
|
9089
|
+
border-color: var(--sw-color-accent);
|
|
9090
|
+
box-shadow: inset 0 0 0 1px var(--sw-color-accent);
|
|
9091
|
+
}
|
|
9092
|
+
|
|
9093
|
+
.sw-stepper-changed .sw-stepper-count:not(:has(> input:placeholder-shown)) { background-image: linear-gradient(color-mix(in srgb, var(--sw-color-accent) 12%, transparent), color-mix(in srgb, var(--sw-color-accent) 12%, transparent)); }
|
|
9094
|
+
|
|
9095
|
+
.sw-stepper-changed .sw-stepper-count:not(:has(> input:placeholder-shown)) > .sw-stepper-unit { color: var(--sw-color-text); }
|
|
9096
|
+
|
|
9097
|
+
/*
|
|
9098
|
+
* The frame draws the surface, so it carries the disabled look for its count,
|
|
9099
|
+
* and only that look: a locked count drops the invalid and changed edges and
|
|
9100
|
+
* tint, which name nothing a person can act on.
|
|
9101
|
+
*/
|
|
9102
|
+
.sw-stepper .sw-stepper-count:has(> input:disabled) {
|
|
9103
|
+
background-color: var(--sw-color-subtle);
|
|
9104
|
+
border-style: dashed;
|
|
9105
|
+
cursor: not-allowed;
|
|
9106
|
+
opacity: 1;
|
|
9107
|
+
-webkit-text-fill-color: currentColor;
|
|
9108
|
+
background-image: none;
|
|
9109
|
+
border-color: var(--sw-color-border);
|
|
9110
|
+
box-shadow: none;
|
|
9111
|
+
}
|
|
9112
|
+
|
|
9113
|
+
.sw-stepper .sw-stepper-count:has(> input:disabled) > .sw-stepper-unit {
|
|
9114
|
+
color: var(--sw-color-muted);
|
|
9115
|
+
}
|
|
9116
|
+
|
|
9117
|
+
@media (forced-colors: active) {
|
|
9118
|
+
.sw-stepper-unit { color: CanvasText; }
|
|
9119
|
+
.sw-stepper-invalid .sw-stepper-count { border-color: Mark; }
|
|
9120
|
+
.sw-stepper-changed .sw-stepper-count { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; background-image: none; }
|
|
9121
|
+
.sw-stepper .sw-stepper-count:has(> input:disabled) {
|
|
9122
|
+
border-color: GrayText;
|
|
9123
|
+
color: GrayText;
|
|
9124
|
+
-webkit-text-fill-color: GrayText;
|
|
9125
|
+
box-shadow: none;
|
|
9126
|
+
}
|
|
9127
|
+
}
|
|
9128
|
+
|
|
8918
9129
|
.sw-steps {
|
|
8919
9130
|
display: flex;
|
|
8920
9131
|
flex-direction: column;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalewing/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.49.0",
|
|
4
4
|
"description": "Scalewing DOM components and package-owned CSS utilities.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"markdown-it": "14.3.2",
|
|
47
47
|
"pdfjs-dist": "6.4.299",
|
|
48
|
-
"@scalewing/tokens": "^1.
|
|
48
|
+
"@scalewing/tokens": "^1.7.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "^19.0.0",
|