@synopackageland/ui 0.23.9 → 0.25.1
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 +53 -0
- package/README.md +1 -1
- package/dist/cli.js +0 -0
- package/dist/components/composite/wizard/Wizard.vue.d.ts +9 -4
- package/dist/components/composite/wizard/WizardDialog.vue.d.ts +5 -2
- package/dist/components/composite/wizard/WizardStep.vue.d.ts +8 -2
- package/dist/components/composite/wizard/types.d.ts +80 -12
- package/dist/components/composite/wizard/wizardContext.d.ts +2 -0
- package/dist/components/primitives/dialog/Dialog.vue.d.ts +3 -0
- package/dist/composables/scrollEdges.d.ts +2 -1
- package/dist/grid.js +9 -0
- package/dist/i18n/text.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13695 -13542
- package/dist/json-preview/preview.js +5066 -5037
- package/dist/reka-runtime.json +1 -1
- package/dist/rich-text.js +1 -1
- package/dist/theme.css +1 -1
- package/dist/themes/dsm7-embedded-theme.css +1 -1
- package/dist/themes/dsm7-embedded.css +1 -1
- package/dist/themes/dsm7.css +1 -1
- package/docs/callout.md +3 -3
- package/docs/layout-rules.md +1 -1
- package/docs/mobile-native.md +56 -10
- package/docs/release-checks.md +1 -1
- package/docs/theme-hooks.md +51 -20
- package/docs/wizard.md +241 -10
- package/package.json +69 -61
package/docs/callout.md
CHANGED
|
@@ -103,9 +103,9 @@ halfway across. A Callout drops it: it stays on the page, where DSM draws its
|
|
|
103
103
|
notes with no decoration.
|
|
104
104
|
|
|
105
105
|
The title reads in the text colour at 600, and the message under it in the
|
|
106
|
-
secondary text colour. Corners
|
|
107
|
-
|
|
108
|
-
of them. Buttons in `#actions` and a toast's action are capsules. A Callout
|
|
106
|
+
secondary text colour. Corners follow `--ui-radius-callout` (10px in a DSM window, 12px on a full
|
|
107
|
+
page and mobile; `--ui-callout-radius` overrides it), rounder than a card on
|
|
108
|
+
purpose: a Callout floats above the settings rather than being one of them. Buttons in `#actions` and a toast's action are capsules. A Callout
|
|
109
109
|
fades in and rises 6px when it appears, and a toast slides in; with reduced
|
|
110
110
|
motion both appear at once.
|
|
111
111
|
|
package/docs/layout-rules.md
CHANGED
|
@@ -30,7 +30,7 @@ These rules keep pages built from `@synopackageland/ui` consistent. The componen
|
|
|
30
30
|
- In a dialog or a side `Sheet`, keep the default `direction="auto"`: as in DSM, the labels sit in a left column whenever the form is at least 400px wide (a 420–480px sheet) and stack above their controls only below that (a phone). Size the column with `Form label-width` (about 120px for short labels); a long label wraps inside it and stays level with its control's first line, whether that is a field, a select, a switch, a toggle group or a segment control.
|
|
31
31
|
- Size a field by its content: number fields are 128px; text fields use `fieldWidth`; in a narrow row that stacks its label (a phone), `md` and `lg` fill the column. Put two short fields side by side rather than stretching one across the page.
|
|
32
32
|
- A checkbox's text sits on the same line as its box. Do not apply `display: grid` to `label` in App CSS; `Checkbox` renders its own label.
|
|
33
|
-
- For 2–4 mutually exclusive settings, use `RadioGroup` (vertical by default, `orientation="horizontal"` when labels fit). In a wizard step, `variant="card"` draws the options as choice cards with an optional `icon` each. A checkbox enables an independent option; a switch turns a feature on or off immediately. Keep the circular radio with a dot, square checkbox with a tick, and pill switch with a moving thumb.
|
|
33
|
+
- For 2–4 mutually exclusive settings, use `RadioGroup` (vertical by default, `orientation="horizontal"` when labels fit). In a wizard step, `variant="card"` draws the options as choice cards with an optional `icon` each. The 2–4 count is a guide, not a limit: the cards wrap, but five or more short options (or longer ones, even three) read better as a vertical radio list. A checkbox enables an independent option; a switch turns a feature on or off immediately. Keep the circular radio with a dot, square checkbox with a tick, and pill switch with a moving thumb.
|
|
34
34
|
- Reserve `SegmentControl` for toolbar view switching: its selected item is a primary-filled capsule. `variant="sheet"` and `SectionTabs` are navigation tabs. Do not use either as a form value selector.
|
|
35
35
|
- Choices side by side are 34px apart (`--ui-choice-inline-gap`, DSM's Task Scheduler) and share one top whether checked or not. For a set of checkboxes use `CheckboxGroup` (`orientation="horizontal"` for a row, `v-model` an array of values), as `RadioGroup` for one choice; Checkboxes written one after another get the same gap. Do not add margins between them.
|
|
36
36
|
- A password field has a show/hide toggle; `Input type="password"` adds it.
|
package/docs/mobile-native.md
CHANGED
|
@@ -22,6 +22,37 @@ App. Each line links to its details below.
|
|
|
22
22
|
| No end space under docked bars | The room under the last row is constant (`--ui-page-end-space`, plus `--ui-bars-room` under docked bars); a fill page's list runs under the FAB and bars to the screen's bottom with no blank band, also when that list is an App's own element nested in the page. |
|
|
23
23
|
| Gesture velocity | One estimator and one set of thresholds for every gesture decision the UI makes. See [Gesture velocity](#gesture-velocity). |
|
|
24
24
|
|
|
25
|
+
## Phone look
|
|
26
|
+
|
|
27
|
+
Since 0.25 a touch phone (narrow and a coarse pointer, the phone tokens'
|
|
28
|
+
gate) draws softer than a DSM window: a pale page with white cards on it and
|
|
29
|
+
no frames, round corners, more air and bigger text. It is all in
|
|
30
|
+
`src/theme/phone.css`, inside `@media (pointer: coarse)` and scoped to the
|
|
31
|
+
phone presentation; a desktop, a DSM window (also one narrowed with a mouse)
|
|
32
|
+
and a touch tablet are unchanged. Tap boxes, bar heights, hide on scroll and
|
|
33
|
+
the commit footer behave as before.
|
|
34
|
+
|
|
35
|
+
| Token or part | Before (phone) | Phone look |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| Page under cards (`--ui-page-canvas`) | `#fafbf9` | `#f1f3f0` (dark: the dark page, `#121513`) |
|
|
38
|
+
| Card | white, 1px frame, card shadow, 6px corner | white, no frame, 1px soft shadow (none in dark), `--ui-radius-card` 20px |
|
|
39
|
+
| Section list (SectionTabs' phone list) | framed group, 6px corner, 44px rows, full-width rules | inset grouped list: white group, 20px corner, 52px rows, hairlines inset 16px, on the mist |
|
|
40
|
+
| A list of ListItems on the page (`role="list"`, `ul`, `ol`, outside a card, sheet or dialog) | full-bleed rows, full-width rules | the same inset grouped list: one white group per list, rows inset 16px, hairlines from the text's edge, the heading just above it inset to the text |
|
|
41
|
+
| Page background | the mist only on a page of cards | the mist on every page (not a data-grid page or `surface="none"`); a sticky row and `--ui-app-web-surface` follow it |
|
|
42
|
+
| `--ui-radius-md` / `-lg` (and the 0.23.9 roles on them) | 4 / 6 | 12 / 14: md rounds the control, container and popover roles (fields, tool buttons, inset blocks, menus), lg the panel and nav roles |
|
|
43
|
+
| `--ui-radius-dialog` / `-window` / `-sheet` | 8 / 8 / 12 | 24 |
|
|
44
|
+
| Primary, danger and secondary buttons | capsule (action) or 4px (tool) | capsule; a primary's fill is accent step 10 (`#0075db`, 4.6:1 with white) |
|
|
45
|
+
| Segmented control | accent pill on a framed track, equal columns | ink pill (`--ui-phone-ink`, `#1f2421`; dark `#e3e8e4`) on a frameless mist track; 6px sides; up to three segments stay equal columns but one whose label is wider than its share grows to it, so a label is not cut (four ellipsize as before) (`check:phone-layout` checks it) |
|
|
46
|
+
| Phone bar | white with a hairline | the page's colour, no hairline, 17px bold title, round pressed tint |
|
|
47
|
+
| Tab bar | full-width white row with a top rule | a floating white pill inset 16px, 8px above the bottom (`--ui-tab-bar-float`); the current tab is ink on a round pad |
|
|
48
|
+
| `--ui-text-muted` | 40% grey (2:1) | 80% (4.8:1 on the mist; dark 62%) |
|
|
49
|
+
| Separators | `--ui-border-subtle` | `--ui-phone-separator` |
|
|
50
|
+
|
|
51
|
+
An App that sets `--ui-mobile-bar-bg`, `--ui-mobile-bar-border` or
|
|
52
|
+
`--ui-tab-bar-bg` keeps its colour. The tab bar's box keeps the full width and
|
|
53
|
+
adds the float gap below the pill, so the room AppLayout measures for the
|
|
54
|
+
bottom bars, the FAB's offset and the end space follow it.
|
|
55
|
+
|
|
25
56
|
## Width containment and the release gate
|
|
26
57
|
|
|
27
58
|
Typography tables scroll horizontally within their parent, including a flex
|
|
@@ -75,12 +106,12 @@ control and grid sizes; it does not change the 16px field rule.
|
|
|
75
106
|
|
|
76
107
|
| Token | Desktop | Touch tablet | Phone | Used for |
|
|
77
108
|
|---|---|---|---|---|
|
|
78
|
-
| `--ui-font-size` | 13 | 13 | **
|
|
79
|
-
| `--ui-font-size-sm` | 12 | 12 | **
|
|
109
|
+
| `--ui-font-size` | 13 | 13 | **15** | primary text: list and record titles, field values, body copy, menu items |
|
|
110
|
+
| `--ui-font-size-sm` | 12 | 12 | **13** | secondary text: labels, meta, captions, helper text, badges |
|
|
80
111
|
| `--ui-font-size-xs` (new) | 12 | 12 | 12 | the smallest text anywhere |
|
|
81
|
-
| `--ui-line-height` | 20 | 20 | **
|
|
82
|
-
| `--ui-font-size-lg` | 14 | 14 | **
|
|
83
|
-
| `--ui-font-size-title` | 16 | 16 | **
|
|
112
|
+
| `--ui-line-height` | 20 | 20 | **22** | body line |
|
|
113
|
+
| `--ui-font-size-lg` | 14 | 14 | **17** | card and section titles |
|
|
114
|
+
| `--ui-font-size-title` | 16 | 16 | **17** | header and dialog titles |
|
|
84
115
|
| `--ui-grid-font-size` (new) | 13 | 13 | **14** | grid cell text (not yet read by the canvas grid; see below) |
|
|
85
116
|
| `--ui-font-size-field` (new) | 16 | 16 | 16 | every field on a coarse pointer, to stop iOS focus zoom |
|
|
86
117
|
| `--ui-control-md` | 28 | **44** | **44** | fields, selects, form and sheet buttons |
|
|
@@ -91,15 +122,15 @@ control and grid sizes; it does not change the 16px field rule.
|
|
|
91
122
|
| `--ui-grid-row-height` / `-header-height` | 28 | **36** | **32** | canvas grid rows (docs/ux-grid.md) |
|
|
92
123
|
| `--ui-list-row-height` | 32 | **44** | **44** | list rows, a DisclosureRow |
|
|
93
124
|
| `--ui-menu-row-height` | 32 | **44** | **44** | menu, select and sheet list rows |
|
|
94
|
-
| `--ui-page-padding-x` / `-y` | 16 (narrow layout 12) | 16 | **12** | page gutter |
|
|
95
|
-
| `--ui-section-gap` | 12 | 12 | **
|
|
96
|
-
| `--ui-card-padding` | 20 (Card default) | 20 | **
|
|
125
|
+
| `--ui-page-padding-x` / `-y` | 16 (narrow layout 12) | 16 | **16 / 12** | page gutter |
|
|
126
|
+
| `--ui-section-gap` | 12 | 12 | **16** | space between sections |
|
|
127
|
+
| `--ui-card-padding` | 20 (Card default) | 20 | **16** | card inset |
|
|
97
128
|
| `--ui-chrome-height` | 44 (web 48, narrow layout 44) | – | **44** | the phone bar (plus its 1px rule) |
|
|
98
129
|
| `--ui-tab-bar-height` | – | – | **48** | the tab bar's row, above `--ui-safe-bottom` |
|
|
99
130
|
| `--ui-section-tab-height` | 44 | 44 | **40** | the drawn height of a phone's tabs strip (its tap box stays 44) |
|
|
100
131
|
| `--ui-sticky-top-offset` | 0 | 0 | measured | what sticks at the top of a PageContent (see below) |
|
|
101
132
|
| `--ui-page-end-space` | 24 | 24 | 24 (constant: hiding the bars never changes it) | room after a page's last row |
|
|
102
|
-
| `--ui-radius-sheet` (new) | 12 | 12 |
|
|
133
|
+
| `--ui-radius-sheet` (new) | 12 | 12 | **24** | top corners of a bottom sheet |
|
|
103
134
|
| `--ui-safe-top/right/bottom/left` (new) | `env(safe-area-inset-*, 0px)` | same | same | notch and home indicator; non-zero only once the viewport meta has `viewport-fit=cover` |
|
|
104
135
|
| `--ui-pressed` (new) | 8% of the text colour | same | same | the pressed tint |
|
|
105
136
|
|
|
@@ -159,6 +190,17 @@ component rule overrides them.
|
|
|
159
190
|
IconButton draw their own pressed state (`data-variant`), so they're
|
|
160
191
|
skipped. A component that sets its own shadow or `active:` style also keeps
|
|
161
192
|
its own.
|
|
193
|
+
- Press feedback on a phone (`phone.css`, a coarse pointer in the mobile
|
|
194
|
+
presentation): a Button or an IconButton takes the `--ui-state-pressed`
|
|
195
|
+
layer over its own active colour and settles to `scale(0.98)` in 100ms
|
|
196
|
+
(`cubic-bezier(0.24, 1, 0.4, 1)`), springing back on release. The scale is
|
|
197
|
+
a transform, so nothing around the control moves. A ghost Button or
|
|
198
|
+
IconButton already fills with the layer and a link button fades, so neither
|
|
199
|
+
takes a second layer. A list row, a tab and a menu item keep the layer
|
|
200
|
+
alone: a row that shrank would pull away from its hairlines. A disabled
|
|
201
|
+
control takes nothing. Reduced motion and `data-ui-motion="off"` keep the
|
|
202
|
+
layer and drop the scale. A DSM window, a desktop page and a touch tablet
|
|
203
|
+
are unchanged.
|
|
162
204
|
- Hover: every hand-written `:hover` rule in the theme sits inside
|
|
163
205
|
`@media (hover: hover)`, so a tap leaves no stuck hover look. Tailwind's
|
|
164
206
|
`hover:` utilities already do this.
|
|
@@ -437,7 +479,11 @@ contain`, a pinned footer and safe-area padding. The top corners use
|
|
|
437
479
|
overrides that.
|
|
438
480
|
|
|
439
481
|
`Dialog` on a phone (an app 640px or narrower) renders as a `BottomSheet`:
|
|
440
|
-
`auto`, or `full` for `fullscreen` and for a form with more than 3 fields.
|
|
482
|
+
`auto`, or `full` for `fullscreen` and for a form with more than 3 fields.
|
|
483
|
+
`presentation="modal"` keeps a centred window instead, with the phone's
|
|
484
|
+
dialog corner and dim scrim, as tall as its content up to its `height` inside
|
|
485
|
+
the safe area (only a longer body scrolls); `WizardDialog` uses it by default, as a DSM
|
|
486
|
+
wizard opens, and `presentation="adaptive"` gives it the bottom sheet. The
|
|
441
487
|
CSS-only dialog-as-sheet in `adaptive.css` is gone. `Sheet side="bottom"` is
|
|
442
488
|
a `BottomSheet` everywhere; without `size` it fits its content. Left and right
|
|
443
489
|
sheets are unchanged, and their footer keeps clear of the home indicator.
|
package/docs/release-checks.md
CHANGED
|
@@ -8,7 +8,7 @@ checks through the optional build-isolation wrapper.
|
|
|
8
8
|
|
|
9
9
|
| Entry point | Default | Explicit full |
|
|
10
10
|
| --- | --- | --- |
|
|
11
|
-
| `npm run gates`, `scripts/gates.mjs` | Internal-content checks, build, lint, typecheck, unit tests, notices, isolated pack and tarball content audit | Adds Chromium and WebKit installation, responsive/adaptive/date-picker/JSON/host/switch/layout/InfoTip/motion/short-viewport/phone-layout/phone-bars (Chromium and WebKit)/focus-restore browser checks, Histoire build, exact-tarball registry consumer smoke |
|
|
11
|
+
| `npm run gates`, `scripts/gates.mjs` | Internal-content checks, build, lint, typecheck, unit tests, notices, isolated pack and tarball content audit | Adds Chromium and WebKit installation, responsive/adaptive/date-picker/JSON/host/switch/layout/InfoTip/motion/short-viewport/phone-layout/phone-bars (Chromium and WebKit)/press/focus-restore browser checks, Histoire build, exact-tarball registry consumer smoke |
|
|
12
12
|
| `pnpm check`, `check:fast` | Basic, with optional Docker host build isolation | `SPL_FULL_GATE=1` selects the same full plan |
|
|
13
13
|
| `fast-ci.yml`, composite verify action | Basic | No automatic extended checks |
|
|
14
14
|
| Disabled CI/release workflow templates | Basic, version/tag/ancestry checks, stage, pack and actual archive audit | No implicit browser or registry consumer smoke |
|
package/docs/theme-hooks.md
CHANGED
|
@@ -182,7 +182,7 @@ before the token existed. Set them on the app root or any ancestor.
|
|
|
182
182
|
| `--ui-mobile-bar-fg` | the phone bar's text, menu button and brand | `var(--ui-text-secondary)` (text), `var(--ui-text)` (button, brand) |
|
|
183
183
|
| `--ui-mobile-bar-border` | the phone bar's bottom rule | `var(--ui-border-subtle)` |
|
|
184
184
|
| `--ui-callout-max-width` | caps how wide a `Callout` grows; it spans its container by default | none |
|
|
185
|
-
| `--ui-callout-radius` | a `Callout`'s and a `Toast`'s corners | 12px |
|
|
185
|
+
| `--ui-callout-radius` | a `Callout`'s and a `Toast`'s corners | `--ui-radius-callout` (window 10px, web and mobile 12px) |
|
|
186
186
|
| `--ui-callout-glow` | how strong the tone's glow is behind a `Callout`'s or `Toast`'s icon; a dark theme raises it (workbench: 14%) | 9% |
|
|
187
187
|
| `--ui-callout-shadow` | the shadow under a `Callout` and a `Toast` (workbench dark: `0 10px 28px -12px rgba(0, 0, 0, 0.6)`) | `0 8px 24px -12px rgba(15, 23, 42, 0.18)` |
|
|
188
188
|
|
|
@@ -193,24 +193,34 @@ The colour, radius, spacing and web tokens are listed in the
|
|
|
193
193
|
## Corner radius
|
|
194
194
|
|
|
195
195
|
Components round their corners with a role, not a step, so a theme can round
|
|
196
|
-
one kind of surface without the others
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
|
201
|
-
|
|
|
202
|
-
| `--ui-radius-
|
|
203
|
-
| `--ui-radius-
|
|
204
|
-
| `--ui-radius-
|
|
205
|
-
| `--ui-radius-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
196
|
+
one kind of surface without the others. Since 0.25 a DSM window rounds a step
|
|
197
|
+
softer than DSM's own 3-5px and a full page softer still; the mobile
|
|
198
|
+
presentation keeps the steps (a phone rounds them itself, `phone.css`):
|
|
199
|
+
|
|
200
|
+
| Token | Rounds | Window | Web | Mobile | Touch phone |
|
|
201
|
+
| --- | --- | --- | --- | --- | --- |
|
|
202
|
+
| `--ui-radius-inner` | a part inside a control or a popover: a menu row, a tag, a tooltip, a checkbox box, a small hit area | 4px | 6px | `sm` (3px) | 3px |
|
|
203
|
+
| `--ui-radius-control` | a button, a field, a select, a segment | 6px | 8px | `md` (4px) | 12px |
|
|
204
|
+
| `--ui-radius-container` | a flat block: a card's surface, an inset area, a thumbnail frame | 8px | 10px | `md` (4px) | 12px |
|
|
205
|
+
| `--ui-radius-popover` | a menu, a select or combobox list, a popover, a picker | 8px | 10px | `md` (4px) | 12px |
|
|
206
|
+
| `--ui-radius-panel` | a raised block: a choice card, a framed region, a drop zone, a side drawer, a grid card | 8px | 12px | `lg` (6px) | 14px |
|
|
207
|
+
| `--ui-radius-nav` | a navigation row in the sidebar or the top bar | 6px | 8px | `lg` (6px) | 14px |
|
|
208
|
+
| `--ui-radius-window` | a dialog with a title bar | 10px | 14px | 8px | 24px |
|
|
209
|
+
| `--ui-radius-dialog` | a message box | 10px | 14px | 8px | 24px |
|
|
210
|
+
| `--ui-radius-callout` | a `Callout` and a `Toast` (`--ui-callout-radius` still wins) | 10px | 12px | 12px | 12px |
|
|
211
|
+
|
|
212
|
+
A part inside a popover stays concentric with it: a menu row (`inner`, 4px) sits
|
|
213
|
+
4px inside its menu (`popover`, 8px). What floats highest rounds most.
|
|
214
|
+
|
|
215
|
+
The steps `--ui-radius-sm`, `-md` and `-lg` stay public at DSM's 3, 4 and 5px
|
|
216
|
+
for an App's own parts. On the mobile presentation the roles follow the steps
|
|
217
|
+
(declared again on its root in `adaptive.css`), so a step overridden there
|
|
218
|
+
moves them. On a touch phone (docs/mobile-native.md, "Phone look") the phone
|
|
219
|
+
scope sets the steps `--ui-radius-md` to 12px and `-lg` to 14px, so the roles
|
|
220
|
+
follow them there; a card on a page of cards and a grouped list take
|
|
221
|
+
`--ui-radius-card` (20px), and a sheet `--ui-radius-sheet` (24px).
|
|
222
|
+
|
|
223
|
+
Override a role to change only that kind of surface:
|
|
214
224
|
|
|
215
225
|
```css
|
|
216
226
|
:root {
|
|
@@ -219,10 +229,31 @@ the role on that element instead.
|
|
|
219
229
|
}
|
|
220
230
|
```
|
|
221
231
|
|
|
232
|
+
To keep the pre-0.25 corners in a DSM window, set the roles back to the steps:
|
|
233
|
+
`inner` 3px, `control`, `container` and `popover` 4px, `panel` and `nav` 5px,
|
|
234
|
+
`window` 3px, `dialog` 6px, `callout` 12px.
|
|
235
|
+
|
|
236
|
+
## Focus, hover and the scrim
|
|
237
|
+
|
|
238
|
+
| Token | What | Value |
|
|
239
|
+
| --- | --- | --- |
|
|
240
|
+
| `--ui-focus-ring` | the keyboard focus colour | the accent (`--ui-accent-9`), solid |
|
|
241
|
+
| `--ui-shadow-focus` | keyboard focus on a control: a 2px gap in the surface colour, then a 2px ring | `0 0 0 2px surface, 0 0 0 4px ring` |
|
|
242
|
+
| `--ui-shadow-focus-inset` | keyboard focus inside a row, a cell or a tab strip | `inset 0 0 0 2px ring` |
|
|
243
|
+
| `--ui-shadow-focus-field` | a focused field, whose own border turns primary: a soft halo | `0 0 0 3px` `--ui-focus-halo` (`--ui-accent-a4`) |
|
|
244
|
+
| `--ui-state-hover` | the pointer on a ghost button or a menu row | 6% of the text colour |
|
|
245
|
+
| `--ui-state-pressed` | pressed (a ghost button, a touch control while the finger is down) | 8% of the text colour; `--ui-pressed` is a deprecated alias |
|
|
246
|
+
| `--ui-surface-hover` | the pointer on a list or table row (DSM's pale blue) | `--ui-accent-a2` |
|
|
247
|
+
| `--ui-overlay` | the scrim behind a modal | window: white 55%; web: `--ui-overlay-page`; mobile: `--ui-overlay-dim` (black 40%, as before) |
|
|
248
|
+
| `--ui-overlay-page` | the scrim on a full page | `rgba(15, 23, 32, 0.32)`; dark `rgba(0, 0, 0, 0.5)` |
|
|
249
|
+
|
|
250
|
+
A component never writes a ring out (`shadow-[0_0_0_3px_…]`); it uses one of
|
|
251
|
+
the focus tokens (`tests/css/focus-ring.test.ts`).
|
|
252
|
+
|
|
222
253
|
## Motion
|
|
223
254
|
|
|
224
255
|
The components move a little by default: dialogs and their masks fade in and
|
|
225
|
-
settle from 97 %, menus, popovers and select lists drop
|
|
256
|
+
settle from 97 %, menus, popovers and select lists drop 6px from their trigger (settling from 96 %),
|
|
226
257
|
a notification slides 8px in, expandable bodies (`Card`, `SettingsSection`,
|
|
227
258
|
`ExpandableItem`, `Callout` details, `TimelineRow`, `FacetPanel`, `Accordion`,
|
|
228
259
|
`Fieldset`) open their height, a row the user just added fades in, and a tab
|
package/docs/wizard.md
CHANGED
|
@@ -57,15 +57,20 @@ const open = ref(false)
|
|
|
57
57
|
does not stack its children with a gap. Several blocks go in one `Form`
|
|
58
58
|
(fields one field gap apart) or a `<Space direction="column">`; a lone
|
|
59
59
|
Callout, paragraph or card grid needs no wrapper.
|
|
60
|
-
- **Choices.**
|
|
60
|
+
- **Choices.** Options that each need an icon and a line are choice cards:
|
|
61
61
|
`<RadioGroup variant="card" orientation="horizontal" :options>`. Each card
|
|
62
62
|
shows the option's `icon` in a tile, its label and one line, and keeps the
|
|
63
63
|
radio dot; the whole card picks it and the chosen card takes the primary
|
|
64
64
|
border and a soft fill. The cards share the row (180px at least) and wrap
|
|
65
|
-
in a narrow window.
|
|
65
|
+
in a narrow window. Cards usually suit 2–4 options; that is a guide, not a
|
|
66
|
+
limit, and nothing cuts a fifth. Five short options (symptoms, say) read
|
|
67
|
+
better as a vertical `RadioGroup` list, and long options do too even when
|
|
68
|
+
there are three. Pick by how the options read, not by their count.
|
|
66
69
|
- **Types.** `welcome` offers only Start; `done` drops Back and Cancel, fills
|
|
67
|
-
the progress and turns the main action into Finish
|
|
68
|
-
`
|
|
70
|
+
the progress and turns the main action into Finish; `result` ends the flow
|
|
71
|
+
with an outcome the app sets ([Result](#result)); `progress` shows work the
|
|
72
|
+
app runs ([Progress](#progress)). The last step without `next-step-key`
|
|
73
|
+
turns the main action into Apply.
|
|
69
74
|
- **Main action.** `next-label`, `next-disabled` and `:show-next="false"` (a step
|
|
70
75
|
that moves on by itself or through its own choices, such as a row of cards)
|
|
71
76
|
change it for one step. `:show-back="false"` hides Back.
|
|
@@ -98,9 +103,12 @@ const open = ref(false)
|
|
|
98
103
|
The `#footer` slot replaces the footer's buttons and receives `nextStep`,
|
|
99
104
|
`previousStep`, `finish`, `cancel`, `canGoPrevious`, `isLastStep`, `busy` and
|
|
100
105
|
`stepKey`. Events: `next-step`, `previous-step` (`{ from, to }`), `finished`,
|
|
101
|
-
`cancel
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
`cancel` and `navigation-error` ([Guards](#guards)). The step list does not
|
|
107
|
+
navigate, so a guard cannot be skipped.
|
|
108
|
+
|
|
109
|
+
`finished` means the user took Finish (or Apply); it does not mean the work
|
|
110
|
+
succeeded. Read the outcome from your own state, and do not run the work again
|
|
111
|
+
in a `finished` callback.
|
|
104
112
|
|
|
105
113
|
A wizard fills the height its container gives it (`WizardDialog` and
|
|
106
114
|
`<Dialog flush height="…">` give it one); on a page, set a height on its
|
|
@@ -117,6 +125,220 @@ there is no Back and nothing shifts. A welcome step offers Start alone; a done
|
|
|
117
125
|
step offers Finish (and its own `#actions`). There is no Close in the footer:
|
|
118
126
|
a `WizardDialog` closes from its header.
|
|
119
127
|
|
|
128
|
+
## Result
|
|
129
|
+
|
|
130
|
+
A `type="result"` step ends the flow with an outcome: `result-tone` is
|
|
131
|
+
`success`, `warning`, `danger` or `neutral` (the default, which claims neither
|
|
132
|
+
success nor failure). A partial import, a failed connection test or a scan
|
|
133
|
+
that finished with warnings is a result, never a `done` step: `done` always
|
|
134
|
+
shows the success mark and its burst.
|
|
135
|
+
|
|
136
|
+
```vue
|
|
137
|
+
<WizardStep
|
|
138
|
+
step-key="result"
|
|
139
|
+
type="result"
|
|
140
|
+
:result-tone="tone"
|
|
141
|
+
headline="Some items were not imported"
|
|
142
|
+
description="The imported items are kept; check why the others failed and try again"
|
|
143
|
+
>
|
|
144
|
+
<FindingList :items="findings" />
|
|
145
|
+
<template #actions>
|
|
146
|
+
<Button variant="ghost" kind="tool" @click="startAgain">Check again</Button>
|
|
147
|
+
</template>
|
|
148
|
+
</WizardStep>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- The step shows its tone's mark (a tick, a warning sign, a cross or an info
|
|
152
|
+
mark) on its colour, read as "Succeeded", "Warning", "Failed" or "Done". It
|
|
153
|
+
pops in once, with no ring and no burst for any tone.
|
|
154
|
+
- Finish is the one main action, blue for every tone: a red mark does not
|
|
155
|
+
make the button red. Back and Cancel are gone; the header's close button and
|
|
156
|
+
the step's `#actions` stay. `next-label` renames Finish.
|
|
157
|
+
- The guide marks the step with the outcome: a warning or a failure is not
|
|
158
|
+
drawn or read as completed. The steps before it read as completed: that says
|
|
159
|
+
the user passed them, not that the backend did each part.
|
|
160
|
+
- A result step never moves on: `next-step-key` is ignored (with a warning
|
|
161
|
+
in development).
|
|
162
|
+
- Map the outcome in the app: a rollback that worked can be `neutral`, a
|
|
163
|
+
partial or unknown rollback `warning`, a failed one `danger`.
|
|
164
|
+
|
|
165
|
+
## Progress
|
|
166
|
+
|
|
167
|
+
A `type="progress"` step shows work the app runs: a progress bar and a status
|
|
168
|
+
line. The wizard only draws them; the app starts, watches and ends the work,
|
|
169
|
+
and moves the wizard on (`v-model:active-step-key`) when the backend says the
|
|
170
|
+
work ended.
|
|
171
|
+
|
|
172
|
+
```vue
|
|
173
|
+
<WizardStep
|
|
174
|
+
step-key="scanning"
|
|
175
|
+
type="progress"
|
|
176
|
+
headline="Checking"
|
|
177
|
+
:progress="total > 0
|
|
178
|
+
? { mode: 'determinate', label: 'Items checked', value: checked, max: total, status: statusText }
|
|
179
|
+
: { mode: 'indeterminate', label: 'Getting status', status: statusText }"
|
|
180
|
+
stop-label="Stop watching"
|
|
181
|
+
@stop="stopWatching"
|
|
182
|
+
/>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- `progress` is `{ mode: 'indeterminate', label, status? }` while the total is
|
|
186
|
+
unknown, or `{ mode: 'determinate', label, value, max, valueLabel?, status? }`.
|
|
187
|
+
Without it the bar is indeterminate and named by the headline. `status` is
|
|
188
|
+
read politely when it changes.
|
|
189
|
+
- The bar is on its own tinted track (never grey) and has no stripes or
|
|
190
|
+
pattern; reduced motion stills it.
|
|
191
|
+
- Next and Back are hidden unless the step sets `:show-next="true"` or
|
|
192
|
+
`:show-back="true"` (for work that only reads). Back never undoes work that
|
|
193
|
+
changed data.
|
|
194
|
+
- `stop-label` adds a secondary button that emits `stop`; `stop-disabled`
|
|
195
|
+
disables it while the app waits for the backend. "Stop watching" only stops
|
|
196
|
+
the app looking; "Cancel task" is for a backend that can cancel, and the app
|
|
197
|
+
shows a cancelled result only once the backend confirms it.
|
|
198
|
+
- There is no timeout, retry count or auto-advance: time passing is not a
|
|
199
|
+
failure. `lazy` only mounts the step's content; it starts and stops nothing.
|
|
200
|
+
|
|
201
|
+
## Guards
|
|
202
|
+
|
|
203
|
+
`pre-enter-next-step` and `pre-enter-previous-step` run before a move. A guard
|
|
204
|
+
gets `{ from, direction, signal }` and returns:
|
|
205
|
+
|
|
206
|
+
| Return | Effect |
|
|
207
|
+
| --- | --- |
|
|
208
|
+
| nothing | the move goes on as the step says |
|
|
209
|
+
| `false` | the step stays |
|
|
210
|
+
| `{ goTo: 'key' }` | the move goes to that step instead, this time only |
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
async function checkAgain({ signal }: WizardGuardContext): Promise<WizardGuardResult> {
|
|
214
|
+
const status = await api.status({ signal })
|
|
215
|
+
if (status.needsAccess) return { goTo: 'access' }
|
|
216
|
+
return status.ready ? undefined : false
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- The main action shows a spinner while a guard runs; presses meanwhile are
|
|
221
|
+
ignored, so the guard runs once.
|
|
222
|
+
- A `goTo` is checked once the app's update has rendered, so a step the
|
|
223
|
+
guard mounts with `v-if` can be its target. It moves through the usual
|
|
224
|
+
`next-step` or `previous-step` event, `update:active-step-key` and focus.
|
|
225
|
+
The target's own guard does not run.
|
|
226
|
+
- A `goTo` that names no registered step (`unknown-step`) or the step itself
|
|
227
|
+
(`same-step`) keeps the step and emits `navigation-error`. A guard that
|
|
228
|
+
throws or rejects keeps the step and emits `navigation-error` with
|
|
229
|
+
`guard-rejected`, without the error's text: show your own message.
|
|
230
|
+
- `signal` aborts when the move no longer matters: the wizard left the step,
|
|
231
|
+
the app reset `active-step-key`, the `WizardDialog` closed (not while "Keep
|
|
232
|
+
editing" is asked) or the wizard unmounted. The spinner stops at once, and
|
|
233
|
+
a reply that comes later moves nothing and emits nothing. Pass `signal` to
|
|
234
|
+
your requests so they stop too.
|
|
235
|
+
- A step key is not a permission. The target step and the backend still check
|
|
236
|
+
what the user may do.
|
|
237
|
+
|
|
238
|
+
A guard written as `() => false | void` still works as before.
|
|
239
|
+
|
|
240
|
+
## Recipes
|
|
241
|
+
|
|
242
|
+
These use the API above; none needs anything new.
|
|
243
|
+
|
|
244
|
+
### Branches the backend decides
|
|
245
|
+
|
|
246
|
+
Steps need not all be known up front. A guard loads the branch, and a
|
|
247
|
+
`next-step-key` function reads it; the wizard reads `next-step-key` after the
|
|
248
|
+
guard. A step only some branches need can be mounted with `v-if`; it takes its
|
|
249
|
+
place in document order.
|
|
250
|
+
|
|
251
|
+
```vue
|
|
252
|
+
<WizardStep step-key="inspect" headline="Check the target"
|
|
253
|
+
:pre-enter-next-step="loadBranch"
|
|
254
|
+
:next-step-key="() => branch.needsAccess ? 'access' : 'preview'" />
|
|
255
|
+
<WizardStep v-if="branch.needsAccess" step-key="access" headline="Allow access" next-step-key="preview" />
|
|
256
|
+
<WizardStep step-key="preview" headline="Confirm" />
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Map each backend kind to a step key in the app, and treat an unknown kind as
|
|
260
|
+
"cannot continue": never use a backend string as a key. The progress counts
|
|
261
|
+
the mounted steps, so mount the path the user takes, not every branch; when
|
|
262
|
+
the total is unknown, `progress-text` can say so.
|
|
263
|
+
|
|
264
|
+
### Two equal ways on
|
|
265
|
+
|
|
266
|
+
Keep or roll back, replace or keep: put the choices in one `RadioGroup` (cards
|
|
267
|
+
when they need an icon and a line) and let one main action confirm the chosen
|
|
268
|
+
one. Its `next-label` follows the choice; a choice never acts by itself, so
|
|
269
|
+
moving through the options with the arrow keys does nothing else. The footer
|
|
270
|
+
keeps one blue button. A choice that destroys data asks in a confirmation
|
|
271
|
+
dialog first, whose confirm button is the only red one.
|
|
272
|
+
|
|
273
|
+
```vue
|
|
274
|
+
<WizardStep step-key="decision" headline="Choose what to do"
|
|
275
|
+
:show-back="false" :next-disabled="!decision"
|
|
276
|
+
:next-label="decision === 'rollback' ? 'Roll back' : 'Keep changes'"
|
|
277
|
+
:next-step-key="() => decision === 'rollback' ? 'working' : 'result'"
|
|
278
|
+
:pre-enter-next-step="confirmDecision">
|
|
279
|
+
<RadioGroup v-model="decision" variant="card" orientation="horizontal" :options="allowedDecisions" />
|
|
280
|
+
</WizardStep>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Offer only the choices the backend allows: an interrupted run has no "keep".
|
|
284
|
+
|
|
285
|
+
### Handing over to another wizard
|
|
286
|
+
|
|
287
|
+
The parent opens one wizard at a time. To hand over (a diagnosis opening a
|
|
288
|
+
fix): check what the source passes, stop the source's pending work, close the
|
|
289
|
+
source, then set the target's props and open it. Do not rely on two
|
|
290
|
+
`v-model:open` flips in one tick; mount the two exclusively (`v-if` on one
|
|
291
|
+
state) so only one dialog holds the focus. A handover is not the source's
|
|
292
|
+
Finish and not a rollback, and secrets do not travel in its props.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
type WizardRequest = { kind: 'diagnose' } | { kind: 'setup'; intent: 'send' | 'receive' } | undefined
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Watching work
|
|
299
|
+
|
|
300
|
+
For a [progress](#progress) step the app watches the backend:
|
|
301
|
+
|
|
302
|
+
1. Watch only while the wizard is open on that step. Leaving, closing,
|
|
303
|
+
unmounting or reopening ends the round: clear its timer and abort its
|
|
304
|
+
request.
|
|
305
|
+
2. One request at a time: schedule the next when the last ends, not with
|
|
306
|
+
`setInterval`. Follow the backend's retry-after; on errors back off slowly
|
|
307
|
+
(from about a second to 15 or 30 seconds). These are a schedule, not a
|
|
308
|
+
deadline.
|
|
309
|
+
3. After every `await`, check that the round, the open state and the step are
|
|
310
|
+
still the same before writing anything.
|
|
311
|
+
4. A network error shows "Status not up to date" with a way to check again and
|
|
312
|
+
keeps the last progress. Only the backend's own failed or interrupted state
|
|
313
|
+
leads to a failure result.
|
|
314
|
+
5. Reopening reads the run from the backend and resumes; it does not apply
|
|
315
|
+
again.
|
|
316
|
+
|
|
317
|
+
### Opening another DSM app
|
|
318
|
+
|
|
319
|
+
When a step needs the user to finish something in another app, open it with
|
|
320
|
+
the platform SDK's app launcher (it knows the app's verified ID; a package
|
|
321
|
+
name is not an app ID), and always show the path in words as well, for a host
|
|
322
|
+
that cannot open it. A secondary button opens it; the step's main action is
|
|
323
|
+
"Check again", whose guard reads the state again. Coming back to the wizard
|
|
324
|
+
only triggers a check: it is never a success by itself.
|
|
325
|
+
|
|
326
|
+
```vue
|
|
327
|
+
<WizardStep step-key="external" headline="Finish in the other app"
|
|
328
|
+
next-label="Check again" :pre-enter-next-step="checkAgain">
|
|
329
|
+
<p>When you are done there, come back and check again</p>
|
|
330
|
+
<Button variant="secondary" kind="tool" @click="openApp">Open the app</Button>
|
|
331
|
+
<p v-if="launchUnavailable">Open it from the DSM main menu</p>
|
|
332
|
+
</WizardStep>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Not provided
|
|
336
|
+
|
|
337
|
+
The wizard has no backend step schema or runner, no timeout or retry settings
|
|
338
|
+
for work, no two-primary footer, no card count option, no cross-wizard
|
|
339
|
+
navigation and no launcher of its own. These stay with the app (or the
|
|
340
|
+
platform SDK) on purpose.
|
|
341
|
+
|
|
120
342
|
## Modal
|
|
121
343
|
|
|
122
344
|
`WizardDialog` opens like a DSM modal window, in the app (app-local, see
|
|
@@ -131,8 +353,16 @@ a `WizardDialog` closes from its header.
|
|
|
131
353
|
step title) shows a ring only after a key, never after a click.
|
|
132
354
|
|
|
133
355
|
These are `Dialog` props (`scrim="light"`, `:close-on-outside="false"`,
|
|
134
|
-
`:header-divider="false"`), so a dialog of your own
|
|
135
|
-
open the same way.
|
|
356
|
+
`:header-divider="false"`, `presentation="modal"`), so a dialog of your own
|
|
357
|
+
around a `Wizard` can open the same way.
|
|
358
|
+
|
|
359
|
+
On a phone `WizardDialog` stays a centred window, as a DSM wizard does: the
|
|
360
|
+
phone's dialog corner, the phone's dim scrim, the same fixed header and
|
|
361
|
+
footer. It is as tall as its step, up to the wizard's height inside the
|
|
362
|
+
phone's safe area; only a longer step scrolls, between the header and the
|
|
363
|
+
footer. `presentation="adaptive"` keeps the bottom sheet instead, for an app
|
|
364
|
+
that wants its earlier phone look; other dialogs are bottom sheets on a phone
|
|
365
|
+
as before. The "Discard changes?" question is always a centred window.
|
|
136
366
|
|
|
137
367
|
## Leaving with input
|
|
138
368
|
|
|
@@ -164,6 +394,7 @@ in every built-in locale.
|
|
|
164
394
|
- **Motion.** A step fades in from 10px on the side it comes from and its
|
|
165
395
|
title fades in; the progress segments ease to their new state; the done mark
|
|
166
396
|
pops with a ring and a short burst. Reduced motion turns all of it off.
|
|
167
|
-
- **Size.** In a phone-width window the dialog
|
|
397
|
+
- **Size.** In a phone-width window the dialog stays a centred window with a
|
|
398
|
+
margin around it; `presentation="adaptive"` makes it a bottom sheet.
|
|
168
399
|
- **Theme.** Colours are the theme's roles, so the wizard follows the accent
|
|
169
400
|
and the dark theme.
|