@crewlethq/ui 0.49.0 → 0.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/README.md +18 -0
- package/dist/ContrastBadge/ContrastBadge.d.ts +25 -3
- package/dist/ContrastBadge/index.d.ts +1 -1
- package/dist/ContrastBadge/index.js +1 -1
- package/dist/EditingStatus/EditingStatus.d.ts +15 -1
- package/dist/EditingStatus/index.js +3 -1
- package/dist/InlineEditor/index.js +4 -2
- package/dist/LogoSlot/index.js +2 -2
- package/dist/SetupWizard/SetupWizard.d.ts +77 -0
- package/dist/SetupWizard/index.d.ts +3 -0
- package/dist/SetupWizard/index.js +11 -0
- package/dist/SetupWizard-Y5BTXZX5.css +197 -0
- package/dist/Stepper/Stepper.d.ts +60 -11
- package/dist/Stepper/index.d.ts +1 -1
- package/dist/Stepper/index.js +1 -1
- package/dist/Stepper-QEJI5RGX.css +355 -0
- package/dist/{chunk-ITI2Z44C.js → chunk-BEAXJARF.js} +1 -1
- package/dist/chunk-KKHCUHWS.js +107 -0
- package/dist/chunk-LDGQIDIZ.js +129 -0
- package/dist/{chunk-MULOZYHL.js → chunk-MMOXWAUG.js} +16 -4
- package/dist/{chunk-B235ANDO.js → chunk-NSSOWQCJ.js} +20 -5
- package/dist/{chunk-7P336RA5.js → chunk-QGRA2RHC.js} +1 -1
- package/dist/generated/index.d.ts +1 -0
- package/dist/generated/index.js +21 -20
- package/dist/index.js +21 -20
- package/dist/styles.css +391 -12
- package/package.json +3 -3
- package/dist/Stepper-HW4JM3YH.css +0 -176
- package/dist/chunk-5QWIJSQM.js +0 -73
- /package/dist/{chunk-3WMOEGEL.js → chunk-HNQ2EIMR.js} +0 -0
package/README.md
CHANGED
|
@@ -204,6 +204,24 @@ places:
|
|
|
204
204
|
- A surface that pans or zooms dispatches `LAYER_REPOSITION_EVENT` on its host
|
|
205
205
|
when the content beneath it moves, and every panel anchored inside follows.
|
|
206
206
|
|
|
207
|
+
## Changes in 0.50.0
|
|
208
|
+
|
|
209
|
+
The pieces of a setup a person walks step by step, first used by the brand setup.
|
|
210
|
+
|
|
211
|
+
**`SetupWizard` is new**: a page of its own for a setup walked step by step. The header is a `PageHeader` title with the trail above it (`trail`) and the draft's status (`status`) and the way out (`actions`) beside it. The rail is a `nav` named by `stepsLabel`, holding a vertical `Stepper` of `steps` with `current`, `reached` and `onSelect`. The content is a `section` named by the step's `heading`, with a `lead` sentence and the step's fields (`children`) in one column of at most 640px. The optional `aside` is a complementary landmark named by `asideLabel` (required with it), held in view beside the content. The `footer` holds the step's actions at the bottom of the view, above the device's safe area. From 70rem of the wizard's own width the rail, the content and the aside stand side by side (240px, the content, 320px); under that, as in a 1024px window beside the application's rail, the aside moves under the content. Under the phone breakpoint the rail becomes a row of steps under "Step 3 of 6 · Colours" (`labels.progress`) and the aside folds into a disclosure (`labels.asideToggle`, "Preview"). Focus moves to the step's heading when the step changes, and the step's content and the aside start one heading level under it.
|
|
212
|
+
|
|
213
|
+
**`Stepper` takes `orientation`, a per-step `status` and `description`, and `onSelect`.** `orientation="vertical"` draws a column of rows: a 20px mark per step (the check, the dot, the warning glyph, or the step's number) on a hairline that joins them, the label beside it, and the description and the status words under it, with the current row lifted as the sidebar's is. A step's `status` (`done`, `current`, `upcoming`, `optional` or `attention`) draws it where its position does not say it all: an optional step says "Optional" (`optionalLabel`), and one that needs attention carries the warning glyph on the warning tint and says "Needs attention" (`attentionLabel`); in a row the words are said rather than shown. The current step is always drawn current, and `status: 'current'` on any other step is refused. With `onSelect`, every visited step (the current one, every step before it, and every step up to `reached`) is a button, the current one carrying `aria-current="step"`, and a step not reached yet stays words rather than a disabled button. The horizontal row draws exactly as before; its step-still-to-come class is now `crewlet-stepper__step--upcoming` (it was `--later`).
|
|
214
|
+
|
|
215
|
+
**`ContrastBadge` takes `advisory`**: under its scale's AA floor the badge reads "Low" in the warning tone with the warning glyph (`crewlet-contrast-badge--low`), rather than "AA large text" or "Fails" in the danger tone, for a pair a person is told about and may keep. A pair that blocks leaves it off and keeps today's behaviour. `labels.levels` now takes any of the level words on its own (`ContrastBadgeLabelsInput`), and the drawn level is typed `ContrastBadgeLevel`.
|
|
216
|
+
|
|
217
|
+
**`EditingStatus` takes `savedAt`**: with nothing unsaved and no save in flight, the status says how long ago the last save landed (`labels.saved`, "Saved 2m ago"; "Draft · Saved 2m ago" with `labels.editing` "Draft"). The time follows the shared clock and is hidden from the live region, which says "Saved" (`labels.savedSpoken`) once rather than every minute.
|
|
218
|
+
|
|
219
|
+
**`@crewlethq/tokens` exports `nearestContrastShade(hex, ground, min)`**: the colour nearest `hex` that clears `min` against `ground`, keeping its hue and moving only its OKLab lightness, or `null` when no shade can. `#00baba` on `#f6f7fb` gives `#007f7f` at 4.5:1. It is the one implementation a "Use #007f7f (4.51:1)" suggestion is computed with; see the tokens README.
|
|
220
|
+
|
|
221
|
+
## Changes in 0.49.1
|
|
222
|
+
|
|
223
|
+
**`@crewlethq/tokens` keeps its palettes and its theme registry in modules of their own.** The `themes` object (both palettes, every value) was generated into the same module as `themeRegistry`, `themeGroups`, `DEFAULT_THEME_ID` and `isThemeId`, and a bundler keeps a module whole in the chunk that first needs it: an application that reads the registry at boot (a theme preference) carried every palette value in its entry as soon as any later screen imported `themes` (since 0.47.0, `LogoSlot`), about 8 KB raw and 2 KB gzipped on every first paint of the engine dashboard. `themes` now comes from a module of its own (`dist/palettes.js`), so a consumer of the registry never carries it and the palettes land in the chunk that uses them. The package's exports are unchanged: `import { themes, themeRegistry } from '@crewlethq/tokens'` works as before.
|
|
224
|
+
|
|
207
225
|
## Changes in 0.49.0
|
|
208
226
|
|
|
209
227
|
The pieces of a company's brand and of the folders artifacts are filed in.
|
|
@@ -9,6 +9,11 @@ export type ContrastScale = 'body' | 'large';
|
|
|
9
9
|
* 3:1 but not 4.5:1), or under every floor its scale has.
|
|
10
10
|
*/
|
|
11
11
|
export type ContrastLevel = 'aaa' | 'aa' | 'aa-large' | 'fail';
|
|
12
|
+
/**
|
|
13
|
+
* What the badge draws: a level, or `low`, which an ADVISORY badge draws for
|
|
14
|
+
* a pair under its scale's AA floor in place of `aa-large` or `fail`.
|
|
15
|
+
*/
|
|
16
|
+
export type ContrastBadgeLevel = ContrastLevel | 'low';
|
|
12
17
|
/**
|
|
13
18
|
* The level a measured ratio reaches, on the floors `@crewlethq/tokens`
|
|
14
19
|
* exports. The ratio is compared UNROUNDED: a pair that passes only once
|
|
@@ -27,7 +32,7 @@ export declare function contrastRatio(foreground: string, background: string): n
|
|
|
27
32
|
*/
|
|
28
33
|
export declare function formatContrastRatio(ratio: number): string;
|
|
29
34
|
export interface ContrastBadgeLabels {
|
|
30
|
-
levels: Record<
|
|
35
|
+
levels: Record<ContrastBadgeLevel, string>;
|
|
31
36
|
/** Said for a pair that cannot be measured (a colour not set yet). */
|
|
32
37
|
unmeasured: string;
|
|
33
38
|
/** The ratio, said in words: "4.49 to 1". */
|
|
@@ -35,6 +40,10 @@ export interface ContrastBadgeLabels {
|
|
|
35
40
|
/** The letters the sample draws the pair with. */
|
|
36
41
|
sample: string;
|
|
37
42
|
}
|
|
43
|
+
/** The words a caller may replace: any of them, and any of the level words. */
|
|
44
|
+
export type ContrastBadgeLabelsInput = Partial<Omit<ContrastBadgeLabels, 'levels'>> & {
|
|
45
|
+
levels?: Partial<Record<ContrastBadgeLevel, string>> | undefined;
|
|
46
|
+
};
|
|
38
47
|
export interface ContrastBadgeProps {
|
|
39
48
|
/** The colour read: text, a logo, an accent. `#rrggbb`. */
|
|
40
49
|
foreground: string;
|
|
@@ -44,8 +53,16 @@ export interface ContrastBadgeProps {
|
|
|
44
53
|
label?: string | undefined;
|
|
45
54
|
/** What it is read as: body text (the default), or large text and marks. */
|
|
46
55
|
scale?: ContrastScale | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The pair is a warning and never a block: under its scale's AA floor it
|
|
58
|
+
* reads "Low" in the warning tone, rather than "AA large text" or "Fails"
|
|
59
|
+
* in the danger tone. For a pair a person is told about and allowed to
|
|
60
|
+
* keep, such as a brand's primary on its light ground; a pair that blocks
|
|
61
|
+
* (a brand's text on its light ground) leaves it off and keeps "Fails".
|
|
62
|
+
*/
|
|
63
|
+
advisory?: boolean | undefined;
|
|
47
64
|
className?: string | undefined;
|
|
48
|
-
labels?:
|
|
65
|
+
labels?: ContrastBadgeLabelsInput | undefined;
|
|
49
66
|
}
|
|
50
67
|
/**
|
|
51
68
|
* Whether one colour reads on another: a sample of the pair, the measured
|
|
@@ -56,8 +73,13 @@ export interface ContrastBadgeProps {
|
|
|
56
73
|
* every theme to, and the floors from its `WCAG_CONTRAST`, so the badge an
|
|
57
74
|
* administrator reads and the gate a theme passes never disagree.
|
|
58
75
|
*
|
|
76
|
+
* BLOCKING OR ADVISORY. By default a pair under every floor "Fails" in the
|
|
77
|
+
* danger tone. An `advisory` pair is one a person may keep: under its AA
|
|
78
|
+
* floor it reads "Low" in the warning tone with the warning glyph, so a
|
|
79
|
+
* screen of pairs tells the one that blocks from the ones that only warn.
|
|
80
|
+
*
|
|
59
81
|
* THE LEVEL IS A WORD AND A GLYPH, never the tint alone, and the ratio is
|
|
60
82
|
* said as "4.49 to 1" rather than read out as punctuation. The sample is
|
|
61
83
|
* decoration: it shows the pair, and the words say how it measures.
|
|
62
84
|
*/
|
|
63
|
-
export declare function ContrastBadge({ foreground, background, label, scale, className, labels }: ContrastBadgeProps): import("react").JSX.Element;
|
|
85
|
+
export declare function ContrastBadge({ foreground, background, label, scale, advisory, className, labels, }: ContrastBadgeProps): import("react").JSX.Element;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import './ContrastBadge.css';
|
|
2
2
|
export { ContrastBadge, contrastLevel, contrastRatio, formatContrastRatio } from './ContrastBadge.js';
|
|
3
|
-
export type { ContrastBadgeLabels, ContrastBadgeProps, ContrastLevel, ContrastScale } from './ContrastBadge.js';
|
|
3
|
+
export type { ContrastBadgeLabels, ContrastBadgeLabelsInput, ContrastBadgeLevel, ContrastBadgeProps, ContrastLevel, ContrastScale, } from './ContrastBadge.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ContrastBadge, contrastLevel, contrastRatio, formatContrastRatio } from '../chunk-
|
|
1
|
+
export { ContrastBadge, contrastLevel, contrastRatio, formatContrastRatio } from '../chunk-NSSOWQCJ.js';
|
|
2
2
|
import '../chunk-KYIAFNU7.js';
|
|
3
3
|
import '../chunk-UZMNR366.js';
|
|
4
4
|
import '../chunk-WIATRXEJ.js';
|
|
@@ -1,14 +1,28 @@
|
|
|
1
|
+
import { type TimeValue } from '../RelativeTime/index.js';
|
|
1
2
|
/** Every string the status draws. */
|
|
2
3
|
export interface EditingStatusLabels {
|
|
3
4
|
editing: string;
|
|
4
5
|
unsaved: string;
|
|
5
6
|
saving: string;
|
|
7
|
+
/** Shown once the last save landed, with how long ago: "Saved 2 min ago". */
|
|
8
|
+
saved: (ago: string) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Said in its place: the time is shown and not announced, so a screen
|
|
11
|
+
* reader hears "Saved" when the save lands rather than every minute after.
|
|
12
|
+
*/
|
|
13
|
+
savedSpoken: string;
|
|
6
14
|
}
|
|
7
15
|
export interface EditingStatusProps {
|
|
8
16
|
/** Whether anything differs from what was opened. */
|
|
9
17
|
dirty: boolean;
|
|
10
18
|
/** Whether a save is in flight. */
|
|
11
19
|
saving?: boolean | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* When the last save landed. Given, a status with nothing unsaved and no
|
|
22
|
+
* save in flight says how long ago: a draft that keeps itself, as a setup
|
|
23
|
+
* wizard's does ("Draft · Saved 2 min ago" with `labels.editing` "Draft").
|
|
24
|
+
*/
|
|
25
|
+
savedAt?: TimeValue;
|
|
12
26
|
labels?: Partial<EditingStatusLabels> | undefined;
|
|
13
27
|
className?: string | undefined;
|
|
14
28
|
}
|
|
@@ -22,4 +36,4 @@ export interface EditingStatusProps {
|
|
|
22
36
|
* from saved to unsaved without being interrupted. On a narrow bar the words
|
|
23
37
|
* fold away and the dot stays, named by the same words.
|
|
24
38
|
*/
|
|
25
|
-
export declare function EditingStatus({ dirty, saving, labels, className }: EditingStatusProps): import("react").JSX.Element;
|
|
39
|
+
export declare function EditingStatus({ dirty, saving, savedAt, labels, className }: EditingStatusProps): import("react").JSX.Element;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export { INLINE_EDITOR_LABELS, InlineEditor } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
1
|
+
export { INLINE_EDITOR_LABELS, InlineEditor } from '../chunk-QGRA2RHC.js';
|
|
2
|
+
import '../chunk-MMOXWAUG.js';
|
|
3
|
+
import '../chunk-3CZ7XAVY.js';
|
|
4
|
+
import '../chunk-EUCC37WY.js';
|
|
3
5
|
import '../chunk-SMBKXWML.js';
|
|
4
6
|
import '../chunk-OMV5W53A.js';
|
|
5
7
|
import '../chunk-OG6CL3EB.js';
|
package/dist/LogoSlot/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { LogoSlot } from '../chunk-
|
|
1
|
+
export { LogoSlot } from '../chunk-BEAXJARF.js';
|
|
2
2
|
import '../chunk-THDGBD6Y.js';
|
|
3
|
-
import '../chunk-
|
|
3
|
+
import '../chunk-NSSOWQCJ.js';
|
|
4
4
|
import '../chunk-KYIAFNU7.js';
|
|
5
5
|
import '../chunk-UZMNR366.js';
|
|
6
6
|
import '../chunk-WIATRXEJ.js';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import { type StepperStep } from '../Stepper/index.js';
|
|
3
|
+
import { type HeadingLevel } from '../utils/headingLevel.js';
|
|
4
|
+
export interface SetupWizardLabels {
|
|
5
|
+
/** Where the reader is, above the steps on a phone, before the step's name: "Step 3 of 6". */
|
|
6
|
+
progress: (position: number, total: number) => string;
|
|
7
|
+
/** The disclosure the aside folds into on a phone: "Preview". */
|
|
8
|
+
asideToggle: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SetupWizardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'onSelect'> {
|
|
11
|
+
/** What is being set up: "Set up your brand". The page's heading. */
|
|
12
|
+
title: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* The title's heading level. It follows the surrounding level by default,
|
|
15
|
+
* as `PageHeader` does; the step's heading is one level under it, and the
|
|
16
|
+
* step's own content and the aside start one level under that.
|
|
17
|
+
*/
|
|
18
|
+
headingLevel?: HeadingLevel | undefined;
|
|
19
|
+
/** Where the page sits, above the title: a `Breadcrumb` "Company › Brand › Set up". */
|
|
20
|
+
trail?: ReactNode;
|
|
21
|
+
/** The draft's state, beside the actions: an `EditingStatus` "Draft saved 2 min ago". */
|
|
22
|
+
status?: ReactNode;
|
|
23
|
+
/** What leaves the wizard: Exit. */
|
|
24
|
+
actions?: ReactNode;
|
|
25
|
+
/** Every step, in order, as `Stepper` takes them (with a status and a description each where it has one). */
|
|
26
|
+
steps: readonly StepperStep[];
|
|
27
|
+
/** The step shown, by its id. */
|
|
28
|
+
current: string;
|
|
29
|
+
/** The furthest step visited, by its id, so the steps up to it stay buttons after walking back. `current` by default. */
|
|
30
|
+
reached?: string | undefined;
|
|
31
|
+
/** Called with a visited step's id when the reader picks it in the rail. Left out, the rail is words only. */
|
|
32
|
+
onSelect?: ((id: string) => void) | undefined;
|
|
33
|
+
/** What the steps are, for the navigation landmark and the list: "Setup steps". */
|
|
34
|
+
stepsLabel: string;
|
|
35
|
+
/** The step's heading: "Colours". */
|
|
36
|
+
heading: ReactNode;
|
|
37
|
+
/** One sentence under the heading saying why the step matters. */
|
|
38
|
+
lead?: ReactNode;
|
|
39
|
+
/** The step's content: its fields. */
|
|
40
|
+
children: ReactNode;
|
|
41
|
+
/** Beside the content on a wide page, under it on a narrower one, folded into a disclosure on a phone: a live preview. */
|
|
42
|
+
aside?: ReactNode;
|
|
43
|
+
/** What the aside is, for its complementary landmark: "Preview of your draft". Required with `aside`. */
|
|
44
|
+
asideLabel?: string | undefined;
|
|
45
|
+
/** The step's actions, held at the bottom of the view: Back, Skip for now, Continue. */
|
|
46
|
+
footer: ReactNode;
|
|
47
|
+
labels?: Partial<SetupWizardLabels> | undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A setup a person walks step by step on a page of its own: a brand, an
|
|
51
|
+
* integration, a company.
|
|
52
|
+
*
|
|
53
|
+
* FIVE PARTS, EACH A LANDMARK OR A HEADING. The header is the page's title
|
|
54
|
+
* (a `PageHeader`) with the trail above it and the draft's status and the way
|
|
55
|
+
* out beside it. The rail is a `nav` holding a vertical `Stepper`, with every
|
|
56
|
+
* visited step a button. The content is a `section` named by the step's
|
|
57
|
+
* heading, with a lead sentence and the step's fields in one column of at
|
|
58
|
+
* most 640px. The aside is a complementary landmark named by `asideLabel`,
|
|
59
|
+
* held in view beside the content. The footer holds the step's actions at the
|
|
60
|
+
* bottom of the view, above the device's safe area.
|
|
61
|
+
*
|
|
62
|
+
* THREE SHAPES. On a wide page the rail, the content and the aside stand side
|
|
63
|
+
* by side (240px, the content, 320px). Where the wizard has under 70rem of its
|
|
64
|
+
* own width, which is a 1024px window beside the application's rail, the aside
|
|
65
|
+
* moves under the content and the rail stays: a container query, because the
|
|
66
|
+
* room is the page's and not the window's, as in `AsideLayout`. Under the
|
|
67
|
+
* phone breakpoint the rail becomes a row of steps above the content, under a
|
|
68
|
+
* line that says "Step 3 of 6 · Colours", and the aside folds into a
|
|
69
|
+
* disclosure, so the fields come first. Which element a phone draws is decided
|
|
70
|
+
* in `useMediaMatch`, the one subscription for that question; how it is laid
|
|
71
|
+
* out is decided in the stylesheet, at the same breakpoint token.
|
|
72
|
+
*
|
|
73
|
+
* FOCUS FOLLOWS THE STEP. When the step changes, focus moves to its heading,
|
|
74
|
+
* so a keyboard or screen reader user starts on the new step rather than on a
|
|
75
|
+
* Continue button that now belongs to it; the first render takes no focus.
|
|
76
|
+
*/
|
|
77
|
+
export declare function SetupWizard({ title, headingLevel, trail, status, actions, steps, current, reached, onSelect, stepsLabel, heading, lead, children, aside, asideLabel, footer, labels, className, ...rest }: SetupWizardProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { SetupWizard } from '../chunk-KKHCUHWS.js';
|
|
2
|
+
import '../chunk-LDGQIDIZ.js';
|
|
3
|
+
import '../chunk-DKB73KZI.js';
|
|
4
|
+
import '../chunk-WIATRXEJ.js';
|
|
5
|
+
import '../chunk-OGXHZHRX.js';
|
|
6
|
+
import '../chunk-FZVJDJFN.js';
|
|
7
|
+
import '../chunk-BHHIIK3M.js';
|
|
8
|
+
import '../chunk-PQADV6P6.js';
|
|
9
|
+
import '../chunk-RTPQDNW3.js';
|
|
10
|
+
import '../chunk-BZFWDIV6.js';
|
|
11
|
+
import '../chunk-ODEQTPFM.js';
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SetupWizard: a setup walked step by step on a page of its own
|
|
3
|
+
* (SetupWizard.tsx).
|
|
4
|
+
*
|
|
5
|
+
* THE GRID. The header across the top, the footer across the bottom, and
|
|
6
|
+
* between them the rail (240px), the content (one column, at most 640px) and,
|
|
7
|
+
* from 70rem of the wizard's own width, the aside (320px). Under 70rem the
|
|
8
|
+
* aside takes the row under the content and the rail runs down beside both.
|
|
9
|
+
* Under the phone breakpoint every part is one column, in reading order.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
.crewlet-setup-wizard {
|
|
13
|
+
container-type: inline-size;
|
|
14
|
+
min-width: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.crewlet-setup-wizard__frame {
|
|
18
|
+
display: grid;
|
|
19
|
+
grid-template-columns: 240px minmax(0, 1fr);
|
|
20
|
+
grid-template-areas:
|
|
21
|
+
'head head'
|
|
22
|
+
'rail content'
|
|
23
|
+
'rail aside'
|
|
24
|
+
'foot foot';
|
|
25
|
+
grid-template-rows: auto auto 1fr auto;
|
|
26
|
+
column-gap: var(--spacing-8);
|
|
27
|
+
row-gap: var(--spacing-6);
|
|
28
|
+
min-width: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.crewlet-setup-wizard__head {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: var(--spacing-2);
|
|
35
|
+
grid-area: head;
|
|
36
|
+
min-width: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.crewlet-setup-wizard__status {
|
|
40
|
+
display: inline-flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* THE RAIL stays in view while the content scrolls past it, as a column of
|
|
46
|
+
* the steps a reader can go back to.
|
|
47
|
+
*/
|
|
48
|
+
.crewlet-setup-wizard__rail {
|
|
49
|
+
position: sticky;
|
|
50
|
+
top: var(--spacing-5);
|
|
51
|
+
align-self: start;
|
|
52
|
+
grid-area: rail;
|
|
53
|
+
min-width: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.crewlet-setup-wizard__content {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
grid-area: content;
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
width: 100%;
|
|
62
|
+
max-width: 640px;
|
|
63
|
+
min-width: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.crewlet-setup-wizard__heading {
|
|
67
|
+
margin: 0;
|
|
68
|
+
color: var(--color-text-primary);
|
|
69
|
+
font-family: var(--font-family-sans);
|
|
70
|
+
font-size: var(--font-size-lg);
|
|
71
|
+
font-weight: var(--font-weight-semibold);
|
|
72
|
+
line-height: var(--font-line-height-tight);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* The heading takes focus when the step changes. It keeps the browser's own
|
|
77
|
+
* focus treatment: after a press nothing is drawn, and after a key the ring
|
|
78
|
+
* shows where the reader now is.
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
.crewlet-setup-wizard__lead {
|
|
82
|
+
margin: var(--spacing-2) 0 0;
|
|
83
|
+
max-width: var(--size-measure);
|
|
84
|
+
color: var(--color-text-secondary);
|
|
85
|
+
font-family: var(--font-family-sans);
|
|
86
|
+
font-size: var(--font-size-sm);
|
|
87
|
+
line-height: var(--font-line-height-body);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.crewlet-setup-wizard__body {
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
gap: var(--spacing-6);
|
|
94
|
+
margin-top: var(--spacing-6);
|
|
95
|
+
min-width: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* UNDER THE CONTENT, the aside is set off by one hairline, as AsideLayout's is. */
|
|
99
|
+
.crewlet-setup-wizard__aside {
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
gap: var(--spacing-4);
|
|
103
|
+
grid-area: aside;
|
|
104
|
+
box-sizing: border-box;
|
|
105
|
+
max-width: 640px;
|
|
106
|
+
min-width: 0;
|
|
107
|
+
padding-top: var(--spacing-6);
|
|
108
|
+
border-top: 1px solid var(--color-border-default);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* THE FOOTER holds the step's actions at the bottom of the view while the
|
|
113
|
+
* content scrolls under it, on the sheet's own ground with a hairline above,
|
|
114
|
+
* and clears the device's safe area where it has one.
|
|
115
|
+
*/
|
|
116
|
+
.crewlet-setup-wizard__footer {
|
|
117
|
+
position: sticky;
|
|
118
|
+
bottom: 0;
|
|
119
|
+
z-index: var(--z-index-sticky);
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-wrap: wrap;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: flex-end;
|
|
124
|
+
gap: var(--spacing-2);
|
|
125
|
+
grid-area: foot;
|
|
126
|
+
padding: var(--spacing-3) 0 calc(var(--spacing-3) + env(safe-area-inset-bottom, var(--spacing-0)));
|
|
127
|
+
border-top: 1px solid var(--color-border-default);
|
|
128
|
+
background: var(--color-surface-background);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* BESIDE THE CONTENT, from 70rem of the wizard's own width: the aside holds in view. */
|
|
132
|
+
@container (min-width: 70rem) {
|
|
133
|
+
.crewlet-setup-wizard__frame {
|
|
134
|
+
grid-template-columns: 240px minmax(0, 1fr) 320px;
|
|
135
|
+
grid-template-areas:
|
|
136
|
+
'head head head'
|
|
137
|
+
'rail content aside'
|
|
138
|
+
'foot foot foot';
|
|
139
|
+
grid-template-rows: auto 1fr auto;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.crewlet-setup-wizard__aside {
|
|
143
|
+
position: sticky;
|
|
144
|
+
top: var(--spacing-5);
|
|
145
|
+
align-self: start;
|
|
146
|
+
padding-top: 0;
|
|
147
|
+
border-top: 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* ON A PHONE, one column in reading order: where the reader is, the step, the
|
|
153
|
+
* folded preview, and the actions. The rail is a row of steps under the
|
|
154
|
+
* position line, and scrolls away with the page rather than covering it.
|
|
155
|
+
*/
|
|
156
|
+
@media (width < 640px) {
|
|
157
|
+
.crewlet-setup-wizard__frame {
|
|
158
|
+
grid-template-columns: minmax(0, 1fr);
|
|
159
|
+
grid-template-areas:
|
|
160
|
+
'head'
|
|
161
|
+
'rail'
|
|
162
|
+
'content'
|
|
163
|
+
'aside'
|
|
164
|
+
'foot';
|
|
165
|
+
grid-template-rows: auto;
|
|
166
|
+
row-gap: var(--spacing-5);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.crewlet-setup-wizard__rail {
|
|
170
|
+
position: static;
|
|
171
|
+
display: flex;
|
|
172
|
+
flex-direction: column;
|
|
173
|
+
gap: var(--spacing-2);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.crewlet-setup-wizard__preview {
|
|
177
|
+
grid-area: aside;
|
|
178
|
+
min-width: 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.crewlet-setup-wizard__preview .crewlet-setup-wizard__aside {
|
|
182
|
+
padding-top: 0;
|
|
183
|
+
border-top: 0;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.crewlet-setup-wizard__position {
|
|
188
|
+
margin: 0;
|
|
189
|
+
color: var(--color-text-secondary);
|
|
190
|
+
font-family: var(--font-family-sans);
|
|
191
|
+
font-size: var(--font-size-xs);
|
|
192
|
+
font-weight: var(--font-weight-medium);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.crewlet-setup-wizard__position-step {
|
|
196
|
+
color: var(--color-text-primary);
|
|
197
|
+
}
|
|
@@ -12,6 +12,16 @@ import type { Tone } from '../utils/tone.js';
|
|
|
12
12
|
* but `aria-current`, which nobody sees.
|
|
13
13
|
*/
|
|
14
14
|
export type StepperTone = Exclude<Tone, 'neutral'>;
|
|
15
|
+
/**
|
|
16
|
+
* Where one step stands. `done`, `current` and `upcoming` are what a step's
|
|
17
|
+
* position against `current` already says; `optional` is a step that may be
|
|
18
|
+
* skipped and has not been completed, drawn quietly with the word "Optional";
|
|
19
|
+
* `attention` is a step with something to look at again (a low-contrast pair,
|
|
20
|
+
* a refused file), drawn with a warning glyph and the words "Needs attention".
|
|
21
|
+
*/
|
|
22
|
+
export type StepperStatus = 'done' | 'current' | 'upcoming' | 'optional' | 'attention';
|
|
23
|
+
/** How the steps are laid out: a row of pills, or a column of rows with a mark each. */
|
|
24
|
+
export type StepperOrientation = 'horizontal' | 'vertical';
|
|
15
25
|
/** One step of the sequence. */
|
|
16
26
|
export interface StepperStep {
|
|
17
27
|
/** What tells two steps apart, unique within the stepper: `execute`, `review`. */
|
|
@@ -21,15 +31,26 @@ export interface StepperStep {
|
|
|
21
31
|
* round 7 of 25", "Context 0.8s".
|
|
22
32
|
*/
|
|
23
33
|
label: ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* Where the step stands, when its position does not say it all. Left out,
|
|
36
|
+
* a step before `current` is `done` and one after it is `upcoming`. The
|
|
37
|
+
* current step is always drawn current: `attention` or `optional` on it adds
|
|
38
|
+
* the warning glyph or the word "Optional" to it, and `current` on any other
|
|
39
|
+
* step is refused, since only `current` names the step under way.
|
|
40
|
+
*/
|
|
41
|
+
status?: StepperStatus | undefined;
|
|
42
|
+
/** A line under the label, in the vertical orientation only: "Name and tagline". */
|
|
43
|
+
description?: ReactNode;
|
|
24
44
|
}
|
|
25
|
-
export interface StepperProps extends Omit<HTMLAttributes<HTMLOListElement>, 'children'> {
|
|
45
|
+
export interface StepperProps extends Omit<HTMLAttributes<HTMLOListElement>, 'children' | 'onSelect'> {
|
|
26
46
|
/** Every step, in order. */
|
|
27
47
|
steps: readonly StepperStep[];
|
|
28
48
|
/**
|
|
29
49
|
* The step under way, by its id. Every step before it is done and every
|
|
30
|
-
* step after it is still to come
|
|
31
|
-
* how a finished run is drawn: a
|
|
32
|
-
* "Execute
|
|
50
|
+
* step after it is still to come, unless a step's `status` says otherwise.
|
|
51
|
+
* `null` once EVERY step is done, which is how a finished run is drawn: a
|
|
52
|
+
* turn's history in a task's timeline reads "Execute ✓, Review ✓" with
|
|
53
|
+
* nothing current.
|
|
33
54
|
*/
|
|
34
55
|
current: string | null;
|
|
35
56
|
/**
|
|
@@ -47,32 +68,60 @@ export interface StepperProps extends Omit<HTMLAttributes<HTMLOListElement>, 'ch
|
|
|
47
68
|
* motion preference.
|
|
48
69
|
*/
|
|
49
70
|
pulse?: boolean | undefined;
|
|
71
|
+
/** A row of pills (the default), or a column of rows, as a setup wizard's rail draws it. */
|
|
72
|
+
orientation?: StepperOrientation | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Called with a step's id when a reader picks it. Given, every VISITED step
|
|
75
|
+
* (the current one, every step before it, and every step up to `reached`)
|
|
76
|
+
* is a button; a step not visited yet stays words, reachable only by
|
|
77
|
+
* whatever moves the sequence on, never a disabled button that is a tab stop
|
|
78
|
+
* with nothing behind it.
|
|
79
|
+
*/
|
|
80
|
+
onSelect?: ((id: string) => void) | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* The furthest step a reader has visited, by its id, for a sequence they
|
|
83
|
+
* can walk back through and forward again: on step 2 after reaching step 5,
|
|
84
|
+
* steps 3 to 5 are still buttons. `current` by default.
|
|
85
|
+
*/
|
|
86
|
+
reached?: string | undefined;
|
|
50
87
|
/**
|
|
51
88
|
* Said before a finished step's words, where the check glyph stands for a
|
|
52
89
|
* reader who sees it: "Done Context".
|
|
53
90
|
*/
|
|
54
91
|
doneLabel?: string | undefined;
|
|
92
|
+
/** Said after an optional step's words, and shown under them in the vertical orientation. "Optional" by default. */
|
|
93
|
+
optionalLabel?: string | undefined;
|
|
94
|
+
/** Said after the words of a step that needs attention, and shown under them vertically. "Needs attention" by default. */
|
|
95
|
+
attentionLabel?: string | undefined;
|
|
55
96
|
}
|
|
56
97
|
/**
|
|
57
|
-
* Where a sequence has got to: the phases of a turn, the stages of a run
|
|
98
|
+
* Where a sequence has got to: the phases of a turn, the stages of a run, the
|
|
99
|
+
* steps of a setup a person walks themselves.
|
|
58
100
|
*
|
|
59
101
|
* AN ORDERED LIST, because the order IS the information: a step's position
|
|
60
102
|
* says whether it is behind the current one or ahead of it, and a list says
|
|
61
103
|
* how many there are before a reader has heard any of them. The step under
|
|
62
104
|
* way carries `aria-current="step"`, the one value ARIA gives this exact
|
|
63
105
|
* meaning, and it is on exactly one step while the sequence is running and on
|
|
64
|
-
* none once it has finished.
|
|
106
|
+
* none once it has finished. When the steps are buttons it is on the button,
|
|
107
|
+
* which is what a reader focuses and so what tells them.
|
|
65
108
|
*
|
|
66
|
-
*
|
|
109
|
+
* EVERY STATE IS DRAWN TWO WAYS, so colour is never the only carrier. A
|
|
67
110
|
* finished step is the secondary ink with a check, and says "Done" before its
|
|
68
111
|
* words to a screen reader, where the check stands for a reader who sees it.
|
|
69
112
|
* The current step is its tone's soft tint under the tone's ink with the
|
|
70
|
-
* tone's dot
|
|
71
|
-
*
|
|
72
|
-
*
|
|
113
|
+
* tone's dot. A step still to come is the tertiary ink and nothing else; an
|
|
114
|
+
* optional one says "Optional", and one that needs attention carries the
|
|
115
|
+
* warning glyph on the warning tint and says "Needs attention".
|
|
116
|
+
*
|
|
117
|
+
* TWO ORIENTATIONS. The row is the approved design's pills joined by a short
|
|
118
|
+
* rule. The column is a setup wizard's rail: a 20px mark per step (the check,
|
|
119
|
+
* the dot, the warning glyph or the step's number) on a hairline that joins
|
|
120
|
+
* them, the label beside it, and a description and the status words under
|
|
121
|
+
* the label.
|
|
73
122
|
*
|
|
74
123
|
* THE TONE IS DRAWN, NEVER SPOKEN: a reader hears which step is current, not
|
|
75
124
|
* that it is in the warning hue. A current step whose state is anything but
|
|
76
125
|
* ordinary work says so in its own words ("Review · sent back").
|
|
77
126
|
*/
|
|
78
|
-
export declare function Stepper({ steps, current, label, tone, pulse, doneLabel, className, ...rest }: StepperProps): import("react").JSX.Element | null;
|
|
127
|
+
export declare function Stepper({ steps, current, label, tone, pulse, orientation, onSelect, reached, doneLabel, optionalLabel, attentionLabel, className, ...rest }: StepperProps): import("react").JSX.Element | null;
|
package/dist/Stepper/index.d.ts
CHANGED
package/dist/Stepper/index.js
CHANGED