@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.
Files changed (79) hide show
  1. package/AGENTS.md +102 -0
  2. package/README.md +73 -0
  3. package/components/index.json +1270 -0
  4. package/components/recipes/README.md +41 -0
  5. package/components/recipes/recipes.json +922 -0
  6. package/components/registry/README.md +44 -0
  7. package/components/registry/_schema.json +47 -0
  8. package/components/registry/button.json +368 -0
  9. package/components/registry/checkbox.json +177 -0
  10. package/components/registry/data-viz-tooltips.json +409 -0
  11. package/components/registry/date-time-pickers.json +296 -0
  12. package/components/registry/drawer.json +222 -0
  13. package/components/registry/dropdown-picker.json +388 -0
  14. package/components/registry/filters.json +155 -0
  15. package/components/registry/form-item.json +281 -0
  16. package/components/registry/input.json +277 -0
  17. package/components/registry/link.json +186 -0
  18. package/components/registry/loose-tags.json +196 -0
  19. package/components/registry/menu.json +145 -0
  20. package/components/registry/modal.json +265 -0
  21. package/components/registry/navigation.json +425 -0
  22. package/components/registry/popover.json +216 -0
  23. package/components/registry/radio.json +238 -0
  24. package/components/registry/scheduler.json +188 -0
  25. package/components/registry/select.json +247 -0
  26. package/components/registry/severity.json +179 -0
  27. package/components/registry/switch.json +177 -0
  28. package/components/registry/table.json +275 -0
  29. package/components/registry/tabs.json +264 -0
  30. package/components/registry/tag.json +345 -0
  31. package/components/registry/tags-list.json +115 -0
  32. package/components/registry/toolbars.json +240 -0
  33. package/components/registry/tooltip.json +175 -0
  34. package/components/specs/README.md +72 -0
  35. package/components/specs/button.md +230 -0
  36. package/components/specs/checkbox.md +162 -0
  37. package/components/specs/data-viz-tooltips.md +93 -0
  38. package/components/specs/date-time-pickers.md +161 -0
  39. package/components/specs/drawer.md +162 -0
  40. package/components/specs/dropdown-picker.md +161 -0
  41. package/components/specs/filters.md +118 -0
  42. package/components/specs/form-item.md +130 -0
  43. package/components/specs/input.md +130 -0
  44. package/components/specs/link.md +131 -0
  45. package/components/specs/loose-tags.md +139 -0
  46. package/components/specs/menu.md +88 -0
  47. package/components/specs/modal.md +176 -0
  48. package/components/specs/navigation.md +181 -0
  49. package/components/specs/popover.md +118 -0
  50. package/components/specs/radio.md +144 -0
  51. package/components/specs/scheduler.md +133 -0
  52. package/components/specs/select.md +118 -0
  53. package/components/specs/switch.md +124 -0
  54. package/components/specs/table.md +115 -0
  55. package/components/specs/tabs.md +136 -0
  56. package/components/specs/tag.md +196 -0
  57. package/components/specs/tags-list.md +105 -0
  58. package/components/specs/toolbars.md +108 -0
  59. package/components/specs/tooltip.md +112 -0
  60. package/foundation/README.md +39 -0
  61. package/foundation/layout-shells.md +67 -0
  62. package/foundation/page-templates.md +69 -0
  63. package/foundation/panel-behaviours.md +61 -0
  64. package/foundation/screen-regions.md +62 -0
  65. package/index.js +75 -0
  66. package/layout/grid.json +34 -0
  67. package/layout/layouts.json +310 -0
  68. package/llms.txt +60 -0
  69. package/package.json +42 -0
  70. package/spec.manifest.json +407 -0
  71. package/tokens/README.md +125 -0
  72. package/tokens/component.json +34 -0
  73. package/tokens/kit-accents.json +14 -0
  74. package/tokens/primitive.json +130 -0
  75. package/tokens/purpose-map.json +67 -0
  76. package/tokens/semantic.dark.json +90 -0
  77. package/tokens/semantic.light.json +90 -0
  78. package/tokens/structural.json +35 -0
  79. 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.