@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.
Files changed (119) 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 +24 -22
  73. package/public/furo-vue/components.json +1 -1
  74. package/types/generated/components/atoms/FuroAvatar/FuroAvatar.vue.d.ts +6 -15
  75. package/types/generated/components/atoms/FuroBadge/FuroBadge.vue.d.ts +8 -43
  76. package/types/generated/components/atoms/FuroButton/FuroButton.vue.d.ts +10 -45
  77. package/types/generated/components/atoms/FuroButton/FuroButtonContext.d.ts +4 -4
  78. package/types/generated/components/atoms/FuroCheckbox/FuroCheckbox.vue.d.ts +8 -20
  79. package/types/generated/components/atoms/FuroNumberField/FuroNumberField.vue.d.ts +12 -12
  80. package/types/generated/components/atoms/FuroRadio/FuroRadio.vue.d.ts +8 -24
  81. package/types/generated/components/atoms/FuroSwitch/FuroSwitch.vue.d.ts +0 -13
  82. package/types/generated/components/atoms/FuroToggle/FuroToggle.vue.d.ts +8 -19
  83. package/types/generated/components/molecules/FuroAccordion/FuroAccordion.vue.d.ts +14 -30
  84. package/types/generated/components/molecules/FuroAttachment/FuroAttachment.vue.d.ts +8 -44
  85. package/types/generated/components/molecules/FuroAutocompleteField/FuroAutocompleteField.vue.d.ts +74 -105
  86. package/types/generated/components/molecules/FuroAvatarGroup/FuroAvatarGroup.vue.d.ts +9 -18
  87. package/types/generated/components/molecules/FuroBreadcrumb/FuroBreadcrumb.vue.d.ts +22 -48
  88. package/types/generated/components/molecules/FuroCollapsible/FuroCollapsible.vue.d.ts +12 -23
  89. package/types/generated/components/molecules/FuroControlBlock/FuroControlBlock.vue.d.ts +6 -4
  90. package/types/generated/components/molecules/FuroDatePicker/FuroDatePicker.vue.d.ts +14 -16
  91. package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenu.vue.d.ts +13 -13
  92. package/types/generated/components/molecules/FuroDropdownMenu/FuroDropdownMenuOptionRows.vue.d.ts +34 -47
  93. package/types/generated/components/molecules/FuroEditableField/FuroEditableField.vue.d.ts +93 -115
  94. package/types/generated/components/molecules/FuroEmptyState/FuroEmptyState.vue.d.ts +8 -7
  95. package/types/generated/components/molecules/FuroErrorState/FuroErrorState.vue.d.ts +8 -7
  96. package/types/generated/components/molecules/FuroPagination/FuroPagination.vue.d.ts +14 -15
  97. package/types/generated/components/molecules/FuroPopover/FuroPopover.vue.d.ts +8 -7
  98. package/types/generated/components/molecules/FuroScrollArea/FuroScrollArea.vue.d.ts +6 -4
  99. package/types/generated/components/molecules/FuroSearchField/FuroSearchField.vue.d.ts +8 -22
  100. package/types/generated/components/molecules/FuroSectionHeader/FuroSectionHeader.vue.d.ts +10 -25
  101. package/types/generated/components/molecules/FuroSelect/FuroSelect.vue.d.ts +97 -108
  102. package/types/generated/components/molecules/FuroSelect/FuroSelectContext.d.ts +14 -0
  103. package/types/generated/components/molecules/FuroSelect/FuroSelectOptionText.vue.d.ts +52 -0
  104. package/types/generated/components/molecules/FuroSelect/FuroSelectOptionTextContext.d.ts +141 -0
  105. package/types/generated/components/molecules/FuroSplitter/FuroSplitter.vue.d.ts +14 -29
  106. package/types/generated/components/molecules/FuroTimeField/FuroTimeField.vue.d.ts +8 -7
  107. package/types/generated/components/molecules/FuroToggleGroup/FuroToggleGroup.vue.d.ts +9 -24
  108. package/types/generated/components/molecules/FuroToolBar/FuroToolBar.vue.d.ts +6 -9
  109. package/types/generated/components/molecules/FuroTooltip/FuroTooltip.vue.d.ts +8 -7
  110. package/types/generated/components/organisms/FuroAlertDialog/FuroAlertDialog.vue.d.ts +17 -19
  111. package/types/generated/components/organisms/FuroDialog/FuroDialog.vue.d.ts +20 -24
  112. package/types/generated/components/organisms/FuroDrawer/FuroDrawer.vue.d.ts +20 -24
  113. package/types/generated/components/organisms/FuroEditor/FuroEditor.vue.d.ts +9 -42
  114. package/types/generated/components/organisms/FuroSidebar/FuroSidebar.vue.d.ts +23 -62
  115. package/types/generated/components/organisms/FuroStepper/FuroStepper.vue.d.ts +19 -19
  116. package/types/generated/components/organisms/FuroTable/FuroTable.vue.d.ts +54 -71
  117. package/types/generated/components/organisms/FuroTable/FuroTableContext.d.ts +2 -2
  118. package/types/generated/components/organisms/FuroTabs/FuroTabs.vue.d.ts +17 -17
  119. package/types/generated/components/organisms/FuroToast/FuroToast.vue.d.ts +25 -31
@@ -0,0 +1,178 @@
1
+ # `<FuroSidebar>` Interface Specification
2
+
3
+ ## Overview
4
+
5
+ `<FuroSidebar>` is a **shipped** navigation organism in the Furo-Vue component system. It is the rail an application navigates from: a column of grouped items, each optionally disclosing a nested run of its own, with a search entry above them and a collapsed form that narrows to the icon boxes.
6
+
7
+ Navigation items arrive as data. The component never knows what an item means, only that it has a key, a label and possibly children, so the same rail serves a catalogue, a console or a document store as readily as a customer list.
8
+
9
+ **Public SFC prop:** `parcel` only. Every other HTML attribute lands on the `nav` element.
10
+
11
+ ## Summary
12
+
13
+ | Field | Value |
14
+ |---|---|
15
+ | **Layer** | Organism |
16
+ | **Path** | `lib/components/organisms/FuroSidebar/` |
17
+ | **Base primitive** | None; a `nav` holding lists of buttons |
18
+ | **Emits** | `navigate`, `update:activeKey`, `expand-change`, `update:expandedKeys`, `search-change`, `search-commit`, `update:searchValue` |
19
+ | **Composes** | `FuroSearchField` for the search entry, `FuroIcon` for item icons and the disclosure caret |
20
+
21
+ ## What this is not
22
+
23
+ The design uses the word sidebar for two different surfaces: this navigation rail on the left of every screen, and a panel on the right of the detail view holding a record's comments and mail. Only the first is here. The second carries the meaning of a record and stays in the consuming application.
24
+
25
+ `FuroOffCanvasMenuLayout` is untouched by this component. It is excluded from the published package and is a layout, not a navigation surface.
26
+
27
+ ## `parcel` keys
28
+
29
+ | Key | Type | Default | Meaning |
30
+ |---|---|---|---|
31
+ | `groups` | `Array<FuroSidebarGroup>` | `[]` | Runs of items, each with an optional heading. |
32
+ | `activeKey` | `string \| null` | — | The key the application currently shows. Absent by default. |
33
+ | `expandedKeys` | `Array<string> \| null` | — | The keys whose sub-items are revealed. Absent by default. |
34
+ | `collapsed` | `boolean` | `false` | Narrows the rail to its icon boxes. |
35
+ | `searchable` | `boolean` | `false` | Renders the search entry above the groups. |
36
+ | `searchValue` | `string \| null` | `null` | The text the search entry holds. |
37
+ | `searchPlaceholder` | `string \| null` | `null` | Placeholder of the search entry. |
38
+ | `searchLabel` | `string` | `'Search'` | Accessible name of the search entry. |
39
+ | `label` | `string` | `'Sidebar'` | Accessible name of the navigation landmark. |
40
+
41
+ ### Types
42
+
43
+ ```text
44
+ FuroSidebarGroup { key: string, label?: string | null, items?: Array<FuroSidebarItem> }
45
+ FuroSidebarItem { key: string, label: string, icon?: string | null, disabled?: boolean, items?: Array<FuroSidebarSubItem> }
46
+ FuroSidebarSubItem { key: string, label: string, disabled?: boolean }
47
+ ```
48
+
49
+ A group is how the design separates one run of items from the next: the first run carries no heading, the later ones do. A flat rail is therefore one group with no `label`.
50
+
51
+ ## Controlled and uncontrolled
52
+
53
+ `activeKey` and `expandedKeys` follow the same rule `FuroTabs` uses, and the presence of the key decides, not its value:
54
+
55
+ | Parcel | Meaning |
56
+ |---|---|
57
+ | Key absent | Uncontrolled. The rail holds the value internally, which is what makes the playground and a quick prototype work with no handler. |
58
+ | Key present with a value | Controlled. The rail draws what you pass and reports every change without moving on its own. |
59
+ | Key present and `null` | Controlled with nothing selected. The rail highlights no row and still moves nowhere on its own. |
60
+
61
+ Pass `activeKey: null` to say "I own this, and right now nothing is selected". A sentinel such as an empty string is never needed.
62
+
63
+ `collapsed` has no uncontrolled form, because the control that toggles it is drawn in the application header, not in the rail. The rail takes the state and never toggles itself.
64
+
65
+ ## Routing
66
+
67
+ The rail emits navigation intent and never imports a router. Two ways to wire it, and they are exclusive:
68
+
69
+ 1. **Listen to `navigate`.** The default item is a `button`, and choosing it emits `{ key, parentKey }`. The application maps the key to whatever it routes with. This is the default because a key is a value, so the rail stays serialisable and testable, and it does not force a router to exist.
70
+ 2. **Fill the `item` and `sub-item` slots.** The slot replaces the whole button, so an application that wants real links renders `RouterLink` or `NuxtLink` there and gets middle-click, hover preview and copy-link for free. The rail still supplies `entry`, `active`, `expanded` and `collapsed`, so the slot content can look identical to the default.
71
+
72
+ An item that declares `items` of its own is a disclosure: choosing it opens and closes its run and it does **not** emit `navigate`. The design gives such an item a caret rather than a destination. An item that is both a destination and a parent should carry its own destination as the first sub-item.
73
+
74
+ ## Events
75
+
76
+ | Event | Payload | When |
77
+ |---|---|---|
78
+ | `navigate` | `{ key, parentKey }` | An item without sub-items, or a sub-item, is chosen. `parentKey` is `null` for a primary item. |
79
+ | `update:activeKey` | `string` | Alongside `navigate`, which is what makes `v-model:active-key` work. |
80
+ | `expand-change` | `{ key, expanded }` | A disclosure opens or closes. |
81
+ | `update:expandedKeys` | `Array<string>` | Alongside `expand-change`, which is what makes `v-model:expanded-keys` work. |
82
+ | `search-change` | Field payload | Every keystroke in the search entry. |
83
+ | `search-commit` | Field payload | Blur and Enter in the search entry. |
84
+ | `update:searchValue` | `string \| null` | Alongside both search events. |
85
+
86
+ The search payload is the one `FuroSearchField` emits, so `payload.trimmedValue` and `payload.hasEnteredTrimmedText()` read the same as anywhere else.
87
+
88
+ ## Slots
89
+
90
+ | Slot | Scope | Purpose |
91
+ |---|---|---|
92
+ | `header` | — | Content above the search entry, for a product mark or an account switcher. |
93
+ | `search` | — | Replaces the default search control while keeping its place in the rail. |
94
+ | `item` | `{ entry, active, expanded, collapsed }` | Replaces the default primary button. |
95
+ | `sub-item` | `{ entry, parent, active }` | Replaces the default nested button. |
96
+ | `footer` | — | Content below the groups. |
97
+
98
+ The product mark is a slot rather than a parcel key because a mark is an image and a wordmark, not a value, and because a design system that shipped one would be shipping somebody's brand.
99
+
100
+ ## States
101
+
102
+ | State | What changes |
103
+ |---|---|
104
+ | Rest | Transparent row, title-tone label, icon in its own rounded box. |
105
+ | Hover | The row takes the muted surface. |
106
+ | Active | The row keeps the muted surface and carries `aria-current="page"`. |
107
+ | Disclosure closed | A caret pointing down; the sub-items are not rendered. |
108
+ | Disclosure open | The caret points up and the sub-items appear, connected by an elbow rule. |
109
+ | Nested sub-item | Indented past the icon column, drawn in the caption tone until it is active. |
110
+ | Disabled | The row dims and leaves the tab order. |
111
+ | Collapsed | The rail narrows to the icon boxes; labels, headings, carets, sub-items and the search entry are dropped. |
112
+
113
+ The search entry is dropped rather than squeezed when the rail collapses, because a collapsed rail is one icon box wide and no text control fits in it.
114
+
115
+ ## Styling
116
+
117
+ | Part | Class | Notes |
118
+ |---|---|---|
119
+ | Root | `.unit-sidebar` | Gains `.collapsed` |
120
+ | Header region | `.header` | Only rendered when the slot is filled |
121
+ | Search region | `.search-entry` | Holds `FuroSearchField` |
122
+ | Group | `.group` | One run of items |
123
+ | Group heading | `.group-label` | Also names the list it heads |
124
+ | Item list | `.items` | One `li.entry` per item |
125
+ | Primary row | `.row` | Gains `.active` and `.disclosure` |
126
+ | Icon box | `.row-icon` | The rounded square the design draws behind every glyph |
127
+ | Disclosure caret | `.caret` | |
128
+ | Sub-item list | `.sub-items` | One `li.sub-entry` per sub-item |
129
+ | Nested sub-row | `.sub-row` | Gains `.active` |
130
+ | Footer region | `.footer` | Only rendered when the slot is filled |
131
+
132
+ ## Read from the design
133
+
134
+ Design node: `4047:22441`, the `Panel_side_bar` instance in the `List_view [Admin_list]` frame.
135
+
136
+ | Part | Design value | Token |
137
+ |---|---|---|
138
+ | The rail | `#ffffff` | `--color-background` |
139
+ | Its trailing edge | `1px` `#e0e2e6` | `--size-thinnest`, `--color-border` |
140
+ | Its width | `256px` | `--size-sidebar-width` |
141
+ | Its width, collapsed | not drawn | `--size-sidebar-width-collapsed` |
142
+ | Its block padding | `12px` | `0.75rem` |
143
+ | Its inline padding | `16px` | `1rem` |
144
+ | Between groups | `12px` | `--size-space-compact` |
145
+ | Between rows | `4px` | `--size-space-tiny` |
146
+ | Group heading | `#8b93a2`, medium 12px/20 | `--color-foreground-secondary`, `--font-label-tiny` |
147
+ | Group heading padding | `8px` | `0.5rem` |
148
+ | Row corners | `10px` | `--size-border-radius-large` |
149
+ | Row padding | `4px`, `10px` at the caret edge | `0.25rem`, `0.625rem` |
150
+ | Between the icon box and the label | `12px` | `--size-space-compact` |
151
+ | Row surface, current | `#f1f2f4` | `--color-muted` |
152
+ | Row label | `#22252a`, medium 13.5px/20 | `--color-foreground-title`, `--font-label-small` |
153
+ | Icon box | `#ffffff`, corners `8px`, padding `6px` | `--color-background`, `--size-border-radius-medium`, `0.375rem` |
154
+ | Icon | `20px` | `1.25rem` |
155
+ | Caret | `14px` | `0.875rem` |
156
+ | Sub-item indent | `36px` | `2.25rem` |
157
+ | Sub-item padding | `8px` block, `12px` inline | `0.5rem`, `0.75rem` |
158
+ | Sub-item label | `#8b93a2`, medium 13.5px/20 | `--color-foreground-secondary`, `--font-label-small` |
159
+ | Elbow connector | `1px` `#e0e2e6`, `16px` wide from `20px` | `--size-thinnest`, `--color-border`, `1rem` |
160
+
161
+ Every colour above is the exact value the token already carries, so nothing is approximated: `#f1f2f4` is `--color-muted`, `#e0e2e6` is `--color-border`, `#22252a` is `--color-foreground-title` and `#8b93a2` is `--color-foreground-glyph`. The two text rows above read `--color-foreground-secondary` (`#646c7d`) instead, because `#8b93a2` is 3.09:1 on the page, under the 4.5:1 text floor. The two text styles are the composite tokens `--font-label-small` and `--font-label-tiny`, both carried through the `font` shorthand per [ADR 0032](./adr/0032-composite-text-style-tokens-via-font-shorthand.md).
162
+
163
+ Four notes on what the design does and does not settle:
164
+
165
+ - **The current row reads `#f1f2f4`, which is `--color-muted` rather than `--color-accent`.** `--color-accent` is the nominal role for a hover surface, but it carries `#e0e2e6`, one palette step darker than the design draws. The token that carries the drawn value wins, so hover and current both use `--color-muted`.
166
+ - **The elbow connector is drawn in CSS, not as an asset.** The design draws it as three vector strokes. They are rules rather than glyphs, so a border on a pseudo-element reproduces them exactly and keeps them on `--color-border` in both themes.
167
+ - **The collapsed width is the library's arithmetic, not a drawn value.** The design contains no collapsed navigation variant. The rail is `16px` of padding plus a `32px` icon box plus `16px` of padding, which is `64px`, and the file's icon-only rail (`4055:85083`) is drawn at exactly that width, though it is a different component. The value is the library's under [ADR 0041](./adr/0041-a-value-the-design-does-not-draw-is-the-librarys-to-choose.md) and moves by overriding `--size-sidebar-width-collapsed`. A drawn collapsed rail supersedes it.
168
+ - **The search entry's place in the rail is the library's ordering.** No node draws a search inside a navigation rail. The design system library carries the search control as `Search_bar`, a component set bound to no container, so the design makes it composable without saying where it goes. The rail puts it under the `header` slot and above the groups, which is the order [#223](https://github.com/openreachtech/furo-vue/issues/223) listed, and the `search` slot replaces the control while keeping that place.
169
+
170
+ The design's product mark is `20px` Roboto SemiBold on a `24px` line. No composite text token carries that pairing: `--font-headline-medium` is the nearest at `20px`, but on a `28px` line at weight `500`. The mark is a slot rather than component-drawn text, so the mismatch is the consuming application's to resolve and no token was invented for it.
171
+
172
+ ## Accessibility
173
+
174
+ The root is a `nav` landmark named by `parcel.label`, so the rail is reachable by landmark rather than by scrolling to it. Each run of items is a `ul`, named by its heading when the group carries one, and each row is a real `button` inside an `li`, so a disabled row leaves the tab order for free.
175
+
176
+ A disclosure row carries `aria-expanded` and keeps its sub-list inside the same `li`, which is the disclosure pattern a screen reader already knows. The row the application currently shows carries `aria-current="page"`.
177
+
178
+ A collapsed row keeps its accessible name through `aria-label`, and its pointer name through `title`, because the label is no longer drawn. An expanded row does neither, so the name is not announced twice.
@@ -0,0 +1,92 @@
1
+ # FuroSkeleton
2
+
3
+ Presentational loading placeholder shown while real content is fetching. A single styled box whose shape comes from the variant and whose size comes from optional width / height overrides. A shimmer animation signals the loading state. It carries no value and emits nothing.
4
+
5
+ ## Summary
6
+
7
+ | Field | Value |
8
+ |---|---|
9
+ | **Layer** | Molecule |
10
+ | **Path** | `lib/components/molecules/FuroSkeleton/` |
11
+ | **Base primitive** | None — a single styled box (no headless primitive needed) |
12
+ | **Composes** | Nothing |
13
+ | **Emits** | None |
14
+ | **Variants** | `text` (default), `circle`, `rectangle` |
15
+
16
+ ---
17
+
18
+ ## Public API
19
+
20
+ ### Props
21
+
22
+ | Prop | Type | Default | Description |
23
+ |---|---|---|---|
24
+ | `parcel` | `SkeletonParcel \| null` | `null` | Reactive data object holding the variant, size overrides, and animation flag. |
25
+
26
+ ### Parcel keys
27
+
28
+ | Key | Type | Default | Description |
29
+ |---|---|---|---|
30
+ | `variant` | `'text' \| 'circle' \| 'rectangle'` | `'text'` | Placeholder shape. `circle` renders a round shape; `text` and `rectangle` use a small radius. |
31
+ | `width` | `string` | variant default | CSS length overriding the width. Defaults to `100%` for `text` / `rectangle` and `2.5rem` for `circle`. |
32
+ | `height` | `string` | variant default | CSS length overriding the height. Defaults to `1rem` for `text` / `rectangle` and `2.5rem` for `circle`. |
33
+ | `animated` | `boolean` | `true` | When `true`, the placeholder shimmers. Reduced-motion users always see a static placeholder. |
34
+
35
+ ### Emits
36
+
37
+ None. The skeleton is presentational.
38
+
39
+ ### Slots
40
+
41
+ None.
42
+
43
+ ---
44
+
45
+ ## Behavior
46
+
47
+ | Concern | Behavior |
48
+ |---|---|
49
+ | **Variant** | `parcel.variant` maps to the `text` / `circle` / `rectangle` root class; no JS branching. |
50
+ | **Sizing** | `parcel.width` / `parcel.height` feed `--furo-skeleton-width` / `--furo-skeleton-height`. Null overrides are dropped so the CSS fallback keeps the variant default. |
51
+ | **Animation** | `isAnimated()` toggles the `animated` class. The shimmer is disabled under `prefers-reduced-motion: reduce`. |
52
+ | **Semantics** | The box sets `aria-hidden="true"`; announce the loading state on the surrounding region. |
53
+ | **Passthrough** | Extra attributes fall through to the box (`inheritAttrs: false` + `v-bind="$attrs"`). |
54
+
55
+ ---
56
+
57
+ ## Usage
58
+
59
+ ```vue
60
+ <!-- Text line placeholder -->
61
+ <FuroSkeleton :parcel="{ variant: 'text' }" />
62
+
63
+ <!-- Avatar circle placeholder -->
64
+ <FuroSkeleton :parcel="{ variant: 'circle', width: '3rem', height: '3rem' }" />
65
+
66
+ <!-- Card block placeholder -->
67
+ <FuroSkeleton :parcel="{ variant: 'rectangle', height: '8rem' }" />
68
+
69
+ <!-- Static placeholder -->
70
+ <FuroSkeleton :parcel="{ animated: false }" />
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Composition notes
76
+
77
+ - Imports nothing from the app — no GraphQL, PrimeVue, Vue Router, or Nuxt-specific imports.
78
+ - All logic lives in `FuroSkeletonContext` as extract-only getters (`variant`, `width`, `height`, `inlineStyle`) and one boolean method (`isAnimated()`).
79
+ - CSS is wrapped in `@layer furo`; dimensions use `rem`, and the box color references the semantic `--color-muted` token only. The shimmer is a highlight of `--color-background` sweeping horizontally across that box once every 1.8s; under `prefers-reduced-motion: reduce` the highlight is removed with the animation, so the box stays a flat `--color-muted`.
80
+
81
+ ---
82
+
83
+ ## Testing
84
+
85
+ - Context logic (`variant` / `width` / `height` / `inlineStyle` getters, `isAnimated()`) is fully covered by jsdom unit tests, with the boolean method split into truthy / falsy describe blocks.
86
+ - No SFC mount test: behavior is declarative bindings over the tested context. Rendered output (variant class, animation, size) is verified by manual QA in the playground.
87
+
88
+ ---
89
+
90
+ ## Playground
91
+
92
+ `/samples/components/playground` → **Skeleton** tab. Demonstrates text lines, an avatar + lines row, a card block, and a static (non-animated) placeholder.
@@ -0,0 +1,103 @@
1
+ # FuroSplitter
2
+
3
+ A resizable split view that lays its child panes out side by side (or stacked) with a draggable handle between each pair. Panes are declared through a `panels` array; each pane's content is supplied via the scoped `panel` slot.
4
+
5
+ ## Summary
6
+
7
+ | Field | Value |
8
+ |---|---|
9
+ | **Layer** | Molecule |
10
+ | **Path** | `lib/components/molecules/FuroSplitter/` |
11
+ | **Base primitive** | Headless resizable split-pane group (`Group` + `Panel` + `ResizeHandle`) |
12
+ | **Value** | Pane sizes (`Array<number>`, percent), surfaced outward via `update:value` |
13
+ | **Emits** | `change-value`, `update:value` |
14
+ | **Orientation** | `horizontal` (default) or `vertical` |
15
+
16
+ ## Public API
17
+
18
+ ### Props
19
+
20
+ | Prop | Type | Default | Description |
21
+ |---|---|---|---|
22
+ | `parcel` | `FuroSplitterParcel \| null` | `null` | Reactive configuration object. |
23
+
24
+ Any extra attributes on the parcel that are not Furo-owned keys (e.g. `autoSaveId`, `aria-label`) are forwarded to the underlying group element.
25
+
26
+ ### Parcel keys
27
+
28
+ | Key | Type | Default | Description |
29
+ |---|---|---|---|
30
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direction the panes are arranged in. |
31
+ | `appearance` | `'default' \| 'plain'` | `'default'` | `default` shows a divider line between panes; `plain` hides it so panes read as separated cards (the grip appears on hover / focus only). |
32
+ | `panels` | `Array<FuroSplitterPanel>` | `[]` | One descriptor per pane. A resize handle is inserted between consecutive panes. |
33
+
34
+ ### `FuroSplitterPanel`
35
+
36
+ | Key | Type | Default | Description |
37
+ |---|---|---|---|
38
+ | `id` | `string` | `pane-{index}` | Stable key; also used as the `panel` slot's `id` binding. |
39
+ | `defaultSize` | `number` | auto | Initial size, in `sizeUnit`. |
40
+ | `minSize` | `number` | `10` | Minimum size. |
41
+ | `maxSize` | `number` | `100` | Maximum size. |
42
+ | `collapsible` | `boolean` | `false` | Whether the pane collapses when dragged below `minSize`. |
43
+ | `collapsedSize` | `number` | `0` | Size when collapsed. |
44
+ | `sizeUnit` | `'%' \| 'px'` | `'%'` | Unit used to interpret the size values. |
45
+
46
+ ### Emits
47
+
48
+ | Event | Payload | When |
49
+ |---|---|---|
50
+ | `change-value` | `SplitterEmitPayload` | A handle is dragged and the layout changes. Use `payload.layout` (and `payload.extractPaneSize({ index })`). |
51
+ | `update:value` | `Array<number>` | Same moment as `change-value`; powers one-way `v-model:value` for persisting the layout. |
52
+
53
+ ### Slots
54
+
55
+ | Slot | Props | Description |
56
+ |---|---|---|
57
+ | `panel` | `{ panel, index, id }` | Content for each pane. Switch on `id` or `index` to render the right region. |
58
+ | `handle` | `{ index }` | Custom resize handle between panes. Defaults to a centered grip. |
59
+
60
+ ## Behavior
61
+
62
+ | Concern | Behavior |
63
+ |---|---|
64
+ | **Layout source** | The underlying primitive owns the live layout; FuroSplitter reflects it outward. There is no inbound controlled `value`. |
65
+ | **Persistence** | Forward `autoSaveId` through the parcel to let the primitive persist the arrangement in `localStorage`, or store the `update:value` layout yourself. |
66
+ | **Handles** | A handle is rendered between every consecutive pair of panes, never after the last pane. |
67
+
68
+ ## Usage
69
+
70
+ ```vue
71
+ <!-- Two panes, capture the layout -->
72
+ <FuroSplitter
73
+ :parcel="{
74
+ orientation: 'horizontal',
75
+ panels: [
76
+ { id: 'sidebar', defaultSize: 30, minSize: 15 },
77
+ { id: 'main', defaultSize: 70 },
78
+ ],
79
+ }"
80
+ @change-value="onResize"
81
+ >
82
+ <template #panel="{ id }">
83
+ <SidebarView v-if="id === 'sidebar'" />
84
+ <MainView v-else />
85
+ </template>
86
+ </FuroSplitter>
87
+ ```
88
+
89
+ ## Composition notes
90
+
91
+ - Imports nothing from the app, Nuxt, PrimeVue, or GraphQL.
92
+ - Renders panes and handles from a declarative `panels` array — consumers never import the headless primitive's sub-components.
93
+ - All logic lives in `FuroSplitterContext`; the template only calls context methods.
94
+ - CSS is wrapped in `@layer furo` with the `unit-splitter` root class and semantic tokens only.
95
+
96
+ ## Testing
97
+
98
+ - `FuroSplitterContext` is covered by jsdom unit tests (parcel defaults, class/attribute extraction, key resolution, last-pane detection, layout emit).
99
+ - No SFC mount test — DOM resize behavior is exercised manually in the playground.
100
+
101
+ ## Playground
102
+
103
+ `/samples/components/playground` → **Splitter** tab. Demonstrates horizontal, vertical, and three-pane (collapsible) layouts.
@@ -0,0 +1,127 @@
1
+ # FuroStepper
2
+
3
+ Organism — multi-step progress indicator built on a headless stepper primitive
4
+ (`Root` + `Item` + `Trigger` + `Indicator` + `Separator` + `Title` +
5
+ `Description`). Renders a step rail from a declarative `steps` list and an
6
+ optional panel per step via named slots, tracking the active step and emitting
7
+ changes. Supports horizontal/vertical orientation, a linear (no-forward-jump)
8
+ mode, and an indicator-only strip. Consumed by `crm-kit-frontend`'s
9
+ `FuroFormSequence` template. Domain-agnostic: no GraphQL, router, or Nuxt
10
+ imports — the consumer wires the emitted step to its own state.
11
+
12
+ Location: `lib/components/organisms/FuroStepper/`
13
+
14
+ ## Props
15
+
16
+ | Prop | Type | Default | Notes |
17
+ |------|------|---------|-------|
18
+ | `parcel` | `FuroStepperParcel \| null` | `null` | Reactive behavior object (below) |
19
+
20
+ ### `parcel`
21
+
22
+ | Key | Type | Default | Notes |
23
+ |-----|------|---------|-------|
24
+ | `steps` | `Array<FuroStepperStep>` | `[]` | Step descriptors that drive the rail and panel slots (below). |
25
+ | `value` | `number \| null` | — | Active step (1-based), where the presence of the key decides ownership. Absent, the component manages its own active step, defaulting to the first step. Present with a value, the parent owns the active step. Present and `null`, the parent owns it and no step is active. |
26
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction of the step rail. |
27
+ | `linear` | `boolean` | `false` | When `true`, steps must be completed in order — forward jumps are blocked. |
28
+ | `showPanels` | `boolean` | `true` | When `false`, no panels render — the component is an indicator strip only (the active value drives an external form region). |
29
+ | `disabled` | `boolean` | `false` | Disables the whole stepper. |
30
+
31
+ ### `FuroStepperStep`
32
+
33
+ | Key | Type | Default | Notes |
34
+ |-----|------|---------|-------|
35
+ | `value` | `string \| number` | — | Unique step identifier; also the default panel slot name and the suffix for `indicator-<value>` / `title-<value>` slots. |
36
+ | `title` | `string` | `''` | Step label (overridable via the `title-<value>` slot). |
37
+ | `description` | `string` | — | Optional secondary line under the title. |
38
+ | `slotName` | `string` | `value` | Overrides the panel slot name for this step. |
39
+ | `disabled` | `boolean` | `false` | Disables this single step. |
40
+
41
+ ## Events
42
+
43
+ | Event | Payload | When |
44
+ |-------|---------|------|
45
+ | `update:value` | `number` | Active step changed — enables `v-model:value`. |
46
+ | `step:change` | `{ value: number }` | Active step changed. |
47
+
48
+ ## Slots
49
+
50
+ | Slot | Purpose |
51
+ |------|---------|
52
+ | `<value>` (or `<slotName>`) | Panel content for the matching step. One slot per step, keyed by the step's `value` (or its `slotName` override), receiving `{ value, step }`. |
53
+ | `indicator-<value>` | Custom indicator content for the matching step, receiving `{ step, value, completed }`. When omitted, the indicator shows a check icon when completed, else the step number. |
54
+ | `title-<value>` | Custom title content for the matching step, receiving `{ value }`. When omitted, the title shows `title`. |
55
+
56
+ ## Usage
57
+
58
+ ### Uncontrolled
59
+
60
+ ```vue
61
+ <FuroStepper
62
+ :parcel="{ steps: [
63
+ { value: 'account', title: 'Account' },
64
+ { value: 'profile', title: 'Profile' },
65
+ { value: 'review', title: 'Review' },
66
+ ]}"
67
+ @step:change="onStepChange($event.value)"
68
+ >
69
+ <template #account>...account panel...</template>
70
+ <template #profile>...profile panel...</template>
71
+ <template #review>...review panel...</template>
72
+ </FuroStepper>
73
+ ```
74
+
75
+ ### Controlled (`v-model:value`)
76
+
77
+ ```vue
78
+ <FuroStepper
79
+ :parcel="{ value: activeStep, steps }"
80
+ @step:change="activeStep = $event.value"
81
+ >
82
+ <template #account>...</template>
83
+ <template #profile>...</template>
84
+ </FuroStepper>
85
+ ```
86
+
87
+ ### Vertical orientation
88
+
89
+ ```vue
90
+ <FuroStepper :parcel="{ orientation: 'vertical', steps }">
91
+ <template #account>...</template>
92
+ <template #profile>...</template>
93
+ </FuroStepper>
94
+ ```
95
+
96
+ ### Linear (no forward jumps)
97
+
98
+ ```vue
99
+ <FuroStepper :parcel="{ linear: true, steps }">
100
+ <!-- panel slots per step -->
101
+ </FuroStepper>
102
+ ```
103
+
104
+ ### Indicator strip (no panels)
105
+
106
+ ```vue
107
+ <FuroStepper :parcel="{ showPanels: false, value: activeStep, steps }" />
108
+ ```
109
+
110
+ ### Custom indicator content
111
+
112
+ ```vue
113
+ <FuroStepper :parcel="{ steps }">
114
+ <template #indicator-account>A</template>
115
+ <template #account>...</template>
116
+ </FuroStepper>
117
+ ```
118
+
119
+ ## Notes
120
+
121
+ - The active step is marked with `data-state="active"` and preceding steps with
122
+ `data-state="completed"` for styling; disabled steps are non-interactive.
123
+ - Root CSS class: `unit-stepper` (inside `@layer furo`). Modifier classes:
124
+ `vertical` (orientation), `linear` (sequential mode), and `disabled` (whole
125
+ control disabled).
126
+ - `value` is 1-based — the first step is `1`. Step numbers are derived from the
127
+ position of each entry in `steps`.
@@ -0,0 +1,107 @@
1
+ # `<FuroSwitch>` Interface Specification
2
+
3
+ ## Overview
4
+
5
+ `<FuroSwitch>` is a **shipped** switch atom in the Furo-Vue component system. It is a track with a sliding knob, bound to a boolean: the control an application reaches for when a setting is on or off and the change takes effect immediately.
6
+
7
+ **Public SFC prop:** `parcel` only. HTML attributes pass through to the root.
8
+
9
+ ## Why the name differs from the design's
10
+
11
+ The design calls this a toggle. That word is already claimed in this library by `FuroToggle`, a pressed/unpressed button carrying text and the building block of `FuroToggleGroup` and `FuroToolBar`. One word carrying two meanings is what let the switch go missing from the component inventory in the first place, and it caused a real defect when `FuroToggle` was given the switch's fully rounded corner. So the component is named for what it is.
12
+
13
+ The two are not interchangeable:
14
+
15
+ | | `FuroSwitch` | `FuroToggle` |
16
+ |---|---|---|
17
+ | Shape | A track with a sliding knob | A button with a label |
18
+ | Meaning | A setting is on or off | An action is currently applied |
19
+ | Where the design uses it | Settings rows | An editor toolbar (bold, italic) |
20
+ | Corner | Fully rounded | The medium step |
21
+
22
+ ## Summary
23
+
24
+ | Field | Value |
25
+ |---|---|
26
+ | **Layer** | Atom |
27
+ | **Path** | `lib/components/atoms/FuroSwitch/` |
28
+ | **Base primitive** | Headless switch (`Root` + `Thumb`) |
29
+ | **Value** | `boolean`, through `parcel.value` and `v-model:value` |
30
+ | **Emits** | `change-value`, `update:value` |
31
+
32
+ ## `parcel` keys
33
+
34
+ | Key | Type | Default | Meaning |
35
+ |---|---|---|---|
36
+ | `value` | `boolean` | `false` | On/off model. Declaring the key makes the switch controlled. |
37
+ | `size` | `FuroSwitchSize` | `'medium'` | `medium` or `small`. |
38
+ | `disabled` | `boolean` | `false` | Blocks interaction and repaints the track with the disabled colour. |
39
+ | `required` | `boolean` | `false` | Marks the control required for the surrounding form. |
40
+
41
+ Every other key on the parcel is forwarded to the root element untouched.
42
+
43
+ ## Controlled and uncontrolled
44
+
45
+ The switch follows the same rule as `FuroCheckbox` and `FuroToggle`: **declaring `value` on the parcel is what makes it controlled**, not the value itself.
46
+
47
+ - **Controlled** (`parcel` has a `value` key): the switch renders what the parent holds and never flips on its own. It reports the flip the user asked for and waits for the parent to hand the new value back.
48
+ - **Uncontrolled** (`parcel` has no `value` key): the switch owns its state, flips itself, and reports the new value.
49
+
50
+ `v-model:value` is the shorthand for the controlled form.
51
+
52
+ ## Events
53
+
54
+ | Event | Payload | When |
55
+ |---|---|---|
56
+ | `change-value` | `SwitchEmitPayload` | Every flip. `payload.value` is the new boolean; `payload.isChecked()` and `payload.isUnchecked()` read it, and `payload.controlElement` is the root element. |
57
+ | `update:value` | `boolean` | Every flip, alongside `change-value`. Backs `v-model:value`. |
58
+
59
+ ## Sizes
60
+
61
+ The design draws two steps, not three. The internal spacing is 2px at both, so the knob and its travel follow the track height:
62
+
63
+ | Size | Track | Knob | Travel |
64
+ |---|---|---|---|
65
+ | `medium` | 36px × 20px | 16px | 16px |
66
+ | `small` | 28px × 16px | 12px | 12px |
67
+
68
+ ## Colours
69
+
70
+ The switch reads the `--color-selected` family, so it re-themes with the checkbox and the radio rather than carrying blues of its own:
71
+
72
+ | Part | Reads | The design's value |
73
+ |---|---|---|
74
+ | Track, on | `--color-selected` | blue-500 |
75
+ | Track, on and hovered | `--color-selected-lighter` | blue-300 |
76
+ | Track, on and pressed | `--color-selected-darker` | blue-700 |
77
+ | Track, off | `--color-input` | the neutral every unfilled form control carries |
78
+ | Track, disabled | `--color-secondary` | gray-200 |
79
+ | Knob | `--color-selected-foreground` | white |
80
+ | Focus ring | `--color-ring` | — |
81
+
82
+ Two notes on the mapping:
83
+
84
+ 1. **The knob is drawn pure white and the token layer has no semantic white.** `--color-selected-foreground` is the role that means "what sits on a selected surface", and in the light theme it resolves to gray-050 rather than `#ffffff`. The role is used instead of the exact value, because it is the one that flips correctly in the dark theme; a token holding pure white unconditionally would leave a white knob on a dark track.
85
+ 2. **The design records no colour for the off track.** `--color-input` is used, the role that already means "the neutral of an unfilled form control". Reusing the recorded gray-200 there would have made the off state and the disabled state identical.
86
+
87
+ Because the design gives the disabled track a colour of its own, the disabled switch is repainted rather than dimmed with `opacity`, which is how `FuroCheckbox` and `FuroRadio` draw their disabled state.
88
+
89
+ ## Slots
90
+
91
+ None. The switch renders a track and a knob and no text.
92
+
93
+ ## Styling
94
+
95
+ | Part | Class | Notes |
96
+ |---|---|---|
97
+ | Root | `.unit-form-control.switch` | Also carries `size-*` and `disabled`; the primitive adds `data-state="checked"` or `"unchecked"` |
98
+ | The knob | `.knob` | Slides by the travel distance for the size |
99
+
100
+ The geometry is held in component-level custom properties on the root: `--furo-switch-track-width`, `--furo-switch-track-height`, `--furo-switch-track-inset`, `--furo-switch-knob-size` and `--furo-switch-knob-travel`. Override them to draw a step the two sizes do not cover.
101
+
102
+ ## Accessibility
103
+
104
+ - The root carries the switch role and its checked state, so a screen reader announces on and off rather than pressed.
105
+ - The switch renders no text of its own. Give it an accessible name through `aria-label`, `aria-labelledby`, or by wrapping it in `FuroControlBlock`.
106
+ - `Space` and `Enter` flip it; `Tab` moves focus onto and off it.
107
+ - Focus draws a ring on the track, and the knob's position carries the state, so colour is never the only signal.