@openreachtech/furo-vue 1.6.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +2 -0
- package/README.md +2 -0
- package/docs/design-tokens.md +419 -0
- package/docs/furo-accordion.md +109 -0
- package/docs/furo-alert-dialog.md +107 -0
- package/docs/furo-attachment.md +133 -0
- package/docs/furo-autocomplete-field.md +719 -0
- package/docs/furo-avatar-group.md +87 -0
- package/docs/furo-avatar.md +101 -0
- package/docs/furo-badge.md +126 -0
- package/docs/furo-breadcrumb.md +89 -0
- package/docs/furo-button.md +263 -0
- package/docs/furo-checkbox.md +384 -0
- package/docs/furo-collapsible.md +104 -0
- package/docs/furo-control-block.md +102 -0
- package/docs/furo-date-picker.md +270 -0
- package/docs/furo-date-time-picker.md +110 -0
- package/docs/furo-dialog.md +88 -0
- package/docs/furo-drawer.md +122 -0
- package/docs/furo-dropdown-menu.md +298 -0
- package/docs/furo-editable-field.md +391 -0
- package/docs/furo-editor.md +304 -0
- package/docs/furo-email-field.md +93 -0
- package/docs/furo-empty-state.md +97 -0
- package/docs/furo-error-state.md +97 -0
- package/docs/furo-file-field.md +163 -0
- package/docs/furo-icon.md +288 -0
- package/docs/furo-number-field.md +397 -0
- package/docs/furo-pagination.md +101 -0
- package/docs/furo-password-field.md +75 -0
- package/docs/furo-popover.md +114 -0
- package/docs/furo-radio.md +125 -0
- package/docs/furo-scroll-area.md +112 -0
- package/docs/furo-search-field.md +94 -0
- package/docs/furo-section-header.md +135 -0
- package/docs/furo-select.md +443 -0
- package/docs/furo-separator.md +87 -0
- package/docs/furo-sidebar.md +178 -0
- package/docs/furo-skeleton.md +92 -0
- package/docs/furo-splitter.md +103 -0
- package/docs/furo-stepper.md +127 -0
- package/docs/furo-switch.md +107 -0
- package/docs/furo-table.md +233 -0
- package/docs/furo-tabs.md +173 -0
- package/docs/furo-text-field.md +249 -0
- package/docs/furo-textarea.md +214 -0
- package/docs/furo-time-field.md +276 -0
- package/docs/furo-toast.md +217 -0
- package/docs/furo-toggle-group.md +123 -0
- package/docs/furo-toggle.md +104 -0
- package/docs/furo-tool-bar.md +88 -0
- package/docs/furo-tooltip.md +107 -0
- package/lib/components/atoms/FuroButton/FuroButtonContext.js +8 -8
- package/lib/components/atoms/FuroCheckbox/FuroCheckboxContext.js +1 -1
- package/lib/components/atoms/FuroSwitch/FuroSwitchContext.js +1 -1
- package/lib/components/atoms/FuroToggle/FuroToggleContext.js +1 -1
- package/lib/components/molecules/FuroAccordion/FuroAccordionContext.js +1 -1
- package/lib/components/molecules/FuroCollapsible/FuroCollapsibleContext.js +1 -1
- package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenuContext.js +1 -2
- package/lib/components/molecules/FuroPopover/FuroPopoverContext.js +1 -1
- package/lib/components/molecules/FuroSelect/FuroSelect.vue +8 -6
- package/lib/components/molecules/FuroSelect/FuroSelectContext.js +35 -0
- package/lib/components/molecules/FuroSelect/FuroSelectOptionText.vue +40 -0
- package/lib/components/molecules/FuroSelect/FuroSelectOptionTextContext.js +275 -0
- package/lib/components/molecules/FuroSplitter/FuroSplitterContext.js +1 -1
- package/lib/components/molecules/FuroToggleGroup/FuroToggleGroupContext.js +1 -1
- package/lib/components/molecules/FuroTooltip/FuroTooltipContext.js +1 -1
- package/lib/components/organisms/FuroTable/FuroTableContext.js +3 -3
- package/lib/components/organisms/FuroToast/FuroToastContext.js +6 -2
- package/lib/components/organisms/FuroToast/ToastQueue.js +4 -4
- package/nuxt/index.js +9 -9
- package/package.json +24 -22
- package/public/furo-vue/components.json +1 -1
- package/types/generated/components/atoms/FuroAvatar/FuroAvatar.vue.d.ts +6 -15
- package/types/generated/components/atoms/FuroBadge/FuroBadge.vue.d.ts +8 -43
- package/types/generated/components/atoms/FuroButton/FuroButton.vue.d.ts +10 -45
- package/types/generated/components/atoms/FuroButton/FuroButtonContext.d.ts +4 -4
- package/types/generated/components/atoms/FuroCheckbox/FuroCheckbox.vue.d.ts +8 -20
- package/types/generated/components/atoms/FuroNumberField/FuroNumberField.vue.d.ts +12 -12
- package/types/generated/components/atoms/FuroRadio/FuroRadio.vue.d.ts +8 -24
- package/types/generated/components/atoms/FuroSwitch/FuroSwitch.vue.d.ts +0 -13
- package/types/generated/components/atoms/FuroToggle/FuroToggle.vue.d.ts +8 -19
- package/types/generated/components/molecules/FuroAccordion/FuroAccordion.vue.d.ts +14 -30
- package/types/generated/components/molecules/FuroAttachment/FuroAttachment.vue.d.ts +8 -44
- package/types/generated/components/molecules/FuroAutocompleteField/FuroAutocompleteField.vue.d.ts +74 -105
- package/types/generated/components/molecules/FuroAvatarGroup/FuroAvatarGroup.vue.d.ts +9 -18
- package/types/generated/components/molecules/FuroBreadcrumb/FuroBreadcrumb.vue.d.ts +22 -48
- package/types/generated/components/molecules/FuroCollapsible/FuroCollapsible.vue.d.ts +12 -23
- package/types/generated/components/molecules/FuroControlBlock/FuroControlBlock.vue.d.ts +6 -4
- package/types/generated/components/molecules/FuroDatePicker/FuroDatePicker.vue.d.ts +14 -16
- package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenu.vue.d.ts +13 -13
- package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenuOptionRows.vue.d.ts +34 -47
- package/types/generated/components/molecules/FuroEditableField/FuroEditableField.vue.d.ts +93 -115
- package/types/generated/components/molecules/FuroEmptyState/FuroEmptyState.vue.d.ts +8 -7
- package/types/generated/components/molecules/FuroErrorState/FuroErrorState.vue.d.ts +8 -7
- package/types/generated/components/molecules/FuroPagination/FuroPagination.vue.d.ts +14 -15
- package/types/generated/components/molecules/FuroPopover/FuroPopover.vue.d.ts +8 -7
- package/types/generated/components/molecules/FuroScrollArea/FuroScrollArea.vue.d.ts +6 -4
- package/types/generated/components/molecules/FuroSearchField/FuroSearchField.vue.d.ts +8 -22
- package/types/generated/components/molecules/FuroSectionHeader/FuroSectionHeader.vue.d.ts +10 -25
- package/types/generated/components/molecules/FuroSelect/FuroSelect.vue.d.ts +97 -108
- package/types/generated/components/molecules/FuroSelect/FuroSelectContext.d.ts +14 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectOptionText.vue.d.ts +52 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectOptionTextContext.d.ts +141 -0
- package/types/generated/components/molecules/FuroSplitter/FuroSplitter.vue.d.ts +14 -29
- package/types/generated/components/molecules/FuroTimeField/FuroTimeField.vue.d.ts +8 -7
- package/types/generated/components/molecules/FuroToggleGroup/FuroToggleGroup.vue.d.ts +9 -24
- package/types/generated/components/molecules/FuroToolBar/FuroToolBar.vue.d.ts +6 -9
- package/types/generated/components/molecules/FuroTooltip/FuroTooltip.vue.d.ts +8 -7
- package/types/generated/components/organisms/FuroAlertDialog/FuroAlertDialog.vue.d.ts +17 -19
- package/types/generated/components/organisms/FuroDialog/FuroDialog.vue.d.ts +20 -24
- package/types/generated/components/organisms/FuroDrawer/FuroDrawer.vue.d.ts +20 -24
- package/types/generated/components/organisms/FuroEditor/FuroEditor.vue.d.ts +9 -42
- package/types/generated/components/organisms/FuroSidebar/FuroSidebar.vue.d.ts +23 -62
- package/types/generated/components/organisms/FuroStepper/FuroStepper.vue.d.ts +19 -19
- package/types/generated/components/organisms/FuroTable/FuroTable.vue.d.ts +54 -71
- package/types/generated/components/organisms/FuroTable/FuroTableContext.d.ts +2 -2
- package/types/generated/components/organisms/FuroTabs/FuroTabs.vue.d.ts +17 -17
- package/types/generated/components/organisms/FuroToast/FuroToast.vue.d.ts +25 -31
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# `<FuroCheckbox>` Interface Specification
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`<FuroCheckbox>` is a **shipped** checkbox atom in the Furo-Vue component system. Its **public contract is Furo-defined**: application developers pass `parcel`, listen to Furo emits, and use documented slots — without importing the underlying primitive library.
|
|
6
|
+
|
|
7
|
+
Implementation uses a headless checkbox primitive inside `lib/components/atoms/FuroCheckbox/*` only. That dependency is **not** part of the application API.
|
|
8
|
+
|
|
9
|
+
Contract style aligns with [`FuroTextField`](./furo-text-field.md) and [`FuroAutocompleteField`](./furo-autocomplete-field.md):
|
|
10
|
+
|
|
11
|
+
- behavior via `:parcel` (`FuroCheckboxParcel`)
|
|
12
|
+
- HTML / form attributes via fallthrough (`name`, `id`, `aria-*`, …)
|
|
13
|
+
- Furo emits (`change-value`, `update:value`, `update:indeterminate`)
|
|
14
|
+
- `v-model:value` syncs checked state (`boolean`); `v-model:indeterminate` syncs the [-] UI flag
|
|
15
|
+
|
|
16
|
+
**Public SFC prop:** `parcel` only.
|
|
17
|
+
|
|
18
|
+
**Model shape:** **`boolean` checked** (`parcel.value` / `v-model:value`) plus **`boolean` indeterminate** (`parcel.indeterminate` / `v-model:indeterminate`). Tri-state is not encoded in `value`.
|
|
19
|
+
|
|
20
|
+
`@change-value` fires on every committed toggle from the checkbox root (`@update:model-value`).
|
|
21
|
+
|
|
22
|
+
Read [`FuroTextField`](./furo-text-field.md) for the shared `parcel` mental model.
|
|
23
|
+
|
|
24
|
+
### Abstraction boundary
|
|
25
|
+
|
|
26
|
+
| Layer | Responsibility |
|
|
27
|
+
| ----- | -------------- |
|
|
28
|
+
| Application | `parcel`, listeners, slots; controls `parcel.value` / `parcel.indeterminate` |
|
|
29
|
+
| Atom (`FuroCheckbox`) | Maps parcel → the underlying checkbox primitive; DOM, emits, default indicator |
|
|
30
|
+
| Underlying primitive (maintainers) | Internal only; not part of the application API |
|
|
31
|
+
|
|
32
|
+
**Rules:**
|
|
33
|
+
|
|
34
|
+
1. Documented `parcel` keys are the **Furo contract**. Furo-owned keys are stripped in `extractRootAttributes()` before optional passthrough to the underlying primitive.
|
|
35
|
+
2. Do **not** import the underlying checkbox primitive in application code.
|
|
36
|
+
3. Use `parcel.indeterminate` (or `v-model:indeterminate`) for the [-] state — not `null` or ad-hoc DOM attributes on `value`.
|
|
37
|
+
|
|
38
|
+
**Source files:**
|
|
39
|
+
|
|
40
|
+
- `lib/components/atoms/FuroCheckbox/FuroCheckbox.vue`
|
|
41
|
+
- `lib/components/atoms/FuroCheckbox/FuroCheckboxContext.js`
|
|
42
|
+
- `lib/components/atoms/FuroCheckbox/CheckboxEmitPayload.js`
|
|
43
|
+
|
|
44
|
+
**Live samples:** `pages/samples/components/playground/FuroCheckboxPlayground/index.vue`
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Public API at a glance
|
|
49
|
+
|
|
50
|
+
| Area | Status | Notes |
|
|
51
|
+
| ---- | ------ | ----- |
|
|
52
|
+
| **SFC surface** | | |
|
|
53
|
+
| Public prop `parcel` only (+ attribute fallthrough) | **Implemented** | See [`FuroCheckboxParcel`](#furocheckboxparcel) |
|
|
54
|
+
| **Value & state** | | |
|
|
55
|
+
| Controlled `parcel.value` + `v-model:value` | **Implemented** | `Object.hasOwn(parcel, 'value')` |
|
|
56
|
+
| Uncontrolled (no `parcel.value`) | **Implemented** | Internal ref starts `false`; parent toggle without `value` key |
|
|
57
|
+
| Indeterminate (`parcel.indeterminate` + `v-model:indeterminate`) | **Implemented** | Separate boolean channel; default minus/check icons |
|
|
58
|
+
| `invalid`, `disabled`, `required` | **Implemented** | `.invalid` class + `aria-invalid` / `disabled` / `required` on the checkbox root |
|
|
59
|
+
| `parcel.size` | **Implemented** | CSS length (e.g. `1rem`, `1.5rem`) via `--furo-checkbox-size` |
|
|
60
|
+
| **Emits** | | |
|
|
61
|
+
| `change-value`, `update:value`, `update:indeterminate` | **Implemented** | No `commit-value` (no commit gesture) |
|
|
62
|
+
| **Slots** | | |
|
|
63
|
+
| `indicator`, `indeterminate-indicator` | **Implemented** | No built-in label — use external `<label>` |
|
|
64
|
+
| Built-in `label` slot | **Not implemented** | Label must live outside `<FuroCheckbox>` |
|
|
65
|
+
| **Spec debt (not in runtime)** | | |
|
|
66
|
+
| Checkbox group fieldset API | **Not implemented** | v1 single checkbox only |
|
|
67
|
+
| Closed `parcel` (reject unknown primitive keys) | **Not implemented** | Extra keys may pass via `extractRootAttributes()` |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Props
|
|
72
|
+
|
|
73
|
+
### Rule: `parcel` vs attributes
|
|
74
|
+
|
|
75
|
+
| Mechanism | When to use | Application sees |
|
|
76
|
+
| --------- | ----------- | ---------------- |
|
|
77
|
+
| `:parcel` | Furo checkbox behavior | `FuroCheckboxParcel` (+ merged defaults) |
|
|
78
|
+
| Attributes on `<FuroCheckbox>` | e.g. `name`, `id` | Vue fallthrough to the checkbox root (merged with `extractRootAttributes()`) |
|
|
79
|
+
|
|
80
|
+
> **`parcel` must be JSON-serializable** — no functions inside `parcel`.
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<div class="unit-checkbox-field">
|
|
84
|
+
<FuroCheckbox
|
|
85
|
+
id="terms"
|
|
86
|
+
v-model:value="context.termsAcceptedRef.value"
|
|
87
|
+
:parcel="{
|
|
88
|
+
value: context.termsAcceptedRef.value,
|
|
89
|
+
invalid: context.statusReactive.isInvalidTerms,
|
|
90
|
+
}"
|
|
91
|
+
name="terms"
|
|
92
|
+
@change-value="context.onChangeTerms({ payload: $event })"
|
|
93
|
+
/>
|
|
94
|
+
<label for="terms">
|
|
95
|
+
I accept the terms
|
|
96
|
+
</label>
|
|
97
|
+
</div>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Component props (public)
|
|
101
|
+
|
|
102
|
+
| Prop | Type | Default | Description |
|
|
103
|
+
| ---- | ---- | ------- | ----------- |
|
|
104
|
+
| `parcel` | `FuroCheckboxParcel` or `null` | `null` | Furo behavior (see table below) |
|
|
105
|
+
|
|
106
|
+
### `FuroCheckboxValue`
|
|
107
|
+
|
|
108
|
+
`boolean` — checked state only (`true` / `false`). Indeterminate UI uses `parcel.indeterminate` (see below).
|
|
109
|
+
|
|
110
|
+
### `FuroCheckboxParcel`
|
|
111
|
+
|
|
112
|
+
Keys read in `FuroCheckboxContext` (defaults from `parcel` getter):
|
|
113
|
+
|
|
114
|
+
| Key | Type | Default (getter) | Description |
|
|
115
|
+
| --- | ---- | ---------------- | ----------- |
|
|
116
|
+
| `value` | `FuroCheckboxValue` | — | Controlled checked state. Presence of this key (`Object.hasOwn`) enables controlled mode. |
|
|
117
|
+
| `indeterminate` | `boolean` | `false` | When `true`, shows [-] indicator; `value` still reflects checked boolean (often `false` for partial “select all”) |
|
|
118
|
+
| `invalid` | `boolean` | `false` | `.invalid` class + `aria-invalid` on the checkbox root |
|
|
119
|
+
| `disabled` | `boolean` | `false` | Disables interaction (also respects fallthrough `disabled`) |
|
|
120
|
+
| `required` | `boolean` | `false` | Forwarded to root (also respects fallthrough `required`) |
|
|
121
|
+
| `size` | `string` | `1.125rem` (`DEFAULT_CHECKBOX_SIZE`) | Box width/height; icon uses `8/9` of box (`DEFAULT_CHECKBOX_ICON_SIZE` = `1rem`) |
|
|
122
|
+
|
|
123
|
+
Furo-owned keys above are **not** forwarded via `extractRootAttributes()`.
|
|
124
|
+
|
|
125
|
+
#### Size
|
|
126
|
+
|
|
127
|
+
Pass any valid CSS length on `parcel.size` (`rem`, `px`, or `em`). The root sets `--furo-checkbox-size` and `--furo-checkbox-icon-size` (box × `8/9`). Default icons receive matching `Icon` `size` via `extractCheckboxIconSize()`.
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<FuroCheckbox
|
|
131
|
+
:parcel="{
|
|
132
|
+
value: context.flagRef.value,
|
|
133
|
+
size: '1.5rem',
|
|
134
|
+
}"
|
|
135
|
+
/>
|
|
136
|
+
``` Other JSON-serializable keys on `parcel` may pass through to the underlying primitive (unsupported for apps).
|
|
137
|
+
|
|
138
|
+
#### Indeterminate state
|
|
139
|
+
|
|
140
|
+
| Mechanism | Behavior |
|
|
141
|
+
| --------- | -------- |
|
|
142
|
+
| `parcel.indeterminate: true` | the underlying primitive's internal model is indeterminate; `parcel.value` stays boolean |
|
|
143
|
+
| `v-model:indeterminate` | Two-way sync for `parcel.indeterminate` on user toggle |
|
|
144
|
+
| `update:indeterminate` | Emits `boolean` on each toggle (with `update:value`) |
|
|
145
|
+
| Default `#indeterminate-indicator` / `#indicator` slots | `ph:minus-bold` when indeterminate; `ph:check-bold` when checked and not indeterminate |
|
|
146
|
+
|
|
147
|
+
Toggling from indeterminate follows the underlying checkbox primitive's rules (typically moves to checked / unchecked). After the user clicks, clear `parcel.indeterminate` when the parent adopts a definite selection (`update:indeterminate` emits `false`).
|
|
148
|
+
|
|
149
|
+
> **Maintainers:** the underlying primitive uses an internal `'indeterminate'` sentinel (`FuroCheckboxContext.INDETERMINATE_VALUE`). Applications must not put that string in `parcel.value` or compare `v-model:value` to it.
|
|
150
|
+
|
|
151
|
+
#### Migration (breaking)
|
|
152
|
+
|
|
153
|
+
| Before | After |
|
|
154
|
+
| ------ | ----- |
|
|
155
|
+
| `v-model:value === 'indeterminate'` | `v-model:indeterminate === true` (keep `value` boolean, often `false`) |
|
|
156
|
+
| `parcel.value: 'indeterminate'` | `parcel: { value: false, indeterminate: true }` |
|
|
157
|
+
| `payload.value === 'indeterminate'` | `payload.isIndeterminate()` or `payload.indeterminate` |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Controlled & uncontrolled
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<!-- Controlled -->
|
|
165
|
+
<FuroCheckbox
|
|
166
|
+
v-model:value="context.flagRef.value"
|
|
167
|
+
:parcel="{ value: context.flagRef.value }"
|
|
168
|
+
/>
|
|
169
|
+
|
|
170
|
+
<!-- Uncontrolled (starts unchecked) -->
|
|
171
|
+
<FuroCheckbox :parcel="{}" />
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| Mode | Detection | Sync |
|
|
175
|
+
| ---- | --------- | ---- |
|
|
176
|
+
| Controlled | `'value' in parcel` (including `value: false`) | `watch` on `resolveSyncedValue()` updates `internalValueRef` |
|
|
177
|
+
| Uncontrolled | no `value` key on `parcel` | Starts `false`; user toggles update internal ref only |
|
|
178
|
+
| Fallthrough `value` attr | Used when controlled via attrs only | Same as `FuroTextField` fallback in `resolveSyncedValue()` |
|
|
179
|
+
|
|
180
|
+
### State matrix
|
|
181
|
+
|
|
182
|
+
| State | Source |
|
|
183
|
+
| ----- | ------ |
|
|
184
|
+
| Underlying primitive `model-value` | `internalValueRef` (may use internal indeterminate sentinel) |
|
|
185
|
+
| Checked (boolean) | `parcel.value` / `v-model:value` |
|
|
186
|
+
| Uncontrolled seed | `false` |
|
|
187
|
+
| Indeterminate UI | `parcel.indeterminate` / `v-model:indeterminate` |
|
|
188
|
+
| Invalid UI | `parcel.invalid` |
|
|
189
|
+
| Disabled | `parcel.disabled` or fallthrough `disabled` |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Emits
|
|
194
|
+
|
|
195
|
+
| Event | Payload | When | Implemented |
|
|
196
|
+
| ----- | ------- | ---- | ----------- |
|
|
197
|
+
| `change-value` | `CheckboxEmitPayload` | Each primitive's model update | Yes |
|
|
198
|
+
| `update:value` | `FuroCheckboxValue` | Checked boolean after toggle (`v-model:value`) | Yes |
|
|
199
|
+
| `update:indeterminate` | `boolean` | Indeterminate flag after toggle (`v-model:indeterminate`) | Yes |
|
|
200
|
+
|
|
201
|
+
### Parent handler style
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
onChangeTerms ({
|
|
205
|
+
payload,
|
|
206
|
+
}) {
|
|
207
|
+
this.statusReactive.isInvalidTerms = payload.isUnchecked()
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
@change-value="context.onChangeTerms({ payload: $event })"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## `CheckboxEmitPayload` API
|
|
218
|
+
|
|
219
|
+
`CheckboxEmitPayload` extends [`BaseEmitPayload`](../lib/emit-payload/BaseEmitPayload.js). Built from `CustomEvent` `detail` on toggle.
|
|
220
|
+
|
|
221
|
+
| Member | Type | Description |
|
|
222
|
+
| ------ | ---- | ----------- |
|
|
223
|
+
| `value` | `FuroCheckboxValue \| null` | Checked state after change |
|
|
224
|
+
| `indeterminate` | `boolean` | Indeterminate flag after change |
|
|
225
|
+
| `controlElement` | `HTMLElement \| null` | Checkbox root element when ref assigned |
|
|
226
|
+
| `isIndeterminate()` | `boolean` | Same as `indeterminate` |
|
|
227
|
+
| `isChecked()` | `boolean \| null` | `true` when `value` is boolean `true`; `null` when `value` is not a boolean |
|
|
228
|
+
| `isUnchecked()` | `boolean \| null` | `true` when `value` is boolean `false`; `null` when `value` is not a boolean |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## `v-model` and state handling
|
|
233
|
+
|
|
234
|
+
```html
|
|
235
|
+
<FuroCheckbox
|
|
236
|
+
v-model:value="context.flagRef.value"
|
|
237
|
+
:parcel="{
|
|
238
|
+
value: context.flagRef.value,
|
|
239
|
+
}"
|
|
240
|
+
/>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
| Binding | Implemented | Notes |
|
|
244
|
+
| ------- | ----------- | ----- |
|
|
245
|
+
| `v-model:value` | **Yes** | Emits `true` / `false` only |
|
|
246
|
+
| `v-model:indeterminate` | **Yes** | Emits `true` / `false` for [-] UI |
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Slots
|
|
251
|
+
|
|
252
|
+
| Slot | Scoped props | Default |
|
|
253
|
+
| ---- | ------------ | ------- |
|
|
254
|
+
| `indicator` | — | `ph:check-bold` when `value === true` |
|
|
255
|
+
| `indeterminate-indicator` | — | `ph:minus-bold` when `parcel.indeterminate` / indeterminate UI |
|
|
256
|
+
|
|
257
|
+
The checkbox root and indicator are not slot-replaceable.
|
|
258
|
+
|
|
259
|
+
There is **no** `label` slot. Associate copy with an external `<label for="…">` pointing at the checkbox `id` (fallthrough attribute on the checkbox root).
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Internal implementation (maintainers only)
|
|
264
|
+
|
|
265
|
+
### Template composition
|
|
266
|
+
|
|
267
|
+
```text
|
|
268
|
+
checkbox root .unit-form-control.checkbox (model-value, aria-*, id for external label)
|
|
269
|
+
checkbox indicator
|
|
270
|
+
[#indeterminate-indicator] | [#indicator]
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Primitive mapping
|
|
274
|
+
|
|
275
|
+
| Furo | Underlying primitive |
|
|
276
|
+
| ---- | -------------------- |
|
|
277
|
+
| `internalValueRef` | `model-value` / `@update:model-value` |
|
|
278
|
+
| `parcel.indeterminate` | internal indeterminate sentinel (not public `value`) |
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Accessibility requirements
|
|
283
|
+
|
|
284
|
+
- Provide an external `<label for="control-id">` (or wrap the atom in `<label>`) — set matching `id` on `<FuroCheckbox>`.
|
|
285
|
+
- `aria-invalid` reflects `parcel.invalid`.
|
|
286
|
+
- Checkbox roles and keyboard support are delegated to the underlying primitive ([checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/)).
|
|
287
|
+
- Set `name` on the component for forms.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Styling convention
|
|
292
|
+
|
|
293
|
+
Styles in SFC `@layer furo`:
|
|
294
|
+
|
|
295
|
+
- Root: the checkbox root `.unit-form-control.checkbox`
|
|
296
|
+
- Parts: `.indicator`, `.indicator-icon`
|
|
297
|
+
- Size: `--furo-checkbox-size`, `--furo-checkbox-icon-size` (from `parcel.size`, default box `1.125rem` / icon `1rem`)
|
|
298
|
+
- Tokens: `--color-input`, `--color-background`, `--color-primary`, `--color-destructive`
|
|
299
|
+
|
|
300
|
+
Use `data-state` / `data-disabled` from the underlying primitive for state styling.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Examples
|
|
305
|
+
|
|
306
|
+
### Basic controlled
|
|
307
|
+
|
|
308
|
+
```html
|
|
309
|
+
<div class="unit-checkbox-field">
|
|
310
|
+
<FuroCheckbox
|
|
311
|
+
id="accepted"
|
|
312
|
+
v-model:value="context.acceptedRef.value"
|
|
313
|
+
:parcel="{
|
|
314
|
+
value: context.acceptedRef.value,
|
|
315
|
+
}"
|
|
316
|
+
name="accepted"
|
|
317
|
+
@change-value="context.onChangeAccepted({ payload: $event })"
|
|
318
|
+
/>
|
|
319
|
+
<label for="accepted">
|
|
320
|
+
Accept policy
|
|
321
|
+
</label>
|
|
322
|
+
</div>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Indeterminate (parent-driven)
|
|
326
|
+
|
|
327
|
+
```html
|
|
328
|
+
<div class="unit-checkbox-field">
|
|
329
|
+
<FuroCheckbox
|
|
330
|
+
id="select-all"
|
|
331
|
+
v-model:value="context.selectAllStateReactive.value"
|
|
332
|
+
v-model:indeterminate="context.selectAllStateReactive.isIndeterminate"
|
|
333
|
+
:parcel="{
|
|
334
|
+
value: context.selectAllStateReactive.value,
|
|
335
|
+
indeterminate: context.selectAllStateReactive.isIndeterminate,
|
|
336
|
+
}"
|
|
337
|
+
name="select-all"
|
|
338
|
+
/>
|
|
339
|
+
<label for="select-all">
|
|
340
|
+
Select all
|
|
341
|
+
</label>
|
|
342
|
+
</div>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Invalid
|
|
346
|
+
|
|
347
|
+
```html
|
|
348
|
+
<FuroCheckbox
|
|
349
|
+
:parcel="{
|
|
350
|
+
value: false,
|
|
351
|
+
invalid: context.statusReactive.isInvalidTerms,
|
|
352
|
+
}"
|
|
353
|
+
name="terms"
|
|
354
|
+
/>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Implementation status
|
|
360
|
+
|
|
361
|
+
| Deliverable | Status |
|
|
362
|
+
| ----------- | ------ |
|
|
363
|
+
| `FuroCheckbox.vue` + context + payload | Shipped |
|
|
364
|
+
| Public prop: `parcel` only | Shipped |
|
|
365
|
+
| Indeterminate + boolean model + controlled/uncontrolled | Shipped |
|
|
366
|
+
| Playground `FuroCheckboxPlayground` | Shipped |
|
|
367
|
+
| Unit tests `FuroCheckboxContext.js`, `CheckboxEmitPayload.js` (jsdom) | Shipped |
|
|
368
|
+
| Checkbox group | Pending |
|
|
369
|
+
| Closed `parcel` | Pending |
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Future / spec debt
|
|
374
|
+
|
|
375
|
+
- Checkbox group API for related checkbox groups
|
|
376
|
+
- `commit-value` (not applicable unless blur/commit semantics added)
|
|
377
|
+
- Strip unknown keys on `v-bind` to the underlying primitive
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Known gaps and follow-ups
|
|
382
|
+
|
|
383
|
+
- **Indeterminate toggle:** after user clicks, the underlying primitive decides next state; bind `v-model:indeterminate` or clear `parcel.indeterminate` when adopting a definite selection.
|
|
384
|
+
- **Group semantics:** multiple related checkboxes need separate instances or future group API.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# FuroCollapsible
|
|
2
|
+
|
|
3
|
+
Single expand/collapse region. A trigger toggles the visibility of one associated content block. Built on a headless collapsible primitive that manages the open state, ARIA wiring, and the measured content height for smooth animation.
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Layer** | Molecule |
|
|
10
|
+
| **Path** | `lib/components/molecules/FuroCollapsible/` |
|
|
11
|
+
| **Base primitive** | Headless collapsible (`Root` + `Trigger` + `Content`) |
|
|
12
|
+
| **Composes** | Nothing — one trigger + one content region |
|
|
13
|
+
| **Model** | `value` (open boolean) via `v-model:value` |
|
|
14
|
+
| **Emits** | `change-value`, `update:value` |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Public API
|
|
19
|
+
|
|
20
|
+
### Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| `parcel` | `FuroCollapsibleParcel \| null` | `null` | Reactive data object holding the open value and disabled flag. |
|
|
25
|
+
|
|
26
|
+
### Parcel keys
|
|
27
|
+
|
|
28
|
+
| Key | Type | Default | Description |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| `value` | `boolean` | unset (`false`) | Open state. When present, the component is **controlled** — the parent owns the value. When absent, the component manages its own state (**uncontrolled**). |
|
|
31
|
+
| `disabled` | `boolean` | `false` | Disables the trigger; the region stays in its current state. |
|
|
32
|
+
|
|
33
|
+
### Emits
|
|
34
|
+
|
|
35
|
+
| Event | Payload | Description |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `change-value` | `CollapsibleEmitPayload` | Fired when the open state changes. `payload.value` is the new boolean; `payload.isOpen()` is a convenience predicate. |
|
|
38
|
+
| `update:value` | `boolean` | `v-model:value` sync of the open state. |
|
|
39
|
+
|
|
40
|
+
### Slots
|
|
41
|
+
|
|
42
|
+
| Slot | Slot props | Description |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `trigger` | `{ open: boolean }` | Trigger content (label + optional indicator). Rendered inside the toggle button. |
|
|
45
|
+
| default | `{ open: boolean }` | The collapsible content revealed when open. |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Behavior
|
|
50
|
+
|
|
51
|
+
| Concern | Behavior |
|
|
52
|
+
|---|---|
|
|
53
|
+
| **Controlled vs uncontrolled** | `isControlled()` returns true when `parcel` owns a `value` key. A watcher syncs the controlled value into the internal open ref; uncontrolled instances start closed and track their own state. |
|
|
54
|
+
| **Value mapping** | The public `value` boolean maps to the primitive's own open prop in the template (`:open` / `@update:open`); the primitive's prop name is not exposed in the public API. |
|
|
55
|
+
| **Disabled** | `isDisabled()` reads `parcel.disabled` or a fallthrough `disabled` attribute. |
|
|
56
|
+
| **Animation** | The content wrapper animates between `0` and the primitive's measured `--reka-collapsible-content-height` for a smooth expand/collapse. |
|
|
57
|
+
| **Passthrough** | Non-Furo parcel keys and extra attributes fall through to the root (`inheritAttrs: false` + `v-bind="$attrs"`). |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```vue
|
|
64
|
+
<!-- Uncontrolled -->
|
|
65
|
+
<FuroCollapsible>
|
|
66
|
+
<template #trigger="{ open }">
|
|
67
|
+
<span>What is furo-vue?</span>
|
|
68
|
+
<Icon :name="open ? 'ph:caret-up' : 'ph:caret-down'" />
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<p>furo-vue is a domain-agnostic Vue component library.</p>
|
|
72
|
+
</FuroCollapsible>
|
|
73
|
+
|
|
74
|
+
<!-- Controlled -->
|
|
75
|
+
<FuroCollapsible
|
|
76
|
+
:parcel="{ value: isOpen }"
|
|
77
|
+
@update:value="isOpen = $event"
|
|
78
|
+
>
|
|
79
|
+
<template #trigger>Release notes</template>
|
|
80
|
+
<p>...</p>
|
|
81
|
+
</FuroCollapsible>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Composition notes
|
|
87
|
+
|
|
88
|
+
- Imports nothing from the app — no GraphQL, PrimeVue, Vue Router, or Nuxt-specific imports.
|
|
89
|
+
- All logic lives in `FuroCollapsibleContext`: extract-only getters, the controlled/uncontrolled sync, and the `extractRootClassNames()` / `extractRootAttributes()` mappers. The component template only binds context methods.
|
|
90
|
+
- The public `value` key is value-oriented; the primitive's open prop name appears only in the template binding.
|
|
91
|
+
- CSS is in `@layer furo`, scoped, with `unit-collapsible` root. Dimensions use `rem`; `gap` uses `--size-space-*`; colors reference semantic tokens only.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Testing
|
|
96
|
+
|
|
97
|
+
- Context logic is fully covered by jsdom unit tests: `parcel` defaults, `isControlled()`, `resolveSyncedValue()`, `isDisabled()` and `isOpen()` (split into truthy / falsy describe blocks), `extractRootClassNames()`, `extractRootAttributes()` passthrough, the controlled/uncontrolled `setupComponent()` sync, and `onChangeValue()` emit behavior.
|
|
98
|
+
- No SFC mount test: behavior is declarative bindings over the tested context plus the headless primitive's own open handling, verified by manual QA in the playground.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Playground
|
|
103
|
+
|
|
104
|
+
`/samples/components/playground` → **Collapsible** tab. Demonstrates uncontrolled, controlled (with an outside toggle button), and disabled regions.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# FuroControlBlock
|
|
2
|
+
|
|
3
|
+
Presentational wrapper that frames a single form control with a **label** and **error messages**. The control is supplied through the default slot, so the block is domain-agnostic — it never knows which atom it wraps and emits nothing; the slotted control owns its own events. Intended as the per-field frame for the consumer-built ListView and DetailView forms (in `crm-kit-frontend`).
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Layer** | Molecule |
|
|
10
|
+
| **Path** | `lib/components/molecules/FuroControlBlock/` |
|
|
11
|
+
| **Composes** | Nothing — wraps an arbitrary control via the default slot |
|
|
12
|
+
| **Emits** | None (the slotted control bubbles its own events to the consumer) |
|
|
13
|
+
| **Orientation** | `vertical` (default) or `horizontal`, via a CSS modifier |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Public API
|
|
18
|
+
|
|
19
|
+
### Props
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Default | Description |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| `parcel` | `ControlBlockParcel \| null` | `null` | Reactive data object holding the label, control id, error messages, required flag, and orientation. |
|
|
24
|
+
|
|
25
|
+
### Parcel keys
|
|
26
|
+
|
|
27
|
+
| Key | Type | Default | Description |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `label` | `string \| null` | `null` | Label text. When `null`, no `<label>` is rendered. |
|
|
30
|
+
| `controlId` | `string \| null` | `null` | Id of the slotted control. Rendered as `<label for>` for a11y association; the consumer passes the same id to the control. When `null`, the label has no `for`. |
|
|
31
|
+
| `errorMessages` | `Array<string>` | `[]` | Error messages. A non-empty array marks the block invalid and renders one line per message. |
|
|
32
|
+
| `required` | `boolean` | `false` | When `true`, renders a required mark (`*`) after the label. |
|
|
33
|
+
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Layout of label vs control. `vertical` stacks; `horizontal` places the label beside the control. |
|
|
34
|
+
|
|
35
|
+
### Emits
|
|
36
|
+
|
|
37
|
+
None. The block is presentational; the control inside the slot emits its own `change-value` / `commit-value` / `update:value` events straight to the consumer.
|
|
38
|
+
|
|
39
|
+
### Slots
|
|
40
|
+
|
|
41
|
+
| Slot | Description |
|
|
42
|
+
|---|---|
|
|
43
|
+
| default | The control to frame (e.g. `FuroTextField`). Pass the same `id` as the parcel's `controlId` so `<label for>` resolves. |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Behavior
|
|
48
|
+
|
|
49
|
+
| Concern | Behavior |
|
|
50
|
+
|---|---|
|
|
51
|
+
| **Label** | Rendered only when `parcel.label` is set. Associated with the control via `<label :for="controlId">` when `controlId` is provided. |
|
|
52
|
+
| **Required mark** | The `*` carries `aria-hidden="true"` — it is a visual cue only; convey required state on the control itself. |
|
|
53
|
+
| **Invalid state** | When `errorMessages` is non-empty, the root gets the `invalid` modifier and the error list renders. |
|
|
54
|
+
| **Error region** | Rendered as a `<ul role="alert">`, one `<li>` per message. |
|
|
55
|
+
| **Orientation** | `parcel.orientation` maps to the `vertical` / `horizontal` root class; no JS branching. |
|
|
56
|
+
| **Fallthrough class / style** | A consumer-supplied `class` and `style` compose onto the block's root element, so a layout selector like `.control.name { flex: 1 }` reaches the wrapper. The block uses `inheritAttrs: false` and explicitly composes `$attrs.class` / `$attrs.style` onto the root. |
|
|
57
|
+
|
|
58
|
+
> **a11y note (deliberate):** the error region carries `role="alert"` only. The block does **not** auto-wire `aria-describedby` onto the slotted control, because it cannot set attributes on a slot child. Consumers that need the stronger association wire it themselves. Upgrade path: expose an `errorId` getter for consumers to bind.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
```vue
|
|
65
|
+
<FuroControlBlock
|
|
66
|
+
:parcel="{
|
|
67
|
+
label: 'Email address',
|
|
68
|
+
controlId: 'field-email',
|
|
69
|
+
required: true,
|
|
70
|
+
errorMessages: emailErrors,
|
|
71
|
+
orientation: 'vertical',
|
|
72
|
+
}"
|
|
73
|
+
>
|
|
74
|
+
<FuroTextField
|
|
75
|
+
id="field-email"
|
|
76
|
+
:parcel="{ value: email }"
|
|
77
|
+
@update:value="onEmailChange({ value: $event })"
|
|
78
|
+
/>
|
|
79
|
+
</FuroControlBlock>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Composition notes
|
|
85
|
+
|
|
86
|
+
- The block imports nothing — no atoms, no GraphQL, PrimeVue, Vue Router, or Nuxt-specific imports. It is a pure slot wrapper.
|
|
87
|
+
- All logic lives in `FuroControlBlockContext` as extract-only getters (`label`, `controlId`, `errorMessages`, `orientation`) and two boolean methods (`isInvalid()`, `isRequired()`).
|
|
88
|
+
- CSS is wrapped in `@layer furo`; `gap` uses `--size-space-*` tokens and all other dimensions use `rem`. Only semantic tokens are referenced (`--color-foreground`, `--color-destructive-text`, `--font-label-small`, `--font-body-small`). The label and the error text each carry a composite text style through the `font` shorthand per [ADR 0032](./adr/0032-composite-text-style-tokens-via-font-shorthand.md), so the label renders at weight `500` and no longer inherits the host page's typography.
|
|
89
|
+
- `inheritAttrs: false`; a consumer-supplied `class` / `style` is composed onto the block root (via `$attrs.class` / `$attrs.style`), so a parent layout selector such as `.control.name { flex: 1 }` reaches the wrapper.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Testing
|
|
94
|
+
|
|
95
|
+
- Context logic (every getter and both boolean methods) is fully covered by jsdom unit tests.
|
|
96
|
+
- No SFC mount test: this repo has no jsdom mount precedent for molecules, and the block's behavior is entirely declarative bindings over the tested context. The rendered output (label show/hide, required mark, error list, orientation class) is verified by **manual QA in the playground**.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Playground
|
|
101
|
+
|
|
102
|
+
`/samples/components/playground` → **Control Block** tab. Demonstrates labeled (vertical), required, invalid (multiple error messages), and horizontal layouts wrapping `FuroTextField`.
|