@moldable-ai/ui 0.2.21 → 0.2.22
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/dist/catalog/catalog-types.d.ts +7 -0
- package/dist/catalog/catalog-types.d.ts.map +1 -1
- package/dist/catalog/catalog-types.js +1 -0
- package/dist/catalog/core-catalog-exclusions.d.ts.map +1 -1
- package/dist/catalog/core-catalog-exclusions.js +4 -0
- package/dist/catalog/core-collection-stories.js +1 -1
- package/dist/catalog/core-control-stories.d.ts.map +1 -1
- package/dist/catalog/core-control-stories.js +6 -3
- package/dist/components/app-error-boundary.d.ts.map +1 -1
- package/dist/components/app-error-boundary.js +3 -2
- package/dist/components/ui/alert-dialog.d.ts +4 -2
- package/dist/components/ui/alert-dialog.d.ts.map +1 -1
- package/dist/components/ui/alert-dialog.js +8 -4
- package/dist/components/ui/alert.d.ts +1 -1
- package/dist/components/ui/button.d.ts +6 -4
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/button.js +40 -8
- package/dist/components/ui/confirm-dialog.d.ts.map +1 -1
- package/dist/components/ui/confirm-dialog.js +1 -1
- package/dist/components/ui/dialog-primary-action.d.ts +4 -0
- package/dist/components/ui/dialog-primary-action.d.ts.map +1 -0
- package/dist/components/ui/dialog-primary-action.js +37 -0
- package/dist/components/ui/dialog.d.ts +6 -2
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +10 -3
- package/dist/components/ui/error-boundary-view.d.ts +12 -0
- package/dist/components/ui/error-boundary-view.d.ts.map +1 -0
- package/dist/components/ui/error-boundary-view.js +8 -0
- package/dist/components/ui/icon-button.d.ts.map +1 -1
- package/dist/components/ui/icon-button.js +1 -2
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +1 -0
- package/dist/components/ui/input-group.d.ts +1 -1
- package/dist/components/ui/spinner.d.ts.map +1 -1
- package/dist/components/ui/spinner.js +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/lib/frame-lifecycle.d.ts +22 -0
- package/dist/lib/frame-lifecycle.d.ts.map +1 -1
- package/dist/lib/frame-lifecycle.js +27 -1
- package/dist/lib/host-date-time-picker.d.ts +36 -0
- package/dist/lib/host-date-time-picker.d.ts.map +1 -0
- package/dist/lib/host-date-time-picker.js +162 -0
- package/dist/lib/host-file-dialog.d.ts +66 -0
- package/dist/lib/host-file-dialog.d.ts.map +1 -0
- package/dist/lib/host-file-dialog.js +221 -0
- package/dist/lib/host-native-menu.d.ts +75 -0
- package/dist/lib/host-native-menu.d.ts.map +1 -0
- package/dist/lib/host-native-menu.js +187 -0
- package/dist/lib/host-services.d.ts +13 -0
- package/dist/lib/host-services.d.ts.map +1 -0
- package/dist/lib/host-services.js +161 -0
- package/dist/lib/host-share.d.ts +39 -0
- package/dist/lib/host-share.d.ts.map +1 -0
- package/dist/lib/host-share.js +161 -0
- package/docs/README.md +1 -0
- package/docs/visual-regression.md +78 -0
- package/package.json +12 -3
- package/src/components/ui/alert-dialog.md +27 -0
- package/src/components/ui/button.md +10 -3
- package/src/components/ui/dialog.md +23 -4
- package/src/components/ui/error-boundary-view.md +98 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Visual and accessibility regression
|
|
2
|
+
|
|
3
|
+
The phase 1 runner exercises every authored story in the core component
|
|
4
|
+
catalog. It does not start the desktop shell or any Moldable-managed app.
|
|
5
|
+
|
|
6
|
+
## What runs
|
|
7
|
+
|
|
8
|
+
`visual-host/` is a small Vite entry that renders one story at a time:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
?story=controls/button&theme=dark&width=360
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Unique bare story ids are also accepted. Optional
|
|
15
|
+
`transparency=reduced` and `contrast=increased` parameters set the same
|
|
16
|
+
manual accessibility attributes used by the catalog. The host bundles Inter
|
|
17
|
+
and Roboto Mono, waits for `document.fonts.ready`, and disables animations,
|
|
18
|
+
transitions, scrolling, and the caret under `data-motion="reduced"`.
|
|
19
|
+
|
|
20
|
+
The Playwright runner builds and serves that static host, blocks non-host
|
|
21
|
+
network requests, freezes time and randomness, and tests Chromium at:
|
|
22
|
+
|
|
23
|
+
- light and dark themes;
|
|
24
|
+
- 360 × 720 and 960 × 720 viewports;
|
|
25
|
+
- rest plus the browser-driven states declared by an exemplar story.
|
|
26
|
+
|
|
27
|
+
A story opts into browser-driven states with `liveStates` and marks exactly
|
|
28
|
+
one element:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
<Button data-state-target="hover focus pressed">Continue</Button>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Playwright performs real hover, focus, and held-pointer interactions on that
|
|
35
|
+
element. Button, segmented control, and toggle stories are the phase 1
|
|
36
|
+
exemplars.
|
|
37
|
+
|
|
38
|
+
Every captured case also receives an axe WCAG 2 A/AA and WCAG 2.1 A/AA scan.
|
|
39
|
+
Serious and critical violations fail. Per-story suppressions belong in
|
|
40
|
+
`visual-tests/visual-matrix.ts` and must include a reason. Global
|
|
41
|
+
suppressions are intentionally unsupported.
|
|
42
|
+
|
|
43
|
+
## Local commands
|
|
44
|
+
|
|
45
|
+
Install the pinned Chromium build once:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm -C packages/ui exec playwright install chromium
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Run against committed baselines:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm -C packages/ui test:visual
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Create or intentionally update baselines after reviewing the UI change:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pnpm -C packages/ui test:visual:update
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Baseline PNGs live in `visual-tests/baselines/`. Failures emit actual,
|
|
64
|
+
expected, diff, and trace artifacts in the gitignored `.visual-results/`
|
|
65
|
+
directory.
|
|
66
|
+
|
|
67
|
+
## Determinism and skips
|
|
68
|
+
|
|
69
|
+
Stories must use fixed inputs and no live data, network, wall-clock timers, or
|
|
70
|
+
unseeded randomness. Add an unavoidable phase 1 skip to
|
|
71
|
+
`deterministicStorySkips` in `visual-tests/visual-matrix.ts` with a specific
|
|
72
|
+
reason. Skips are reported in the runner summary; silent conditional skips
|
|
73
|
+
are not allowed.
|
|
74
|
+
|
|
75
|
+
## CI
|
|
76
|
+
|
|
77
|
+
The `ui-regression` CI job installs Chromium and its Linux dependencies,
|
|
78
|
+
builds only `@moldable-ai/ui`, then runs the combined visual and axe suite.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moldable-ai/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
4
|
"description": "Shared UI components for Moldable applications",
|
|
5
5
|
"author": "Desiderata LLC",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -42,11 +42,15 @@
|
|
|
42
42
|
],
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc",
|
|
45
|
+
"build:visual-host": "vite build --config vite.visual.config.ts",
|
|
45
46
|
"dev": "tsc --watch",
|
|
46
47
|
"lint": "eslint . --ext .ts,.tsx --max-warnings 0",
|
|
47
48
|
"format": "prettier --write . --ignore-path ../../.prettierignore",
|
|
48
|
-
"check-types": "tsc --noEmit",
|
|
49
|
-
"test": "vitest run"
|
|
49
|
+
"check-types": "tsc --noEmit && tsc --noEmit -p tsconfig.visual.json",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:visual": "pnpm build:visual-host && playwright test --config playwright.visual.config.ts",
|
|
52
|
+
"test:visual:update": "pnpm build:visual-host && playwright test --config playwright.visual.config.ts --update-snapshots",
|
|
53
|
+
"visual:serve": "vite preview --config vite.visual.config.ts --host 127.0.0.1 --port 4179 --strictPort"
|
|
50
54
|
},
|
|
51
55
|
"dependencies": {
|
|
52
56
|
"@base-ui/react": "^1.1.0",
|
|
@@ -105,14 +109,19 @@
|
|
|
105
109
|
"zod": "^4.3.5"
|
|
106
110
|
},
|
|
107
111
|
"devDependencies": {
|
|
112
|
+
"@axe-core/playwright": "^4.12.1",
|
|
113
|
+
"@fontsource/inter": "^5.3.0",
|
|
114
|
+
"@fontsource/roboto-mono": "^5.3.0",
|
|
108
115
|
"@moldable-ai/eslint-config": "workspace:*",
|
|
109
116
|
"@moldable-ai/prettier-config": "workspace:*",
|
|
110
117
|
"@moldable-ai/typescript-config": "workspace:*",
|
|
118
|
+
"@playwright/test": "^1.62.0",
|
|
111
119
|
"@types/node": "^24.0.3",
|
|
112
120
|
"@types/react": "^19.1.6",
|
|
113
121
|
"@types/react-dom": "^19.1.6",
|
|
114
122
|
"jsdom": "^26.1.0",
|
|
115
123
|
"typescript": "^5.8.3",
|
|
124
|
+
"vite": "^6.4.3",
|
|
116
125
|
"vitest": "^4.1.8"
|
|
117
126
|
},
|
|
118
127
|
"peerDependencies": {
|
|
@@ -25,6 +25,33 @@ shown. The default error deliberately avoids exposing internal details.
|
|
|
25
25
|
host safe area. Use `AlertDialogBody` when explanation or affected-item content
|
|
26
26
|
can grow. Keep the consequence visible while the action is pending.
|
|
27
27
|
|
|
28
|
+
For composed alert dialogs, put `loading` on `AlertDialogAction` while its
|
|
29
|
+
request is in flight. This forwards the standard `Button` busy state, blocks
|
|
30
|
+
duplicate activation, and preserves the action label width and accessible name.
|
|
31
|
+
Use `pending` with `pendingLabel` when maintaining the confirmation-specific
|
|
32
|
+
status-label pattern; `ConfirmDialog` manages that state automatically. Disable
|
|
33
|
+
the surrounding form only when its other controls must not change during the
|
|
34
|
+
request.
|
|
35
|
+
|
|
36
|
+
Mark one confirming action with `primary` to enable Command+Enter on macOS or
|
|
37
|
+
Control+Enter on other platforms. The shortcut does not run without a marked
|
|
38
|
+
action, while that action is loading or disabled, during key composition or
|
|
39
|
+
repeat, or when a focused control consumes the event with `preventDefault()`.
|
|
40
|
+
Plain Enter remains native and Escape remains the Radix cancel behavior.
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<AlertDialogFooter>
|
|
44
|
+
<span className="text-muted-foreground mr-auto inline-flex items-center gap-1 text-xs">
|
|
45
|
+
Confirm <Kbd>⌘</Kbd>
|
|
46
|
+
<Kbd>Enter</Kbd>
|
|
47
|
+
</span>
|
|
48
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
49
|
+
<AlertDialogAction primary loading={isDeleting}>
|
|
50
|
+
Delete recording
|
|
51
|
+
</AlertDialogAction>
|
|
52
|
+
</AlertDialogFooter>
|
|
53
|
+
```
|
|
54
|
+
|
|
28
55
|
Use concrete labels such as “Delete recording,” not “OK.” Put the safe,
|
|
29
56
|
reversible action first in keyboard order and the confirming action last. Do
|
|
30
57
|
not use an alert dialog for ordinary navigation, long forms, or success
|
|
@@ -25,9 +25,15 @@ import { RefreshCw } from 'lucide-react'
|
|
|
25
25
|
>
|
|
26
26
|
<RefreshCw />
|
|
27
27
|
</Button>
|
|
28
|
+
|
|
29
|
+
<Button type="submit" loading={isSaving}>
|
|
30
|
+
Save changes
|
|
31
|
+
</Button>
|
|
28
32
|
```
|
|
29
33
|
|
|
30
|
-
Keep toolbar actions compact (`xs`, `sm`, `icon-xs`, or `icon-sm`). Reserve `default` or `lg` for the primary action in a form, setup state, or focused control dock.
|
|
34
|
+
Keep toolbar actions compact (`xs`, `sm`, `icon-xs`, or `icon-sm`). Reserve `default` or `lg` for the primary action in a form, setup state, or focused control dock. Set `loading` while that button's async action is in flight. The button blocks pointer and keyboard activation, exposes `aria-busy`, keeps its accessible label mounted, and centers a width-stable spinner.
|
|
35
|
+
|
|
36
|
+
Use `loading` for the action that started the request. Disable the whole form only when editing during the request would be unsafe or would invalidate the submitted payload; do not disable unrelated controls merely to communicate progress.
|
|
31
37
|
|
|
32
38
|
For links that should look like buttons:
|
|
33
39
|
|
|
@@ -42,13 +48,14 @@ For links that should look like buttons:
|
|
|
42
48
|
- `variant`: `default`, `destructive`, `outline`, `secondary`, `ghost`, or `link`. Defaults to `default`.
|
|
43
49
|
- `size`: `default`, `xs`, `sm`, `lg`, `icon`, `icon-xs`, `icon-sm`, or `icon-lg`. Defaults to `default`.
|
|
44
50
|
- `asChild`: renders a Radix `Slot` so the child owns the element and semantics.
|
|
51
|
+
- `loading`: blocks activation, applies disabled behavior and `aria-busy`, and overlays the shared `Spinner` without unmounting or resizing the label. Defaults to `false`.
|
|
45
52
|
- Accepts native button props, including `type`, `disabled`, `aria-*`, and event handlers.
|
|
46
53
|
- `buttonVariants` is exported for cases that need the same visual treatment on another component.
|
|
47
54
|
|
|
48
55
|
## Do
|
|
49
56
|
|
|
50
57
|
- Set `type="button"` unless the control intentionally submits a form.
|
|
51
|
-
- Add `cursor-pointer`; use the real `disabled` attribute for unavailable
|
|
58
|
+
- Add `cursor-pointer`; use the real `disabled` attribute for unavailable actions and `loading` for an in-flight action.
|
|
52
59
|
- Give icon-only buttons an `aria-label` and a tooltip.
|
|
53
60
|
- Use a specific Lucide icon that names the action.
|
|
54
61
|
- Put one visually primary action in a local action group.
|
|
@@ -60,7 +67,7 @@ For links that should look like buttons:
|
|
|
60
67
|
- Do not rely on color alone to explain a destructive or selected action.
|
|
61
68
|
- Do not use raw color classes, decorative gradients, or oversized call-to-action styling.
|
|
62
69
|
- Do not use `Sparkles` as a generic AI marker.
|
|
63
|
-
- Do not
|
|
70
|
+
- Do not use `disabled` alone as the progress signal; use `loading` so progress is announced and the label width stays stable.
|
|
64
71
|
- Do not hide essential actions behind hover-only icon buttons.
|
|
65
72
|
|
|
66
73
|
## Moldable considerations
|
|
@@ -18,7 +18,15 @@ spatial context.
|
|
|
18
18
|
<DialogDescription>Update the selected record.</DialogDescription>
|
|
19
19
|
</DialogHeader>
|
|
20
20
|
<DialogBody>{/* the only scrolling region */}</DialogBody>
|
|
21
|
-
<DialogFooter>
|
|
21
|
+
<DialogFooter>
|
|
22
|
+
<span className="text-muted-foreground mr-auto inline-flex items-center gap-1 text-xs">
|
|
23
|
+
Save <Kbd>⌘</Kbd>
|
|
24
|
+
<Kbd>Enter</Kbd>
|
|
25
|
+
</span>
|
|
26
|
+
<DialogAction primary loading={isSaving} onClick={saveChanges}>
|
|
27
|
+
Save changes
|
|
28
|
+
</DialogAction>
|
|
29
|
+
</DialogFooter>
|
|
22
30
|
</DialogContent>
|
|
23
31
|
</Dialog>
|
|
24
32
|
```
|
|
@@ -30,9 +38,20 @@ scrolling; do not put overflow on both the content and body.
|
|
|
30
38
|
Keep dialogs opaque and visually stable. Translucent material belongs on small
|
|
31
39
|
temporary control overlays where seeing the underlying context is useful.
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
Use `DialogAction` for footer commands that need the standard async and keyboard
|
|
42
|
+
contracts. Its `loading` prop forwards to `Button`, preventing duplicate
|
|
43
|
+
activation while retaining the label width and accessible name. Keep the dialog
|
|
44
|
+
controlled, show inline field errors, and close only after success. Disable the
|
|
45
|
+
whole form only when edits during submission would be unsafe. Use
|
|
46
|
+
`ConfirmDialog` when confirmation is the entire workflow.
|
|
47
|
+
|
|
48
|
+
Set `primary` on at most one `DialogAction` to opt into the default-action
|
|
49
|
+
shortcut. Command+Enter activates it on macOS; Control+Enter does so on other
|
|
50
|
+
platforms. The shortcut works from anywhere in the dialog unless a focused
|
|
51
|
+
control consumes the key event with `preventDefault()`, or the primary action is
|
|
52
|
+
loading or disabled. Plain Enter keeps the focused control's native behavior.
|
|
53
|
+
Use `Kbd` to show the shortcut when it improves discovery; render the
|
|
54
|
+
platform-appropriate modifier in app code.
|
|
36
55
|
|
|
37
56
|
Every dialog needs a `DialogTitle`. Add `DialogDescription` when the outcome or
|
|
38
57
|
scope is not obvious. Escape, outside dismissal, initial focus, and focus return
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# ErrorBoundaryView
|
|
2
|
+
|
|
3
|
+
`ErrorBoundaryView` presents a compact recovery state without owning error
|
|
4
|
+
catching, reporting, or host policy.
|
|
5
|
+
|
|
6
|
+
## When to use
|
|
7
|
+
|
|
8
|
+
Use it as the renderer for a React error boundary, route-level failure, or
|
|
9
|
+
host-provided pane error. Supply `actions` when the host owns reporting,
|
|
10
|
+
navigation, or chat recovery.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
Do not use it for inline field validation, routine empty states, or transient
|
|
15
|
+
request failures that can stay beside the initiating control. It does not catch
|
|
16
|
+
errors itself; use `AppErrorBoundary` when the package should own catching and
|
|
17
|
+
Moldable reporting.
|
|
18
|
+
|
|
19
|
+
## Anatomy
|
|
20
|
+
|
|
21
|
+
The view contains one compact surface with a title, optional message, optional
|
|
22
|
+
error-details disclosure, and one action row. It does not add illustrations or
|
|
23
|
+
nested cards.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { ErrorBoundaryView } from '@moldable-ai/ui'
|
|
29
|
+
|
|
30
|
+
function PaneError() {
|
|
31
|
+
return (
|
|
32
|
+
<ErrorBoundaryView
|
|
33
|
+
title="Could not load this pane"
|
|
34
|
+
message="Retry the request or report the captured error."
|
|
35
|
+
details={error.stack}
|
|
36
|
+
onRetry={retry}
|
|
37
|
+
actions={
|
|
38
|
+
<Button type="button" variant="secondary" onClick={reportError}>
|
|
39
|
+
Report error
|
|
40
|
+
</Button>
|
|
41
|
+
}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Variants and sizes
|
|
48
|
+
|
|
49
|
+
The renderer has one responsive layout. It uses the available height in a pane
|
|
50
|
+
and can fill a window when its parent gives it full height. The content remains
|
|
51
|
+
bounded to a readable width.
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
- `title`: heading content. Defaults to `"Something went wrong"`.
|
|
56
|
+
- `message`: optional supporting copy.
|
|
57
|
+
- `details`: optional string shown in a collapsed disclosure with a bounded,
|
|
58
|
+
scrollable monospace region.
|
|
59
|
+
- `onRetry`: when supplied, renders a `Retry` button and calls the handler once
|
|
60
|
+
per activation.
|
|
61
|
+
- `actions`: optional host-provided action content rendered after Retry.
|
|
62
|
+
- `className`: optional classes merged onto the outer renderer.
|
|
63
|
+
|
|
64
|
+
## States and interaction
|
|
65
|
+
|
|
66
|
+
Omit `details` when there is no safe diagnostic text to show. Omit both
|
|
67
|
+
`onRetry` and `actions` for a read-only failure. Keep retry state in the caller
|
|
68
|
+
and pass `loading` to a custom action when retrying is asynchronous.
|
|
69
|
+
|
|
70
|
+
## Keyboard and accessibility
|
|
71
|
+
|
|
72
|
+
The details use native `details` and `summary` semantics. The title is the
|
|
73
|
+
surface heading, the Retry control keeps native button behavior, and diagnostic
|
|
74
|
+
text remains selectable.
|
|
75
|
+
|
|
76
|
+
## Platform behavior
|
|
77
|
+
|
|
78
|
+
The component has no host calls or platform branches. `AppErrorBoundary`
|
|
79
|
+
supplies Moldable-specific reload and chat-prefill actions.
|
|
80
|
+
|
|
81
|
+
## Do
|
|
82
|
+
|
|
83
|
+
- Keep the title calm and literal.
|
|
84
|
+
- Give the message a concrete recovery path.
|
|
85
|
+
- Redact secrets before passing diagnostic details.
|
|
86
|
+
- Use semantic theme tokens in custom actions.
|
|
87
|
+
|
|
88
|
+
## Don't
|
|
89
|
+
|
|
90
|
+
- Do not add decorative error art or nested cards.
|
|
91
|
+
- Do not put binary data or unbounded logs in `details`.
|
|
92
|
+
- Do not make this presentational component report or reload implicitly.
|
|
93
|
+
|
|
94
|
+
## Testing
|
|
95
|
+
|
|
96
|
+
Contract tests should verify copy, disclosure behavior, Retry activation, and
|
|
97
|
+
host action rendering. Error-catching and reporting tests belong to the owning
|
|
98
|
+
boundary.
|