@maltjoy/mcp-server 0.4.0 → 0.6.0-beta.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/LICENSE +21 -0
- package/README.md +30 -3
- package/dist/doc/VJoyAdminBanner/guideline.md +284 -0
- package/dist/doc/VJoyAvailability/guideline.md +352 -0
- package/dist/doc/VJoyAvatar/guideline.md +192 -0
- package/dist/doc/VJoyAvatarsList/guideline.md +328 -0
- package/dist/doc/VJoyBadge/guideline.md +169 -0
- package/dist/doc/VJoyBadgeLevel/guideline.md +166 -0
- package/dist/doc/VJoyBlockSkeleton/guideline.md +150 -0
- package/dist/doc/VJoyBottomSheet/guideline.md +345 -0
- package/dist/doc/VJoyBottomSheetTrigger/guideline.md +156 -0
- package/dist/doc/VJoyButton/guideline.md +371 -0
- package/dist/doc/VJoyButton/metadata.json +0 -16
- package/dist/doc/VJoyButtonCard/guideline.md +384 -0
- package/dist/doc/VJoyCheckbox/guideline.md +317 -0
- package/dist/doc/VJoyCheckboxCard/guideline.md +381 -0
- package/dist/doc/VJoyCheckboxGroup/guideline.md +363 -0
- package/dist/doc/VJoyCheckboxGroup/metadata.json +7 -7
- package/dist/doc/VJoyCollapse/guideline.md +362 -0
- package/dist/doc/VJoyCollapseItem/guideline.md +394 -0
- package/dist/doc/VJoyCompanyAvatar/guideline.md +162 -0
- package/dist/doc/VJoyDialog/guideline.md +425 -0
- package/dist/doc/VJoyDialogTrigger/guideline.md +186 -0
- package/dist/doc/VJoyDot/guideline.md +177 -0
- package/dist/doc/VJoyDrawer/guideline.md +392 -0
- package/dist/doc/VJoyDrawerTrigger/guideline.md +145 -0
- package/dist/doc/VJoyDropdown/guideline.md +393 -0
- package/dist/doc/VJoyDropdown/metadata.json +6 -6
- package/dist/doc/VJoyDropdownList/guideline.md +396 -0
- package/dist/doc/VJoyDropdownList/metadata.json +7 -7
- package/dist/doc/VJoyDropzone/guideline.md +390 -0
- package/dist/doc/VJoyDropzone/metadata.json +36 -1
- package/dist/doc/VJoyFilterBar/guideline.md +419 -0
- package/dist/doc/VJoyFilterBarButton/guideline.md +359 -0
- package/dist/doc/VJoyFloatingActionsBar/guideline.md +372 -0
- package/dist/doc/VJoyFormError/guideline.md +325 -0
- package/dist/doc/VJoyFormError/metadata.json +0 -14
- package/dist/doc/VJoyFormFieldSkeleton/guideline.md +159 -0
- package/dist/doc/VJoyFormGroup/guideline.md +285 -0
- package/dist/doc/VJoyFunnel/guideline.md +349 -0
- package/dist/doc/VJoyFunnelFooter/guideline.md +178 -0
- package/dist/doc/VJoyFunnelHeader/guideline.md +204 -0
- package/dist/doc/VJoyHighlight/guideline.md +169 -0
- package/dist/doc/VJoyIcon/JoyIcon.types.ts +1 -1
- package/dist/doc/VJoyIcon/guideline.md +168 -0
- package/dist/doc/VJoyIcon/metadata.json +0 -1
- package/dist/doc/VJoyIconButton/guideline.md +169 -0
- package/dist/doc/VJoyIconButton/metadata.json +1 -1
- package/dist/doc/VJoyIndicator/guideline.md +168 -0
- package/dist/doc/VJoyIndicators/guideline.md +183 -0
- package/dist/doc/VJoyInput/JoyInput.types.ts +1 -6
- package/dist/doc/VJoyInput/guideline.md +401 -0
- package/dist/doc/VJoyInput/metadata.json +5 -14
- package/dist/doc/VJoyInputDigit/guideline.md +381 -0
- package/dist/doc/VJoyInputNumber/guideline.md +414 -0
- package/dist/doc/VJoyInputNumber/metadata.json +22 -17
- package/dist/doc/VJoyInputPassword/guideline.md +379 -0
- package/dist/doc/VJoyInputPassword/metadata.json +0 -14
- package/dist/doc/VJoyLabel/guideline.md +200 -0
- package/dist/doc/VJoyLink/guideline.md +360 -0
- package/dist/doc/VJoyListItem/guideline.md +335 -0
- package/dist/doc/VJoyMenu/guideline.md +304 -0
- package/dist/doc/VJoyMenu/metadata.json +21 -21
- package/dist/doc/VJoyMenuItem/guideline.md +352 -0
- package/dist/doc/VJoyPagination/guideline.md +355 -0
- package/dist/doc/VJoyPanel/guideline.md +238 -0
- package/dist/doc/VJoyPanelSection/guideline.md +160 -0
- package/dist/doc/VJoyPasswordRequirement/guideline.md +336 -0
- package/dist/doc/VJoyPasswordRequirement/metadata.json +1 -1
- package/dist/doc/VJoyProductTour/JoyProductTour.types.ts +0 -1
- package/dist/doc/VJoyProductTour/guideline.md +455 -0
- package/dist/doc/VJoyProductTour/metadata.json +1 -1
- package/dist/doc/VJoyProductTourTrigger/guideline.md +167 -0
- package/dist/doc/VJoyProgressBar/guideline.md +348 -0
- package/dist/doc/VJoyRadio/guideline.md +346 -0
- package/dist/doc/VJoyRadioGroup/guideline.md +331 -0
- package/dist/doc/VJoyRatingStars/guideline.md +306 -0
- package/dist/doc/VJoyScreenLoader/guideline.md +172 -0
- package/dist/doc/VJoySelect/guideline.md +396 -0
- package/dist/doc/VJoySelectableItem/guideline.md +351 -0
- package/dist/doc/VJoySelectableItemGroup/guideline.md +370 -0
- package/dist/doc/VJoySeparator/guideline.md +161 -0
- package/dist/doc/VJoySnackbar/guideline.md +402 -0
- package/dist/doc/VJoySpinner/guideline.md +168 -0
- package/dist/doc/VJoyStep/guideline.md +182 -0
- package/dist/doc/VJoyStepper/guideline.md +390 -0
- package/dist/doc/VJoyTab/guideline.md +298 -0
- package/dist/doc/VJoyTabs/guideline.md +353 -0
- package/dist/doc/VJoyTag/guideline.md +226 -0
- package/dist/doc/VJoyTagsInput/guideline.md +387 -0
- package/dist/doc/VJoyTagsList/guideline.md +311 -0
- package/dist/doc/VJoyTagsList/metadata.json +27 -27
- package/dist/doc/VJoyTemplate/guideline.md +366 -0
- package/dist/doc/VJoyText/guideline.md +175 -0
- package/dist/doc/VJoyText/metadata.json +1 -1
- package/dist/doc/VJoyTextarea/guideline.md +378 -0
- package/dist/doc/VJoyTitleBrand/guideline.md +155 -0
- package/dist/doc/VJoyToggle/guideline.md +327 -0
- package/dist/doc/VJoyTooltip/guideline.md +322 -0
- package/dist/doc/VJoyUserCard/guideline.md +268 -0
- package/dist/doc/VJoyUserCard/metadata.json +3 -17
- package/dist/doc/VJoyWalkthrough/guideline.md +351 -0
- package/dist/doc/VJoyWalkthroughTrigger/guideline.md +171 -0
- package/dist/doc/VJoyWrapper/guideline.md +173 -0
- package/dist/doc/css-classes.json +1 -15
- package/dist/doc/design-tokens.json +1 -0
- package/dist/doc/index.json +20 -30
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/tools/get-component-guideline.d.ts +30 -0
- package/dist/tools/get-component-guideline.d.ts.map +1 -0
- package/dist/tools/get-component-guideline.js +51 -0
- package/dist/tools/get-component-guideline.js.map +1 -0
- package/dist/tools/get-documentation-page.d.ts +22 -0
- package/dist/tools/get-documentation-page.d.ts.map +1 -0
- package/dist/tools/get-documentation-page.js +21 -0
- package/dist/tools/get-documentation-page.js.map +1 -0
- package/dist/tools/list-documentation-pages.d.ts +22 -0
- package/dist/tools/list-documentation-pages.d.ts.map +1 -0
- package/dist/tools/list-documentation-pages.js +17 -0
- package/dist/tools/list-documentation-pages.js.map +1 -0
- package/dist/tools/search-documentation.d.ts +41 -0
- package/dist/tools/search-documentation.d.ts.map +1 -0
- package/dist/tools/search-documentation.js +25 -0
- package/dist/tools/search-documentation.js.map +1 -0
- package/dist/zeroheight-parser.d.ts +27 -0
- package/dist/zeroheight-parser.d.ts.map +1 -0
- package/dist/zeroheight-parser.js +77 -0
- package/dist/zeroheight-parser.js.map +1 -0
- package/package.json +10 -10
- package/dist/doc/VJoyMultiCheckbox/metadata.json +0 -332
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoySnackbar
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoySnackbar.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Snackbar
|
|
10
|
+
|
|
11
|
+
Use **`pushVJoySnackbar`** when you need a brief, non-blocking status update after a user or system action — saved changes, a failed request, or a reversible outcome.
|
|
12
|
+
|
|
13
|
+
Do **not** mount `<VJoySnackbar>` in templates. The service creates the overlay, wires the event bus, and handles stacking and teardown.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `pushVJoySnackbar`, `createAllSnackbarsContainer` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [ZeroHeight — Snackbar](https://zeroheight.com/6bf479565/p/72e810-snackbar) |
|
|
19
|
+
| **Storybook** | [VJoySnackbar docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-overlays-vjoysnackbar--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoySnackbar.dsds.json`](./JoySnackbar.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoySnackbar")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Show a snackbar for **short, contextual feedback** that does not block the current task — for example confirming that settings were saved, reporting a recoverable API error, or offering a single follow-up action (undo, retry, view details).
|
|
28
|
+
|
|
29
|
+
Snackbars auto-dismiss after a timeout (default 5 seconds) unless you set `duration` to `"forever"` or the user closes them.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Inline, persistent helper or field-level error | `VJoyHighlight` |
|
|
36
|
+
| Blocking confirmation or multi-step decision | `VJoyDialog` |
|
|
37
|
+
| Page-level error that must stay until resolved | `VJoyHighlight` (error level) or page-level messaging — not a transient snackbar |
|
|
38
|
+
| Long-form or multi-paragraph content | A dialog, drawer, or dedicated page section |
|
|
39
|
+
| Custom fixed toast / banner markup | Still use `pushVJoySnackbar` + Joy tokens — don't reinvent overlay chrome |
|
|
40
|
+
|
|
41
|
+
> **Not in the API:** `level="neutral"` is not supported (`TJoySnackbarLevels` excludes `neutral`). Use `info` for neutral informational feedback.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## How it works
|
|
46
|
+
|
|
47
|
+
### Service flow
|
|
48
|
+
|
|
49
|
+
1. **`SnackbarPlugin`** (installed with the app) calls `createAllSnackbarsContainer()` on the client, injecting a fixed host (`#joy-snackbar__container`) at the bottom of the viewport.
|
|
50
|
+
2. **`pushVJoySnackbar({ props })`** creates a dedicated Vue app per instance, attaches a `mitt` event bus, mounts into a shell inside the host, and returns `{ snackbarEventBus, snackbarApp }`.
|
|
51
|
+
3. On hide (timeout, close click, or manual destroy), the bus emits `snackbar:hide`, which unmounts the app and removes the shell.
|
|
52
|
+
|
|
53
|
+
### Anatomy
|
|
54
|
+
|
|
55
|
+
| Part | Required? | What it is |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Level icon | Yes | `VJoyIcon` in a colored badge — mapped from `level` (`check-circle`, `warning-triangle`, `info-circle`) |
|
|
58
|
+
| Header | No | Bold title (`header` prop) |
|
|
59
|
+
| Message | Yes | Main copy (`message` prop); rendered with `v-html` so inline links are supported |
|
|
60
|
+
| Action CTA | No | Text link styled CTA (`actionText`); emits `snackbar:action-click` on the bus |
|
|
61
|
+
| Close control | No* | Icon button (cross) when `closable` is true — *default is true |
|
|
62
|
+
| Level underline | Yes | 4px bottom bar colored by `level` token |
|
|
63
|
+
|
|
64
|
+
### Default values
|
|
65
|
+
|
|
66
|
+
- `level` defaults to **`success`**
|
|
67
|
+
- `duration` defaults to **`5000`** (milliseconds)
|
|
68
|
+
- `closable` defaults to **`true`**
|
|
69
|
+
- `frozen` defaults to **`false`** (Storybook / docs only — skips enter/leave animation timing)
|
|
70
|
+
|
|
71
|
+
Props, events, and exposed members: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
72
|
+
|
|
73
|
+
### Event bus
|
|
74
|
+
|
|
75
|
+
Each instance gets its own bus (you do not pass `bus` yourself):
|
|
76
|
+
|
|
77
|
+
| Event | When |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `snackbar:show` | Emitted on mount |
|
|
80
|
+
| `snackbar:hide` | Emitted after close animation (~300ms after `isOpen` becomes false) |
|
|
81
|
+
| `snackbar:action-click` | Emitted when the user clicks `actionText` |
|
|
82
|
+
|
|
83
|
+
Unregister listeners when the calling component unmounts.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Variants
|
|
88
|
+
|
|
89
|
+
In the API, visual emphasis is controlled by **`level`** (semantic severity), not a separate `variant` prop.
|
|
90
|
+
|
|
91
|
+
### Success (`level="success"`)
|
|
92
|
+
|
|
93
|
+
Use when an action completed as expected — saved profile, sent message, file uploaded.
|
|
94
|
+
|
|
95
|
+
Avoid for errors or warnings; the green accent misleads.
|
|
96
|
+
|
|
97
|
+
### Error (`level="error"`)
|
|
98
|
+
|
|
99
|
+
Use for failed actions, API errors, or critical issues the user should notice.
|
|
100
|
+
|
|
101
|
+
Avoid long explanations — keep the header/message short; link out for details if needed.
|
|
102
|
+
|
|
103
|
+
### Warning (`level="warning"`)
|
|
104
|
+
|
|
105
|
+
Use for non-blocking conditions the user should be aware of (quota almost reached, partial save).
|
|
106
|
+
|
|
107
|
+
Avoid when the user must stop and fix something before continuing — prefer `VJoyHighlight` or inline validation.
|
|
108
|
+
|
|
109
|
+
### Information (`level="info"`)
|
|
110
|
+
|
|
111
|
+
Use for neutral contextual updates (background sync, tips).
|
|
112
|
+
|
|
113
|
+
Avoid overusing info snackbars where static page content would suffice.
|
|
114
|
+
|
|
115
|
+
### Not in the API: `neutral`
|
|
116
|
+
|
|
117
|
+
Design docs sometimes reference a neutral tone. The code type is `Exclude<TLevels, 'neutral'>`. Do not emit `level="neutral"` — use `info`.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Sizes
|
|
122
|
+
|
|
123
|
+
The snackbar has a **single layout** — no `size` prop.
|
|
124
|
+
|
|
125
|
+
| Constraint | Value (implementation) |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| Desktop width | **580px** fixed (`width: 580px` on `.joy-snackbar` and host container) |
|
|
128
|
+
| Responsive max | `max-width: calc(100% - var(--joy-core-spacing-6) * 2)` |
|
|
129
|
+
| Padding | `var(--joy-core-spacing-4)` (16px) |
|
|
130
|
+
| Position | Fixed, bottom center of viewport (`bottom: 0`, horizontal center via `left`/`right`/`margin: auto`) |
|
|
131
|
+
| Z-index | `var(--joy-core-z-index-snackbar)` |
|
|
132
|
+
|
|
133
|
+
**Do not** hardcode `580px` or pixel heights in product CSS — the component owns layout. ZeroHeight mentions 375px mobile width; the implementation uses fluid max-width instead.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## States
|
|
138
|
+
|
|
139
|
+
| State | What the user sees / can do |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| Entering | Slides up from below (`showAnimation`, `--joy-transition-duration-long`) |
|
|
142
|
+
| Visible | Message (and optional header, action, close) readable; auto-dismiss timer running when `duration` is a number |
|
|
143
|
+
| Action hover | `actionText` underlines on hover |
|
|
144
|
+
| Closing (user) | Close icon click → `isOpen` false → hide animation → `snackbar:hide` |
|
|
145
|
+
| Closing (timeout) | After `duration` ms → hide animation → `snackbar:hide` ~300ms later |
|
|
146
|
+
| Persistent | `duration: "forever"` — no auto-dismiss; user must close if `closable` |
|
|
147
|
+
| Non-closable | `closable: false` — no close icon; rely on timeout or programmatic teardown |
|
|
148
|
+
| Frozen (docs) | `frozen: true` — near-instant transitions for Storybook screenshots |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Best practices
|
|
153
|
+
|
|
154
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
155
|
+
|
|
156
|
+
### When to show
|
|
157
|
+
|
|
158
|
+
- **SHOULD** use snackbars for **reversible or low-stakes** outcomes (saved, copied, added to favorites).
|
|
159
|
+
- **SHOULD** prefer a snackbar over a dialog when no explicit user decision is required.
|
|
160
|
+
- **MUST NOT** use snackbars for errors that block further progress on the page — use `VJoyHighlight` or inline errors instead.
|
|
161
|
+
|
|
162
|
+
### Implementation
|
|
163
|
+
|
|
164
|
+
- **MUST** call `pushVJoySnackbar` — never place `<VJoySnackbar>` in app templates.
|
|
165
|
+
- **MUST** pass at least `props.message`.
|
|
166
|
+
- **MUST** use only documented `level` values from MCP / `JoySnackbar.types.ts` (`success`, `error`, `warning`, `info`).
|
|
167
|
+
- **MUST** use Joy tokens — no custom snackbar chrome CSS.
|
|
168
|
+
- **MUST NOT** invent props (`variant`, `neutral`, `position`, …).
|
|
169
|
+
- **MUST** unregister `snackbarEventBus` listeners on component destroy.
|
|
170
|
+
- **SHOULD** set `closable: true` when `actionText` or inline links are present (design guideline for accessibility).
|
|
171
|
+
|
|
172
|
+
### Stacking
|
|
173
|
+
|
|
174
|
+
- Multiple calls to `pushVJoySnackbar` stack in the host container (newest shell appended).
|
|
175
|
+
- `disableStacking` exists on `IJoynackbarServiceOptions` but is **not implemented** in the service — do not rely on it.
|
|
176
|
+
|
|
177
|
+
### Do / don't
|
|
178
|
+
|
|
179
|
+
| Do | Don't |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `pushVJoySnackbar({ props: { message: '…' } })` | `<VJoySnackbar message="…" />` in templates |
|
|
182
|
+
| Match `level` to message meaning | Default everything to `success` |
|
|
183
|
+
| Short header + optional detail in `message` | Paragraphs of text |
|
|
184
|
+
| `actionText` + bus listener for undo/retry | Multiple CTAs (only one `actionText` slot) |
|
|
185
|
+
| `duration: "forever"` only when truly needed | Infinite snackbars for routine success toasts |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Content
|
|
190
|
+
|
|
191
|
+
Snackbars carry user-facing copy. Keep tone neutral and didactic.
|
|
192
|
+
|
|
193
|
+
### Structure
|
|
194
|
+
|
|
195
|
+
| Field | Role | Guidance |
|
|
196
|
+
| --- | --- | --- |
|
|
197
|
+
| `header` | Title (most visible) | Up to **5 words**; specific status, not generic labels |
|
|
198
|
+
| `message` | Body / subtitle | 1–2 short sentences (~90 characters); supports HTML links |
|
|
199
|
+
| `actionText` | Single CTA | Short, action-oriented verb phrase |
|
|
200
|
+
|
|
201
|
+
### Header formula
|
|
202
|
+
|
|
203
|
+
**Subject + status** — let the `level` icon and color carry severity:
|
|
204
|
+
|
|
205
|
+
- ✅ Profile updated
|
|
206
|
+
- ✅ Payment failed
|
|
207
|
+
- ❌ Error
|
|
208
|
+
- ❌ Something went wrong
|
|
209
|
+
- ❌ Success
|
|
210
|
+
- ❌ Congrats
|
|
211
|
+
|
|
212
|
+
### Writing rules
|
|
213
|
+
|
|
214
|
+
| Strength | Rule |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| **MUST** | Be specific — name what happened (`Invoice sent`, not `Done`) |
|
|
217
|
+
| **MUST** | Keep headers ≤ 5 words |
|
|
218
|
+
| **MUST NOT** | Generic titles (`Error`, `Success`, `Info`, `Congrats`) |
|
|
219
|
+
| **MUST NOT** | Redundant adverbs (`successfully saved`) — the success level already signals outcome |
|
|
220
|
+
| **SHOULD** | Use `message` only when it adds guidance or next steps |
|
|
221
|
+
| **SHOULD** | For error feedback, title alone is often enough (per design guidelines) |
|
|
222
|
+
| **SHOULD** | Sentence case for header and message |
|
|
223
|
+
| **SHOULD NOT** | Repeat the header verbatim in `message` |
|
|
224
|
+
|
|
225
|
+
### Action CTA (`actionText`)
|
|
226
|
+
|
|
227
|
+
- **MUST** use a single, contextual verb (`Undo`, `Retry`, `View invoice`).
|
|
228
|
+
- **MUST NOT** use vague dismissals (`Got it`, `Ok`, `I understand`) — use the close icon instead.
|
|
229
|
+
- **SHOULD** use `actionText` for undo, retry, or navigation — not for merely acknowledging the toast.
|
|
230
|
+
|
|
231
|
+
### Links in `message`
|
|
232
|
+
|
|
233
|
+
Inline `<a>` tags are supported (`v-html`). Use `VJoyLink` styling patterns (bold, neutral-0 color in the snackbar context).
|
|
234
|
+
|
|
235
|
+
- **SHOULD** pair links with `closable: true`.
|
|
236
|
+
- **SHOULD** link to FAQ, help, or an in-app destination — not duplicate the `actionText` job.
|
|
237
|
+
|
|
238
|
+
### Patterns
|
|
239
|
+
|
|
240
|
+
**Save feedback** — `level: 'success'`, header names the object: `Brief saved`. Optional `message` only if you guide next steps.
|
|
241
|
+
|
|
242
|
+
**Recoverable error** — `level: 'error'`, specific header: `Upload failed`. `actionText: 'Retry'` with `snackbar:action-click` handler.
|
|
243
|
+
|
|
244
|
+
**Background info** — `level: 'info'`, short header; skip `actionText` unless there is a real follow-up.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Accessibility
|
|
249
|
+
|
|
250
|
+
Behaviors (what must happen), not slogans.
|
|
251
|
+
|
|
252
|
+
| Behavior | Expectation |
|
|
253
|
+
| --- | --- |
|
|
254
|
+
| Role | Transient notification region — not a modal dialog |
|
|
255
|
+
| Name | Meaning comes from `header` + `message` text; avoid icon-only meaning |
|
|
256
|
+
| Keyboard | Close button is a native `<button>` and focusable when `closable` |
|
|
257
|
+
| Focus | Transient auto-dismiss snackbars should not trap focus; when `actionText` or links are present, **SHOULD** keep `closable: true` so users can dismiss explicitly |
|
|
258
|
+
| Motion | Enter/leave animations use `--joy-transition-duration-long`; respect `prefers-reduced-motion` at app level where implemented |
|
|
259
|
+
| Links | Inline links in `message` must have discernible text; don't rely on color alone |
|
|
260
|
+
| Timing | Default 5s may be short for long messages — increase `duration` proportionally to copy length |
|
|
261
|
+
|
|
262
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Examples
|
|
267
|
+
|
|
268
|
+
```vue
|
|
269
|
+
<script setup lang="ts">
|
|
270
|
+
import { pushVJoySnackbar } from '@maltjoy/core-vue'
|
|
271
|
+
|
|
272
|
+
function notifySaved() {
|
|
273
|
+
pushVJoySnackbar({
|
|
274
|
+
props: {
|
|
275
|
+
level: 'success',
|
|
276
|
+
header: 'Profile updated',
|
|
277
|
+
message: 'Your changes are live.',
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function retryUpload() {
|
|
283
|
+
// your retry logic
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function notifyErrorWithRetry() {
|
|
287
|
+
const { snackbarEventBus } = pushVJoySnackbar({
|
|
288
|
+
props: {
|
|
289
|
+
level: 'error',
|
|
290
|
+
header: 'Upload failed',
|
|
291
|
+
message: 'Check your connection and try again.',
|
|
292
|
+
actionText: 'Retry',
|
|
293
|
+
},
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
snackbarEventBus.on('snackbar:action-click', retryUpload)
|
|
297
|
+
snackbarEventBus.on('snackbar:hide', () => {
|
|
298
|
+
snackbarEventBus.off('snackbar:action-click', retryUpload)
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function persistentInfo() {
|
|
303
|
+
pushVJoySnackbar({
|
|
304
|
+
props: {
|
|
305
|
+
level: 'info',
|
|
306
|
+
header: 'Sync in progress',
|
|
307
|
+
message: 'Large files may take a few minutes.',
|
|
308
|
+
duration: 'forever',
|
|
309
|
+
closable: true,
|
|
310
|
+
},
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
</script>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Custom mount (tests / Storybook only):
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { createAllSnackbarsContainer, pushVJoySnackbar } from '@maltjoy/core-vue'
|
|
320
|
+
|
|
321
|
+
createAllSnackbarsContainer({
|
|
322
|
+
appendTo: '#my-canvas',
|
|
323
|
+
defaultMountSelector: 'snackbar-host',
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
pushVJoySnackbar({
|
|
327
|
+
snackbarHostId: 'snackbar-host',
|
|
328
|
+
props: { message: 'Hello from a custom host' },
|
|
329
|
+
})
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
In Nuxt (or apps with auto-import), omit the import if `pushVJoySnackbar` is already global.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Related
|
|
337
|
+
|
|
338
|
+
| Component | Use when |
|
|
339
|
+
| --- | --- |
|
|
340
|
+
| `VJoyHighlight` | Inline, persistent contextual messages or field-level errors |
|
|
341
|
+
| `VJoyDialog` | Blocking confirmations or multi-field input |
|
|
342
|
+
| `VJoyLink` | Inline navigation patterns referenced inside `message` HTML |
|
|
343
|
+
| `VJoyButton` | Primary actions on the page — not inside the snackbar API |
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## For agents
|
|
348
|
+
|
|
349
|
+
Optional for readers; **required** for vibe-coding quality. Maps to `agentDocumentBlocks` in [`JoySnackbar.dsds.json`](./JoySnackbar.dsds.json).
|
|
350
|
+
|
|
351
|
+
### Pick the right component
|
|
352
|
+
|
|
353
|
+
| If the intent is… | Emit… | Do not emit… |
|
|
354
|
+
| --- | --- | --- |
|
|
355
|
+
| Brief post-action feedback | `pushVJoySnackbar({ props: { message } })` | `<VJoySnackbar>` in template |
|
|
356
|
+
| Field / section error blocking work | `VJoyHighlight` | Snackbar with `level="error"` |
|
|
357
|
+
| Confirm destructive action | `VJoyDialog` | Snackbar with only `actionText` |
|
|
358
|
+
| Neutral info tone | `level: 'info'` | `level: 'neutral'` (invalid) |
|
|
359
|
+
| Undo / retry | `actionText` + `snackbar:action-click` listener | Second button or invented slot |
|
|
360
|
+
|
|
361
|
+
### Hard rules
|
|
362
|
+
|
|
363
|
+
1. **MUST** call MCP (or read types) before generating props.
|
|
364
|
+
2. **MUST** use `pushVJoySnackbar` — never render `<VJoySnackbar>` directly in product code.
|
|
365
|
+
3. **MUST** include `props.message` (required).
|
|
366
|
+
4. **MUST NOT** invent props (`variant`, `size`, `neutral`, `disableStacking` behavior, …).
|
|
367
|
+
5. **MUST NOT** pass `bus` or `instanceNb` — the service sets them.
|
|
368
|
+
6. **MUST NOT** override `.joy-snackbar` layout, colors, or animation in app CSS.
|
|
369
|
+
7. **SHOULD** default `level` to `success` only for genuine success; match severity otherwise.
|
|
370
|
+
8. **SHOULD** set `closable: true` when `actionText` or links are present.
|
|
371
|
+
9. **SHOULD** clean up `snackbarEventBus.on` handlers in `onUnmounted`.
|
|
372
|
+
|
|
373
|
+
### Checklist
|
|
374
|
+
|
|
375
|
+
- [ ] `pushVJoySnackbar` (not template component)
|
|
376
|
+
- [ ] `props.message` present
|
|
377
|
+
- [ ] `level` from `success` \| `error` \| `warning` \| `info` only
|
|
378
|
+
- [ ] No invented props / no custom snackbar chrome CSS
|
|
379
|
+
- [ ] Event bus listeners removed on destroy
|
|
380
|
+
- [ ] Header ≤ 5 words, not generic (`Error`, `Success`, …)
|
|
381
|
+
- [ ] `actionText` is action-oriented (not `Ok` / `Got it`)
|
|
382
|
+
- [ ] Inline errors that block the flow → `VJoyHighlight`, not snackbar
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## DSDS mapping
|
|
387
|
+
|
|
388
|
+
This Markdown mirrors [`JoySnackbar.dsds.json`](./JoySnackbar.dsds.json) (DSDS **0.15.2**).
|
|
389
|
+
|
|
390
|
+
| Section here | DSDS block |
|
|
391
|
+
| --- | --- |
|
|
392
|
+
| When to use / not | `use-cases` |
|
|
393
|
+
| Anatomy / service flow | `anatomy` |
|
|
394
|
+
| Levels (`level`) | `variants` |
|
|
395
|
+
| Layout constraints | `design-specifications` |
|
|
396
|
+
| States | `states` |
|
|
397
|
+
| Best practices / content rules | `guidelines` (+ `content`) |
|
|
398
|
+
| Accessibility | `accessibility` |
|
|
399
|
+
| Examples | `sections` |
|
|
400
|
+
| For agents | `agentDocumentBlocks` |
|
|
401
|
+
| Props detail | `api` in JSON + Joy MCP |
|
|
402
|
+
| Service imports | `imports` in JSON (`pushVJoySnackbar`, `createAllSnackbarsContainer`) |
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoySpinner
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoySpinner.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Spinner
|
|
10
|
+
|
|
11
|
+
Use **`VJoySpinner`** when you need a compact, animated loading indicator — for example inside a button, a form field area, or a localized content block while data is being fetched or an action is in progress.
|
|
12
|
+
|
|
13
|
+
It is a lightweight CSS-only spinner with two color presets (`teal`, `white`) for light and dark backgrounds. For full-page or section-blocking loaders with overlay and optional message, prefer **`VJoyScreenLoader`** or built-in **`loading`** on **`VJoyButton`** / **`VJoyIconButton`**.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoySpinner` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [Figma — Joy Core Design System](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
|
|
19
|
+
| **Storybook** | [VJoySpinner docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-loader-vjoyspinner--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoySpinner.dsds.json`](./JoySpinner.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoySpinner")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a spinner when the user should see that work is **in progress** in a small, inline context — a button submitting a form, a card body waiting for API data, or a custom loader slot inside **`VJoyScreenLoader`**.
|
|
28
|
+
|
|
29
|
+
Pick **`color="white"`** on filled or dark surfaces (primary/danger buttons, dark overlays). Use **`color="teal"`** (or omit `color`) on light backgrounds.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Button or icon button in a loading state | `VJoyButton` / `VJoyIconButton` with `loading` — spinner color is chosen automatically |
|
|
36
|
+
| Full-page or section overlay with optional message | `VJoyScreenLoader` |
|
|
37
|
+
| Placeholder layout while content shape is unknown | `VJoyBlockSkeleton`, `VJoyFormFieldSkeleton` |
|
|
38
|
+
| Determinate progress (percentage or steps) | `VJoyProgressBar` |
|
|
39
|
+
| Custom animated GIF / ad-hoc CSS spinner | Still use `VJoySpinner` + Joy tokens — don’t reinvent loader chrome |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
### Anatomy
|
|
46
|
+
|
|
47
|
+
| Part | Required? | What it is |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| Root `.joy-spinner` | Yes | Single `div`; circular border animation |
|
|
50
|
+
| Color modifier | No | `joy-spinner_white` when `color="white"`; default tokens when `color` is omitted or `teal` |
|
|
51
|
+
|
|
52
|
+
No slots, events, or child elements. The component is purely presentational.
|
|
53
|
+
|
|
54
|
+
### Default values
|
|
55
|
+
|
|
56
|
+
- `color` is **optional** — when omitted, default Joy tokens apply (`secondary-50` / `secondary-10`, ~25px size)
|
|
57
|
+
- Storybook defaults `color` to **`teal`**
|
|
58
|
+
|
|
59
|
+
Sizing and fine-tuning use CSS custom properties on the root (or a positioned parent), not props:
|
|
60
|
+
|
|
61
|
+
- `--spinner-size` — diameter (default **25px**)
|
|
62
|
+
- `--spinner-default-color` — active arc color
|
|
63
|
+
- `--spinner-default-bg-color` — track color
|
|
64
|
+
|
|
65
|
+
Props and types: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
66
|
+
|
|
67
|
+
### Positioning
|
|
68
|
+
|
|
69
|
+
The spinner uses **`position: absolute`** in its base styles. Place it inside a **relatively positioned** container with enough space for the spinner size, or override positioning in a parent wrapper (as **`VJoyScreenLoader`** does with `position: relative` on `.joy-spinner`).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Best practices
|
|
74
|
+
|
|
75
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
76
|
+
|
|
77
|
+
### When to show a spinner
|
|
78
|
+
|
|
79
|
+
- **SHOULD** use **`VJoyButton` `loading`** instead of embedding `VJoySpinner` manually in buttons.
|
|
80
|
+
- **SHOULD** pair inline spinners with **visible context** (button label, section heading, or `VJoyScreenLoader` content slot) so the wait state is understandable.
|
|
81
|
+
- **MUST NOT** use a spinner as the only loading feedback on a full view — use **`VJoyScreenLoader`** or skeletons when the whole region is empty.
|
|
82
|
+
|
|
83
|
+
### Implementation
|
|
84
|
+
|
|
85
|
+
- **MUST** use only documented `color` values: **`teal`**, **`white`** (from MCP / `TJoySpinnerColors`).
|
|
86
|
+
- **MUST** tune size via **`--spinner-size`** (and token-based color vars if needed) — not hardcoded width/height in product CSS.
|
|
87
|
+
- **MUST NOT** invent props (`size`, `variant`, `label`, …).
|
|
88
|
+
- **MUST NOT** override `.joy-spinner` border animation or colors with one-off app CSS — use `color` or documented CSS variables.
|
|
89
|
+
- **SHOULD** set `color="white"` on dark or filled primary/danger surfaces; `teal` on light backgrounds.
|
|
90
|
+
|
|
91
|
+
### Do / don’t
|
|
92
|
+
|
|
93
|
+
| Do | Don’t |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| `VJoyButton` `loading` for submit actions | Manual spinner + disabled button without `loading` |
|
|
96
|
+
| `color="white"` on dark overlays / filled buttons | Teal spinner on dark blue button fill |
|
|
97
|
+
| `--spinner-size` in a sized container | Fixed `px` spinner div with custom `@keyframes` |
|
|
98
|
+
| `VJoyScreenLoader` for blocking section load | Lone spinner centered on an empty page with no message |
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Accessibility
|
|
103
|
+
|
|
104
|
+
Behaviors (what must happen), not slogans.
|
|
105
|
+
|
|
106
|
+
| Behavior | Expectation |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| Role | Decorative motion only — **no** implicit `role="status"` or live region on `VJoySpinner` itself |
|
|
109
|
+
| Name | **MUST** expose progress in a **parent** control or region: e.g. `aria-busy="true"` on the loading button, `aria-live` on `VJoyScreenLoader` content, or visible “Loading…” copy |
|
|
110
|
+
| Keyboard | Spinner does not receive focus; ensure the trigger stays focusable or clearly disabled while loading |
|
|
111
|
+
| Motion | Continuous rotation (~1.1s linear); respect `prefers-reduced-motion` at app level if you add global animation overrides |
|
|
112
|
+
| Color | Don’t rely on motion alone — pair with text or `aria-busy` on the relevant widget |
|
|
113
|
+
|
|
114
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Examples
|
|
119
|
+
|
|
120
|
+
```vue
|
|
121
|
+
<script setup lang="ts">
|
|
122
|
+
import { VJoySpinner } from '@maltjoy/core-vue'
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<template>
|
|
126
|
+
<!-- Default — light background -->
|
|
127
|
+
<div style="position: relative; min-height: 48px">
|
|
128
|
+
<VJoySpinner />
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<!-- Explicit teal on light surface -->
|
|
132
|
+
<div style="position: relative; min-height: 48px">
|
|
133
|
+
<VJoySpinner color="teal" />
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<!-- White on dark / filled surface -->
|
|
137
|
+
<div
|
|
138
|
+
style="position: relative; min-height: 48px; background: var(--joy-color-secondary-50)"
|
|
139
|
+
>
|
|
140
|
+
<VJoySpinner color="white" />
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<!-- Custom size via token (parent or :style on wrapper) -->
|
|
144
|
+
<div style="position: relative; min-height: 64px; --spinner-size: 40px">
|
|
145
|
+
<VJoySpinner color="teal" />
|
|
146
|
+
</div>
|
|
147
|
+
</template>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Prefer **`VJoyButton loading`** over manual spinner placement in buttons:
|
|
151
|
+
|
|
152
|
+
```vue
|
|
153
|
+
<VJoyButton variant="main" label="Save" :loading="isSaving" />
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoySpinner` is already global.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Related
|
|
161
|
+
|
|
162
|
+
| Component | Use when |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| `VJoyButton` / `VJoyIconButton` | Async action with built-in spinner via `loading` |
|
|
165
|
+
| `VJoyScreenLoader` | Overlay or centered section loader with optional message |
|
|
166
|
+
| `VJoyDropzone` | File upload processing (embeds spinner internally) |
|
|
167
|
+
| `VJoyBlockSkeleton` / `VJoyFormFieldSkeleton` | Content-shaped placeholders instead of spinners |
|
|
168
|
+
| `VJoyProgressBar` | Known or estimable completion progress |
|