@openreachtech/furo-vue 1.6.0 → 1.7.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.
Files changed (79) hide show
  1. package/README.ja.md +2 -0
  2. package/README.md +2 -0
  3. package/docs/design-tokens.md +419 -0
  4. package/docs/furo-accordion.md +109 -0
  5. package/docs/furo-alert-dialog.md +107 -0
  6. package/docs/furo-attachment.md +133 -0
  7. package/docs/furo-autocomplete-field.md +719 -0
  8. package/docs/furo-avatar-group.md +87 -0
  9. package/docs/furo-avatar.md +101 -0
  10. package/docs/furo-badge.md +126 -0
  11. package/docs/furo-breadcrumb.md +89 -0
  12. package/docs/furo-button.md +263 -0
  13. package/docs/furo-checkbox.md +384 -0
  14. package/docs/furo-collapsible.md +104 -0
  15. package/docs/furo-control-block.md +102 -0
  16. package/docs/furo-date-picker.md +270 -0
  17. package/docs/furo-date-time-picker.md +110 -0
  18. package/docs/furo-dialog.md +88 -0
  19. package/docs/furo-drawer.md +122 -0
  20. package/docs/furo-dropdown-menu.md +298 -0
  21. package/docs/furo-editable-field.md +391 -0
  22. package/docs/furo-editor.md +304 -0
  23. package/docs/furo-email-field.md +93 -0
  24. package/docs/furo-empty-state.md +97 -0
  25. package/docs/furo-error-state.md +97 -0
  26. package/docs/furo-file-field.md +163 -0
  27. package/docs/furo-icon.md +288 -0
  28. package/docs/furo-number-field.md +397 -0
  29. package/docs/furo-pagination.md +101 -0
  30. package/docs/furo-password-field.md +75 -0
  31. package/docs/furo-popover.md +114 -0
  32. package/docs/furo-radio.md +125 -0
  33. package/docs/furo-scroll-area.md +112 -0
  34. package/docs/furo-search-field.md +94 -0
  35. package/docs/furo-section-header.md +135 -0
  36. package/docs/furo-select.md +443 -0
  37. package/docs/furo-separator.md +87 -0
  38. package/docs/furo-sidebar.md +178 -0
  39. package/docs/furo-skeleton.md +92 -0
  40. package/docs/furo-splitter.md +103 -0
  41. package/docs/furo-stepper.md +127 -0
  42. package/docs/furo-switch.md +107 -0
  43. package/docs/furo-table.md +233 -0
  44. package/docs/furo-tabs.md +173 -0
  45. package/docs/furo-text-field.md +249 -0
  46. package/docs/furo-textarea.md +214 -0
  47. package/docs/furo-time-field.md +276 -0
  48. package/docs/furo-toast.md +217 -0
  49. package/docs/furo-toggle-group.md +123 -0
  50. package/docs/furo-toggle.md +104 -0
  51. package/docs/furo-tool-bar.md +88 -0
  52. package/docs/furo-tooltip.md +107 -0
  53. package/lib/components/atoms/FuroButton/FuroButtonContext.js +8 -8
  54. package/lib/components/atoms/FuroCheckbox/FuroCheckboxContext.js +1 -1
  55. package/lib/components/atoms/FuroSwitch/FuroSwitchContext.js +1 -1
  56. package/lib/components/atoms/FuroToggle/FuroToggleContext.js +1 -1
  57. package/lib/components/molecules/FuroAccordion/FuroAccordionContext.js +1 -1
  58. package/lib/components/molecules/FuroCollapsible/FuroCollapsibleContext.js +1 -1
  59. package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenuContext.js +1 -2
  60. package/lib/components/molecules/FuroPopover/FuroPopoverContext.js +1 -1
  61. package/lib/components/molecules/FuroSelect/FuroSelect.vue +8 -6
  62. package/lib/components/molecules/FuroSelect/FuroSelectContext.js +35 -0
  63. package/lib/components/molecules/FuroSelect/FuroSelectOptionText.vue +40 -0
  64. package/lib/components/molecules/FuroSelect/FuroSelectOptionTextContext.js +275 -0
  65. package/lib/components/molecules/FuroSplitter/FuroSplitterContext.js +1 -1
  66. package/lib/components/molecules/FuroToggleGroup/FuroToggleGroupContext.js +1 -1
  67. package/lib/components/molecules/FuroTooltip/FuroTooltipContext.js +1 -1
  68. package/lib/components/organisms/FuroTable/FuroTableContext.js +3 -3
  69. package/lib/components/organisms/FuroToast/FuroToastContext.js +6 -2
  70. package/lib/components/organisms/FuroToast/ToastQueue.js +4 -4
  71. package/nuxt/index.js +9 -9
  72. package/package.json +19 -17
  73. package/public/furo-vue/components.json +1 -1
  74. package/types/generated/components/atoms/FuroButton/FuroButtonContext.d.ts +4 -4
  75. package/types/generated/components/molecules/FuroSelect/FuroSelect.vue.d.ts +23 -0
  76. package/types/generated/components/molecules/FuroSelect/FuroSelectContext.d.ts +14 -0
  77. package/types/generated/components/molecules/FuroSelect/FuroSelectOptionText.vue.d.ts +50 -0
  78. package/types/generated/components/molecules/FuroSelect/FuroSelectOptionTextContext.d.ts +141 -0
  79. package/types/generated/components/organisms/FuroTable/FuroTableContext.d.ts +2 -2
@@ -0,0 +1,304 @@
1
+ # FuroEditor
2
+
3
+ Rich-text editor organism for chatter-style composition. Built on a headless
4
+ rich-text engine and composed from furo controls (`FuroToolBar`, `FuroButton`,
5
+ `FuroTooltip`, `FuroDropdownMenu`, `FuroDialog`, `FuroTextField`).
6
+
7
+ FuroEditor is domain-agnostic: it ships formatting, links, paste handling, and the
8
+ mention/attachment *seams*, but never touches GraphQL, network, or domain schemas.
9
+ Mentions are supplied through a search adapter; attachment UI is rendered by the
10
+ consumer through a slot.
11
+
12
+ ## Props
13
+
14
+ | Prop | Type | Default | Description |
15
+ |------|------|---------|-------------|
16
+ | `parcel` | `object \| null` | `null` | Configuration + state (see below). |
17
+
18
+ ## Parcel
19
+
20
+ | Key | Type | Default | Description |
21
+ |-----|------|---------|-------------|
22
+ | `value` | `string` | `''` | HTML content. Controlled — pair with `update:value`. |
23
+ | `placeholder` | `string` | `''` | Empty-state placeholder. |
24
+ | `readOnly` | `boolean` | `false` | Hides the toolbar and makes content non-editable (keeps the bordered surface). |
25
+ | `appearance` | `'editor' \| 'plain'` | `'editor'` | `'plain'` renders content as bare text — no toolbar, border, background, or padding. Implies display-only. |
26
+ | `autofocus` | `boolean \| 'start' \| 'end' \| 'all' \| number` | `false` | Where the caret lands when the editor is created. Read once, at creation (see Focus). |
27
+ | `contentAttributes` | `Record<string, string>` | `{}` | Attributes set on the writing area, for example `aria-label`. Read once, at creation (see Accessibility). |
28
+ | `toolbar` | `Array<Array<string>>` | default | Toolbar layout as groups of action names; separators sit between groups. See Custom toolbar. |
29
+ | `attachedFiles` | `Array<*>` | `[]` | Attachment records held for the consumer; shape is the consumer's own. |
30
+ | `labels` | `object` | built-in | Partial overrides for toolbar tooltips and dialog text (any default label can be replaced). |
31
+ | `mention` | `{ searchMentions }` | absent | Enables `@`-mentions when present (see Mentions). |
32
+ | `image` | `{ uploadImage }` | absent | Enables inline images when present (see Inline images). |
33
+ | `actionOverrides` | `Record<string, fn>` | `{}` | Override the click behavior of a built-in control by name (see Custom actions). |
34
+
35
+ `mention.searchMentions` is `({ query }) => Promise<Array<{ id, label }>>`. Supplying
36
+ it turns on the mention control and `@` trigger; omit it to disable mentions entirely.
37
+
38
+ ## Events
39
+
40
+ | Event | Payload | When |
41
+ |-------|---------|------|
42
+ | `update:value` | `string` (HTML) | Content changed (v-model:value). |
43
+ | `change-value` | `{ value }` | Content changed. |
44
+ | `commit-value` | `{ value }` | Editor blurred. |
45
+ | `update:attachedFiles` | `Array<*>` | Attachment list changed (e.g. a removal). |
46
+ | `mention:add` | `{ id, label }` | A mention was inserted. |
47
+ | `files:add` | `{ files }` | Files were dropped or pasted that the editor does not insert itself (see Dropped and pasted files). |
48
+ | `image:error` | `{ file, error }` | An inline-image upload was refused; the rest of the batch continues. |
49
+
50
+ ## Slots
51
+
52
+ | Slot | Props | Description |
53
+ |------|-------|-------------|
54
+ | `toolbar-start` | `{ command, editor }` | Custom controls prepended to the toolbar. |
55
+ | `toolbar-end` | `{ command, editor, hiddenEntries }` | Custom controls appended to the toolbar. `hiddenEntries` carries the render descriptors for the actions no group named, for drawing a custom overflow menu. |
56
+ | `attachments` | `{ attachedFiles, remove, isEditable }` | Render the attachment UI. `remove` is `({ index }) => void`. |
57
+ | `footer` | — | Content rendered below the editor (e.g. submit row). |
58
+
59
+ ## Toolbar
60
+
61
+ Formatting controls: text-style dropdown (normal / headings 1–4), text-color dropdown,
62
+ alignment dropdown (left / center / right / justify), and toggles for bold, italic,
63
+ underline, strikethrough, bullet list, numbered list, blockquote, link, and (when
64
+ enabled) mention. Active state is reflected per control.
65
+
66
+ ## Content typography
67
+
68
+ The writing surface renders Body/Medium, and each heading renders a composite text
69
+ role at semibold weight, so a size never arrives paired with a line height the design
70
+ does not draw.
71
+
72
+ | Element | Role | Size | Line height |
73
+ |---------|------|------|-------------|
74
+ | Body text | `--font-body-medium` | `0.84375rem` | `1.25rem` |
75
+ | `h1` | `--font-headline-large` | `1.5rem` | `2rem` |
76
+ | `h2` | `--font-headline-medium` | `1.25rem` | `1.75rem` |
77
+ | `h3` | `--font-headline-small` | `1.125rem` | `1.75rem` |
78
+ | `h4` | `--font-label-medium` | `0.9375rem` | `1.5rem` |
79
+
80
+ Every value is `rem`, so the whole surface scales with the reader's root font size.
81
+
82
+ `h4` renders Label/Medium because the type ramp carries no `1rem` step; `0.9375rem` is
83
+ the step below the `1.125rem` its `h3` renders. Override any of these by redefining the
84
+ token, which re-themes every component that reads it, or by restyling the heading in
85
+ your own layer.
86
+
87
+ ## Copy-paste behavior
88
+
89
+ Paste is handled in layers so external content lands cleanly:
90
+
91
+ 1. **Plain-text normalization** — CRLF/CR collapse to LF; 3+ blank lines collapse to one.
92
+ 2. **HTML sanitization** — pasted `font-family` / `font-size` declarations are stripped so external typography does not override the editor.
93
+ 3. **Markdown auto-convert** — text that looks like Markdown is converted to HTML on paste.
94
+ 4. **Newline preservation** — multi-line plain text keeps its empty lines (the default engine collapses runs of newlines), and Markdown links / bare URLs in pasted text become real links.
95
+
96
+ ## Custom toolbar
97
+
98
+ `parcel.toolbar` defines the toolbar as an array of **groups**, each group an array
99
+ of action names. A vertical separator is rendered between groups, so reordering,
100
+ adding, removing, and re-grouping actions (and thus separators) is fully declarative.
101
+
102
+ Available names: `heading`, `color`, `align` (dropdowns); `bold`, `italic`,
103
+ `underline`, `strike`, `bulletList`, `orderedList`, `blockquote`, `link`, `image`,
104
+ `mention` (controls); `overflow` (menu). `mention` is dropped unless a mention
105
+ adapter is supplied, and `image` is dropped unless an image adapter is supplied.
106
+
107
+ Outside a production build, a name outside that vocabulary throws, naming the value
108
+ and listing what the toolbar accepts. A production build drops it instead. Watch the
109
+ two list controls in particular: they are named `bulletList` and `orderedList`, while
110
+ the labels that title them are keyed `bullets` and `numbers`.
111
+
112
+ ```js
113
+ // Bold/italic, then heading, then link/quote — three groups, two separators.
114
+ toolbar: [
115
+ ['bold', 'italic'],
116
+ ['heading'],
117
+ ['link', 'blockquote'],
118
+ ]
119
+ ```
120
+
121
+ When omitted, the default layout is:
122
+ `[['heading','color','align'], ['bold','italic','underline','strike'], ['bulletList','orderedList','blockquote'], ['link', 'mention'?]]`.
123
+
124
+ ## Overflow
125
+
126
+ A narrow toolbar parks the rest of its actions behind `overflow`, which renders a `…`
127
+ menu holding every default action no group named. The commands, labels and active
128
+ states stay inside the component, so a compact toolbar costs one word:
129
+
130
+ ```js
131
+ // Bold and italic stay on the bar; heading, colour, align, underline, strike,
132
+ // the two lists, quote and link move into the menu.
133
+ toolbar: [
134
+ ['bold', 'italic'],
135
+ ['overflow'],
136
+ ]
137
+ ```
138
+
139
+ A hidden dropdown becomes a submenu carrying its own options, and a hidden control
140
+ becomes a single row. Each row draws the same icon the on-bar control would, a
141
+ toggle row marks itself when its formatting is active, and colour rows keep their
142
+ swatch. When the groups already name every action, `overflow` renders nothing rather
143
+ than an empty menu. Override the `…` trigger's accessible name through the `overflow`
144
+ key of `parcel.labels`.
145
+
146
+ To draw the menu yourself instead, read `hiddenEntries` off the `toolbar-end` slot and
147
+ leave `overflow` out of the groups. Both paths expose the same descriptors:
148
+
149
+ ```vue
150
+ <template #toolbar-end="{ hiddenEntries }">
151
+ <MyOverflowMenu :entries="hiddenEntries" />
152
+ </template>
153
+ ```
154
+
155
+ ## Custom actions
156
+
157
+ Two seams let consumers extend or change the toolbar without forking the component:
158
+
159
+ - **Add controls** — render your own buttons in the `toolbar-start` / `toolbar-end`
160
+ slots. Each slot exposes `command` (the command sub-context: `toggleNodeOrMark`,
161
+ `executeDropdownCommand`, `insertText`, `applyLink`, `isActive`, …) and `editor`
162
+ (the raw engine instance), so a custom control can drive the editor directly.
163
+ - **Override built-ins** — `parcel.actionOverrides` maps a built-in control name
164
+ (`bold`, `italic`, `underline`, `strike`, `bulletList`, `orderedList`,
165
+ `blockquote`, `link`, `image`, `mention`) to `({ control, command, editor }) => void`.
166
+ When present, the override runs instead of the default behavior for that control.
167
+
168
+ ```vue
169
+ <FuroEditor
170
+ :parcel="{
171
+ actionOverrides: {
172
+ link: ({ editor }) => myCustomLinkFlow(editor),
173
+ },
174
+ }"
175
+ >
176
+ <template #toolbar-end="{ command }">
177
+ <FuroButton :parcel="{ variant: 'ghost', size: 'sm' }" @click="command.insertText({ text: '✦ ' })">
178
+ Insert ✦
179
+ </FuroButton>
180
+ </template>
181
+ </FuroEditor>
182
+ ```
183
+
184
+ ## Inline images
185
+
186
+ Supply `parcel.image.uploadImage` to enable embedding images **inside the content**
187
+ (distinct from `attachedFiles`, which render below the editor via the slot). The
188
+ adapter is `({ file }) => Promise<{ src, alt? }>` — the consumer uploads the file and
189
+ returns the URL to embed; the library never uploads anything itself.
190
+
191
+ When the adapter is present, an `image` toolbar control appears, and three triggers
192
+ insert inline images:
193
+
194
+ - **Toolbar button** — opens a native multi-image file picker.
195
+ - **Paste** — image files on the clipboard are uploaded and inserted at the cursor.
196
+ - **Drag & drop** — image files dropped onto the editor are inserted at the drop point.
197
+
198
+ ```js
199
+ image: {
200
+ uploadImage: async ({ file }) => {
201
+ const url = await myUploadService.put(file)
202
+ return { src: url, alt: file.name }
203
+ },
204
+ }
205
+ ```
206
+
207
+ Available names include `image` for `parcel.toolbar` ordering. Omit the adapter to
208
+ disable inline images entirely (the control and the paste/drop handlers stay off).
209
+
210
+ An adapter that rejects refuses that one file: the editor emits `image:error` with
211
+ `{ file, error }` and moves on to the next file in the batch, so four accepted pictures
212
+ still land when the fifth is turned away.
213
+
214
+ ## Dropped and pasted files
215
+
216
+ Files the editor does not embed itself reach the consumer through `files:add`, carrying
217
+ `{ files }` as an array of `File`. That is every dropped or pasted file when no image
218
+ adapter is supplied, and every non-image file when one is. Images the editor uploads and
219
+ inserts inline never arrive this way, because it consumed them.
220
+
221
+ A drop that carries files is claimed by the editor, so the browser never navigates away
222
+ from the page. A paste is left alone, so pasted text and HTML still land as before.
223
+
224
+ ```vue
225
+ <FuroEditor
226
+ :parcel="{ value: htmlRef }"
227
+ @files:add="attachFiles($event.files)"
228
+ />
229
+ ```
230
+
231
+ ## Focus
232
+
233
+ `parcel.autofocus` says where the caret lands when the editor is created: `true`,
234
+ `'start'`, `'end'`, `'all'`, a document position, or `false` (the default). It is read
235
+ once, while the editor is being built, so changing it later focuses nothing. To focus an
236
+ editor that already exists, call `focusChain()` on the command context.
237
+
238
+ ## Accessibility
239
+
240
+ `parcel.contentAttributes` sets attributes on the writing area, which is how a consumer
241
+ gives it an accessible name:
242
+
243
+ ```js
244
+ contentAttributes: {
245
+ 'aria-label': 'Write a comment',
246
+ }
247
+ ```
248
+
249
+ Like `placeholder`, it is read once while the editor is being built. Toolbar controls
250
+ name themselves from their labels, so every icon-only control and dropdown ships an
251
+ `aria-label` that `parcel.labels` overrides along with the tooltip text.
252
+
253
+ ## Mentions
254
+
255
+ Supply `parcel.mention.searchMentions`. As the user types after `@`, the adapter is
256
+ queried with `{ query }` and returns `{ id, label }` items shown in a popup; selecting
257
+ one inserts a mention node and emits `mention:add`. The library performs no fetching —
258
+ the consumer's adapter owns the data source.
259
+
260
+ ## Example
261
+
262
+ ```vue
263
+ <FuroEditor
264
+ :parcel="{
265
+ value: htmlRef,
266
+ placeholder: 'Write something…',
267
+ mention: { searchMentions },
268
+ attachedFiles,
269
+ }"
270
+ @update:value="htmlRef = $event"
271
+ @update:attached-files="attachedFiles = $event"
272
+ @mention:add="onMentionAdd"
273
+ >
274
+ <template #attachments="{ attachedFiles, remove, isEditable }">
275
+ <!-- consumer-owned attachment UI -->
276
+ </template>
277
+ </FuroEditor>
278
+ ```
279
+
280
+ ## Future enhancements (planned)
281
+
282
+ **Markdown-driven image input + extensibility (option C).** Not built yet — current
283
+ inline-image support (toolbar / paste / drop) is considered sufficient for now. When
284
+ this is picked up, do all three parts together:
285
+
286
+ 1. **Expose an extension seam** — surface `parcel.extensions: Array<AnyExtension>`
287
+ through to `buildEditorExtensions` (the internal `extraExtensions` param already
288
+ exists; it just isn't wired to the parcel/SFC yet). This lets consumers plug in any
289
+ future input style (markdown shortcuts, slash commands, emoji, …) without forking
290
+ the core. This is the primary lever for handling diverse input modes.
291
+ 2. **Decouple Image-node registration from the upload adapter** — e.g. allow
292
+ `parcel.image: true | { uploadImage }`. `true` registers the Image node so
293
+ URL-based images (markdown `![](url)`, pasted URLs) render *without* requiring an
294
+ upload adapter; the object form additionally enables file upload. Today the Image
295
+ node is registered only when `parcel.image.uploadImage` is present, which couples
296
+ plain URL images to the upload path.
297
+ 3. **Ship a markdown-image input rule** — an opt-in Tiptap input rule so typing
298
+ `![alt](url)` auto-converts to an image as you type (paste of `![alt](url)` already
299
+ works today via the markdown-paste layer + `marked`). Domain-agnostic; plugs in via
300
+ the seam from (1).
301
+
302
+ Note: markdown image syntax refers to an existing URL, so it intentionally bypasses
303
+ `uploadImage` (there is no file to upload) — which is why part (2) is needed for the
304
+ markdown path to work independently of the upload adapter.
@@ -0,0 +1,93 @@
1
+ # `<FuroEmailField>` Interface Specification
2
+
3
+ `<FuroEmailField>` follows the same architecture as [`<FuroTextField>`](./furo-text-field.md). Read that document first — this page only lists the differences.
4
+
5
+ ---
6
+
7
+ ## Differences from `<FuroTextField>`
8
+
9
+ ### `type` attribute
10
+
11
+ Always renders `type="email"`. The `type` fallthrough attribute is excluded — the underlying control is always an HTML email input (`<input type="email">`), which enables the browser’s built‑in email format hints and constraint validation API where supported.
12
+
13
+ ### CSS class
14
+
15
+ `.unit-form-control.email` instead of `.unit-form-control.text`.
16
+
17
+ ### `parcel.value` type
18
+
19
+ | Component | `parcel.value` type |
20
+ |---------------------|----------------------------|
21
+ | `<FuroTextField>` | `string \| number \| null` |
22
+ | `<FuroEmailField>` | `string \| null` |
23
+
24
+ Email fields only accept textual values; numeric `parcel.value` is not supported.
25
+
26
+ ### Class naming
27
+
28
+ All class and type names are prefixed with `Email` instead of `Text`:
29
+
30
+ | Role | `FuroTextField` | `FuroEmailField` |
31
+ |----------------|---------------------------|--------------------------------|
32
+ | Component | `FuroTextField` | `FuroEmailField` |
33
+ | Context | `FuroTextFieldContext` | `FuroEmailFieldContext` |
34
+ | EmitPayload | `TextFieldEmitPayload` | `EmailFieldEmitPayload` |
35
+ | Parcel typedef | `FuroTextFieldParcel` | `FuroEmailFieldParcel` |
36
+
37
+ ### `EmailFieldEmitPayload`
38
+
39
+ `EmailFieldEmitPayload` extends [`BaseEmitPayload`](../lib/emit-payload/BaseEmitPayload.js) with no additional members beyond the shared text-field helpers. The API (`.value`, `.extractTrimmedValue()`, `.hasEmptyText()`, `.hasEnteredTrimmedText()`, etc.) matches [`TextFieldEmitPayload`](./furo-text-field.md#textfieldemitpayload-api), which also extends `BaseEmitPayload`.
40
+
41
+ Structured email validation (format, domain rules, uniqueness) stays in the parent Context; `<FuroEmailField>` does not enforce a format beyond what the native `email` input type provides.
42
+
43
+ ---
44
+
45
+ ## Fallthrough attributes (recommended usage)
46
+
47
+ Same fallthrough rules as `<FuroTextField>`. Typical pairings:
48
+
49
+ | Attribute | Typical value | Notes |
50
+ |----------------|-----------------|-------|
51
+ | `autocomplete` | `email` | Single email field; use `username` only when it doubles as login identifier per HTML autofill guidelines. |
52
+ | `name` | e.g. `email` | Stable form field name for submission. |
53
+ | `placeholder` | Free text | UX hint only — not a substitute for an accessible label. |
54
+
55
+ Optional native attributes remain available when passed as fallthrough (for example `required`, `disabled`, `readonly`, `maxlength`, `pattern`) per project needs.
56
+
57
+ ---
58
+
59
+ ## Example
60
+
61
+ ```html
62
+ <FuroEmailField
63
+ :parcel="{
64
+ value: context.emailRef.value,
65
+ invalid: context.statusReactive.isEmailInvalid,
66
+ }"
67
+ :required="true"
68
+ placeholder="you@example.com"
69
+ name="email"
70
+ autocomplete="email"
71
+ @change-value="context.onChangeValue({
72
+ payload: $event
73
+ })"
74
+ />
75
+ ```
76
+
77
+ ```js
78
+ onChangeValue ({
79
+ payload
80
+ }) {
81
+ if (payload.hasEmptyTrimmedText()) {
82
+ this.statusReactive.isEmailInvalid = true
83
+
84
+ return
85
+ }
86
+
87
+ this.statusReactive.isEmailInvalid = !this.validatesEmail({
88
+ payload
89
+ })
90
+ }
91
+ ```
92
+
93
+ ---
@@ -0,0 +1,97 @@
1
+ # FuroEmptyState
2
+
3
+ Presentational placeholder shown when a region has no content to display — no views, no records, or nothing found. Title and description text come from the parcel so the consumer owns the wording; an icon and action controls are supplied via slots. It carries no value and emits nothing.
4
+
5
+ ## Summary
6
+
7
+ | Field | Value |
8
+ |---|---|
9
+ | **Layer** | Molecule |
10
+ | **Path** | `lib/components/molecules/FuroEmptyState/` |
11
+ | **Base primitive** | None — a styled centered block (no headless primitive needed) |
12
+ | **Composes** | Nothing (action slot typically hosts `FuroButton`) |
13
+ | **Emits** | None |
14
+
15
+ ---
16
+
17
+ ## Public API
18
+
19
+ ### Props
20
+
21
+ | Prop | Type | Default | Description |
22
+ |---|---|---|---|
23
+ | `parcel` | `EmptyStateParcel \| null` | `null` | Reactive data object holding the title and description text. |
24
+
25
+ ### Parcel keys
26
+
27
+ | Key | Type | Default | Description |
28
+ |---|---|---|---|
29
+ | `title` | `string` | — | Heading text. Rendered only when supplied; the consumer owns the wording. |
30
+ | `description` | `string` | — | Supporting text under the title. Rendered only when supplied. |
31
+
32
+ ### Emits
33
+
34
+ None. The empty state is presentational; wire actions through the `action` slot.
35
+
36
+ ### Slots
37
+
38
+ | Slot | Description |
39
+ |---|---|
40
+ | `icon` | Leading icon or illustration above the title. |
41
+ | `action` | Action controls (e.g. a "Create" `FuroButton`) below the text. |
42
+
43
+ ---
44
+
45
+ ## Behavior
46
+
47
+ | Concern | Behavior |
48
+ |---|---|
49
+ | **Title / description** | `title` and `description` getters extract from `parcel` (null when absent). Each block renders only when its text is present, so there are no hardcoded strings. |
50
+ | **Icon / action** | Rendered only when the matching slot is provided. |
51
+ | **Semantics** | Plain visible content; the consumer announces the empty state on the surrounding region when needed. |
52
+ | **Passthrough** | Extra attributes fall through to the root (`inheritAttrs: false` + `v-bind="$attrs"`). |
53
+
54
+ ---
55
+
56
+ ## Usage
57
+
58
+ ```vue
59
+ <!-- No records, with icon and action -->
60
+ <FuroEmptyState
61
+ :parcel="{
62
+ title: 'No records yet',
63
+ description: 'Create your first record to see it listed here.',
64
+ }"
65
+ >
66
+ <template #icon>
67
+ <Icon name="ph:tray" />
68
+ </template>
69
+ <template #action>
70
+ <FuroButton>Create record</FuroButton>
71
+ </template>
72
+ </FuroEmptyState>
73
+
74
+ <!-- Title only -->
75
+ <FuroEmptyState :parcel="{ title: 'Nothing to show' }" />
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Composition notes
81
+
82
+ - Imports nothing from the app — no GraphQL, PrimeVue, Vue Router, or Nuxt-specific imports.
83
+ - All logic lives in `FuroEmptyStateContext` as extract-only getters (`title`, `description`).
84
+ - CSS is wrapped in `@layer furo`; spacing uses `--size-space-*` for `gap`, text colors reference semantic `--color-foreground` / `--color-muted-foreground` tokens only.
85
+
86
+ ---
87
+
88
+ ## Testing
89
+
90
+ - Context logic (`title` / `description` getters) is fully covered by jsdom unit tests, including the empty-parcel and null-parcel fallbacks.
91
+ - No SFC mount test: behavior is declarative bindings over the tested context. Rendered output (icon / title / description / action) is verified by manual QA in the playground.
92
+
93
+ ---
94
+
95
+ ## Playground
96
+
97
+ `/samples/components/playground` → **Empty State** tab. Demonstrates a no-records state with icon + action, a no-results state with icon only, and a title-only state.
@@ -0,0 +1,97 @@
1
+ # FuroErrorState
2
+
3
+ Presentational placeholder shown when a region fails to load — a fetch error or a failed request. Title and description text come from the parcel so the consumer owns the wording; an icon and action controls (typically a retry button) are supplied via slots. It carries no value and emits nothing.
4
+
5
+ ## Summary
6
+
7
+ | Field | Value |
8
+ |---|---|
9
+ | **Layer** | Molecule |
10
+ | **Path** | `lib/components/molecules/FuroErrorState/` |
11
+ | **Base primitive** | None — a styled centered block (no headless primitive needed) |
12
+ | **Composes** | Nothing (action slot typically hosts `FuroButton`) |
13
+ | **Emits** | None |
14
+
15
+ ---
16
+
17
+ ## Public API
18
+
19
+ ### Props
20
+
21
+ | Prop | Type | Default | Description |
22
+ |---|---|---|---|
23
+ | `parcel` | `ErrorStateParcel \| null` | `null` | Reactive data object holding the title and description text. |
24
+
25
+ ### Parcel keys
26
+
27
+ | Key | Type | Default | Description |
28
+ |---|---|---|---|
29
+ | `title` | `string` | — | Heading text. Rendered only when supplied; the consumer owns the wording. |
30
+ | `description` | `string` | — | Supporting text under the title. Rendered only when supplied. |
31
+
32
+ ### Emits
33
+
34
+ None. The error state is presentational; wire the retry through the `action` slot.
35
+
36
+ ### Slots
37
+
38
+ | Slot | Description |
39
+ |---|---|
40
+ | `icon` | Leading icon or illustration above the title. |
41
+ | `action` | Action controls (e.g. a "Retry" `FuroButton`) below the text. |
42
+
43
+ ---
44
+
45
+ ## Behavior
46
+
47
+ | Concern | Behavior |
48
+ |---|---|
49
+ | **Title / description** | `title` and `description` getters extract from `parcel` (null when absent). Each block renders only when its text is present, so there are no hardcoded strings. |
50
+ | **Icon / action** | Rendered only when the matching slot is provided. |
51
+ | **Semantics** | Root carries `role="alert"` so assistive technology announces the failure; the icon uses the destructive color token. |
52
+ | **Passthrough** | Extra attributes fall through to the root (`inheritAttrs: false` + `v-bind="$attrs"`). |
53
+
54
+ ---
55
+
56
+ ## Usage
57
+
58
+ ```vue
59
+ <!-- Fetch failed, with icon and retry action -->
60
+ <FuroErrorState
61
+ :parcel="{
62
+ title: 'Failed to load records',
63
+ description: 'Check your connection and try again.',
64
+ }"
65
+ >
66
+ <template #icon>
67
+ <Icon name="ph:warning-circle" />
68
+ </template>
69
+ <template #action>
70
+ <FuroButton @click="refetch">Retry</FuroButton>
71
+ </template>
72
+ </FuroErrorState>
73
+
74
+ <!-- Title only -->
75
+ <FuroErrorState :parcel="{ title: 'Unable to load' }" />
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Composition notes
81
+
82
+ - Imports nothing from the app — no GraphQL, PrimeVue, Vue Router, or Nuxt-specific imports.
83
+ - All logic lives in `FuroErrorStateContext` as extract-only getters (`title`, `description`).
84
+ - CSS is wrapped in `@layer furo`; spacing uses `--size-space-*` for `gap`, the icon references the semantic `--color-destructive-text` token and text colors reference `--color-foreground` / `--color-muted-foreground` only.
85
+
86
+ ---
87
+
88
+ ## Testing
89
+
90
+ - Context logic (`title` / `description` getters) is fully covered by jsdom unit tests, including the empty-parcel and null-parcel fallbacks.
91
+ - No SFC mount test: behavior is declarative bindings over the tested context. Rendered output (icon / title / description / action) is verified by manual QA in the playground.
92
+
93
+ ---
94
+
95
+ ## Playground
96
+
97
+ `/samples/components/playground` → **Error State** tab. Demonstrates a fetch-failed state with icon + retry action, a server-error state with icon only, and a title-only state.