@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,181 @@
1
+ # Navigation β€” Spec, Findings & Solutions
2
+
3
+ | | |
4
+ | --- | --- |
5
+ | **Tier** | Molecule |
6
+ | **Maturity** | 🟒 Stable |
7
+ | **Source** | `layout/navbar.vue` (`FlotoNavBar`) Β· `settings/components/left-menu.vue` Β· `report/components/report-steps.vue` (`ReportSteps`) + `product-setup/components/guide-section-step.vue` + `auth/two-factor-verification.vue` (the steppers β€” all bespoke, no `MSteps`) Β· `report/components/compliance-breadcrumb.vue` Β· `_base-back-button.vue` (`FlotoBackButton`). |
8
+ | **Storybook** | Molecules/Navigation |
9
+ | **Registry** | [`registry/navigation.json`](../registry/navigation.json) |
10
+ | **Family** | [Navigation](../family-map.md) |
11
+
12
+ ## Why this is a family
13
+
14
+ The product's **wayfinding** components β€” distinct from **Toolbars** (which hold *actions*) and
15
+ **Tabs** (sibling views of one page). Catalogued by archetype.
16
+
17
+ ## The archetypes
18
+
19
+ | Archetype | Source | Usage | What it is |
20
+ | --- | --- | --- | --- |
21
+ | **Primary nav** | `FlotoNavBar` | global | left vertical **module** nav (icon + label) |
22
+ | **Side menu** | `settings/left-menu` Β· Log/Topology hierarchy Β· Dashboard Β· Report/saved-views | many | the **left-panel** nav β€” 4 forms: section Β· tree Β· category-list Β· list/saved-views |
23
+ | **Steps** | `report-steps.vue` Β· `product-setup` guide Β· 2FA | 3 flows | numbered **wizard / stepper** (bespoke, no `MSteps`) |
24
+ | **Breadcrumb** | `compliance-breadcrumb` | β€” | back + **path trail** |
25
+ | **Back button** | `FlotoBackButton` | 3Γ— | a **chevron-left** router link |
26
+ | **Tabs** | `MTab` | 86Γ— | *its own family* (Molecules/Tabs) β€” cross-referenced |
27
+
28
+ ### Primary nav
29
+
30
+ - The left vertical **module** sidebar (`MLayoutSider`). **Collapsed 65px** by default (brand mark +
31
+ module icons only); **hover-expands to 170px** (`@mouseover`/`@mouseout` β†’ `pinned`) revealing the
32
+ **ObserveOps** logo + module labels. The **16 modules** come from `visibleMenuItems`
33
+ (role/license-gated): Dashboards, Monitors, Alerts, SLO *(BETA)*, Reports, Topology, NCCM, NetRoute,
34
+ Metric Explorer, Log Explorer, APM Explorer, RUM Explorer, Flow Explorer, Trap Explorer, Audits,
35
+ Settings β€” each with its registered module icon. **Active** module highlights `--primary` on
36
+ `--code-tag-background-color` with an inset left rule when expanded. `MMenu theme="dark"` +
37
+ `MMenuItem` + `FlotoLink`. **Collapsed ↔ expanded is the primary variant.**
38
+
39
+ ### Side menu (the left-panel β€” 4 forms)
40
+
41
+ The product's **left-hand panel** in list/explorer views. One archetype, **four forms** (all share a
42
+ shell: optional **tabs** + **search** + a scrollable list; selecting an item filters/navigates the main
43
+ view). Surfaced by a per-module sweep β€” the original entry only covered the first.
44
+
45
+ 1. **Section menu** β€” `settings/components/left-menu.vue` (**20Γ—**): a search atop an **`MCollapse`**
46
+ accordion of sections (icon + name + optional beta tag) β†’ sub-items; active item gets a `--primary`
47
+ left rule. The Settings sub-nav.
48
+ 2. **Tree** β€” **Log Explorer** `log/components/hierarchy/*`, **Topology** `topology-hierarchy.vue`, and
49
+ **Inventory** (Monitors) via the shared **`@views/layouts/monitor-hierarchy-layout.vue`** β€” all on the
50
+ shared **`components/hierarchy/infinite-tree.vue`**: tabs (Type / Group / Saved Query), search, then a
51
+ **virtualised hierarchy** β€” each node a **chevron + type icon (or severity badge) + name + count
52
+ badge** (`57.25 M`), nested; the Saved-Query tab is a flat `MMenu` list with Create/​delete.
53
+ 3. **Category list** β€” **Dashboard** `dashboard/components/dashboard-dropdown.vue`: a **segmented**
54
+ Dashboard / NOC View toggle + round **οΌ‹**, search + layout-toggle, then `MCollapse` **categories with
55
+ count badges** (My Favorite 7, Server 6 …) and a **favourite star** per row.
56
+ 4. **List / Saved views** β€” **Report** `report/components/report-sidebar.vue` + the **`ExplorerSavedViewList`**
57
+ reused by **APM / RUM / Metric Explorer** (and the Log Saved-Query tab): tabs + search + a flat
58
+ `MMenu` list with a **Favorites (β˜…)** row, an **active** highlight, and a per-row **pencil** to inline-rename.
59
+
60
+ > **Not in this family (cross-refs):** the **faceted checkbox** left-panel (`vertical-filter/filters.vue` β€”
61
+ > APM/RUM/NCM) is a **Filter** β†’ **Molecules/Filters β†’ Vertical filter**; the **Metric Explorer**
62
+ > picker (`metric-picker.vue` β†’ `counter-list.vue`, βŠ•-add + drag) is a **picker** β†’ cross-ref
63
+ > **DropdownPicker**. The **Flow** "Result By" sidebar is a *config* panel (sortable list + dropdowns),
64
+ > not nav.
65
+
66
+ ### Steps
67
+
68
+ - A **count circle + label** per step: **completed** (βœ“ filled), **current** (filled + ring),
69
+ **remaining** (grey); connectors fill `--primary` up to the current step. **All steppers are bespoke**
70
+ (the product has no `MSteps`). Three real usages:
71
+ - **Report builder** (`report-steps.vue`, `ReportSteps`) β€” horizontal 3-step wizard:
72
+ *Report Properties β†’ Visualizations & Preview β†’ Schedule*. The reproduction models this one.
73
+ - **Product setup / onboarding guide** (`product-setup/guide-sections.vue` + `guide-section-step.vue`)
74
+ β€” vertical numbered step list (completed shows a green βœ“) for the Log / Metric / Flow setup guides.
75
+ - **Two-factor auth setup** (`auth/two-factor-verification.vue`) β€” a *Step 1 / 2 / 3* indicator with
76
+ dividers.
77
+
78
+ ### Breadcrumb / Back button
79
+
80
+ - **Breadcrumb** β€” a back chevron + a path (`Reports / Compliance / …`), current crumb bold. (Some are
81
+ a back + title/subtitle context header rather than a full trail.)
82
+ - **Back button** (`FlotoBackButton`, 3Γ—) β€” a `chevron-left` `MIcon` in a `FlotoLink`; also the Page
83
+ header's `back-button` slot.
84
+
85
+ ## App chrome & specialised navigators (from the full per-module sweep)
86
+
87
+ The 2026-06-16 per-module fan-out surfaced six more wayfinding components beyond the core archetypes:
88
+
89
+ | Member | Source | What it is |
90
+ | --- | --- | --- |
91
+ | **User account menu** | `components/layout/user-dropdown.vue` | header avatar β†’ `MPopover`: profile, theme toggle, logout |
92
+ | **Notification dropdown** | `components/layout/notification-dropdown.vue` | header bell + badge β†’ **Alerts / System** tabs + View all |
93
+ | **Global search / Omnibox** | `components/omnibox/searchbar.vue` | command/search palette (category + CodeMirror query + Execute) |
94
+ | **NOC Player** | `dashboard/components/noc-player.vue` | wallboard **rotator** β€” β€Ή prev/next β€Ί, countdown, play/pause |
95
+ | **Timeline scrollbar** | `netroute/components/timeline-scrollbar.vue` | **temporal** navigator (single β€Ή β€Ί + batch β€Ήβ€Ή β€Ίβ€Ί through time buckets) |
96
+ | **Graph expansion breadcrumb** | `netroute/components/graph-view.vue` | a **Breadcrumb variant** β€” closable tags of expanded graph nodes |
97
+
98
+ ## Checked & scoped out (sweeps)
99
+
100
+ Swept the product for nav variants beyond the archetypes; these were considered and deliberately placed
101
+ elsewhere or confirmed absent:
102
+
103
+ | Candidate | Usage | Verdict |
104
+ | --- | --- | --- |
105
+ | Topology Full/Tree switch Β· chart-type selector | β€” | **Radio (segmented)** usage, not a new nav entry β€” cross-ref Atoms/Radio. |
106
+ | Topology category / APM section / Alert two-level tabs | β€” | **Tabs** usage (Alert adds a hover sub-menu) β€” cross-ref Molecules/Tabs. |
107
+ | ViewDetailDrawer (tabs inside a drawer) | β€” | **Drawer + Tabs** composition β€” cross-ref Organisms/Drawer. |
108
+ | Faceted checkbox sidebar (`vertical-filter/filters.vue`) | APM/RUM/NCM | A **Filter**, not nav β†’ **Molecules/Filters β†’ Vertical filter**. |
109
+ | Swap/reorder control Β· Role-navigation form Β· flame-graph zoom Β· Layer-3 toggle Β· shortcuts tooltip | β€” | **Not navigation** (reorder control / settings form / chart-zoom / filter toggle / help legend). |
110
+ | **Pagination** (`FlotoPaginatedCrud` / `k-pager`) | **79Γ—** | A **page-nav** archetype, but it belongs to the **Table** family (grid footer) β€” cross-referenced, not duplicated here. |
111
+ | **Menu toggle** (`menu-toggle-button`) | 7Γ— | The Primary nav's own collapse control β€” folded into Primary nav's **collapsed ↔ expanded** variant, not a separate entry. |
112
+ | `role="navigation"` landmark | 2Γ— | An a11y attribute, not a component β†’ tracked in **F2**. |
113
+ | `MMenu` | 13Γ— | The shared primitive under Primary nav / Side menu β†’ now catalogued in its own right in the **Menu** family (Molecules/Menu). |
114
+ | `MSubMenu` (flyout submenus) | **0Γ—** | Confirmed **absent** β€” the product has no flyout/mega-menu pattern. |
115
+ | Context / action menu (`FlotoGridActions` Β· `MDropdown`) | grid rows Β· 1Γ— | An action surface ("do something"), not wayfinding β†’ now homed in the **Menu** family (Molecules/Menu). |
116
+
117
+ ## Which navigation? (decision)
118
+
119
+ 1. **Top-level modules?** β†’ **Primary nav**.
120
+ 2. **Sections within a module?** β†’ **Side menu**.
121
+ 3. **A multi-step flow?** β†’ **Steps**.
122
+ 4. **Location + a way up?** β†’ **Breadcrumb**.
123
+ 5. **Just back one level?** β†’ **Back button**.
124
+ 6. **Sibling views of one page?** β†’ **Tabs** (Molecules/Tabs).
125
+
126
+ ## Accessibility
127
+
128
+ - **Verify:** nav **landmarks** (`role="navigation"`), **`aria-current`** on the active item,
129
+ focus-visible ring (**SF-001**), and that the **stepper** conveys step state (not color-only) to
130
+ assistive tech.
131
+
132
+ ## Design tokens used
133
+
134
+ `--nav-panel-bg` Β· `--code-tag-background-color` (active) Β· `--left-menu-text-color` Β· `--primary` Β·
135
+ `--primary-alt` Β· `--border-color` Β· `--neutral-lightest` Β· `--neutral-light` Β· `--page-text-color`.
136
+
137
+ ## Findings & Inconsistencies
138
+
139
+ | # | Severity | Status | Finding |
140
+ | --- | --- | --- | --- |
141
+ | F1 | Low | Noted | Router/store-bound (`visibleMenuItems`, `FlotoLink`, `MCollapse`) β†’ reproductions. |
142
+ | F2 | Low (a11y) | Open | Verify nav landmarks / `aria-current` / focus ring / stepper semantics. |
143
+
144
+ ## Do / Don't
145
+
146
+ - **Do** use Primary nav for modules, Side menu for sections, Steps for ordered flows; put the Back
147
+ button in the Page header slot.
148
+ - **Don't** use Tabs for module/section nav; don't hand-roll a sidebar; don't confuse a breadcrumb
149
+ (location) with a toolbar (actions).
150
+
151
+ ## Related components
152
+
153
+ **Tabs** (sibling-view nav) Β· **Toolbars** (Page header hosts the back button + breadcrumb) Β·
154
+ `MMenu` / `MCollapse` (the primitives) Β· `Input` (the side-menu search).
155
+
156
+ ## Changelog
157
+
158
+ - **2026-06-16** β€” Added a **second Steps example** β€” **Setup guide steps** (vertical
159
+ `product-setup/guide-section-step.vue` pattern: 50Γ—50 rounded index box, number β†’ green βœ“ when done,
160
+ title + description) alongside the existing horizontal **report-builder** wizard. Verified light +
161
+ dark.
162
+ - **2026-06-16** β€” Fidelity pass (round 2, against a product screenshot) β€” corrected the Primary nav
163
+ logo from a square "M" to the real **ObserveOps donut mark** (coral wedge + ring + hollow centre,
164
+ theme-aware ring via `--nav-text-color`); changed the active item from a tinted row + left rule to a
165
+ **filled rounded pill** (`--primary` bg, `--nav-panel-bg` text, inset margins) matching the product;
166
+ made the **BETA** tag a rounded grey pill; added the bottom **MaskGroup** decoration (translucent
167
+ overlapping circles). Also **corrected the Steps source**: the product has **no `MSteps`** β€” the
168
+ steppers are bespoke (`report-steps.vue`, the `product-setup` guide, and `two-factor-verification`).
169
+ - **2026-06-16** β€” Recheck β€” **rebuilt Primary nav for product fidelity** against the real
170
+ `FlotoNavBar`: collapsible `MLayoutSider` (**collapsed 65px β†’ hover-expand 170px**), the **ObserveOps**
171
+ logo, the real **16 modules** with their registered icons, the **SLO BETA** tag, and the `--primary`
172
+ active highlight + inset left rule. Verified collapsed + expanded in **both light and dark** themes
173
+ (screenshots). Recorded the recheck sweep: **Pagination** (79Γ—) β†’ cross-referenced to the **Table**
174
+ family; **menu toggle** folded into the collapsed↔expanded variant; `MSubMenu` flyouts confirmed
175
+ **absent** (0Γ—).
176
+ - **2026-06-16** β€” Added β€” the **Navigation** family (the wayfinding archetypes): **Primary nav**
177
+ (`FlotoNavBar`), **Side menu** (`settings/left-menu`, 20Γ—), **Steps** (`MSteps`), **Breadcrumb**,
178
+ **Back button** (`FlotoBackButton`). **Tabs** stay their own family (cross-referenced). Surfaced as a
179
+ separate family during the Toolbars recheck (nav β‰  toolbar). Reproductions (router/store-bound);
180
+ verified Primary nav + Side menu + Steps render, no console errors. Findings F1 (reproduction),
181
+ F2 (a11y landmarks/aria-current).
@@ -0,0 +1,118 @@
1
+ # Popover (`MPopover`) β€” Spec, Findings & Solutions
2
+
3
+ | | |
4
+ | --- | --- |
5
+ | **Tier** | Molecule |
6
+ | **Maturity** | 🟒 Stable |
7
+ | **Source** | `ui/components/Popover/Popover.vue` (kit; wraps Ant `a-popover`). Positioning primitive: `src/components/_base-popper.vue` (**MPopper**, wraps `v-popover`). |
8
+ | **Storybook** | Molecules/Popover |
9
+ | **Registry** | [`registry/popover.json`](../registry/popover.json) |
10
+ | **Family** | [Popover / Tooltip](../family-map.md) |
11
+
12
+ ## Usage analytics
13
+
14
+ - **MPopover 35Γ—** Β· **MPopper 2Γ—** (direct β€” but MPopper also powers **FlotoDropdownPicker**'s
15
+ positioning, so its real footprint is much larger).
16
+ - Patterns: **action/kebab menus** (`_base-grid-actions.vue`), **color picker**
17
+ (`color-picker.vue`), **date/time pickers**, rich panels, **"+N overflow" hover reveals**
18
+ (`tags-list.vue`). Overlay classes: `picker-action-dropdown`, `color-picker-popover`,
19
+ `picker-overlay`, `readable-content-overlay`.
20
+ - **Opening behavior:** `trigger` β€” **click 20Γ—**, **hover 13Γ—** (passive reveals), explicit
21
+ click 2Γ—. Transition: **`transition-name="slide-up"` 16Γ—**.
22
+ - **Placement (Ant names):** **`bottomLeft` 11Γ—** (most common) Β· `bottomRight` 9Γ— Β· `bottom` 7Γ— Β·
23
+ `leftTop` 4Γ— Β· `top`/`right` 1Γ—. Default `bottom`. ⚠️ Ant names β€” **not** the Tooltip's VTippy
24
+ `top-start` names.
25
+
26
+ ## Overview
27
+
28
+ A **click-triggered floating panel** anchored to a trigger, holding **interactive** content β€”
29
+ unlike a Tooltip (hover, non-interactive). `MPopover` wraps Ant `a-popover`; **`MPopper`** is the
30
+ lower-level `v-popover` primitive used as the picker positioning engine. Use a Popover for
31
+ anchored menus/pickers/mini-forms; for a center-interrupting task use **`MModal`**, and for
32
+ select-from-options use **`FlotoDropdownPicker`**.
33
+
34
+ ## Anatomy
35
+
36
+ - **Trigger** (`trigger` slot, scope `{ hide, show, toggle }`) β€” the anchor.
37
+ - **Title** (optional `title` slot) β€” a header row.
38
+ - **Content** (default slot, scope `{ hide, show, toggle }`) β€” the interactive panel.
39
+
40
+ ## Options (props)
41
+
42
+ | Prop | Default | Notes |
43
+ | --- | --- | --- |
44
+ | `trigger` | `click` | `click` / `hover` / `focus` / `contextmenu` |
45
+ | `placement` | `bottom` | `bottom` / `bottomRight` / `top` / `left` / `right` … |
46
+ | `visible` | β€” | controlled mode; pair with `@visibleChange` (one-way β€” F1) |
47
+ | `overlayClassName` | β€” | e.g. `color-picker-popover`, `picker-action-dropdown` |
48
+ | `overlayStyle` | β€” | inline overlay style (width/padding/radius) |
49
+ | `destroyTooltipOnHide` | `true` | re-creates content each open |
50
+ | `getPopupContainer` | β€” | defaults to closest `.__panel` or `document.body` |
51
+
52
+ ## Behaviors
53
+
54
+ - Opens on **click** by default; the slot scope `{ hide, show, toggle }` lets content drive
55
+ dismissal (an action item calls `hide()` on select).
56
+ - `destroyTooltipOnHide` re-mounts the content each open (fresh state).
57
+ - **MPopper** adds a ResizeObserver to keep the panel positioned as content resizes; boundary is
58
+ the closest `.__panel` or the viewport.
59
+
60
+ ## Content & writing
61
+
62
+ - Title is a short noun; content is the interactive panel. Keep menus to a handful of items β€”
63
+ beyond ~7, reconsider the pattern.
64
+
65
+ ## Accessibility
66
+
67
+ - **Disclosure semantics missing (F2):** the trigger has no `aria-haspopup`/`aria-expanded`,
68
+ focus is **not** moved into the panel on open, and there is **no focus trap** β€” keyboard/SR
69
+ users aren't told a panel opened or guided into it. Recommended: set `aria-haspopup` +
70
+ `aria-expanded` on the trigger; move focus to the first focusable on open; restore on close.
71
+ - **Focus ring** removed on the overlay (`:focus { outline:none }` in `_base-popper.less`) β€”
72
+ **F3**, links to [SF-001](../../findings/SF-001-focus-visible.md).
73
+ - Escape/outside-click dismissal comes from `a-popover`; verify Escape returns focus to the trigger.
74
+
75
+ ## Props / API
76
+
77
+ See the table above and [`registry/popover.json`](../registry/popover.json).
78
+
79
+ ## Design tokens used
80
+
81
+ `--page-background-color` Β· `--border-color` Β· `--page-text-color` (panel surface/border/text).
82
+
83
+ ## Findings & Inconsistencies
84
+
85
+ | # | Severity | Status | Finding |
86
+ | --- | --- | --- | --- |
87
+ | F1 | Low | Open | `visible` is one-way β€” controlled mode needs an explicit `@visibleChange` handler to stay in sync. |
88
+ | F2 | Medium (a11y) | Open | No `aria-haspopup`/`aria-expanded`, no focus move into the panel, no focus trap. |
89
+ | F3 | Medium (a11y) | Open | No focus ring on the overlay β€” links to **SF-001**. |
90
+
91
+ ## Recommended solutions
92
+
93
+ - **F1:** document/standardize the controlled pattern (`:visible` + `@visibleChange`), or expose a
94
+ `v-model:visible`.
95
+ - **F2:** add disclosure ARIA + focus management (move in on open, restore on close, Escape closes).
96
+ - **F3:** adopt the `:focus-visible` ring from SF-001.
97
+
98
+ ## Do / Don't
99
+
100
+ - **Do** use for interactive, click-triggered content; give items a `hide()` on select; anchor row
101
+ actions `bottomRight`.
102
+ - **Don't** use for plain hints (use **Tooltip**); don't use for center-interrupting tasks or
103
+ confirmations (use **Modal**); don't hand-roll a select dropdown (use **FlotoDropdownPicker**).
104
+
105
+ ## Related components
106
+
107
+ `MTooltip` (hover label) Β· `FlotoDropdownPicker` (select; uses MPopper) Β· `MModal` (dialog).
108
+
109
+ ## Changelog
110
+
111
+ - **2026-06-11 (nth-level audit)** β€” Documented the real opening/placement behavior: `trigger`
112
+ click 20Γ— / **hover 13Γ—** (+N overflow reveals), `transition-name="slide-up"` 16Γ—, placement
113
+ **`bottomLeft` 11Γ—** (most common) / `bottomRight` 9Γ— / `leftTop` 4Γ—; Ant placement names (not
114
+ VTippy). Added **Behavior: hover trigger** + **Placements** stories.
115
+ - **2026-06-11** β€” Added (decision-grade Usage). Deep-dive of `Popover.vue` (35Γ—, kit `a-popover`)
116
+ and `_base-popper.vue` (MPopper, the picker engine). Stories: Basic (title + content) Β· Action /
117
+ kebab menu Β· Rich panel (color picker) Β· Playground β€” verified click-open with title + items, no
118
+ console errors. Findings F1 (one-way `visible`), F2 (disclosure ARIA + focus), F3 (focus β†’ SF-001).
@@ -0,0 +1,144 @@
1
+ # Radio (`MRadioGroup`) β€” Spec, Findings & Solutions
2
+
3
+ | | |
4
+ | --- | --- |
5
+ | **Tier** | Atom (selection control) |
6
+ | **Maturity** | 🟒 Stable (core selection control) |
7
+ | **Source** | `@motadata/ui` β†’ `ui/components/Radio/RadioGroup.vue` (+ `Radio.vue`); wraps Ant `a-radio-group` |
8
+ | **Storybook** | `Atoms/Radio` (Examples Β· Usage Β· Accessibility Β· Changelog) |
9
+ | **Registry** | [`../registry/radio.json`](../registry/radio.json) |
10
+ | **Family** | selection controls β€” sibling of [Checkbox](./checkbox.md) & [Switch](./switch.md); the `as-button` segmented control is the Button family's "segmented" relative |
11
+ | **Figma** | TODO |
12
+
13
+ ## Usage (product analytics)
14
+
15
+ - **`<MRadioGroup>` used 225Γ— across 136 files.**
16
+ - **Standalone `<MRadio>` is used 0Γ—** β€” radios are always rendered through the **group** with
17
+ an `:options` array.
18
+ - **`as-button` (segmented control) used 255Γ—** β€” the segmented form is at least as common as
19
+ the plain radio list. `buttonStyle` is never overridden (always `solid`).
20
+
21
+ ## Overview
22
+
23
+ A **one-of-many** selection control. Use `MRadioGroup` with an `:options` array
24
+ (`{ value, text }`) and v-model the selected `value` (`model: { event: 'change' }`). Set
25
+ `as-button` to render a **segmented control** (joined buttons) instead of a radio list. An
26
+ `option` slot allows custom row rendering.
27
+
28
+ ## Anatomy
29
+
30
+ ```text
31
+ Radio list: β—‰ Low β—‹ Medium β—‹ High ← a-radio per option
32
+ Segmented: β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”
33
+ (as-button) β”‚ 1h β”‚ 24h* β”‚ 7d β”‚ 30d β”‚ ← a-radio-button; selected fills navy
34
+ β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜
35
+ ```
36
+
37
+ ## Options / API
38
+
39
+ - **`options`** (Array) β€” `{ value, text | label | title, disabled? }`; the selectable set.
40
+ - **`value`** (v-model) β€” the selected `value`.
41
+ - **`asButton`** (Boolean) β€” segmented control vs radio list.
42
+ - **`size`** (`small` Β· `default` Β· `large`) β€” mainly affects the segmented buttons.
43
+ - **`disabled`** (Boolean) β€” disable the whole group (per-option via `option.disabled`).
44
+ - **`buttonStyle`** (`solid` Β· `outline`, default `solid`) β€” never changed in the product.
45
+ - **`name`**, **`defaultValue`**.
46
+ - Slots: default (custom `a-radio`/`a-radio-button` children) Β· `option` (custom row).
47
+ - Emits **`change`**.
48
+
49
+ ## Behaviors
50
+
51
+ - **Single selection** β€” picking one option deselects the others.
52
+ - **Segmented** (`as-button`): the selected button fills the brand color; the rest are
53
+ outlined/joined. **The selected fill is context-dependent (see F4).**
54
+ - **Keyboard:** native radio behavior β€” Arrow keys move selection within the group, Space
55
+ selects.
56
+
57
+ ## Segmented variants (all real in the product)
58
+
59
+ - **Plain segmented** (`as-button`) β€” kit default; selected = navy `#111c2c` @ 0.8 opacity.
60
+ - **With icons** β€” an icon per option via the `option` slot (the `without-icon-margin`
61
+ context, **14Γ—**) β€” view/mode toggles.
62
+ - **Severity switch** β€” `class="radio-toggle-shadow alert-severity-buttons"` (the real
63
+ `severity-switch.vue` combo): borderless segments with thin separators; selected = a tinted
64
+ rounded chip. (`radio-toggle-shadow` 2Γ—, `alert-severity-buttons` 2Γ—.)
65
+ - **Radio dot color** (list) = cyan `#099dd9` (Ant `@primary-color`) β€” matches the product.
66
+ - `buttonStyle="outline"` exists but is **unused** (always `solid`).
67
+
68
+ ## Design tokens used
69
+
70
+ Selected segment fill = primary/brand (measured `#111c2c` light β†’ `#e3e8f2` dark); inherits
71
+ Ant radio tokens for the dot and borders.
72
+
73
+ ## Accessibility
74
+
75
+ - Built on native `<input type="radio">` grouped by `name` β€” correct role and arrow-key
76
+ navigation. βœ…
77
+ - ⚠️ **No visible keyboard focus indicator** ([SF-001](../../findings/SF-001-focus-visible.md)).
78
+ - Ensure the group has a label (a field label or `aria-label`) describing what's being chosen.
79
+
80
+ ## Findings & Inconsistencies
81
+
82
+ ### F1 β€” Standalone `MRadio` + `variant`/`type` are effectively dead Β· Low Β· Open
83
+
84
+ `MRadio` is never used directly (0Γ—); its `variant="info"` (β†’ `.radio-info`) and
85
+ `type="button"` paths are unexercised. The group is the real API. **Solution:** document the
86
+ group as the entry point; treat `MRadio` as an internal building block.
87
+
88
+ ### F2 β€” `buttonStyle` outline unused Β· Low Β· Open
89
+
90
+ `buttonStyle` defaults to `solid` and is never set to `outline` (0Γ—). **Solution:** document
91
+ `solid` as the de-facto style; keep `outline` only if a real need appears.
92
+
93
+ ### F3 β€” No visible focus indicator Β· High Β· Open *(a11y)* β†’ see [SF-001](../../findings/SF-001-focus-visible.md)
94
+
95
+ Same system-wide focus-ring gap as other controls.
96
+
97
+ ### F4 β€” Segmented selected style is context-dependent Β· Medium Β· Open
98
+
99
+ The selected segment renders differently depending on the **ancestor**: standalone it uses the
100
+ kit's `.ant-radio-group-solid` (navy `#111c2c` **@ 0.8 opacity**), but inside form/panel
101
+ contexts `src/design/form.less` overrides it to a **solid `--primary` fill (`!important`),
102
+ 11px padding, and icon spacing**. So the *same* `as-button` group looks slightly different
103
+ (opacity + padding) inside a form vs standalone. Storybook (loading the same stylesheets)
104
+ matches the product **for the same markup**; the divergence is purely the missing form
105
+ ancestor. **Solution:** decide on one canonical segmented appearance and move it out of the
106
+ form-scoped selectors into the component (kit) styles so it's consistent everywhere; meanwhile
107
+ the catalog shows the standalone look + documents the in-form override here.
108
+
109
+ ## Do / Don't
110
+
111
+ ### Do
112
+
113
+ - Use `MRadioGroup` with `:options` for one-of-many; v-model the value.
114
+ - Use `as-button` for a small set of always-visible, mutually-exclusive choices.
115
+ - Disable individual options with `option.disabled`.
116
+
117
+ ### Don't
118
+
119
+ - Don't use a radio group for multi-select (use `Checkbox`) or an instant on/off (use `Switch`).
120
+ - Don't use radios for **many** options or when space is tight (use `DropdownPicker`).
121
+ - Don't render standalone `MRadio` β€” go through the group.
122
+
123
+ ## Related
124
+
125
+ `Checkbox` (multi-select) Β· `Switch` (instant on/off) Β· `DropdownPicker` (many options) Β·
126
+ Button's **segmented control** is this component with `as-button`.
127
+
128
+ ## Changelog
129
+
130
+ - **2026-06-07** β€” Added (first authored to the decision-grade Usage standard). Deep-dive of
131
+ `RadioGroup.vue`/`Radio.vue` (225Γ—; standalone radio 0Γ—; `as-button` 255Γ—). Verified radio
132
+ list + segmented in light/dark. Findings F1 (dead standalone/variants), F2 (`outline`
133
+ unused), F3 (focus β†’ SF-001). Closes the Button family's "segmented control" relative.
134
+ - **2026-06-07** β€” Fidelity deep-dive (owner flagged the segmented didn't match the product).
135
+ Confirmed radio-list dots = cyan `#099dd9` (matches). Found the full segmented variant set
136
+ and added stories: **with icons** (14Γ—) and the **severity switch** combo
137
+ (`radio-toggle-shadow` + `alert-severity-buttons`, the real `severity-switch.vue`). New
138
+ finding **F4**: segmented selected style is context-dependent (kit navy @0.8 standalone vs
139
+ solid `--primary` inside forms). Storybook matches the product for identical markup.
140
+ - **2026-06-07** β€” Fixed the icon-segmented story: the icon was wrapped in a `flex` span, whose
141
+ baseline offset the selected segment by 7px (stair-step). Switched to the product's inline
142
+ icon pattern (`.without-icon-margin`, icon `mr-1`) β€” segments now align (all `top: 40`).
143
+ Authoring note: don't `flex`-wrap content inside Ant `vertical-align: baseline` radio
144
+ buttons; keep it inline.
@@ -0,0 +1,133 @@
1
+ # Scheduler / Recurrence (`ScheduleInput`) β€” Spec, Findings & Solutions
2
+
3
+ | | |
4
+ | --- | --- |
5
+ | **Tier** | Molecule |
6
+ | **Maturity** | 🟒 Stable |
7
+ | **Source** | `src/components/schedule-input/index.vue` (`ScheduleInput`) + `once-input.vue` Β· `weekly-input.vue` Β· `monthly-input.vue` (sub-forms). |
8
+ | **Storybook** | Molecules/Scheduler |
9
+ | **Registry** | [`registry/scheduler.json`](../registry/scheduler.json) |
10
+ | **Family** | [Scheduler / Recurrence](../family-map.md) |
11
+
12
+ ## Usage analytics
13
+
14
+ - **16Γ—** across settings & reports: **backups** (`backup-profile-form`,
15
+ `device-inventory-schedule-backup`), **network discovery** (`discovery-schedules`), **compliance
16
+ audits** (`compliance-audit-schedules`), **monitor (re)discovery** (`monitor-schedule-form`,
17
+ `rediscover/custom-schedule-form`, `topology-scanner/schedule-form`), **runbooks**
18
+ (`runbook-schedules`), **reports** (`report-form`), **policies** (`policy-form/basic-info`).
19
+ - Built entirely from already-catalogued primitives β€” it's a **composition**, not a new control.
20
+
21
+ ## Overview
22
+
23
+ A **recurrence builder**: pick *how often* a job runs and *when*. A **"Scheduler Type"** segmented
24
+ control (`MRadioGroup as-button`) β€” **Once Β· Daily Β· Weekly Β· Monthly** β€” swaps in a sub-form. This is
25
+ distinct from the **Date & Time Pickers** family (which picks a *value* or a *window*); the Scheduler
26
+ produces a **recurrence rule**.
27
+
28
+ ## Anatomy
29
+
30
+ - **Scheduler Type** β€” segmented `MRadioGroup as-button` (Once/Daily/Weekly/Monthly).
31
+ - **Sub-form** (swaps on type):
32
+ - **Once / Daily** β†’ **Start Date** (`MDatePicker`, date-only, `min-date` = today) + **Hours** (the
33
+ custom dropdown `TimePicker` β€” a `FlotoDropdownPicker` of times, multi-select).
34
+ - **Weekly** β†’ **Days** (`FlotoDropdownPicker`, Mon–Sun multi) + Start Date + Hours.
35
+ - **Monthly** β†’ **Months** (Jan–Dec multi) + **Dates** (1–31 multi) + Start Date + Hours.
36
+ - Each field is a **`FlotoFormItem`** with `rules="required"`.
37
+
38
+ ## Options (props)
39
+
40
+ ### ScheduleInput
41
+
42
+ | Prop | Default | Notes |
43
+ | --- | --- | --- |
44
+ | `value` | β€” | v-model; `{ scheduleType, scheduleInfo }` |
45
+ | `excludedScheduleOptions` | `[]` | drop types from the control (e.g. `['Weekly','Monthly']`) (1Γ—) |
46
+ | `showOnlyOnce` | `false` | collapse to **Once** only (no recurrence) (1Γ—) |
47
+ | `disabled` (passthrough) | β€” | via `$attrs` β€” disables the fields (2Γ—) |
48
+ | `$attrs` β†’ sub-form | β€” | `disabled` (2Γ—), `time-options` (2Γ—), `use-single-selection` (1Γ—), `hide-date-time` (1Γ—), `multiple` |
49
+
50
+ ### Sub-forms (once / weekly / monthly)
51
+
52
+ | Prop | Default | Notes |
53
+ | --- | --- | --- |
54
+ | `value` | β€” | the `scheduleInfo` object |
55
+ | `hideDateTime` | `false` | hide Start Date + Hours |
56
+ | `timeOptions` | β€” | override the Hours options |
57
+ | `multiple` | `false` | allow multiple Hours (`use-single-selection` caps to 1) |
58
+
59
+ ## Behaviors
60
+
61
+ - **Emit:** `{ scheduleType, scheduleInfo: { startDate:<ms>, times:[…], days:[…] (weekly),
62
+ months:[…] + dates:[…] (monthly) } }`. Changing the **type resets** `scheduleInfo` to
63
+ `{ startDate: now }`. Start Date defaults to **today** if unset.
64
+ - **`Daily` reuses the `Once` sub-form** (Start Date + Hours) β€” the difference is the `scheduleType`
65
+ value, not the fields.
66
+
67
+ ## Content & writing
68
+
69
+ - Field labels are fixed (Scheduler Type, Start Date, Hours, Days, Months, Dates) β€” keep them.
70
+
71
+ ## Accessibility
72
+
73
+ - **Provided by** the composed primitives: `MRadioGroup` (radio semantics + arrow-key type selection),
74
+ `MDatePicker` / `FlotoDropdownPicker` (their own a11y), `FlotoFormItem` (labels + required state).
75
+ - **Verify:** focus-visible ring (**SF-001**) on the segmented buttons and the selects.
76
+
77
+ ## Design tokens used
78
+
79
+ Inherits from its primitives β€” segmented control `--primary` (selected), `--neutral-lightest`
80
+ (unselected); fields use `--border-color`, `--page-text-color`, `--neutral-light`. No new tokens.
81
+
82
+ ## Findings & Inconsistencies
83
+
84
+ | # | Severity | Status | Finding |
85
+ | --- | --- | --- | --- |
86
+ | F1 | Low | Open | All three sub-forms declare `name: 'OnceForm'` (copy-paste leftover) β€” should be `WeeklyForm` / `MonthlyForm`. Harmless but confusing. |
87
+ | F2 | Low | Noted | `ScheduleInput` pulls the local DB (**lokijs β†’ Node `fs`**) transitively, so it can't render live in Storybook β€” **reference reproduction** built from the real primitives. |
88
+
89
+ ## Recommended solutions
90
+
91
+ - **F1:** rename each sub-form's `name` to match its file (`WeeklyForm`, `MonthlyForm`).
92
+ - **F2:** if a headless schedule builder is extracted (Vue 3), drop the DB import so it can render
93
+ standalone.
94
+
95
+ ## Do / Don't
96
+
97
+ - **Do** use for recurring jobs; drop unsupported recurrences with `excluded-schedule-options`; use
98
+ `show-only-once` for a single run.
99
+ - **Don't** use it for a plain date/time value (β†’ `MDatePicker`) or a chart time window
100
+ (β†’ `TimeRangePicker`); don't hand-roll the Once/Daily/Weekly/Monthly switcher.
101
+
102
+ ## Related components
103
+
104
+ `MRadioGroup` (segmented type control) Β· **Date & Time Pickers** (`MDatePicker` / the custom
105
+ `TimePicker` it embeds) Β· `FlotoDropdownPicker` (Days/Months/Dates) Β· `FlotoFormItem`.
106
+
107
+ ## Checked & scoped out (sweep recheck)
108
+
109
+ A census of every `<ScheduleInput>` (16Γ—) confirmed the **types are Once/Daily/Weekly/Monthly only**
110
+ and accounted for every prop. Deliberately *separate* (not ScheduleInput variants):
111
+
112
+ - **`MetricPollTime` / `metric-collection-time/*`** β€” metric **polling-frequency** config (a distinct
113
+ monitoring sub-system) β†’ candidate for a future *Monitoring-config* entry, not job recurrence.
114
+ - **`MonitoringHourPicker`** β€” a monitoring-hours selector.
115
+ - **`monitor-schedules.vue`** (`MonitorSchedule`) β€” a CRUD **list** of schedules (on/off windows) that
116
+ edits via `monitor-schedule-form` β†’ which *uses* `ScheduleInput`; a consumer, not a new component.
117
+ - **`'Hourly'`** β€” a **granularity** option in policy anomaly/forecast conditions (aggregation period),
118
+ **not** a recurrence type.
119
+
120
+ ## Changelog
121
+
122
+ - **2026-06-16 (sweep recheck)** β€” Full census of `<ScheduleInput>` (16Γ—): types are **Once/Daily/
123
+ Weekly/Monthly only** (the `'Hourly'` elsewhere is a policy-condition *granularity* dropdown, not a
124
+ recurrence type). Added the missed **`:disabled`** passthrough (2Γ—). Confirmed `MetricPollTime` /
125
+ `MonitoringHourPicker` / `monitor-schedules` (CRUD list) are **separate**, not variants β€” documented
126
+ in **Checked & scoped out**. No new types/variants missed.
127
+ - **2026-06-16** β€” Added (decision-grade) β€” the **Scheduler / Recurrence** family (`ScheduleInput`,
128
+ 16Γ—). Documented the **Once/Daily/Weekly/Monthly** segmented switcher and each sub-form's fields,
129
+ the **emit shape**, and the `excluded-schedule-options` / `show-only-once` props. Stories built as a
130
+ **reference reproduction** from the real primitives (`MRadioGroup as-button` + `MDatePicker` +
131
+ `FlotoDropdownPicker`) because the live `ScheduleInput` pulls the local DB (lokijs). Verified the
132
+ type-switch swaps the sub-form (Monthly β†’ Months + Dates) and the `scheduleInfo` shape, no console
133
+ errors. Findings F1 (sub-forms all named `OnceForm`), F2 (lokijs β†’ reproduction).