@mtdt/observeops-ds-spec 0.1.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/AGENTS.md +102 -0
- package/README.md +73 -0
- package/components/index.json +1270 -0
- package/components/recipes/README.md +41 -0
- package/components/recipes/recipes.json +922 -0
- package/components/registry/README.md +44 -0
- package/components/registry/_schema.json +47 -0
- package/components/registry/button.json +368 -0
- package/components/registry/checkbox.json +177 -0
- package/components/registry/data-viz-tooltips.json +409 -0
- package/components/registry/date-time-pickers.json +296 -0
- package/components/registry/drawer.json +222 -0
- package/components/registry/dropdown-picker.json +388 -0
- package/components/registry/filters.json +155 -0
- package/components/registry/form-item.json +281 -0
- package/components/registry/input.json +277 -0
- package/components/registry/link.json +186 -0
- package/components/registry/loose-tags.json +196 -0
- package/components/registry/menu.json +145 -0
- package/components/registry/modal.json +265 -0
- package/components/registry/navigation.json +425 -0
- package/components/registry/popover.json +216 -0
- package/components/registry/radio.json +238 -0
- package/components/registry/scheduler.json +188 -0
- package/components/registry/select.json +247 -0
- package/components/registry/severity.json +179 -0
- package/components/registry/switch.json +177 -0
- package/components/registry/table.json +275 -0
- package/components/registry/tabs.json +264 -0
- package/components/registry/tag.json +345 -0
- package/components/registry/tags-list.json +115 -0
- package/components/registry/toolbars.json +240 -0
- package/components/registry/tooltip.json +175 -0
- package/components/specs/README.md +72 -0
- package/components/specs/button.md +230 -0
- package/components/specs/checkbox.md +162 -0
- package/components/specs/data-viz-tooltips.md +93 -0
- package/components/specs/date-time-pickers.md +161 -0
- package/components/specs/drawer.md +162 -0
- package/components/specs/dropdown-picker.md +161 -0
- package/components/specs/filters.md +118 -0
- package/components/specs/form-item.md +130 -0
- package/components/specs/input.md +130 -0
- package/components/specs/link.md +131 -0
- package/components/specs/loose-tags.md +139 -0
- package/components/specs/menu.md +88 -0
- package/components/specs/modal.md +176 -0
- package/components/specs/navigation.md +181 -0
- package/components/specs/popover.md +118 -0
- package/components/specs/radio.md +144 -0
- package/components/specs/scheduler.md +133 -0
- package/components/specs/select.md +118 -0
- package/components/specs/switch.md +124 -0
- package/components/specs/table.md +115 -0
- package/components/specs/tabs.md +136 -0
- package/components/specs/tag.md +196 -0
- package/components/specs/tags-list.md +105 -0
- package/components/specs/toolbars.md +108 -0
- package/components/specs/tooltip.md +112 -0
- package/foundation/README.md +39 -0
- package/foundation/layout-shells.md +67 -0
- package/foundation/page-templates.md +69 -0
- package/foundation/panel-behaviours.md +61 -0
- package/foundation/screen-regions.md +62 -0
- package/index.js +75 -0
- package/layout/grid.json +34 -0
- package/layout/layouts.json +310 -0
- package/llms.txt +60 -0
- package/package.json +42 -0
- package/spec.manifest.json +407 -0
- package/tokens/README.md +125 -0
- package/tokens/component.json +34 -0
- package/tokens/kit-accents.json +14 -0
- package/tokens/primitive.json +130 -0
- package/tokens/purpose-map.json +67 -0
- package/tokens/semantic.dark.json +90 -0
- package/tokens/semantic.light.json +90 -0
- package/tokens/structural.json +35 -0
- package/tokens/variables.json +2018 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Drawer (`FlotoDrawer`) โ Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Organism (overlay) |
|
|
6
|
+
| **Maturity** | ๐ข Stable โ the product's most-used overlay |
|
|
7
|
+
| **Source** | `src/components/_base-drawer.vue` (global `FlotoDrawer`, wraps kit `MDrawer` โ Ant `a-drawer`); form variant `src/components/crud/_base-drawer-form.vue` |
|
|
8
|
+
| **Storybook** | `Organisms/Drawer` (Examples ยท Usage ยท Accessibility ยท Changelog) |
|
|
9
|
+
| **Registry** | [`../registry/drawer.json`](../registry/drawer.json) |
|
|
10
|
+
| **Family** | overlays โ sibling of [Modal](./modal.md); `FlotoDrawerForm` is the form variant |
|
|
11
|
+
| **Figma** | TODO |
|
|
12
|
+
|
|
13
|
+
## Usage (product analytics)
|
|
14
|
+
|
|
15
|
+
- **`FlotoDrawer` 99ร** + **`FlotoDrawerForm` 59ร** = **158ร** โ the **most-used overlay**
|
|
16
|
+
(more than `MModal` 39 + `FlotoConfirmModal` 71 combined).
|
|
17
|
+
- `open` 27ร ยท `width` 14ร (default 40%).
|
|
18
|
+
- Dozens of app-specific `*Drawer` composites (IncidentDetails, SyncApproval, RunbookApproval,
|
|
19
|
+
FirmwareUpgradeApproval, CredentialSelection, CatalogSelection, AttachRules, ViewDetail, โฆ).
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
A **slide-in side panel** (from the right) over a backdrop. Use it for **detail views, edit
|
|
24
|
+
forms, and record-contextual content** that's too long or too rich for a centered modal. Open
|
|
25
|
+
via the **`open`** prop (toggles) or a **`trigger`** slot. `FlotoDrawerForm` = `FlotoForm`
|
|
26
|
+
(vertical) + `FlotoDrawer` for the common Add/Edit-in-a-drawer flow.
|
|
27
|
+
|
|
28
|
+
## Anatomy
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
32
|
+
โ Title [ร] โ โ title slot (text-primary) + built-in ร
|
|
33
|
+
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
34
|
+
(dimmed backdrop) โ Body (scrollable โ โ โ default slot, scrolls via FlotoScrollView
|
|
35
|
+
โ FlotoScrollView) โ
|
|
36
|
+
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
37
|
+
โ [Cancel][Save] โ โ actions slot (fixed footer; gets `hide`)
|
|
38
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Widths / sizes (the full range)
|
|
42
|
+
|
|
43
|
+
`width` (default **40%**) spans a real range in the product:
|
|
44
|
+
|
|
45
|
+
| Width | Use |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `360`px / 40% | simple detail / form panels (default) |
|
|
48
|
+
| 50โ70% | richer content / editors |
|
|
49
|
+
| **85โ96%** (`width="90%"` ~24ร, plus `96%` / `85%` / `100%`) | **large / full-screen drawers** for complex **multi-pane** flows โ a left nav + main scrollable form + a right reference panel (e.g. **APM Application Registration** at `width="96%"`). Use a wide drawer (not a modal) for rich config/registration wizards. See the **Large / full-screen** story. |
|
|
50
|
+
|
|
51
|
+
## Options / API
|
|
52
|
+
|
|
53
|
+
**FlotoDrawer:** `open` (Boolean, toggles) ยท `width` (px/%, default **40%**) ยท
|
|
54
|
+
`scrolledContent` (default **true** โ wraps the body in `FlotoScrollView`) ยท `usePadding` ยท
|
|
55
|
+
plus Ant `a-drawer` attrs via `$attrs` (e.g. `placement`). Slots: **`trigger`**
|
|
56
|
+
(`{ open, close, toggle }`) ยท **`title`** / `title-row` ยท default (body) ยท **`actions`**
|
|
57
|
+
(`{ hide }`, a fixed footer). Emits **`show`** / **`hide`**.
|
|
58
|
+
|
|
59
|
+
**FlotoDrawerForm:** `open` ยท `width` (40%) ยท `scrolledContent`. Slots: `trigger` ยท `header` /
|
|
60
|
+
`header-row` ยท default (body, gets `submit`) ยท `actions` (`{ hide, submit }`). It wraps a
|
|
61
|
+
vertical `FlotoForm` and **suppresses `MForm`'s default Submit** (`<template #submit><span/>`),
|
|
62
|
+
so only the footer submits.
|
|
63
|
+
|
|
64
|
+
## Behaviors
|
|
65
|
+
|
|
66
|
+
- **Open/close:** `open` prop / `trigger` slot; the drawer has a **built-in close ร** (Ant
|
|
67
|
+
`a-drawer`, unlike `MModal`), plus the actions footer. **`maskClosable: false`** โ clicking
|
|
68
|
+
the backdrop doesn't close (avoids losing form input).
|
|
69
|
+
- **Scroll:** the body scrolls inside the panel (`scrolledContent` โ `FlotoScrollView`); the
|
|
70
|
+
title and actions footer stay pinned. With an `actions` slot, the body reserves ~65px at the
|
|
71
|
+
bottom for the footer.
|
|
72
|
+
- **`@hide`** fires ~500ms after close (after the slide-out animation).
|
|
73
|
+
|
|
74
|
+
## Accessibility
|
|
75
|
+
|
|
76
|
+
- Ant `a-drawer` โ `role="dialog"`, focus trap, **built-in close ร** (a real, discoverable
|
|
77
|
+
dismiss โ better than `MModal`, which has none). Escape closes.
|
|
78
|
+
- โ ๏ธ No visible focus ring inside ([SF-001](../../findings/SF-001-focus-visible.md)).
|
|
79
|
+
- Give the drawer a clear **title** describing the record/task.
|
|
80
|
+
|
|
81
|
+
## Findings & Inconsistencies
|
|
82
|
+
|
|
83
|
+
### F1 โ `@hide` is delayed ~500ms ยท Low ยท Documented
|
|
84
|
+
|
|
85
|
+
`handlevisibleChange` emits `hide` after a 500ms `setTimeout` (to let the slide-out animation
|
|
86
|
+
finish). Parents toggling `open` off should account for the delay (the panel lingers briefly).
|
|
87
|
+
Not a bug โ document so consumers don't double-handle.
|
|
88
|
+
|
|
89
|
+
### F2 โ No visible focus ring ยท High ยท Open *(a11y)* โ [SF-001](../../findings/SF-001-focus-visible.md)
|
|
90
|
+
|
|
91
|
+
System-wide; matters in a focus-trapped panel.
|
|
92
|
+
|
|
93
|
+
### F3 โ `.actions` footer has no inter-button gap ยท Low ยท Open
|
|
94
|
+
|
|
95
|
+
The `.actions` bar is `display:flex; justify-content:flex-end` but sets **no gap**, so adjacent
|
|
96
|
+
footer buttons **touch** (measured 0px) unless you add **`mr-2`** to the non-last button (the
|
|
97
|
+
product convention, e.g. `instance-grid`). **Solution:** add `gap: 8px` (or `& > * + * { margin-left }`)
|
|
98
|
+
to `.actions` in `drawer.less`; until then, put `class="mr-2"` on every button except the last.
|
|
99
|
+
|
|
100
|
+
## Footer actions โ button layout (2 / 3 / 4)
|
|
101
|
+
|
|
102
|
+
The footer/`actions` bar is **right-aligned** (`justify-content: flex-end`), buttons spaced
|
|
103
|
+
with **`mr-2`** (8px) on the non-last. Layout by count:
|
|
104
|
+
|
|
105
|
+
| Count | Layout |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| **2** | `[ โฆ Cancel Save ]` โ Cancel `mr-2`, primary far-right |
|
|
108
|
+
| **3** | all right-aligned (`Reset ยท Cancel ยท Save`, `mr-2` gaps) โ **or** split: a **destructive** action (Delete) on the **left**, the confirm group (Cancel + Save) on the right (`justify-between`) |
|
|
109
|
+
| **4** | **split** โ a tertiary action or a "* mandatory" note on the **left**, the confirm group on the right; beyond this, move rarely-used actions into a menu |
|
|
110
|
+
|
|
111
|
+
Rules: **primary far-right**, **Cancel** immediately to its left; **destructive / tertiary /
|
|
112
|
+
notes go left** (separated from the confirm group). See the **Footer actions** story.
|
|
113
|
+
|
|
114
|
+
## Do / Don't
|
|
115
|
+
|
|
116
|
+
### Do
|
|
117
|
+
|
|
118
|
+
- Use a Drawer for **detail views, edit forms, and long/record-contextual content**.
|
|
119
|
+
- Use **`FlotoDrawerForm`** for Add/Edit forms (vertical form + validation + footer submit).
|
|
120
|
+
- Put primary/secondary actions in the **`actions`** footer (Cancel left, Save right).
|
|
121
|
+
|
|
122
|
+
### Don't
|
|
123
|
+
|
|
124
|
+
- Don't use a Drawer for a **short confirmation** โ use **`FlotoConfirmModal`**.
|
|
125
|
+
- Don't rely on backdrop-click to close (disabled) โ use the ร or a footer Cancel.
|
|
126
|
+
- Don't add an `MForm` inside without suppressing its default Submit (two submit buttons).
|
|
127
|
+
|
|
128
|
+
## Related
|
|
129
|
+
|
|
130
|
+
[Modal](./modal.md) (`MModal` / `FlotoConfirmModal`) ยท `FlotoDrawerForm` (form variant) ยท
|
|
131
|
+
`FlotoForm` / `FlotoFormItem` (fields inside) ยท `MButton` (footer actions).
|
|
132
|
+
|
|
133
|
+
## Changelog
|
|
134
|
+
|
|
135
|
+
- **2026-06-08** โ Added (decision-grade Usage) โ the **Drawer**, the product's most-used
|
|
136
|
+
overlay (158ร). Deep-dive of `_base-drawer.vue` (99ร) + `FlotoDrawerForm` (59ร): `open`-prop /
|
|
137
|
+
`trigger` opening; `title` / body (scrollable via `FlotoScrollView`) / `actions` (fixed
|
|
138
|
+
footer) slots; `width` default 40%; built-in ร with `maskClosable:false`. Registered
|
|
139
|
+
`FlotoDrawer` and `FlotoScrollView` in the preview; verified Basic (slide-in, title, actions,
|
|
140
|
+
backdrop) and a Form drawer (2 fields, Cancel/Save). Findings F1 (delayed `@hide`), F2
|
|
141
|
+
(focus โ SF-001).
|
|
142
|
+
- **2026-06-11** โ Reworked the **Large / full-screen** story for real fidelity. Read the actual
|
|
143
|
+
`apm-application-registration-drawer.vue` end-to-end: it's `width="96%"` `:scrolled-content="false"`
|
|
144
|
+
with a **2 : 6 : 4 `MRow`** body โ a tinted deployment **nav** (`--drawer-sidebar-background`,
|
|
145
|
+
selected on `--code-tag-background-color`), a **sectioned form** column (`--dashboard-background`,
|
|
146
|
+
`section-heading`+`helper-text` split by **dividers**, ending in an *Apply Configuration* button),
|
|
147
|
+
and a **right info** column (`vertical-line` headings + supported-tech **data tables** + a bordered
|
|
148
|
+
**Verification** box, each column scrolling independently). The prior story was a loose 3-div
|
|
149
|
+
approximation; replaced it with a faithful reproduction. Verified headless: drawer = 1382px (96%
|
|
150
|
+
of 1440), columns render 230 / 691 / 461px (17 / 50 / 33%) side-by-side, no console errors. The
|
|
151
|
+
left-nav uses the **real brand icons** (`vm` / `docker` / `kubernetes`, prefix `fal`) โ already
|
|
152
|
+
in `src/assets/icons/icons.js` and registered via the preview's `@assets/icons` import (verified
|
|
153
|
+
each resolves to a real SVG path).
|
|
154
|
+
- **2026-06-11** โ Replaced the story's inline one-off styles with **shared DS primitive classes**
|
|
155
|
+
(`.ds-section-heading` / `.ds-helper-text` / `.ds-divider` / `.ds-accent-bar` / `.ds-panel` /
|
|
156
|
+
`.ds-panel-heading` / `.ds-nav-item` / `.ds-spec-table`) โ new file
|
|
157
|
+
[`storybook/ds-primitives.less`](../../storybook/ds-primitives.less), loaded in the preview, mapped
|
|
158
|
+
1:1 from the product's scoped classes. See [`ds-primitives.md`](../ds-primitives.md). Verified
|
|
159
|
+
headless that each class resolves to the **same** token-derived computed value as the prior inline
|
|
160
|
+
style (14px/600, `--primary` accent, `--border-color` divider, `--help-card-bg-color` panel, etc.)
|
|
161
|
+
โ zero visual change, now real reuse. Interactive pieces remain the real components (FlotoDrawer,
|
|
162
|
+
MRow/MCol, MRadioGroup, FlotoFormItem, MButton, MIcon).
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# DropdownPicker (`FlotoDropdownPicker`) โ Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Organism |
|
|
6
|
+
| **Maturity** | ๐ก Stable-but-flawed (the product's core select โ heavily used; significant a11y gaps) |
|
|
7
|
+
| **Source** | `src/components/_base-dropdown-picker.vue` (global `FlotoDropdownPicker`); triggers in `src/components/dropdown-trigger/` |
|
|
8
|
+
| **Storybook** | `Organisms/DropdownPicker` (Examples ยท Usage ยท Accessibility ยท Changelog) |
|
|
9
|
+
| **Registry** | [`../registry/dropdown-picker.json`](../registry/dropdown-picker.json) |
|
|
10
|
+
| **Family** | Tag family's host โ renders `SelectedItemPills` (the teal `+N` pills) in read-only/multi modes |
|
|
11
|
+
| **Figma** | TODO |
|
|
12
|
+
|
|
13
|
+
## Usage (product analytics)
|
|
14
|
+
|
|
15
|
+
- **`<FlotoDropdownPicker>` used 510ร across 241 files** โ one of the most-used components
|
|
16
|
+
in the product (the de-facto select).
|
|
17
|
+
- **`allow-clear` 218ร** ยท **`:searchable="false"` 119ร** ยท **`multiple` ~66ร** ยท
|
|
18
|
+
`text-only` 15ร ยท `use-popover` 26ร ยท `:as-input="false"` 7ร.
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
The product's **custom select**: a popover-anchored, **virtualized** option menu with a
|
|
23
|
+
built-in search box, single or multi-select, keyboard navigation, and an optional inline
|
|
24
|
+
"add new option" affordance. It does **not** use a native `<select>` or Ant `a-select` โ it
|
|
25
|
+
is composed from a popover (`MPopper` by default, `MPopover` when `use-popover`), an `MInput`
|
|
26
|
+
trigger, an `MMenu` + `RecycleScroller` list, and `MCheckbox` rows.
|
|
27
|
+
|
|
28
|
+
**Options** are objects: `{ key, text }` (label is resolved as `text || name || label`;
|
|
29
|
+
value/identity as `key || id || value`). **v-model** is the selected `key` (single) or an
|
|
30
|
+
**array of keys** (`multiple`); `model: { event: 'change' }`.
|
|
31
|
+
|
|
32
|
+
## Anatomy
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
Trigger (asInput): โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
36
|
+
โ Web Server โ โ โ MInput (readonly) + chevron / clear ร
|
|
37
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
38
|
+
Open overlay: โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
39
|
+
โ ๐ Searchโฆ [+] โ โ search box (+ optional add button)
|
|
40
|
+
โ โโโโโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
41
|
+
โ โ Web Server โ โ virtualized rows (RecycleScroller)
|
|
42
|
+
โ โ Database โ (checkboxes when multiple)
|
|
43
|
+
โ โฆ โ
|
|
44
|
+
โ โโโโโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
45
|
+
โ [ร Clear] โ โ multi-select footer
|
|
46
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Options / API (selected โ full list in the registry)
|
|
50
|
+
|
|
51
|
+
**Selection & data:** `value` (v-model) ยท `options` (via `$attrs`) ยท `externalOptions` ยท
|
|
52
|
+
`disabledOptions` ยท `multiple` ยท `allowSelectAll` ยท `maxValues` (real cap) ยท
|
|
53
|
+
`maxAllowedSelection` (**dead โ F2**).
|
|
54
|
+
|
|
55
|
+
**Trigger & display:** `asInput` (default **true**) ยท `textOnly` ยท `placeholder`
|
|
56
|
+
(default "Select") ยท `allowClear` ยท `wrap` (default **true**).
|
|
57
|
+
|
|
58
|
+
**Menu behavior:** `searchable` (default **true**) ยท `itemSize` (32) ยท `itemsToShow` (10) ยท
|
|
59
|
+
`hideAllDropdownOptions` ยท `useAfterMenuDescription` (two-pane) ยท `showHelp` ยท
|
|
60
|
+
`canUserAddOptions` + `addLabel` (inline add) ยท `defaultOpen` ยท `avoidKeyboardNavigation`.
|
|
61
|
+
|
|
62
|
+
**Popover:** `usePopover` (MPopover vs MPopper) ยท `placement` ยท `overlayClassName`
|
|
63
|
+
(default "picker-overlay") ยท `overlayStyle` ยท `minWidth` ยท `fixedPosition`.
|
|
64
|
+
|
|
65
|
+
**Emits:** `change` (model) ยท `show` ยท `hide` ยท `search` ยท `add` ยท `active-item-index-change`.
|
|
66
|
+
|
|
67
|
+
**Slots:** default (custom menu) ยท `menu-item` ยท `before-menu-text` ยท `after-menu-text` ยท
|
|
68
|
+
`after-menu` ยท `helpbox` ยท `hovered-menu-description` ยท `trigger` (full trigger override).
|
|
69
|
+
|
|
70
|
+
## Behaviors
|
|
71
|
+
|
|
72
|
+
- **Search** filters on `text`/`name`/`label` via a **web worker** (`arrayWorker.search`),
|
|
73
|
+
debounced through a watcher; emits `search`.
|
|
74
|
+
- **Virtualization:** the list renders through `RecycleScroller` (`itemSize` px rows) โ long
|
|
75
|
+
option sets stay performant.
|
|
76
|
+
- **Keyboard (when open):** โ/โ move the active row, **Enter** selects, **Escape** closes
|
|
77
|
+
(a `window` keydown listener bound on open). `avoidKeyboardNavigation` disables this.
|
|
78
|
+
- **Multi-select:** checkboxes per row, optional **Select All** header, **Clear** footer; the
|
|
79
|
+
trigger shows `First item (+N)`. Read-only/`disabled` multi renders teal `SelectedItemPills`.
|
|
80
|
+
- **Inline add:** with `canUserAddOptions`, a `+` reveals an input; confirming emits `add`.
|
|
81
|
+
- **No data:** shows a themed illustration (light/dark SVG).
|
|
82
|
+
|
|
83
|
+
## Design tokens used
|
|
84
|
+
|
|
85
|
+
`--dropdown-hover-background` / `--left-menu-text-color-hover` (active/hover row) ยท the menu
|
|
86
|
+
inherits dropdown surface tokens (dark menu bg verified `#1d2a3e`) ยท trigger uses input
|
|
87
|
+
tokens. Pills (multi read-only) use the teal `--main-tags-*` (see [`tag.md`](./tag.md)).
|
|
88
|
+
|
|
89
|
+
## Accessibility
|
|
90
|
+
|
|
91
|
+
- **Keyboard (open):** โ/โ/Enter/Escape work once the menu is open.
|
|
92
|
+
- โ ๏ธ See findings **F1** โ this is the product's biggest a11y gap (no combobox semantics,
|
|
93
|
+
not keyboard-openable, no focus ring).
|
|
94
|
+
|
|
95
|
+
## Findings & Inconsistencies
|
|
96
|
+
|
|
97
|
+
### F1 โ Not an accessible combobox ยท High ยท Open *(a11y)* โ promoted to [SF-003](../../findings/SF-003-combobox-a11y.md)
|
|
98
|
+
|
|
99
|
+
It re-implements a select with `div`/`span` + a **readonly `MInput`** trigger and an `MMenu`,
|
|
100
|
+
but exposes **no combobox semantics**: no `role="combobox"`/`listbox"`/`option`, no
|
|
101
|
+
`aria-expanded`, no `aria-activedescendant`, no labelled relationship. The trigger opens
|
|
102
|
+
**only on click** (no Enter/Space handler), so **keyboard users may be unable to open it**;
|
|
103
|
+
and there's no visible focus ring ([SF-001](../../findings/SF-001-focus-visible.md)). For a
|
|
104
|
+
control used **510ร**, this is the highest-impact a11y issue in the system โ **promoted to the
|
|
105
|
+
system register as [SF-003](../../findings/SF-003-combobox-a11y.md)** (full evidence + the
|
|
106
|
+
combobox-ARIA + keyboard-open + focus solution there).
|
|
107
|
+
|
|
108
|
+
### F2 โ `maxAllowedSelection` prop is dead ยท Medium ยท Open
|
|
109
|
+
|
|
110
|
+
`maxAllowedSelection` is declared but **never referenced** in the logic; the actual cap is
|
|
111
|
+
`maxValues` (sliced in `handleChange`). Consumers pass `:max-allowed-selection` (e.g.
|
|
112
|
+
`LooseTags` `asDropdown` uses it for `singleSelection`) expecting a limit that **never
|
|
113
|
+
applies**. **Solution:** implement `maxAllowedSelection` (or alias it to `maxValues`) and
|
|
114
|
+
document the canonical prop.
|
|
115
|
+
|
|
116
|
+
### F3 โ Three boolean props default to `true` ยท Low ยท Open
|
|
117
|
+
|
|
118
|
+
`searchable`, `asInput`, and `wrap` all default to **true** (each `eslint-disable`'d against
|
|
119
|
+
the project's `vue/no-boolean-default`). Consumers must remember to pass `:searchable="false"`
|
|
120
|
+
etc. **Solution:** keep but document loudly; revisit if a future API cleanup is done.
|
|
121
|
+
|
|
122
|
+
### F4 โ Loose option schema ยท Low ยท Open
|
|
123
|
+
|
|
124
|
+
Labels resolve as `text || name || label` and identity as `key || id || value` in different
|
|
125
|
+
spots โ three accepted shapes for the same concept. **Solution:** document a canonical
|
|
126
|
+
`{ key, text }` contract and normalize inputs.
|
|
127
|
+
|
|
128
|
+
## Storybook fidelity caveats
|
|
129
|
+
|
|
130
|
+
Two behaviors depend on the product runtime and **don't fully work in Storybook** (not
|
|
131
|
+
product bugs): **search filtering** needs the `arrayWorker` web worker (absent in SB, so the
|
|
132
|
+
list doesn't filter), and the **no-data illustration** needs an SVG-as-component loader
|
|
133
|
+
(absent in SB). Stories therefore use non-empty option sets; the search box renders but won't
|
|
134
|
+
filter. The preview registers `v-tooltip` (โ `MPopper`), `RecycleScroller`, and the picker
|
|
135
|
+
itself to render its real default mode.
|
|
136
|
+
|
|
137
|
+
## Do / Don't
|
|
138
|
+
|
|
139
|
+
### Do
|
|
140
|
+
|
|
141
|
+
- Use it as the standard select; pass `options` as `{ key, text }`, v-model the `key`(s).
|
|
142
|
+
- Use `multiple` + `allow-select-all` for multi-select; `:searchable="false"` for short lists.
|
|
143
|
+
- Use the `trigger` slot when you need a custom trigger (e.g. a button or pills).
|
|
144
|
+
|
|
145
|
+
### Don't
|
|
146
|
+
|
|
147
|
+
- Don't rely on `maxAllowedSelection` to cap selection (F2 โ use `maxValues`).
|
|
148
|
+
- Don't assume keyboard/AT users can operate it today (F1).
|
|
149
|
+
- Don't forget `searchable`/`asInput`/`wrap` default to **true** (F3).
|
|
150
|
+
|
|
151
|
+
## Related
|
|
152
|
+
|
|
153
|
+
`SingleTrigger` / `MultipleTrigger` / `SelectedItemPills` (its triggers) ยท `LooseTags`
|
|
154
|
+
(uses it via `asDropdown`) ยท `MSelect` ยท `MPopover` / `MPopper` ยท `MMenu` ยท `MCheckbox`.
|
|
155
|
+
|
|
156
|
+
## Changelog
|
|
157
|
+
|
|
158
|
+
- **2026-06-07** โ Added. Full deep-dive of `_base-dropdown-picker.vue` + the
|
|
159
|
+
`dropdown-trigger/` family (510ร/241 files). Verified live (trigger, open menu, virtualized
|
|
160
|
+
list, multi-select + Select All + Clear, dark theme) by registering `v-tooltip`/`MPopper`/
|
|
161
|
+
`RecycleScroller` in the preview. Findings F1 (a11y, High) โ F4; Storybook caveats noted.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Filters โ Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Molecule |
|
|
6
|
+
| **Maturity** | ๐ข Stable |
|
|
7
|
+
| **Source** | `filters/filters-container.vue` (`FiltersContainer`) + `FilterGroup` + `FilterCondition` + `FilterTrigger` ยท `filter-bar/_base-filter-bar.vue` (`FlotoFilterBar`) ยท `filter-bar/filter-quick-menu.vue` ยท the filter-row pattern. |
|
|
8
|
+
| **Storybook** | Molecules/Filters |
|
|
9
|
+
| **Registry** | [`registry/filters.json`](../registry/filters.json) |
|
|
10
|
+
| **Family** | [Filters](../family-map.md) |
|
|
11
|
+
|
|
12
|
+
## Why this is a family
|
|
13
|
+
|
|
14
|
+
The product's filtering UIs were scattered across screens (Grid Toolbar, Monitoring config). They're
|
|
15
|
+
really **four filter archetypes** โ catalogued here by *what they are*, regardless of where they sit.
|
|
16
|
+
The **Toolbars** (organisms) *compose* these molecules.
|
|
17
|
+
|
|
18
|
+
## The five archetypes
|
|
19
|
+
|
|
20
|
+
| Archetype | Source | Usage | What it is |
|
|
21
|
+
| --- | --- | --- | --- |
|
|
22
|
+
| **Expression builder** | `FiltersContainer` | **32ร** | a nested **AND/OR** query builder |
|
|
23
|
+
| **Filter bar** | `FlotoFilterBar` | ~50ร | an inline **chip** bar + Match All/Any |
|
|
24
|
+
| **Quick filters** | `filter-quick-menu` | โ | a **preset** one-click menu |
|
|
25
|
+
| **Filter row** | pattern | โ | a few **multi-selects** + Reset/Apply |
|
|
26
|
+
| **Vertical filter** | `vertical-filter/filters.vue` ยท NCM | APM/RUM/NCM | the **faceted left-panel** (checkbox + count groups) |
|
|
27
|
+
|
|
28
|
+
### Vertical filter (the faceted left-panel sidebar)
|
|
29
|
+
|
|
30
|
+
- The left-hand **faceted filter** panel โ a **search** atop **collapsible groups** (`MCollapse`), each
|
|
31
|
+
row a **checkbox + optional status/type icon + label + count** (right-aligned); checking rows filters
|
|
32
|
+
the grid. The shared component is **`components/filters/vertical-filter/filters.vue`** (used by **APM
|
|
33
|
+
Explorer**, **APM Error Tracker**, **RUM Sessions**); **NCM**'s `explorer-grid-virtical-filter.vue` is
|
|
34
|
+
a richer variant adding **status icons** (backup) and **device-type icons** (switch/router).
|
|
35
|
+
- It **looks like a Navigation side menu but it filters, it doesn't navigate** โ so it lives here, not in
|
|
36
|
+
Navigation (cross-referenced from **Navigation โ Side menu**). Surfaced by the 2026-06-16 left-panel
|
|
37
|
+
sweep.
|
|
38
|
+
|
|
39
|
+
### Expression builder (the 32ร one)
|
|
40
|
+
|
|
41
|
+
- **Presentation:** it is an **`MPopover`** (placement `bottomLeft`, `has-arrow`) opened from a
|
|
42
|
+
**`FilterTrigger`** input (a filter-icon box that renders the applied query, or a "Search"
|
|
43
|
+
placeholder) โ **not** an inline panel. The popover has a **close ร**, **Pre Filters / Post Filters**
|
|
44
|
+
tabs (`MTab`), the builder, and a **Reset / Clear / Apply** footer. **Apply** emits the query (the
|
|
45
|
+
trigger then shows it) and closes; **Clear** empties; **Reset** reverts to the applied state.
|
|
46
|
+
- **"Group(s) matching All/Any"** wraps one or more **groups** (Pre allows up to 3 groups, Post 1).
|
|
47
|
+
- Each group: **"Include/Exclude โ Group matching All/Any โ Criteria"** + a list of **conditions**.
|
|
48
|
+
- Each condition: **counter ยท operator ยท value** (type-aware โ `FilterCondition` picks the operator set
|
|
49
|
+
and value control from the field type; **Between** โ From/To). **Add Condition** (max 3) /
|
|
50
|
+
**Add New Group** extend it; **ร** removes.
|
|
51
|
+
|
|
52
|
+
### Filter bar (~50ร) โ *moved here from Grid Toolbar*
|
|
53
|
+
|
|
54
|
+
- An inline **chip** bar: `default-chips` (no ร) + completed chips (`Type = โฆ (+N) ร`) + a stub.
|
|
55
|
+
- **+ Filter** adds ยท **ร** removes ยท **Match All/Any** toggle + **Clear All** (once a filter applies).
|
|
56
|
+
- Chips scroll horizontally; the segment picker floats `position:fixed` so the row never clips it.
|
|
57
|
+
|
|
58
|
+
### Quick filters โ *moved here from Grid Toolbar*
|
|
59
|
+
|
|
60
|
+
- A **thumbs-up** button โ a panel of **preset** filters (`{ key, label, condition }`); picking one
|
|
61
|
+
applies that condition.
|
|
62
|
+
|
|
63
|
+
### Filter row โ *re-homed from the retired Monitoring Config (Collection Filters)*
|
|
64
|
+
|
|
65
|
+
- A small set of multi-selects (**Groups ยท Severity ยท Tags**) + **Reset / Apply** + close **ร**.
|
|
66
|
+
|
|
67
|
+
## Which filter? (decision)
|
|
68
|
+
|
|
69
|
+
1. **Complex AND/OR across groups?** โ **Expression builder**.
|
|
70
|
+
2. **Compact chip bar over a grid?** โ **Filter bar**.
|
|
71
|
+
3. **One-click presets?** โ **Quick filters**.
|
|
72
|
+
4. **A few simple facets?** โ **Filter row**.
|
|
73
|
+
5. **Plain text search?** โ the **Input** `search` type (181ร), **not** a filter.
|
|
74
|
+
|
|
75
|
+
## Accessibility
|
|
76
|
+
|
|
77
|
+
- Inherits from the composed controls (`FlotoDropdownPicker`, chips, `MButton`). **Verify**
|
|
78
|
+
focus-visible ring (**SF-001**) on the chips/selects, and an `aria-label` on icon-only triggers
|
|
79
|
+
(thumbs-up, ร).
|
|
80
|
+
|
|
81
|
+
## Design tokens used
|
|
82
|
+
|
|
83
|
+
`--code-tag-background-color` (chips) ยท `--border-color` ยท `--page-background-color` ยท
|
|
84
|
+
`--page-text-color` ยท `--neutral-light` ยท `--neutral-button-text` ยท `--primary` ยท `--dropdown-background`.
|
|
85
|
+
|
|
86
|
+
## Findings & Inconsistencies
|
|
87
|
+
|
|
88
|
+
| # | Severity | Status | Finding |
|
|
89
|
+
| --- | --- | --- | --- |
|
|
90
|
+
| F1 | Low | Noted | Store-bound; `FilterCondition` is 500+ lines โ catalogued as reproductions (the Filter bar reproduction is fully interactive). |
|
|
91
|
+
| N1 | Info | โ | **Taxonomy:** this family was created (2026-06-16) to gather filters by archetype, moving Filter bar + Quick filters out of "Grid Toolbar" and re-homing "Collection Filters" from the retired "Monitoring Config." |
|
|
92
|
+
|
|
93
|
+
## Do / Don't
|
|
94
|
+
|
|
95
|
+
- **Do** use the Expression builder for real query logic; the Filter bar for a compact chip filter;
|
|
96
|
+
Quick filters for presets; a Filter row for a few facets.
|
|
97
|
+
- **Don't** hand-roll a condition builder; don't put plain search here (โ Input); don't duplicate a
|
|
98
|
+
filter across screens โ compose the one molecule in a Toolbar.
|
|
99
|
+
|
|
100
|
+
## Related components
|
|
101
|
+
|
|
102
|
+
`FlotoDropdownPicker` (the selects inside) ยท **Input** (`search` type) ยท `Tag` (the chips) ยท **Table**
|
|
103
|
+
(filters sit above grids) ยท **Toolbars** (compose these).
|
|
104
|
+
|
|
105
|
+
## Changelog
|
|
106
|
+
|
|
107
|
+
- **2026-06-16** โ Expression builder โ corrected the **presentation** to match the product: it now
|
|
108
|
+
lives inside an **`MPopover`** opened from a **`FilterTrigger`** input (it was an inline panel before),
|
|
109
|
+
with **Pre / Post Filters** tabs, a close ร, and a **Reset / Clear / Apply** footer. Made it **fully
|
|
110
|
+
functional** โ open/close, switch tabs, add/remove conditions (max 3) and groups (3 pre / 1 post),
|
|
111
|
+
Include/Exclude + All/Any selects, **Between** โ From/To, and **Apply** renders the query back into the
|
|
112
|
+
trigger. Verified interaction (add/edit/apply/clear) + light + dark.
|
|
113
|
+
- **2026-06-16** โ Added โ the **Filters** family, by component archetype. Catalogued the **missing**
|
|
114
|
+
**Expression builder** (`FiltersContainer`, **32ร** โ a nested AND/OR query builder), **moved** the
|
|
115
|
+
**Filter bar** (~50ร) and **Quick filters** out of the context-shaped "Grid Toolbar," and **re-homed**
|
|
116
|
+
the **Filter row** from the retired "Monitoring config." Stories: Expression builder ยท Filter bar
|
|
117
|
+
(interactive) ยท Quick filters ยท Filter row. Reproductions (store-bound); verified render + the moved
|
|
118
|
+
Filter bar still interactive, no console errors.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Form Field (`FlotoFormItem`) โ Spec, Findings & Solutions
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Tier** | Molecule (form field) |
|
|
6
|
+
| **Maturity** | ๐ข Stable โ the backbone of every form |
|
|
7
|
+
| **Source** | `src/components/_base-form-item.vue` (global `FlotoFormItem`) โ `MValidationProvider` (vee-validate) โ `MFormItem` โ label + control |
|
|
8
|
+
| **Storybook** | `Molecules/FormItem` (Examples ยท Usage ยท Accessibility ยท Changelog) |
|
|
9
|
+
| **Registry** | [`../registry/form-item.json`](../registry/form-item.json) |
|
|
10
|
+
| **Family** | the form-controls host โ wraps `Input` / `Select` / `Radio` / `Checkbox` / `LooseTags` / `DropdownPicker` |
|
|
11
|
+
| **Figma** | TODO |
|
|
12
|
+
|
|
13
|
+
## Usage (product analytics)
|
|
14
|
+
|
|
15
|
+
- **`<FlotoFormItem>` used 1783ร** โ the **most-used component** in the product (every field).
|
|
16
|
+
- `label` 611ร ยท `rules` 272ร ยท `id` 52ร ยท `help`/`info-tooltip` a few. Wrapped by `FlotoForm`
|
|
17
|
+
(`layout="horizontal"` or `"vertical"`).
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
The **Form Field**: a label + validation + control + error/help message, in one wrapper. It
|
|
22
|
+
binds a vee-validate `MValidationProvider` to a `MFormItem` (Ant form-item). v-model binds
|
|
23
|
+
`value` + `update`. With **no default slot** it renders its own `MInput` (routed by `type`/
|
|
24
|
+
`inputType`); otherwise the **default slot is the control** (any input component).
|
|
25
|
+
|
|
26
|
+
## Anatomy
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
Label * โ โ label ยท required asterisk (from rules) ยท optional info (i) tooltip
|
|
30
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
31
|
+
โ control (MInput or slot) โ
|
|
32
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
33
|
+
Help text / Error message โ help (hint) OR the validation error (red) when invalid
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Options / API
|
|
37
|
+
|
|
38
|
+
- **`label`** โ field label (renders the `<label>` + optional info tooltip).
|
|
39
|
+
- **`rules`** (String|Object) โ vee-validate rules (`required|email|max:255` โฆ). **`required`
|
|
40
|
+
is derived from `rules`** โ the asterisk shows when rules include `required` (no separate prop).
|
|
41
|
+
- **`help`** โ hint text under the field.
|
|
42
|
+
- **`info-tooltip`** โ an **(i)** icon by the label with hover help (HTML allowed).
|
|
43
|
+
- **`type`/`inputType`** โ control type for the built-in `MInput` (text/number/textarea/โฆ).
|
|
44
|
+
- **`validationLabel`**, **`vid`**, **`mode`** (`eager`), **`immediate`**, **`hideErrorMessage`**.
|
|
45
|
+
- **Layout** (from the parent `FlotoForm`/`MForm` `layout`): **vertical** (label above the
|
|
46
|
+
control, 13ร) or **horizontal** (label beside, via `labelCol`/`wrapperCol`, 14ร) โ both common.
|
|
47
|
+
- **`labelCol`/`wrapperCol`** โ responsive grid split for label vs control (horizontal layout).
|
|
48
|
+
- **Sizes:** none โ fields are one height (the control's). **`is-view`** read-only mode lives on
|
|
49
|
+
the *inner controls*, not on `FlotoFormItem`.
|
|
50
|
+
- **Slots:** default (the control, gets `slotData`) ยท `before-input` ยท `input-children` ยท plus
|
|
51
|
+
passthrough scoped slots to the inner `MInput`.
|
|
52
|
+
- **Methods:** `validate()` ยท `setValue(v)` ยท `addError(err)` ยท `focus()`.
|
|
53
|
+
- v-model: `value` + `update`. Emits `blur`.
|
|
54
|
+
|
|
55
|
+
## Behaviors
|
|
56
|
+
|
|
57
|
+
- **Required asterisk** appears when `rules` contain `required` (not `required_if`).
|
|
58
|
+
- **Validation:** on `eager` mode the field validates on interaction; **error** sets a red
|
|
59
|
+
border + an inline message (`errors[0]`); **success** sets a valid status. The message is
|
|
60
|
+
**pristine-gated** โ it appears after the user interacts (type/blur), not on first paint.
|
|
61
|
+
- **Built-in vs custom control:** omit the slot for a plain `MInput`; use the slot to wrap a
|
|
62
|
+
Select/Radio/Checkbox/picker and still get the label + validation.
|
|
63
|
+
- **Programmatic:** parents call `validate()` / `addError()` / `setValue()` / `focus()` via a ref.
|
|
64
|
+
|
|
65
|
+
## Content & writing
|
|
66
|
+
|
|
67
|
+
- Labels: short noun phrases, Title Case ("Polling Interval"). Don't repeat the label in the
|
|
68
|
+
placeholder. Use `help` for a persistent hint, `info-tooltip` for a longer explanation.
|
|
69
|
+
|
|
70
|
+
## Accessibility
|
|
71
|
+
|
|
72
|
+
- The label is associated with the control via `MFormItem`; the required asterisk is visual โ
|
|
73
|
+
ensure the rule also conveys requirement to AT (the error message does).
|
|
74
|
+
- โ ๏ธ Inner controls have **no visible focus ring** ([SF-001](../../findings/SF-001-focus-visible.md)).
|
|
75
|
+
- Error messages are text (not color-only) โ good; keep them specific.
|
|
76
|
+
|
|
77
|
+
## Findings & Inconsistencies
|
|
78
|
+
|
|
79
|
+
### F1 โ `required` is derived, not explicit ยท Low ยท Documented
|
|
80
|
+
|
|
81
|
+
There's no `required` prop โ the asterisk comes from parsing `rules` for `required`. Passing a
|
|
82
|
+
`required` attribute does nothing. **Solution:** document (done); a `required` convenience prop
|
|
83
|
+
that injects the rule could reduce confusion.
|
|
84
|
+
|
|
85
|
+
### F2 โ Error message is pristine-gated ยท Low ยท Documented
|
|
86
|
+
|
|
87
|
+
Even with `immediate`, the inline message only shows after interaction (the red border shows
|
|
88
|
+
immediately). Expected vee-validate behavior; document so it's not mistaken for a bug.
|
|
89
|
+
|
|
90
|
+
### F3 โ No visible focus ring on controls ยท High ยท Open *(a11y)* โ [SF-001](../../findings/SF-001-focus-visible.md)
|
|
91
|
+
|
|
92
|
+
System-wide (the wrapped inputs strip `outline`).
|
|
93
|
+
|
|
94
|
+
## Do / Don't
|
|
95
|
+
|
|
96
|
+
### Do
|
|
97
|
+
|
|
98
|
+
- Wrap **every** form control in `FlotoFormItem` for a consistent label + validation + error.
|
|
99
|
+
- Put validation in `rules` (`required|email|max:255`); add reusable rules in `src/validations.js`.
|
|
100
|
+
- Use the default slot to wrap non-text controls (Select/Radio/Checkbox/picker).
|
|
101
|
+
- Use `help` for hints, `info-tooltip` for longer guidance.
|
|
102
|
+
|
|
103
|
+
### Don't
|
|
104
|
+
|
|
105
|
+
- Don't pass a `required` attribute expecting an asterisk โ put `required` in `rules` (F1).
|
|
106
|
+
- Don't hand-roll labels/validation around a bare input โ use this wrapper.
|
|
107
|
+
- Don't rely on `@input` โ v-model uses `update`.
|
|
108
|
+
|
|
109
|
+
## Related
|
|
110
|
+
|
|
111
|
+
`Input` (the default control) ยท `Radio` / `Checkbox` / `Switch` / `DropdownPicker` / `LooseTags`
|
|
112
|
+
(slot controls) ยท `FlotoForm` (the form + layout) ยท `MValidationObserver` (form-level validate).
|
|
113
|
+
|
|
114
|
+
## Changelog
|
|
115
|
+
|
|
116
|
+
- **2026-06-07** โ Added (decision-grade Usage from the start) โ the Form Field molecule
|
|
117
|
+
(1783ร). Deep-dive of `_base-form-item.vue`: vee-validate provider โ MFormItem โ label +
|
|
118
|
+
control + error; `required` derived from `rules`; built-in MInput vs slot control; help +
|
|
119
|
+
info-tooltip; `validate()`/`setValue()`/`addError()`/`focus()`. Storybook: registered
|
|
120
|
+
`MValidationProvider`/`Observer` + `src/validations` rules + FlotoFormItem in the preview;
|
|
121
|
+
stories Basic ยท Required ยท Help ยท Info tooltip ยท Custom control ยท Validation error (verified
|
|
122
|
+
the live email error message + red border). Findings F1 (derived required), F2 (pristine-gated
|
|
123
|
+
message), F3 (focus โ SF-001).
|
|
124
|
+
- **2026-06-07** โ Fixed a Storybook-wide tooltip bug + coverage. Root cause: `MTooltip`
|
|
125
|
+
renders `<VTippy>` (vue-tippy), which wasn't registered in the preview โ so the info-tooltip
|
|
126
|
+
dumped its content inline (always-on dark box, no (i) icon, no hover). Registered
|
|
127
|
+
`VTippy` (vue-tippy `TippyComponent`) โ **all `MTooltip`s now work** (icon trigger + hover
|
|
128
|
+
popup; verified). Added a **Layout** story (vertical vs horizontal). Confirmed coverage: no
|
|
129
|
+
size variants (one height); `is-view` is on the inner controls, not the field; the repeatable
|
|
130
|
+
`multiple-form-items` pattern is a separate (related) component.
|